Compare commits

...

649 Commits

Author SHA1 Message Date
Palanikannan1437
ab5e698065 feat: init working version of image blobs rendering 2024-02-09 21:23:53 +05:30
Nikhil
e09d7f9533 Merge branch 'develop' into chore/file-uploads 2024-02-08 16:23:34 +05:30
sriram veeraghanta
f9e187d8b9 Merge branch 'preview' of github.com:makeplane/plane into develop 2024-02-08 13:30:39 +05:30
Aaryan Khandelwal
9545dc77d6 fix: cycle and module reordering in the gantt chart (#3570)
* fix: cycle and module reordering in the gantt chart

* chore: hide duration from sidebar if no dates are assigned

* chore: updated date helper functions to accept undefined params

* chore: update cycle sidebar condition
2024-02-08 13:30:16 +05:30
pablohashescobar
16c78100c0 dev: docker variables upgrade and nginx template update 2024-02-08 13:22:20 +05:30
sriram veeraghanta
1fc987c6c9 Merge branch 'preview' of github.com:makeplane/plane into develop 2024-02-08 13:14:25 +05:30
guru_sainath
c1c0297b6d fix: handled issue create modal submission on clicking enter key (#3593) 2024-02-08 13:08:19 +05:30
Manish Gupta
1a7b5d7222 build fix (#3594) 2024-02-08 12:26:55 +05:30
rahulramesha
fb3dd77b66 feat: Keyboard navigation spreadsheet layout for issues (#3564)
* enable keyboard navigation for spreadsheet layout

* move the logic to table level instead of cell level

* fix perf issue that made it unusable

* fix scroll issue with navigation

* fix build errors
2024-02-08 11:49:00 +05:30
Manish Gupta
a43dfc097d feat: muti-arch build (#3569)
* arm build, supporting centos & alpine
2024-02-08 11:47:52 +05:30
Ramesh Kumar Chandra
346c6f5414 fix: module header hide on bigger screens (#3590)
* fix: module header hide on bigger screens

* fix: Add Inbox back on mobile

---------

Co-authored-by: Maximilian Engel <maximilian.engel@swg.de>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2024-02-08 00:16:00 +05:30
Anmol Singh Bhatia
729b6ac79e fix: Handled the draft issue from issue create modal and optimised the draft issue store (#3588)
Co-authored-by: gurusainath <gurusainath007@gmail.com>
2024-02-07 20:45:05 +05:30
pablohashescobar
2688e41cef dev: update the default create bucket script to create private bucket 2024-02-07 20:33:37 +05:30
Bavisetti Narayan
0a35fcfbc0 chore: email template logo changed (#3575)
* chore: email template logo changed

* fix: icons image extensions

* fix: state icons

---------

Co-authored-by: LAKHAN BAHETI <lakhanbaheti9@gmail.com>
2024-02-07 20:20:54 +05:30
pablohashescobar
6776c2d2d1 dev: add nginx headers for request host 2024-02-07 20:20:54 +05:30
Anmol Singh Bhatia
75b4c6e7d6 chore: posthog code refactor (#3586) 2024-02-07 17:29:39 +05:30
João Lucas de Oliveira Lopes
a1d6c40627 fix: show window closing alert only when page is not saved (#3577)
* fix: show window closing alert only when page is not saved

* chore: Refactor useReloadConfirmations hook

- Removed the `message` parameter, as it was not being used and not
  supported in modern browsers
- Changed the `isActive` flag to a temporary flag and added a TODO comment to remove it later.
- Implemented the `handleRouteChangeStart` function to handle route change events and prompt the user with a confirmation dialog before leaving the page.
- Updated the dependencies of the `handleBeforeUnload` and `handleRouteChangeStart` callbacks.
- Added event listeners for `beforeunload` and `routeChangeStart` events in the `useEffect` hook.
- Cleaned up the event listeners in the cleanup function of the `useEffect` hook.

fix: Fix reload confirmations in PageDetailsPage

- Removed the TODO comment regarding fixing reload confirmations with MobX, as it has been resolved.
- Passed the `pageStore?.isSubmitting === "submitting"` flag to the `useReloadConfirmations` hook instead of an undefined message.

This commit refactors the `useReloadConfirmations` hook to improve its functionality and fixes the usage in the `PageDetailsPage` component.

---------

Co-authored-by: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com>
2024-02-07 17:10:44 +05:30
Anmol Singh Bhatia
4a2e648f6d chore: workspace dashboard refactor (#3584) 2024-02-07 16:21:20 +05:30
Bavisetti Narayan
76db394ab1 chore: mention notification and webhook faliure (#3573)
* fix: mention rstrip error

* chore: webhook deactivation email

* chore: changed template

* chore: current site for external api's

* chore: mention in template displayed

* chore: mention tag fix

* chore: comment user name displayed
2024-02-07 15:14:30 +05:30
Bavisetti Narayan
4563b50fad chore: module issue count (#3566) 2024-02-07 15:13:35 +05:30
Anmol Singh Bhatia
065226f8b2 fix: draft issue peek overview (#3582)
* chore: project, view and shortcut modal alignment consistency

* chore: issue highlight list layout improvement

* fix: draft issue peek overview fix

* fix: draft issue layout inline editing
2024-02-07 15:06:07 +05:30
Anmol Singh Bhatia
0a99a1a091 fix: dashboard header z index and workspace active cycles fix (#3581)
* fix: dashboard header z index fix

* chore: workspace active cycles upgrade page improvement
2024-02-07 13:44:45 +05:30
Ramesh Kumar Chandra
4b2a9c8335 style: responsive breadcrumbs and headers for dashboard, projects, project issues, cycles, cycle issues, module issues (#3580) 2024-02-07 13:44:03 +05:30
Nikhil
751b15a7a7 dev: update the response for conflicting errors (#3568) 2024-02-06 17:00:42 +05:30
Bavisetti Narayan
ac22769220 chore: email trigger for new assignee (#3572) 2024-02-06 16:47:14 +05:30
Manish Gupta
46ae0f98dc app_release value handled (#3571) 2024-02-06 15:35:05 +05:30
Anmol Singh Bhatia
30aaec9097 chore: module date validation (#3565) 2024-02-05 20:46:01 +05:30
Anmol Singh Bhatia
4041c5bc5b chore: cycle and module sidebar improvement (#3562) 2024-02-05 19:13:21 +05:30
Bavisetti Narayan
403595a897 chore: removed email notification for new users (#3561) 2024-02-05 19:13:02 +05:30
Aaryan Khandelwal
0ee93dfd8c chore: added None filter option to the dashboard widgets (#3556)
* chore: added tab change animation

* chore: widgets filtering logic updated

* refactor: issues list widget

* fix: tab navigation transition

* fix: extra top spacing on opening the peek overview
2024-02-05 19:12:33 +05:30
pablohashescobar
e19eb3f074 dev: update the issue attachment default type 2024-02-05 16:00:13 +05:30
pablohashescobar
f95f24231f dev: comment assets 2024-02-05 15:12:56 +05:30
Anmol Singh Bhatia
ee0e3e2e25 chore: active cycle issue transfer validation (#3560)
* fix: completed cycle list layout validation

* fix: completed cycle kanban layout validation

* fix: completed cycle spreadsheet layout validation

* fix: date dropdown disabled fix

* chore: quick action validation added for list, kanban and spreadsheet layout

* fix: calendar layout validation added
2024-02-05 14:47:40 +05:30
pablohashescobar
02e5e0da4b dev: update attachments for issues 2024-02-05 14:30:49 +05:30
Lakhan Baheti
0165abab3e chore: posthog events improved (#3554)
* chore: events naming convention changed

* chore: track element added for project related events

* chore: track element added for cycle related events

* chore: track element added for module related events

* chore: issue related events updated

* refactor: event tracker store

* refactor: event-tracker store

* fix: posthog changes

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2024-02-05 13:19:07 +05:30
pablohashescobar
cedc08bc08 dev: sync data for issue attachments 2024-02-05 13:10:24 +05:30
Anmol Singh Bhatia
7d07afd59c chore: cycle and module sidebar analytics improvement (#3559)
* chore: cycle and module store update action updated

* chore: cycle and module issue store actions updated

* chore: cycle and module retrieve endpoints updated

* fix: app sidebar z index and priority icon fix

* chore: cycle and module sidebar and stats updated
2024-02-05 12:34:53 +05:30
pablohashescobar
31cca8d07e dev: back migration for assets in issue, comments and page 2024-02-05 12:04:20 +05:30
pablohashescobar
6c97bcefbf dev: back migration for urls 2024-02-04 11:27:50 +05:30
sriram veeraghanta
efaba43494 Merge branch 'preview' of github.com:makeplane/plane into develop 2024-02-03 14:55:38 +05:30
sriram veeraghanta
a8ec2b6914 fix: error handling in pages list page 2024-02-03 00:35:29 +05:30
Nikhil
39eb8c98d1 dev: validation for external id and external source (#3552)
* dev: error response for duplicate items created through external apis

* dev: return identifier and also add the validation for state

* fix: validation for external id and external source
2024-02-02 16:43:05 +05:30
rahulramesha
138d06868b fix: all issues spreadsheet sorting and kanban dnd for long lists (#3550)
* fix all issues filter for spreadsheet view

* fix kanban dnd with long lists
2024-02-02 14:50:23 +05:30
Ramesh Kumar Chandra
2eab3b41a2 chore: responsive and styling fixes (#3541) 2024-02-02 14:49:42 +05:30
pablohashescobar
e1f0da5e6c dev: update file image urls to backend apis 2024-02-02 14:48:50 +05:30
Anmol Singh Bhatia
662b497082 fix: create issue modal project select (#3549) 2024-02-02 14:29:59 +05:30
Anmol Singh Bhatia
67cf1785b8 chore: breadcrumb component improvement (#3537)
* chore: breadcrumb component improvement

* chore: code refactor
2024-02-01 18:13:30 +05:30
pablohashescobar
94f445cc08 dev: update user assets with backend streaming 2024-02-01 15:55:43 +05:30
Aaryan Khandelwal
7d08a57be6 chore: remove build warnings (#3534)
* chore: remove build warnings

* fix: posthog wrapper fixes

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2024-02-01 14:53:08 +05:30
Anmol Singh Bhatia
b0ad48e35a chore: enhance loading state for setting page updates (#3533) 2024-02-01 13:35:01 +05:30
Anmol Singh Bhatia
d68669df51 fix: resolved issue with resetting the draft issue form (#3532) 2024-02-01 13:33:08 +05:30
Anmol Singh Bhatia
4e600e4e9b fix: update cycle error (#3530)
* fix: update cycle response and implement required changes

* chore: update cycle response
2024-02-01 13:32:37 +05:30
Lakhan Baheti
4fc4da7982 fix: email-template heading overview (#3525)
* fix: email-template heading

* fix: typo
2024-01-31 20:16:47 +05:30
M. Palanikannan
6f210e1f4b chore: removing unnecessary code (#3524)
* fix: adding back enter key extension with mentions

* removed unncessary code
2024-01-31 19:38:37 +05:30
pablohashescobar
42f307421a dev: update the response for assets 2024-01-31 18:43:02 +05:30
Aaryan Khandelwal
f7803dab56 chore: added validation to cloud hostname field (#3523) 2024-01-31 18:06:57 +05:30
M. Palanikannan
70172f8e3d fix: adding back enter key extension with mentions (#3499) 2024-01-31 18:06:12 +05:30
M. Palanikannan
21bc668a56 fix: horizontal rule no more causes issues on last node (#3507) 2024-01-31 18:05:06 +05:30
Anmol Singh Bhatia
dc5a5f4a91 fix/draft issue modal focus issue fix (#3522) 2024-01-31 17:47:47 +05:30
Anmol Singh Bhatia
2c67aced15 fix: cycle and module sidebar mutation fix (#3521)
* fix: cycle and module sidebar mutation

* fix: cycle and module calendar drag n drop fix
2024-01-31 17:09:24 +05:30
Ramesh Kumar Chandra
3a4c893368 Style/workspace project settings layout (#3520)
* style: project settings layout for mobile screens

* style: workspace settings layout for mobile screens
2024-01-31 15:42:20 +05:30
Aaryan Khandelwal
0d036e6bf5 refactor: dropdown button components (#3508)
* refactor: dropdown button components

* chore: dropdowns accessibility improvement

* chore: update module dropdown

* chore: update option content

* chore: hide icon from the peek overview

---------

Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so>
2024-01-31 15:36:55 +05:30
rahulramesha
3ef0570f6a Fix all issues custom views by defining list layout for my-issue (#3517) 2024-01-31 13:59:46 +05:30
Prateek Shourya
c9d2ea36b8 chore: allow guests/ viewers to comment. (#3515) 2024-01-30 20:13:28 +05:30
Lakhan Baheti
f0836ceb10 chore: email-template changes overview (#3494)
* style: template design

* fix: project url and actors

* fix: heading jinja

* fix: typo in workspace

* fix: issue title change

* fix: top project, issue redirection

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2024-01-30 20:12:59 +05:30
Anmol Singh Bhatia
888665783e feat: issue highlighting (#3514)
* chore: kanban issue highlight

* chore: issue highlighting added
2024-01-30 20:12:39 +05:30
Prateek Shourya
817737b2c0 improvement: add hide/ unhide icon for all password field in the platform. (#3513) 2024-01-30 20:11:16 +05:30
rahulramesha
638c1e21c9 fix quick add issue creation in cycles (#3511) 2024-01-30 18:03:49 +05:30
Prateek Shourya
c67e097fc2 chore: fix assignee tooltip logic in list and kanban layout for consistency. (#3510) 2024-01-30 16:25:13 +05:30
guru_sainath
804dd8300d chore: implemented multiple modules select in the issues (#3484)
* fix: add multiple module in an issue

* feat: implemented multiple modules select in the issue detail and issue peekoverview and resolved build errors.

* feat: handled module parameters type error in the issue create and draft modal

* feat: handled UI for modules select dropdown

* fix: delete module activity updated

* ui: module issue activity

* fix: module search endpoint and issue fetch in the modules

* fix: module ids optimized

* fix: replaced module_id from boolean to array of module Id's in module search modal params

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2024-01-30 15:23:20 +05:30
Ramesh Kumar Chandra
c6d6b9a0e9 style/responsive sidebar (#3505)
* style: added sidebar toggle in all the screens for mobile responsive

* chore: close sidebar on click of empty space when opened

* chore: setting the sidebar collapsed in smaller screens
2024-01-30 15:22:24 +05:30
Aaryan Khandelwal
9debd81a50 dev: show all issues on the gantt chart (#3487) 2024-01-30 14:25:15 +05:30
Anmol Singh Bhatia
d53a086206 chore: project active cycle and linear progress indicator improvement (#3504)
* chore: linear progress indicator improvement

* chore: project active cycle improvement
2024-01-30 13:59:49 +05:30
Anmol Singh Bhatia
ef8472ce5e chore: unused var removed (#3503) 2024-01-30 13:59:07 +05:30
Anmol Singh Bhatia
4aa34f3eda fix: create update cycle modal project id pre-load data updated (#3502) 2024-01-30 13:58:18 +05:30
Anmol Singh Bhatia
c7616fda11 chore: empty state theme improvement (#3501) 2024-01-29 20:38:32 +05:30
Anmol Singh Bhatia
483fc57601 chore: issue sidebar and peek overview improvement (#3488)
* chore: issue peek overview and sidebar properties focused state improvement

* fix: added name of the issue in issue relation

* chore: issue sidebar and peek overview properties improvement

* chore: issue assignee improvement for sidebar and peek overview

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2024-01-29 20:36:14 +05:30
Aaryan Khandelwal
09a1a55da8 fix: unique key errors (#3500) 2024-01-29 20:35:23 +05:30
Anmol Singh Bhatia
61f92563a9 fix: profile issue application error (#3496)
* fix: profile issue application error

* chore: app sidebar projects updated to your projects

* fix: profile issues update
2024-01-29 18:04:25 +05:30
rahulramesha
00e07443b0 fix: Add missing project and subscriber filters to the list of filter params (#3497)
* add missing project and subscriber filters to the list of filter params

* add toast message on successfully marking all notifications as read
2024-01-29 17:26:48 +05:30
sriram veeraghanta
c4efdcd704 Merge branch 'preview' of github.com:makeplane/plane into develop 2024-01-29 16:12:57 +05:30
Aaryan Khandelwal
3c9679dff9 chore: update time in real-time in dashboard and profile sidebar (#3489)
* chore: update dashboard and profile time in realtime

* chore: remove seconds

* fix: cycle and module sidebar datepicker
2024-01-29 15:42:57 +05:30
rahulramesha
b3393f5c48 fix: Filters in all issues and enable dnd in kanban based on roles (#3493)
* fix filters mutation issue

* fix all issue filters for state group

* disable drag in Kanban for non members

* remove unused imports
2024-01-29 15:27:14 +05:30
dependabot[bot]
f995736642 chore(deps): bump cryptography in /apiserver/requirements (#3492)
Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.5 to 41.0.6.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/41.0.5...41.0.6)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-29 15:26:08 +05:30
Anmol Singh Bhatia
f7f1f2bea4 fix: issue preload data in issue create update modal (#3491) 2024-01-29 14:08:45 +05:30
guru_sainath
532da80375 fix: handled attachment upload filename size error in the issue detail (#3485)
* fix: handled attachment upload filename size error in the issue detail

* ui: profile detail sidebar border
2024-01-28 18:30:53 +05:30
Anmol Singh Bhatia
212f2b54f8 chore: issue relation modal and issue peek overview mutation fix (#3482)
* fix: resolve addtocycle and addtomodule mutation in peek overview and issue sidebar

* fix: issue relation modal fix for all issues peek overview

* fix: cycle and module mutation in issue detail and issue peek overview

* fix: updated the issue actions for cycle and module mutation in peek overview

* chore: module issue store updated

* chore: existing isssue modal improvement and build error fix

---------

Co-authored-by: gurusainath <gurusainath007@gmail.com>
2024-01-27 15:20:36 +05:30
Anmol Singh Bhatia
9ecdcc6fde chore: spreadsheet layout improvement (#3483)
* chore: spreadsheet layout improvement

* chore: spreadsheet layout improvement

* chore: spreadsheet layout improvement
2024-01-27 15:18:42 +05:30
rahulramesha
ddae745669 fix exception in member list (#3479) 2024-01-25 19:50:34 +05:30
Prateek Shourya
e78c1f2060 Fix/inbox issue bugs (#3477)
* fix: inbox pending_issue_count updation from the store

* fix: inbox list item overflow issue on issue title

* fix: inbox issue mutation

---------

Co-authored-by: gurusainath <gurusainath007@gmail.com>
2024-01-25 19:20:02 +05:30
Manish Gupta
ebc891b985 fix: one click install fixes (#3476)
* test 1

* test 2

* test 3

* test 4

* minor fix

* installer script created

* minor fix

* installer modified

* cleanup
2024-01-25 18:21:21 +05:30
Prateek Shourya
60b5589c48 chore: archived issues restructure. (#3469)
* chore: archived issues restructure.

* fix issue detail functionalities

---------

Co-authored-by: rahulramesha <rahulramesham@gmail.com>
2024-01-25 18:09:01 +05:30
Prateek Shourya
f8208b1b5e Fix/empty state flicker (#3475)
* fix: flicker issue between loader and empty states.

* chore: fix `All Issues` tab highlight when we switch between tabs inside all issues.
2024-01-25 18:08:21 +05:30
Anmol Singh Bhatia
6c6b764421 chore: empty state and project active cycle improvement (#3472)
* chore: pages empty state improvement

* chore: workspace all issues empty state improvement

* chore: profile issue empty state improvement

* chore: empty state sm size updated

* chore: project view empty state image updated

* chore: dashboard widgets permission uodated

* chore: draft issues and project issue empty state image

* chore: active cycle label updated
2024-01-25 18:00:45 +05:30
Manish Gupta
5c912b8821 feat: One Click Deployment (#3474)
* wip

* 1-click install script added
2024-01-25 17:13:30 +05:30
Nikhil
ff19980502 chore: update python version (#3471) 2024-01-25 16:39:19 +05:30
rahulramesha
a1a24e4574 fix: pre release bug fixes (#3473)
* clear store on signout

* fix: project member list response change

* fix adding member to project

* fix exceptions with invitations

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2024-01-25 16:38:34 +05:30
guru_sainath
c1c2a6ddce fix: removing the issue from the issue root store while we are deleting from the issue bulk delete modal (#3470) 2024-01-25 16:37:59 +05:30
guru_sainath
ec3cad1f25 chore: applying query params to the global issues filters in the global views (#3464)
* chore: applying filters from the route params to the global issue filters store and Typos

* chore: enabled posthog

* fix: labels disbaled and loader while creating the label in isse detail and relation modal loader and mutation issue
2024-01-25 14:27:35 +05:30
Anmol Singh Bhatia
336c97d336 chore: bug fixes and ui improvements (#3468)
* chore: empty state improvement

* chore: app sidebar improvement

* chore: dashboard empty state improvement
2024-01-25 14:23:55 +05:30
Aaryan Khandelwal
e4a3d0db5c fix: addIssue function logic (#3467) 2024-01-25 14:09:52 +05:30
Anmol Singh Bhatia
7f2e99dd2d fix: resolve module and cycle mutation issues in peek overview, issue sidebar and empty state (#3466)
* fix: resolve module and cycle mutation issues in peek overview, sidebar, and empty state

* chore: code refactor
2024-01-25 14:06:03 +05:30
Prateek Shourya
a104cc4814 chore: remove deprecated components related to issue activity/ comments. (#3465) 2024-01-25 14:04:38 +05:30
Prateek Shourya
03cbad5110 fix: inbox issue bug fixes and improvements. (#3460)
* style: fix create comment card overflow issue.

* chore: improved loader and filter selection logic.

* chore: implement inbox issue navigation functionality.

* chore: loaders in inbox issue sidebar and the content root

* chore: inbox issue detail sidebar revamp.

---------

Co-authored-by: gurusainath <gurusainath007@gmail.com>
2024-01-25 13:41:02 +05:30
Aaryan Khandelwal
2956c43ed5 fix: issues list modal not closing on escape key (#3463) 2024-01-25 13:30:44 +05:30
Aaryan Khandelwal
eae32593cb chore: added optional tooltip to dropdowns (#3462) 2024-01-25 13:29:56 +05:30
Anmol Singh Bhatia
7fd625e0e3 fix: project pages loader (#3461) 2024-01-25 12:38:55 +05:30
Bavisetti Narayan
f007dcff26 fix: page archive and unarchive (#3459) 2024-01-25 00:48:18 +05:30
Bavisetti Narayan
adf091fa07 fix: email notifications (#3457)
* fix: email-template design

* fix: priority and state new value

* dev: update template with comments, cta text and view issue button.

* dev: fix priority and state

* dev: update data condition

* fix: added avatar url

* fix: comment avatar url

* fix: priority, labels, state design

* style: assignee property

* dev: fix template for comments and profile changes

* fix: spacing between properties

* fix: todo image for state change

* fix: blocking and blocked by value change

* dev: update template summsary

* fix: blocking, duplicate

* fix: comments spacing

* chore: improve `state change` checkbox logic.

* fix: email notification message change

* fix: updated date format

* fix: updates text color

* fix: labels sequence rendering

* fix: schedular time change

---------

Co-authored-by: LAKHAN BAHETI <lakhanbaheti9@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
2024-01-24 20:34:32 +05:30
guru_sainath
b66f07845a chore: inbox issue restructure the components and store (#3456)
* chore: inbox-issues store and type updates

* chore: issue inbox payload change for GET and POST

* chore: issue inbox payload change for PATCH

* chore: inbox-issue new hooks and store updates

* chore: update inbox issue template.

* chore: UI root

* chore: sidebar issues render

* chore: inbox issue details page layout.

* chore: inbox issue filters

* chore: inbox issue status card.

* chore: add loader.

* chore: active inbox issue styles.

* chore: inbox filters

* chore: inbox applied filters UI

* chore: inbox issue approval header

* chore: inbox issue approval header operations

* chore: issue reaction and activity fetch in issue_inbox store

* chore: posthog enabled

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
2024-01-24 20:33:54 +05:30
rahulramesha
911211cf3d Chore: change kanban layout and issue activity fixes (#3458)
* change back the to kanban to enable full board scrolling

* fix adding context to activity
2024-01-24 20:32:13 +05:30
Aaryan Khandelwal
53b41481a2 chore: dashboard empty states, fetching logic (#3455)
* chore: remove one-time fetching

* chore: update empty states

* chore: updated icons

* chore: empty state content
2024-01-24 19:41:02 +05:30
Anmol Singh Bhatia
9d9d703c62 chore: workspace active cycles upgrade page implementation (#3454)
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2024-01-24 19:37:49 +05:30
Aaryan Khandelwal
a2f34e9573 style: peek overview and issue details properties (#3447)
* style: peek overview and issue details properties

* fix: cycle and module remove function

* style: update placeholder text color

* fix: relation constant

* chore: added todos to fix later
2024-01-24 19:21:59 +05:30
Aaryan Khandelwal
81f84f24f7 fix: outsideClickDetector not working on the date dropdown (#3450) 2024-01-24 19:13:38 +05:30
Anmol Singh Bhatia
87f39d7372 chore: empty state revamp and loader improvement (#3448)
* chore: empty state asset added

* chore: empty state asset updated and image path helper function added

* chore: empty state asset updated

* chore: empty state asset updated and empty state details constant added

* chore: empty state component, helper function and comicbox button added

* chore: draft, archived and project issue empty state

* chore: cycle, module and issue layout empty state

* chore: analytics, dashboard, all issues, pages and project view empty state

* chore:projects empty state

* chore:projects empty state improvement

* chore: cycle, module, view and page loader improvement

* chore: code refactor
2024-01-24 19:12:54 +05:30
Henit Chobisa
1a1594e818 chore: Removed Issue Embeds from the Document Editor (#3449)
* fix: issue embed going to next line when selected on slash commands

* fix: issue suggestions selecting next embed on arrow down

* fix: pages crashing, because of incorrect data type

* chore: removed issue embeds from document editor and page interface

* fix: upgraded issue widget card to show only Placeholder

* fix: pricing url changes for issue embed placeholder

* fix: build errors
2024-01-24 19:12:36 +05:30
M. Palanikannan
8d3ea5bb3e fix: delete and restore for dynamic urls for minio hosted images fix… (#3452)
* feat: delete and restore for dynamic urls for minio hosted images fixed in spaces

* feat: delete and restore images calls fixed for web
2024-01-24 18:56:19 +05:30
rahulramesha
6a2be6afc4 fix: archived issues and minor bug fixes (#3451)
* make computedFn without optional arguments

* fix archived issues

* fix activity changes with proper context

* fix display filters that require server side filtering
2024-01-24 18:50:54 +05:30
Henit Chobisa
338d58f79d fix: fixed checklist scrolling on click (#3453) 2024-01-24 18:49:44 +05:30
M. Palanikannan
e23e4bc392 fix: inline codes now exitable with right arrow key and inclusive (#3446)
* fix: inline codes now exitable with right arrow key and inclusive

* regression: toggle inline code on bubble menu selection

* feat: added different code block behaviour on selection and not selection

* fix: blockquote toggling and isActive state fixed
2024-01-24 14:59:48 +05:30
Prateek Shourya
c1598c3d38 fix: input field value not changing in delete import modal. (#3445) 2024-01-24 13:16:46 +05:30
Nikhil
4a436eeee2 fix: project exports (#3441)
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2024-01-23 22:32:53 +05:30
rahulramesha
47681fe9f8 fix: minor bug fixes and quality of life improvements (#3444)
* add concurrency to dev command to avaoid erroring out

* add context to issue activity

* minor quality of life improvement for exporter modal

* show the option to save draft issue only when there is content in name and description

* maintain commonality while referencing the user in activity

* fix minor changes in draft save issue modal logical condition

* minor change is state component for filter selection

* change logic for create issue activity

* change use last draft issue button to state control over previous on hover as that was inconsistent
2024-01-23 20:45:44 +05:30
Nikhil
f27efb80e1 dev: email notifications (#3421)
* dev: create email notification preference model

* dev: intiate models

* dev: user notification preferences

* dev: create notification logs for the user.

* dev: email notification stacking and sending logic

* feat: email notification preference settings page.

* dev: delete subscribers

* dev: issue update ui implementation in email notification

* chore: integrate email notification endpoint.

* chore: remove toggle switch.

* chore: added labels part

* fix: refactored base design with tables

* dev: email notification templates

* dev: template updates

* dev: update models

* dev: update template for labels and new migrations

* fix: profile settings preference sidebar.

* dev: update preference endpoints

* dev: update the schedule to 5 minutes

* dev: update template with priority data

* dev: update templates

* chore: enable `issue subscribe` button for all users.

* chore: notification handling for external api

* dev: update origin request

---------

Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
Co-authored-by: LAKHAN BAHETI <lakhanbaheti9@gmail.com>
Co-authored-by: Ramesh Kumar Chandra <rameshkumar2299@gmail.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2024-01-23 17:49:22 +05:30
guru_sainath
c1e1b81b99 chore: sub issue mutation for state and project change (#3442)
* chore: sub-issues mutation

* chore: posthog un commented
2024-01-23 16:56:22 +05:30
sriram veeraghanta
d9db765ae3 chore: updating package json version (#3443)
* chore: updating package json version

* chore: update package json in apiserver
2024-01-23 16:37:51 +05:30
Aaryan Khandelwal
0531dc3308 chore: fix authorization for new projects in the peek overview (#3439)
* chore: fix authorization for new projects in the peek overview

* fix: prjects empty state authorization

* fix: peek overview auth
2024-01-23 16:16:55 +05:30
rahulramesha
e36b7a5ab9 fix: Kanban related issues (#3436)
* fix for drag and drop issues

* add horizontal scroll for kanban

* fix all issues quick action overlap

---------

Co-authored-by: Rahul R <rahul.ramesha@plane.so>
2024-01-23 16:09:37 +05:30
Anmol Singh Bhatia
c6b756d918 chore: module and cycle bug fixes (#3435)
* chore: project active cycle improvement

* chore: cycle and module add existing mutation fix
2024-01-23 15:57:26 +05:30
sriram veeraghanta
f3ae57bc85 Merge branch 'preview' of github.com:makeplane/plane into develop 2024-01-23 15:56:29 +05:30
Manish Gupta
2374161030 Sync Action Modified (#3437) 2024-01-23 15:53:07 +05:30
Nikhil
512ad83c08 dev: migration check (#3440)
* dev: wait for  migrations script for beat, worker, takeoff

* dev: update docker compose and migrator commands

* dev: migrator commands for self hosted setup

* dev: add recursive flag for chown
2024-01-23 15:07:45 +05:30
Aaryan Khandelwal
801f75f406 chore: drop-downs improvements and bug fixes (#3433)
* chore: dropdowns should close on selecting an option

* style: @plane/ui dropdown styling

* refactor: @plane/ui dropdowns

* fix: build errors

* fix: list layout dropdowns positioning

* fix: priority dropdown text in dark mode
2024-01-23 14:25:09 +05:30
guru_sainath
f88109ef04 chore: issue activity, comments, and comment reaction store and component restructure (#3428)
* fix: issue activity and comment change

* chore: posthog enabled

* chore: comment creation in activity

* chore: comment crud in store mutation

* fix: issue activity/ comments `disable` and `showAccessSpecifier` logic.

* chore: comment reaction serializer change

* conflicts: merge conflicts resolved

* conflicts: merge conflicts resolved

* chore: add issue activity/ comments to peek-overview.
* imporve `showAccessIdentifier` logic.

* chore: remove quotes from issue activity.

* chore: use `projectLabels` instead of `workspaceLabels` in labels activity.

* fix: project publish `is_deployed` not updating bug.

* cleanup

* fix: posthog enabled

* fix: typos and the comment endpoint updates

* fix: issue activity icons update

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
2024-01-23 13:28:58 +05:30
Anmol Singh Bhatia
bb50df0dff chore: bug fixes and improvement (#3434)
* fix: peek overview issue delete bug

* chore: create cycle workflow improvement

* chore: project setting improvement
2024-01-23 13:11:39 +05:30
Aaryan Khandelwal
2986769f28 fix: bugs in dashboard widgets (#3420)
* fix: bugs in dashboard widgets

* chore: updated view all issues button

* refactor: make use of getWidgetDetails and getWidgetStats computed functions

* fix: widgets redirection

* fix: build errors
2024-01-22 20:50:30 +05:30
Anmol Singh Bhatia
fd5326dec6 chore: project cycle bug fixes and improvement (#3427)
* chore: burndown chart's completed at changes

* chore: project cycle bug fixes and improvement

* chore: cycle state constant updated

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2024-01-22 20:42:09 +05:30
M. Palanikannan
49452a68ab ♻️ fix: Added new drag handle hide behavior to Pages (#3426)
- Removed unused `switchLinkView` function in `PageRenderer`
- Added `hideDragHandle` prop to `PageRenderer` component
- Updated `EditorContainer` component in `PageRenderer` to include `hideDragHandle` prop
- Removed unused code and variables in `DocumentEditor` component
- Added `hideDragHandleOnMouseLeave` state variable in `DocumentEditor` component
- Added `setHideDragHandleFunction` to set the hideDragHandle function from the DragAndDrop extension
- Passed `hideDragHandleOnMouseLeave` as prop to `PageRenderer` component in `DocumentEditor`
- Removed unused code and variables in `PageDetailsPage` component
- Updated `customClassName` prop in `PageRenderer` component used in `PageDetailsPage`

chore(deps): Update dependencies

- Updated `@tippyjs/react` dependency to version 4.2.6
- Updated `mobx-react-lite` dependency to version 4.0.5
- Updated `tippy.js` dependency to version 6.3.7

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2024-01-22 17:47:44 +05:30
rahulramesha
b3ac9def8d fix: issue property dropdown data flow (#3425)
* dev: workspace states and estimates

* refactor issue dropdown logic to help work properly with issues on global level

* fix: project labels response change

* fix label type

* change store computed actions to computed functions from mobx-utils

* fix: state response change

* chore: project and workspace state change

* fix state and label types

* chore: state and label serializer change

* modify state and label types

* fix dropdown reset on project id change

* fix label sort order

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Rahul R <rahulr@Rahuls-MacBook-Pro.local>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: Rahul R <rahul.ramesha@plane.so>
2024-01-22 17:07:32 +05:30
Prateek Shourya
be62662bb1 chore: remove active cycle link from dashboard sidebar. (#3423) 2024-01-22 15:27:49 +05:30
sriram veeraghanta
4eba5c115a fix: docker branch builds (#3424)
* fix: build branch fixes

* fix: build branch fixes typos

* fix: changing push branches
2024-01-22 15:27:41 +05:30
sriram veeraghanta
24442ccc9c fix: deleting old issue store 2024-01-22 13:58:34 +05:30
sriram veeraghanta
e32a50fba6 fix: merge conflicts reoslved 2024-01-22 13:56:38 +05:30
Aaryan Khandelwal
4baf2a2f09 fix: workspace project ids computed (#3417) 2024-01-22 13:23:10 +05:30
Aaryan Khandelwal
577118ca02 chore: new sign-in, sign-up and forgot password workflows (#3415)
* chore: sign up workflow updated

* chore: sign in workflow updated

* refactor: folder structure

* chore: forgot password workflow

* refactor: form component props

* chore: forgot password popover for instances with smtp unconfigured

* chore: updated UX copy

* chore: update reset password link

* chore: update email placeholder
2024-01-22 13:23:10 +05:30
Nikhil
4a26f11e23 dev: update apiserver .env.example (#3412) 2024-01-22 13:23:10 +05:30
Facundo Martin Gordillo
9926e321f6 fix: Updated "deployment documentation" link (#3413) 2024-01-22 13:23:10 +05:30
Prateek Shourya
7f5028a4f6 refactor: move all sidebar links to constant file. (#3414)
* chore: move all workspace, project and profile links constants into their own constants file.

* chore: sidebar menu links improvement.
2024-01-22 13:23:10 +05:30
Henit Chobisa
06a7bdffd7 Improvement: High Performance MobX Integration for Pages ✈︎ (#3397)
* fix: removed parameters `workspace`, `project` & `id` from the patch calls

* feat: modified components to work with new pages hooks

* feat: modified stores

* feat: modified initial component

* feat: component implementation changes

* feat: store implementation

* refactor pages store

* feat: updated page store to perform async operations faster

* fix: added types for archive and restore pages

* feat: implemented archive and restore pages

* fix: page creating twice when form submit

* feat: updated create-page-modal

* feat: updated page form and delete page modal

* fix: create page modal not updating isSubmitted prop

* feat: list items and list view refactored for pages

* feat: refactored project-page-store for inserting computed pagesids

* chore: renamed project pages hook

* feat: added favourite pages implementation

* fix: implemented store for archived pages

* fix: project page store for recent pages

* fix: issue suggestions breaking pages

* fix: issue embeds and suggestions breaking

* feat: implemented page store and project page store in page editor

* chore: lock file changes

* fix: modified page details header to catch mobx updates instead of swr calls

* fix: modified usePage hook to fetch page details when reloaded directly on page

* fix: fixed deleting pages

* fix: removed render on props changed

* feat: implemented page store inside page details

* fix: role change in pages archives

* fix: rerending of pages on tab change

* fix: reimplementation of peek overview inside pages

* chore: typo fixes

* fix: issue suggestion widget selecting wrong issues on click

* feat: added labels in pages

* fix: deepsource errors fixed

* fix: build errors

* fix: review comments

* fix: removed swr hooks from the `usePage` store hook and refactored `issueEmbed` hook

* fix: resolved reviewed comments

---------

Co-authored-by: Rahul R <rahulr@Rahuls-MacBook-Pro.local>
2024-01-22 13:22:09 +05:30
Bavisetti Narayan
d3dedc8e51 fix: user profile issues (#3409) 2024-01-22 13:22:09 +05:30
rahulramesha
d656f8e62a fix: update global issues filter and enable profile issues (#3410)
* fix all issues and fix profile issues

* minor comments update

* minor change nullish check logic

* update nullish check logic

---------

Co-authored-by: Rahul R <rahulr@Rahuls-MacBook-Pro.local>
2024-01-22 13:22:09 +05:30
Anmol Singh Bhatia
864519e770 chore: issue filter loader improvement (#3406) 2024-01-22 13:22:09 +05:30
Nikhil
034f0a06db fix: key validation when magic sign in (#3403) 2024-01-22 13:22:09 +05:30
dependabot[bot]
7263cb072c chore(deps): bump follow-redirects from 1.15.3 to 1.15.4 (#3349)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-22 13:22:09 +05:30
rahulramesha
ea3a0362b0 fix: enable global/ all issues (#3405)
* fix global issues and views

* remove separate layouts for specific views

* add permissions to views

* fix global issues filters

---------

Co-authored-by: Rahul R <rahulr@Rahuls-MacBook-Pro.local>
2024-01-22 13:22:09 +05:30
Bavisetti Narayan
c9337d4a41 feat: dashboard widgets (#3362)
* fix: created dashboard, widgets and dashboard widget model

* fix: new user home dashboard

* chore: recent projects list

* chore: recent collaborators

* chore: priority order change

* chore: payload changes

* chore: collaborator's active issue count

* chore: all dashboard widgets added with services and typs

* chore: centered metric for pie chart

* chore: widget filters

* chore: created issue filter

* fix: created and assigned issues payload change

* chore: created issue payload change

* fix: date filter change

* chore: implement filters

* fix: added expansion fields

* fix: changed issue structure with relation

* chore: new issues response

* fix: project member fix

* chore: updated issue_relation structure

* chore: code cleanup

* chore: update issues response and added empty states

* fix: button text wrap

* chore: update empty state messages

* fix: filters

* chore: update dark mode empty states

* build-error: Type check in the issue relation service

* fix: issues redirection

* fix: project empty state

* chore: project member active check

* chore: project member check in state and priority

* chore: remove console logs and replace harcoded values with constants

* fix: code refactoring

* fix: key name changed

* refactor: mapping through similar components using an array

* fix: build errors

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
Co-authored-by: gurusainath <gurusainath007@gmail.com>
2024-01-22 13:22:09 +05:30
Prateek Shourya
f347c1cd69 refactor: update create/update issue modal to use currently active store's create/update method. (#3395)
* refactor: update `create/update issue` modal to use currently active store's create/update method.

* chore: add condition to avoid multiple API calls if the current store is MODULE or CYCLE.

* remove: console log

* chore: update `currentStore` to `storeType`.
2024-01-22 13:21:45 +05:30
Nikhil
3f07c48b35 chore: update issue template to auto assign and update labels (#3404) 2024-01-22 13:21:45 +05:30
M. Palanikannan
04b2214bf2 🐛 fix: Hide drag handle when cursor leaves the editor container (#3401)
This fix adds support for hiding the drag handle when the cursor leaves the editor container. It improves the user experience by providing a cleaner interface and removing unnecessary visual elements especially while scrolling.

- Add `hideDragHandle` prop to `EditorContainer` component in `editor-container.tsx`.
- Implement `onMouseLeave` event handler in `EditorContainer` to invoke `hideDragHandle` function.
- Update `DragAndDrop` extension in `drag-drop.tsx` to accept a `setHideDragHandle` function as an optional parameter.
- Pass the `setHideDragHandle` function from `RichTextEditor` component to `DragAndDrop` extension in `RichTextEditorExtensions` function in `index.tsx`.
- Set `hideDragHandleOnMouseLeave` state in `RichTextEditor` component to store the `hideDragHandlerFromDragDrop` function.
- Create `setHideDragHandleFunction` callback function in `RichTextEditor` to update the `hideDragHandleOnMouseLeave` state.
- Pass `hideDragHandleOnMouseLeave` as `hideDragHandle` prop to `EditorContainer` component in `RichTextEditor`.
2024-01-22 13:21:45 +05:30
Prateek Shourya
1adb38655a fix: stack integration disable button mutation issue in project settings. (#3402) 2024-01-22 13:21:45 +05:30
Manish Gupta
4ab64b6905 dev: self host local build for other arch cpu (#3358)
handled x86_64 along with amd64
2024-01-22 13:21:45 +05:30
sriram veeraghanta
4b0d85591e fix: updated env variables at root (#3390) 2024-01-22 13:21:45 +05:30
Erhan
8e2789af3e Fix self-hosting docker-compose doc link (#3389)
Fixes the link as it leads to a 404 at the moment in the documentation / readme.
2024-01-22 13:21:45 +05:30
rahulramesha
3592feb3d7 fix error meesage on successful error message (#3387)
Co-authored-by: Rahul R <rahulr@Rahuls-MacBook-Pro.local>
2024-01-22 13:21:45 +05:30
Nikhil
bc6a2542f5 dev: remove slack ping (#3377) 2024-01-22 13:20:06 +05:30
Prateek Shourya
5e2d93df52 fix: project views bugs related to store refactor. (#3391)
* chore: remove debounce logic to fix create/ update view modal bugs.

* fix: bug in delete views not mutating the store.

* chore: replace `Project Empty State` with `Project Views Empty State`.

* chore: add issue peek overview.

* refactor: issue update, delete actions for project views layout.
fix: issue update and delete action throwing error bug.
fix: issue quick add throwing error bug.
2024-01-22 13:19:44 +05:30
M. Palanikannan
cce349b805 [chore]: Removed explicit dependencies and cleaned up turbo config (#3388)
* Removed explicit dependencies and cleaned up turbo config

* fix: upgrade turbo

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2024-01-22 13:19:44 +05:30
rahulramesha
fadda7cf04 fix: refactor related bugs (#3384)
* fix sub issues inside issue detail

* close peek over view after opening issue detail

* fix error while opening peek overview

* fix saving project views

---------

Co-authored-by: Rahul R <rahulr@Rahuls-MacBook-Pro.local>
2024-01-22 13:19:44 +05:30
Anmol Singh Bhatia
c4093d29a7 chore: filter and display properties improvement (#3382) 2024-01-22 13:19:44 +05:30
Anmol Singh Bhatia
8c89e9cc01 chore: esc to close peek overview added (#3380) 2024-01-22 13:19:44 +05:30
Anmol Singh Bhatia
5625a3581a fix: drag and delete issue (#3379) 2024-01-22 13:19:44 +05:30
Lakhan Baheti
ee387c4222 chore webhook create page removed (#3376)
* chore webhook create page removed

* fix: removed unused variables
2024-01-22 13:19:44 +05:30
guru_sainath
6a16a98b03 chore: update in sub-issues component and property validation and issue loaders (#3375)
* fix: handled undefined issue_id in list layout

* chore: refactor peek overview and user role validation.

* chore: sub issues

* fix: sub issues state distribution changed

* chore: sub_issues implementation in issue detail page

* chore: fixes in cycle/ module layout.
* Fix progress chart
* Module issues's update/ delete.
* Peek Overview for Modules/ Cycle.
* Fix Cycle Filters not applying bug.

---------

Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2024-01-22 13:19:43 +05:30
sriram veeraghanta
11f84a986c chore: formatting all python files using black formatter (#3366) 2024-01-22 13:19:43 +05:30
rahulramesha
4b0d48b290 enable peekoverview for spreadsheet and minor refactor for faster opening of the peekoverview component (#3361)
Co-authored-by: Rahul R <rahulr@Rahuls-MacBook-Pro.local>
2024-01-22 13:19:43 +05:30
rahulramesha
9789068880 fix: project loaders for mobx store (#3356)
* add loaders to all the dropdowns outside project wrpper

* fix build errors

* minor refactor for project states color

---------

Co-authored-by: Rahul R <rahulr@Rahuls-MacBook-Pro.local>
2024-01-22 13:19:43 +05:30
Henit Chobisa
151c355177 [FIX] Pages Malfunctioning on Load and Recent Pages Computation (#3359)
* fix: fixed `usePage` hook returning context instead of IPageStore

* fix: updated recent pages with `updated_at` instead of `created_at`

* fix: thown error instead of returning empty array
2024-01-22 13:19:43 +05:30
AbId KhAn
38580c3940 Fix env substitute issue in websocket docker setup (#3296)
* fix websocket connection issue in docker makeplane/plane#3195

* fix websocket connection issue for local env with removing from the prod nginx conf template makeplane/plane#319

* fix env substitution issue  of proxy_set_header makeplane/plane#3196

* review fixes

---------

Co-authored-by: Manish Gupta <manish@mgupta.me>
2024-01-22 13:19:43 +05:30
sriram veeraghanta
7ff91fdb82 fix: create more toggle fixes in create issue modal (#3355)
* fix: create more issue bugfixes

* fix: removing all warning
2024-01-22 13:19:43 +05:30
sriram veeraghanta
a679b42200 fix: create sync action (#3353)
* fix: create sync action changes

* fix: typo changes
2024-01-22 13:19:43 +05:30
M. Palanikannan
27762ea500 fix: inline code blocks, code blocks and links have saner behaviour (#3318)
* fix: removed backticks in inline code blocks

* added better error handling while cancelling uploads

* fix: inline code blocks, code blocks and links have saner behaviour

- Inline code blocks are now exitable, don't have backticks, have better padding vertically and better regex matching
- Code blocks on the top and bottom of the document are now exitable via Up and Down Arrow keys
- Links are now exitable while being autolinkable via a custom re-write of the tiptap-link-extension

* fix: more robust link checking
2024-01-22 13:19:43 +05:30
guru_sainath
2cd5dbcd02 chore: Error Handling and Validation Updates (#3351)
* fix: handled undefined issue_id in list layout

* chore: updated label select dropdown in the issue detail

* fix: peekoverview issue is resolved

* chore: user role validation for issue details.

* fix: Link, Attachement, parent mutation

* build-error: build error resolved in peekoverview

* chore: user role validation for issue details.

* chore: user role validation for `issue description`, `parent`, `relation` and `subscription`.

* chore: issue subscription mutation

* chore: user role validation for `labels` in issue details.

---------

Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
2024-01-22 13:19:43 +05:30
rahulramesha
96868760a3 update swr config to not fetch everything on focus (#3350)
Co-authored-by: Rahul R <rahulr@Rahuls-MacBook-Pro.local>
2024-01-22 13:19:43 +05:30
rahulramesha
df97b35a99 chore: Refactor Spreadsheet view for better code maintainability and performance (#3322)
* refcator spreadsheet to use table and roow based approach rather than column based

* update spreadsheet and optimized layout

* fix issues in spread sheet

* close quick action menu on click

---------

Co-authored-by: Rahul R <rahulr@Rahuls-MacBook-Pro.local>
2024-01-22 13:19:43 +05:30
guru_sainath
4611ec0b83 chore: refactored and resolved build issues on the issues and issue detail page (#3340)
* fix: handled undefined issue_id in list layout

* dev: issue detail store and optimization

* dev: issue filter and list operations

* fix: typo on labels update

* dev: Handled all issues in the list layout in project issues

* dev: handled kanban and auick add issue in swimlanes

* chore: fixed peekoverview in kanban

* chore: fixed peekoverview in calendar

* chore: fixed peekoverview in gantt

* chore: updated quick add in the gantt chart

* chore: handled issue detail properties and resolved build issues

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
2024-01-22 13:19:43 +05:30
Henit Chobisa
e6b31e2550 fix: link preview editor (#3335)
* feat: added link preview plugin in document editor

* fix: readonly editor page renderer css

* fix: autolink issue with links

* chore: added floating UI

* feat: added link preview components

* feat: added floating UI to page renderer for link previews

* feat: added actionCompleteHandler to page renderer

* chore: Lock file changes

* fix: regex security error

* chore: updated radix with lucid icons

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
2024-01-22 13:19:43 +05:30
Nikhil
59fb371e3d dev: fix smtp configuration (#3339) 2024-01-22 13:19:43 +05:30
Nikhil
23e5306f6d dev: update the instance urls (#3329) 2024-01-22 13:19:43 +05:30
Anmol Singh Bhatia
0f99fb302b chore: modal and dropdown improvement (#3332)
* dev: dropdown key down custom hook added

* chore: plane ui dropdowns updated

* chore: cycle and module tab index added in modals

* chore: view and page tab index added in modals

* chore: issue modal tab indexing added

* chore: project modal tab indexing added

* fix: build fix

* build-error: build error in pages new structure and reverted back to old page structure

---------

Co-authored-by: gurusainath <gurusainath007@gmail.com>
2024-01-22 13:19:43 +05:30
Manish Gupta
0e49d616b7 fixes web container public assets (#3336) 2024-01-22 13:19:43 +05:30
Nikhil
e72920d33e fix: update jira summary endpoints (#3333)
* dev: update jira summary endpoints

* dev: update jira project key validations

* dev: updated key length
2024-01-22 13:19:43 +05:30
Nikhil
80dc38b649 fix: jira importer validations (#3323)
* fix: jira importer validations

* dev: update validation for cloud hostname

* dev: update the function to be used externally

* dev: update codeql workflow

* dev: update repository selection api
2024-01-22 13:19:43 +05:30
Nikhil
43b503c756 fix: security warnings related to information exposure and regex validations (#3325) 2024-01-22 13:19:43 +05:30
dependabot[bot]
68d370fd86 chore(deps): bump tj-actions/changed-files in /.github/workflows (#3327)
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 38 to 41.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](https://github.com/tj-actions/changed-files/compare/v38...v41)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-22 13:19:43 +05:30
Bavisetti Narayan
5e03f3dd82 chore: mobile configs (#3328)
* chore: mobile configs

* chore: mobile configurations changed

* chore: removed the slack id

* chore: reversed google client id
2024-01-22 13:19:43 +05:30
rahulramesha
1257a88089 fix: breaking cycle issues and replacing router.push with Links (#3330)
* fix cycle creation and active cycle map

* minor fix in cycle store

* create cycle breaking fix

* replace last possible bits of router.push with Link

---------

Co-authored-by: Rahul R <rahulr@Rahuls-MacBook-Pro.local>
2024-01-22 13:19:43 +05:30
Prateek Shourya
12a3392722 fix: estimate order not maintained in create/ update modal. (#3326)
* fix: estimate order not maintained in create/ update modal.

* fix: estimate points mutation on update.
2024-01-22 13:19:43 +05:30
sriram veeraghanta
b62a1b11b1 fix: pages store structure changes 2024-01-22 13:19:43 +05:30
Anmol Singh Bhatia
0a05aef046 fix: workspace invitations response updated (#3321) 2024-01-22 13:19:43 +05:30
Anmol Singh Bhatia
efd3ebf067 chore: bug fixes and improvement (#3303)
* refactor: updated preloaded function for the list view quick add

* fix: resolved bug in the assignee dropdown

* chore: issue sidebar link improvement

* fix: resolved subscription store bug

* chore: updated preloaded function for the kanban layout quick add

* chore: resolved issues in the list filters and component

* chore: filter store updated

* fix: issue serializer changed

* chore: quick add preload function updated

* fix: build error

* fix: serializer changed

* fix: minor request change

* chore: resolved build issues and updated the prepopulated data in the quick add issue.

* fix: build fix and code refactor

* fix: spreadsheet layout quick add fix

* fix: issue peek overview link section updated

* fix: cycle status bug fix

* fix: serializer changes

* fix: assignee and labels listing

* chore: issue modal parent_id default value updated

* fix: cycle and module issue serializer change

* fix: cycle list serializer changed

* chore: prepopulated validation in both list and kanban for quick add and group header add issues

* chore: group header validation added

* fix: issue response payload change

* dev: make cycle and module issue create response simillar

* chore: custom control link component added

* dev: make issue create and update response simillar to list and retrieve

* fix: build error

* chore: control link component improvement

* chore: globalise issue peek overview

* chore: control link component improvement

* chore: made changes and optimised the issue peek overview root

* build-error: resolved build erros for issueId dependancy from issue detail store

* chore: peek overview link fix

* dev: update state nullable rule

---------

Co-authored-by: gurusainath <gurusainath007@gmail.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
2024-01-22 13:19:43 +05:30
Prateek Shourya
266f14d550 fix: project identifier cursor behaviour in create project modal. (#3320) 2024-01-22 13:19:43 +05:30
Prateek Shourya
9eb8f41008 chore: UI/UX improvements (#3319)
* chore: add proper message for cycle/ module having start & end date but isn't active yet.

* fix: infinite loader after updating workspace settings.

* fix: user profile icon dropdown doesn't closes automatically.

* style: fix inconsistent padding in cycle empty state.

* chore: remove multiple `empty state` in labels settings and improve add label logic.

* style: fix inconsistent padding in project label, integration and estimates empty state.

* style: fix integrations settings breadcrumb title.

* style: add proper `disabled` styles for email field in profile settings.

* style: fix cycle layout height.
2024-01-22 13:19:43 +05:30
M. Palanikannan
b340232e76 chore: Updated TableView component in table extension to solve sentry (#3309)
error of table not being defined while getting getBoundingClientRect()
and solve other TS issues

- Added ResolvedPos import from @tiptap/pm/model
- Updated setCellsBackgroundColor function parameter type to string
- Declared ToolboxItem type for toolbox items
- Modified columnsToolboxItems and rowsToolboxItems to use the ToolboxItem type
- Updated createToolbox function parameters to specify Element or null for triggerButton and ToolboxItem[] for items
- Added ts-expect-error comment above the toolbox variable declaration
- Updated update method parameter type to readonly Decoration[]
- Changed destructuring assignment of hoveredTable and hoveredCell in updateControls method to use Object.values and reduce method
- Added null check for this.table in updateControls method
- Wrapped the code that updates columnsControl and rowsControl with null checks for each control
- Replaced ts-ignore comments with proper dispatch calls in selectColumn and selectRow methods

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2024-01-22 13:19:43 +05:30
Aaryan Khandelwal
64ca267db4 dev: new create issue modal (#3312) 2024-01-22 13:19:43 +05:30
sriram veeraghanta
46e79dde27 fix: Login workflow depending on smtp is configured (#3307) 2024-01-22 13:19:43 +05:30
Jigin Jayaprakash
7272c54439 Fixes 3299 (#3308) 2024-01-22 13:19:20 +05:30
Nikhil
543636eb40 dev: update apiserver .env.example (#3412) 2024-01-19 16:13:22 +05:30
Facundo Martin Gordillo
6c2fecd322 fix: Updated "deployment documentation" link (#3413) 2024-01-19 16:10:41 +05:30
Nikhil
af5057defa fix: key validation when magic sign in (#3403) 2024-01-18 16:04:04 +05:30
dependabot[bot]
c5e7c2f6a8 chore(deps): bump follow-redirects from 1.15.3 to 1.15.4 (#3349)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-18 15:52:53 +05:30
Nikhil
eda1e46a2d chore: update issue template to auto assign and update labels (#3404) 2024-01-18 13:45:52 +05:30
Manish Gupta
ee78b4fe52 dev: self host local build for other arch cpu (#3358)
handled x86_64 along with amd64
2024-01-17 19:03:57 +05:30
sriram veeraghanta
a19598fec1 fix: updated env variables at root (#3390) 2024-01-17 16:44:31 +05:30
Erhan
afff9790d4 Fix self-hosting docker-compose doc link (#3389)
Fixes the link as it leads to a 404 at the moment in the documentation / readme.
2024-01-17 16:42:11 +05:30
rahulramesha
c0cd201b7c fix error meesage on successful error message (#3387)
Co-authored-by: Rahul R <rahulr@Rahuls-MacBook-Pro.local>
2024-01-17 16:35:21 +05:30
Nikhil
942785b7c0 dev: remove slack ping (#3377) 2024-01-16 19:09:42 +05:30
AbId KhAn
a70f551d17 Fix env substitute issue in websocket docker setup (#3296)
* fix websocket connection issue in docker makeplane/plane#3195

* fix websocket connection issue for local env with removing from the prod nginx conf template makeplane/plane#319

* fix env substitution issue  of proxy_set_header makeplane/plane#3196

* review fixes

---------

Co-authored-by: Manish Gupta <manish@mgupta.me>
2024-01-10 13:35:24 +05:30
Manish Gupta
2580e66d4b fixes web container public assets (#3336) 2024-01-09 22:50:51 +05:30
Nikhil
d887b780ae fix: update jira summary endpoints (#3333)
* dev: update jira summary endpoints

* dev: update jira project key validations

* dev: updated key length
2024-01-09 20:40:23 +05:30
Nikhil
4b0ccea146 fix: jira importer validations (#3323)
* fix: jira importer validations

* dev: update validation for cloud hostname

* dev: update the function to be used externally

* dev: update codeql workflow

* dev: update repository selection api
2024-01-08 23:27:09 +05:30
Nikhil
02a776396b fix: security warnings related to information exposure and regex validations (#3325) 2024-01-08 23:26:32 +05:30
dependabot[bot]
5cd93f5e59 chore(deps): bump tj-actions/changed-files in /.github/workflows (#3327)
Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 38 to 41.
- [Release notes](https://github.com/tj-actions/changed-files/releases)
- [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md)
- [Commits](https://github.com/tj-actions/changed-files/compare/v38...v41)

---
updated-dependencies:
- dependency-name: tj-actions/changed-files
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-08 22:45:10 +05:30
Jigin Jayaprakash
69b1d0a929 Fixes 3299 (#3308) 2024-01-04 17:26:14 +05:30
Anmol Singh Bhatia
b522de99ba chore: profile setting improvement (#3306) 2024-01-03 18:25:41 +05:30
Prateek Shourya
b58d7a715a style: remove unnecessary vertical scroll in All Issues Tabs. (#3300) 2024-01-03 12:52:55 +05:30
Bavisetti Narayan
87cd44bcd2 fix: migration file fixes (#3302) 2024-01-02 18:52:46 +05:30
Aaryan Khandelwal
804b7d8663 refactor: MobX store structure (#3228)
* query params from router as computed

* chore: setup workspace store and sub-stores

* chore: update router query store

* chore: update store types

* fix: pages store changes

* change observables and retain object reference

* fix build errors

* chore: changed the structure of workspace, project, cycle, module and pages

* fix: pages fixes

* fix: merge conflicts resolved

* chore: fixed workspace list

* chore: update workspace store accroding to the new response

* fix: adding page details to store

* fix: adding new contexts and providers

* dev: issues store and filters in new store

* dev: optimised the issue fetching in issue base store

* chore: project views id mapped

* update lodash set to directly run inside runInaction since it mutates the object

* fix: context changes

* code refactor kanban for better mainatinability

* optimize Kanban for performance

* chore: implemented hooks for all the created stores

* chore: removed bridge id

* css change and refactor

* chore: update cycle store structure

* chore: implement the new label root store

* chore: removed object structure

* chore: implement project view hook

* Kanban new store implementation for project issues

* fix project root for kanban

* feat: workspace and project members endpoint (#3092)

* fix: merge conflicts resolved

* issue properties optimization

* chore: user stores

* chore: create new store context and update hooks

* chore: setup inbox store and implement router store

* chore: initialize and implement project estimate store

* chore: initialize global view store

* kanban and list view optimization

* chore: use new cycle and module store. (#3172)

* chore: use new cycle and module store.

* chore: minor improvements.

* Revert "chore: merge develop"

This reverts commit 9d2e0e29e7370b55b48fc2fee4fd126093a6cc48, reversing
changes made to 9595493c42be3ea0ddd17b23a0b124555075c062.

* chore: implement useGlobalView hook

* refactor: projects & inbox store instances (#3179)

* refactor: projects & inbox store instances

* fix: formatting

* fix: action usage

* chore: implement useProjectState hook. (#3185)

* dev: issue, cycle store optimiation

* fix build for code

* dev: removed dummy variables

* dev: issue store

* fix: adding todos

* chore: removing legacy store

* dev: issues store types and typos

* chore: cycle module user properties

* fix legacy store deletion issues

* chore: change POST to PATCH

* fix issues rendering for project root

* chore: removed workspace details in workpsaceinvite

* chore: created models for display properties

* chore: setup member store and implement it everywhere

* refactor: module store (#3202)

* refactor: cycle store (#3192)

* refator: cycle store

* some more improvements.

* chore: implement useLabel hook. (#3190)

* refactor: inbox & project related stores. (#3193)

* refactor: inbox -> filter, issues, inoxes & project -> publish, projects store

* refactor: workspace-project-id name

* fix kanban dropdown overlapping issue

* fix kanban layout minor re rendering

* chore: implement useMember store everywhere

* chore: create and implement editor mention store

* chore: removed the issue view user property

* chore: created at id changed

* dev: segway intgegration (#3132)

* feat: implemented rabbitmq

* dev: initialize segway with queue setup

* dev: import refactors

* dev: create communication with the segway server

* dev: create new workers

* dev: create celery node queue for consuming messages from django

* dev: node to celery connection

* dev: setup segway and django connection

* dev: refactor the structure and add database integration to the app

* dev: add external id and source added

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>

* dev: github importer (#3205)

* dev: initiate github import

* dev: github importer all issues import

* dev: github comments and links for the imported issues

* dev: update controller to use logger and spread the resultData in getAllEntities

* dev: removed console log

* dev: update code structure and sync functions

* dev: updated retry logic when exception

* dev: add imported data as well

* dev: update logger and repo fetch

* dev: update jira integration to new structure

* dev: update migrations

* dev: update the reason field

* chore: workspace object id removed

* chore: view's creation fixed

* refactor: mobx store improvements. (#3213)

* fix: state and label errors

* chore: remove legacy code

* fix: branch build fix (#3214)

* branch build fix for release-* in case of space,backend,proxy

* fixes

* chore: update store names and types

* fix - file size limit not work on plane.settings.production (#3160)

* fix - file size limit not work on plane.settings.production

* fix - file size limit not work on plane.settings.production

* fix - file size limit not work on plane.settings.production, move to common.py

---------

Co-authored-by: luanduongtel4vn <hoangluan@tel4vn.com>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>

* style: instance admin email settings ui & ux update. (#3186)

* refactor: use-user-auth hook (#3215)

* refactor: use-user-auth hook

* fix: user store currentUserLoader

* refactor: project-view & application related stores (#3207)

* refactor: project-view & application related stores

* rename: projectViews -> projectViewIds

* fix: project-view favourite state in store

* chore: remove unnecessary hooks and contexts (#3217)

* chore: update issue assignee property component

* chore: bug fixes & improvement (#3218)

* chore: draft issue validation added to prevent saving empty or whitespace title

* chore: resolve scrolling issue in page empty state

* chore: kanban layout quick add issue improvement

* fix: bugs & improvements (#3189)

* fix: workspace invitation modal form values reset

* fix: profile sidebar avatar letter

* [refactor] Editor code refactoring (#3194)

* removed relative imports from editor core

* Update issue widget file paths and imports to use kebab case instead of camel case, to align with coding conventions and improve consistency.

* Update Tiptap core and extensions versions to 2.1.13 and Tiptap React version to 2.1.13. Update Tiptap table imports to use the new location in package @tiptap/pm/tables. Update AlertLabel component to use the new type definition for LucideIcon.

* updated lock file

* removed default exports from editor/core

* fixed injecting css into the core package itself

* seperated css code to have single source of origin wrt to the package

* removed default imports from document editor

* all instances using index as key while mapping fixed

* Update Lite Text Editor package.json to remove @plane/editor-types as a dependency.

Update Lite Text Editor index.ts to update the import of IMentionSuggestion and IMentionHighlight from @plane/editor-types to @plane/editor-core.

Update Lite Text Editor ui/index.tsx to update the import of UploadImage, DeleteImage, IMentionSuggestion, and RestoreImage from @plane/editor-types to @plane/editor-core.

Update Lite Text Editor ui/menus/fixed-menu/index.tsx to update the import of UploadImage from @plane/editor-types to @plane/editor-core.

Update turbo.json to remove @plane/editor-types#build as a dependency for @plane/lite-text-editor#build, @plane/rich-text-editor#build, and @plane/document-editor#build.

* Remove deprecated import and adjust tippy.js usage in the slash-commands.tsx file of the editor extensions package.

* Update dependencies in `rich-text-editor/package.json`, remove `@plane/editor-types` and add `@plane/editor-core` in `rich-text-editor/src/index.ts`, and update imports in `rich-text-editor/src/ui/extensions/index.tsx` and `rich-text-editor/src/ui/index.tsx` to use `@plane/editor-core` instead of `@plane/editor-types`.

* Update package.json dependencies and add new types for image deletion, upload, restore, mention highlight, mention suggestion, and slash command item.

* Update import statements in various files to use the new package "@plane/editor-core" instead of "@plane/editor-types".

* fixed document editor to follow conventions

* Refactor imports in the Rich Text Editor package to use relative paths instead of absolute paths.

- Updated imports in `index.ts`, `ui/index.tsx`, and `ui/menus/bubble-menu/index.tsx` to use relative paths.
- Updated `tsconfig.json` to include the `baseUrl` compiler option and adjust the `include` and `exclude` paths.

* Refactor Lite Text Editor code to use relative import paths instead of absolute import paths.

* Added LucideIconType to the exports in index.ts for use in other files.
Created a new file lucide-icon.ts which contains the type LucideIconType.
Updated the icon type in HeadingOneItem in menu-items/index.tsx to use LucideIconType.
Updated the Icon type in AlertLabel in alert-label.tsx to use LucideIconType.
Updated the Icon type in VerticalDropdownItemProps in vertical-dropdown-menu.tsx to use LucideIconType.
Updated the Icon type in BubbleMenuItem in fixed-menu/index.tsx to use LucideIconType.
Deleted the file tooltip.tsx since it is no longer used.
Updated the Icon type in BubbleMenuItem in bubble-menu/index.tsx to use LucideIconType.

* ♻️ refactor: simplify rendering logic in slash-commands.tsx

The rendering logic in the file "slash-commands.tsx" has been simplified. Previously, the code used inline positioning for the popup, but it has now been removed. Instead of appending the popup to the document body, it is now appended to the element with the ID "tiptap-container". The "flip" option has also been removed. These changes have improved the readability and maintainability of the code.

* fixed build errors caused due to core's internal imports

* regression: fixed pages not saving issue and not duplicating with proper content issue

* build: Update @tiptap dependencies

Updated the @tiptap dependencies in the package.json files of `document-editor`, `extensions`, and `rich-text-editor` packages to version 2.1.13.

* 🚑 fix: Correct appendTo selector in slash-commands.tsx

Update the `appendTo` function call in `slash-commands.tsx` to use the correct selector `#editor-container` instead of `#tiptap-container`. This ensures that the component is appended to the appropriate container in the editor extension.

Note: The commit message assumes that the change is a fix for an issue or error. If it's not a fix, please provide more context so that an appropriate commit type can be determined.

* style: email placeholder changed across the platform (#3206)

* style: email placeholder changed across the platform

* fix: placeholder text

* dev: updated new filter endpoints and restructured issue and issue filters store

* implement issues and replace useMobxStore

* remove all store legacy references

* dev: updated the orderby and subgroupby filters data

* dev:added projectId in issue filters for consistency

* fix more build errors

* dev: updated profile issues

* dev: removed store legacy

* dev: active cycle issues in the cycle issue store

* fix additional build errors and memoize issueActions in each layout component

* change store enums

* remove all useMobxStore references

* fix more build errors

* dev: reverted workspace invitation

* fix: build errors and warnings

* fix: optimistic update for instant operations (#3221)

* fix: update functions failed case

* fix: typo

* chore: revert back to optimistic update approach for all `update related actions` (#3219)

* fix: merge conflicts resolved

* chore: update memberMap logic in components

* add assignees to kanban groups and properties

* dev: migration fixes

* final bit of optimization on list view

* change all TODOs that are to be done before this release to FIXME

* change base Kanban TODOs that are to be done before this release to FIXME

* dev: add fields and expand for app serializers

* dev: issue detail store

* dev: update issue serializer to return object ids

* fix: Instance key added in settings and converted issues list api to arry instead of dict

* fix: removing segway files

* dev: control expand through query parameters

* revert: github importer

* Revert "dev: segway intgegration (#3132)"

This reverts commit 1cc18a0915.

* dev: remove migrations for segway

* dev: issue structure change and created workspacebasemodel

* dev: issue detail serializer

* fix: changed workspace dict

* dev: updated new issue structure

* chore: build fix

* dev: issue detail store refactor

* dev: created list endpoint for issue-relation

* dev: added issue attachments in issue detail store

* dev: added issue activity computed

* fix: build error

* chore: peek overview modal context added

* chore: build error fix

* dev: added sub_issues in issue details store

* dev: added complete issue serializer for sub issues

* dev: resolved type errors in issue root store

* dev: changed the issue relation structure

* chore: new global dropdowns

* chore: build error fix

* chore: cycle and module selection if disabled

* dev: removed unnecessary code from the workspace root

* chore: build error fix

* chore: issue relation remove endpoint

* fix: build error

* dev: typos and implemented issue relation store

* fix: yarn lock updated

* style: update the UI of all the dropdowns

* fix: state store fixes

* fix: key issue

* fix: state store console logs removed

* refactor: member dropdowns

* fix: moving types to packages

* fix: dropdown arrow positioning

* dev: removed logs

* style: label dropdown

* chore: restrict description notifications

* chore: description changes

* chore: update spreadsheet layout dropdowns

* fix: build errors

* chore: duplicate key change

* fix: ui bugs

* chore: relation activity change

* chore: comment activity changes

* chore: blocking issue removal

* chore: added project_id for relation

* chore: issue relation store and component

* chore: issue redirection issue in the issue realtion in detail page

* chore: created activity changed

* chore: issue links new store implementation on the issue detail

* chore: issue relation deletion acitivity changed

* chore: issue attachments new store implementation on the issue detail

* chore: workspace level issues

* fix: build errors

---------

Co-authored-by: rahulramesha <rahulramesham@gmail.com>
Co-authored-by: gurusainath <gurusainath007@gmail.com>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com>
Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
Co-authored-by: Lakhan Baheti <94619783+1akhanBaheti@users.noreply.github.com>
Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com>
Co-authored-by: Manish Gupta <59428681+mguptahub@users.noreply.github.com>
Co-authored-by: Hoang Luan <luandnh98@gmail.com>
Co-authored-by: luanduongtel4vn <hoangluan@tel4vn.com>
Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Co-authored-by: M. Palanikannan <73993394+Palanikannan1437@users.noreply.github.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so>
2024-01-02 18:12:55 +05:30
Prateek Shourya
1539340113 chore: date and time standardization all across the platform. (#3283)
* chore: date and time standardization all across the platform.

* chore: update `renderFormattedTime` function.
* remove unwanted code.

* fix: build errors

* chore: update `renderFormattedTime` function params.
2024-01-02 14:45:51 +05:30
Anmol Singh Bhatia
d9ee692ce9 chore: gpt modal refactor (#3276)
* chore: gpt modal refactor

* chore: refactored gpt assistant modal to popover component
2024-01-02 13:07:12 +05:30
sriram veeraghanta
c3ba9f61d8 Merge pull request #3290 from makeplane/develop
fix: project settings form not loading new data while switching between projects
2023-12-30 21:33:40 +05:30
sriram veeraghanta
6e702d6cc7 fix: project settings form not loading new data while switching between projects 2023-12-30 21:32:18 +05:30
sriram veeraghanta
447a8bc2f8 Merge branch 'master' of github.com:makeplane/plane into preview 2023-12-30 18:25:53 +05:30
sriram veeraghanta
34d872beca Merge pull request #3288 from makeplane/develop
Style: custom theme UI fixes (#3284)
2023-12-30 11:07:04 +05:30
Prateek Shourya
4263e9b507 Style: custom theme UI fixes (#3284)
* style: fix `background-color` inconsistency in issue layout when custom theme is applied.

* fix: theme dropdown overlapping with input color picker icons (z-index issue).
2023-12-30 11:05:15 +05:30
sriram veeraghanta
768b4abf22 Merge pull request #3287 from makeplane/preview
promote: preview to master
2023-12-29 22:11:37 +05:30
sriram veeraghanta
6f0af4689b Merge pull request #3286 from makeplane/develop
promote: develop to preview
2023-12-29 22:10:24 +05:30
sriram veeraghanta
22bbdd5ab8 fix: add validations 2023-12-29 22:07:52 +05:30
sriram veeraghanta
28a9c53202 Merge pull request #3281 from makeplane/preview
release: preview to master
2023-12-29 20:28:18 +05:30
sriram veeraghanta
33d88f56da Merge pull request #3280 from makeplane/develop
promote: develop changes to preview
2023-12-29 18:21:26 +05:30
sriram veeraghanta
23001d425f Merge branch 'preview' of github.com:makeplane/plane into develop 2023-12-29 18:16:08 +05:30
sriram veeraghanta
5de94c575a fix: workspace invitation status fixes (#3279) 2023-12-29 17:49:39 +05:30
AbId KhAn
f200acc1e8 fix websocket connection issue in docker makeplane/plane#3195 (#3196)
* fix websocket connection issue in docker makeplane/plane#3195

* fix websocket connection issue for local env with removing from the prod nginx conf template makeplane/plane#319
2023-12-29 16:49:09 +05:30
Prateek Shourya
f132fe59ae Chore/user role validation (#3260)
* chore: remove `Save View` option for guest/ viewer from project issues header.

* chore: allow guest/ viewer to clear applied filters in project issues.

* chore: disable `date`, `label` property access for guests/ viewer in all project issue views.

* chore: update `Workspace Issues` -> `All Issues` header.

* chore: refactor apply/ clear filter implementation in All Issues.

* Revert "chore: refactor apply/ clear filter implementation in All Issues."

This reverts commit 024822d54f4061eb2686d811a2b87cd0789b6b90.

* Revert "chore: allow guest/ viewer to clear applied filters in project issues."

This reverts commit 3dae871d23424d55abac95e16522696ad3c2a5c4.

* Revert "chore: update `Workspace Issues` -> `All Issues` header."

This reverts commit 03f90be188bc6b2f98a780ae22e0a29d9c59268d.

* chore: remove `cursor-pointer` style from non actionable issue properties.
2023-12-29 16:35:03 +05:30
Nikhil
e584259c04 feat: add external id and external source field to issue, cycle, module, label, comment, state and inbox issues (#3277) 2023-12-29 16:32:12 +05:30
sriram veeraghanta
a77ceb636f Merge branches 'develop' and 'develop' of github.com:makeplane/plane into develop 2023-12-29 16:25:54 +05:30
Bavisetti Narayan
10ab081a0b chore: cycle current status (#3270)
* dev: cycle status

* chore: cycle status logic updated

---------

Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so>
2023-12-29 15:24:07 +05:30
Anmol Singh Bhatia
1d5a3a02c1 fix: issue parent select (#3267) 2023-12-29 15:22:30 +05:30
sriram veeraghanta
62b9b259c0 fix: adding issue title to the activity (#3271) 2023-12-28 18:53:06 +05:30
Anmol Singh Bhatia
91e84aede1 fix: onboarding redirection loop and bug fixes (#3250)
* chore: try and catch added in handleSignInRedirection

* chore: remove unnecessary hooks

* fix: handleCopyIssueLink url updated

* chore: swap next_url with next_path and validate redirection logic for next_path url
2023-12-28 17:17:04 +05:30
Prateek Shourya
71bf049e89 Style/UI improvements (#3269)
* style: update `Workspace Issues` -> `All Issues` header.

* style: fix issue activity text overflow issue.
2023-12-28 14:30:33 +05:30
sriram veeraghanta
685e62a72f fix: replacing onclick redirections with link tag. (#3263)
* fix: adding links to dashboard summary items

* fix: adding links to workspace sidebar dropdown

* fix: adding links to the sidebar
2023-12-28 14:29:20 +05:30
sriram veeraghanta
d9bd43f43c fix: adding links to the sidebar 2023-12-28 11:54:16 +05:30
sriram veeraghanta
10bdcc906c fix: adding links to workspace sidebar dropdown 2023-12-27 16:35:43 +05:30
sriram veeraghanta
54964924f0 fix: adding links to dashboard summary items 2023-12-27 16:15:56 +05:30
sriram veeraghanta
ff8008cbed Merge branch 'preview' of github.com:makeplane/plane into develop 2023-12-27 15:37:03 +05:30
Bavisetti Narayan
78428fb564 chore: estimates and issue parent id fixes (#3188)
* chore: validations and bug fixes

* chore: estimate point character limit validation

---------

Co-authored-by: LAKHAN BAHETI <lakhanbaheti9@gmail.com>
2023-12-27 15:35:15 +05:30
Bavisetti Narayan
b2824366a8 fix: external api serializer (#3259)
* fix: passed workspace id and project id as context

* fix: issue activity
2023-12-27 15:33:31 +05:30
Anmol Singh Bhatia
05eb728c40 chore: cycle & module sidebar improvement (#3251)
* chore: no lead option added in module lead select dropdown

* chore: module lead select dropdown code refactor

* chore: cycle sidebar improvement

* chore: module sidebar improvement

* style: cycle and module sidebar improvement

* style: app sidebar improvement
2023-12-27 15:16:13 +05:30
sriram veeraghanta
ad2471c5a7 Merge pull request #3258 from makeplane/develop
fix: adding access token to build Pr (#3257)
2023-12-26 19:00:10 +05:30
sriram veeraghanta
440cfc0f20 fix: adding access token to build Pr (#3257) 2023-12-26 18:59:24 +05:30
sriram veeraghanta
9a32d722dc Merge pull request #3256 from makeplane/develop
promote: develop to preview
2023-12-26 18:22:04 +05:30
sriram veeraghanta
37fe9a185c fix: adding token to the build pr workflow (#3254)
* fix: adding token to the build pr workflow

* fix: using automatic github token
2023-12-26 18:13:13 +05:30
sriram veeraghanta
eabb31a764 fix: upgrading turbo repo changes 2023-12-26 17:04:59 +05:30
sriram veeraghanta
1e27c7936d fix: eslint dependencies fixes 2023-12-26 17:03:43 +05:30
M. Palanikannan
0a617eec26 🔧 fix: Pages mutation on component mount (#3230)
* 🔧 chore: Update revalidate option and add useEffect hook in PageDetailsPage component

- Set the `revalidate` option to `false` in the `commonSwrOptions` object.
- Add an `useEffect` hook that calls the `mutatePageDetails` function with specific options.
- Inside the `useEffect` hook, set `revalidate` to `true`, `populateCache` to `true`, and define a rollback function for error handling.
- If an error occurs during the rollback, display an error alert message using the `actionCompleteAlert` function.
- In the `updatePage` function, ensure that `workspaceSlug`, `projectId`, and `pageId` are all defined before proceeding.

* ♻️ refactor: Update code styling for inline code in CoreEditorExtensions

In the `packages/editor/core/src/ui/extensions/index.tsx` file, the code for inline code styling in the CoreEditorExtensions component has been updated. Previously, it was set to `false`, but now it is defined as an object with specific HTML attributes. The code styling includes a rounded-md background, custom primary color, margin, padding, font styling, and disabling spellcheck. This change improves the visual appearance and readability of inline code elements in the editor.
2023-12-26 16:45:00 +05:30
sriram veeraghanta
ad20079e0c Merge pull request #3249 from makeplane/develop
promote: moving stable changes from develop to preview
2023-12-26 14:25:01 +05:30
Lakhan Baheti
e14baf17a7 style: create webhook page to modal (#3223)
* style: create webhook page to modal

* fix: create page removed

* fix: auto modal close on empty state

* fix: secret key heading removed from generated modal
2023-12-26 13:28:47 +05:30
Anmol Singh Bhatia
542b18a585 fix: custom theme validation added (#3246) 2023-12-26 13:11:59 +05:30
sriram veeraghanta
37d88cc05b Merge pull request #3233 from makeplane/fix/types-slash-command
fix: Fixed types for slash commands by inspecting generics
2023-12-22 19:02:20 +05:30
Palanikannan1437
b652d1a8f1 fix: Fixed types for slash commands by inspecting generics 2023-12-22 18:52:00 +05:30
sriram veeraghanta
98b7a941f9 fix: yarn lock fixes 2023-12-22 18:37:14 +05:30
sriram veeraghanta
dc131ee05b Merge pull request #3232 from makeplane/release-0.15
fix: moving patch fixes from release 0.15 to 0.14.2
2023-12-22 18:19:47 +05:30
M. Palanikannan
0e3d15215d [fix] Image insertion position at cursor position and Updated placeholder text. (#3224)
* Fix position bug in the UploadImagesPlugin widget decoration and adjust transaction to correctly insert image node and set meta data for image removal.

* Update CSS styles in editor.css to remove margin top and bottom on images and img placeholders and adjust the margin on the table in the editor container.

* Better typescript support for images extension.

Update the `Command` extension in `slash-commands.tsx` to include a `SlashCommandOptions` type for better TS support and allow spaces in the suggestion options and modify the `image` suggestion's search terms to include "img" in addition to "photo", "picture", and "media".
2023-12-22 18:10:21 +05:30
Lakhan Baheti
816b6abf3b chore: allow non-latin characters to project identifier (#3226) 2023-12-22 18:10:21 +05:30
Lakhan Baheti
6ecaa661a7 style: email placeholder changed across the platform (#3206)
* style: email placeholder changed across the platform

* fix: placeholder text
2023-12-22 18:10:21 +05:30
M. Palanikannan
0b1efb173f [refactor] Editor code refactoring (#3194)
* removed relative imports from editor core

* Update issue widget file paths and imports to use kebab case instead of camel case, to align with coding conventions and improve consistency.

* Update Tiptap core and extensions versions to 2.1.13 and Tiptap React version to 2.1.13. Update Tiptap table imports to use the new location in package @tiptap/pm/tables. Update AlertLabel component to use the new type definition for LucideIcon.

* updated lock file

* removed default exports from editor/core

* fixed injecting css into the core package itself

* seperated css code to have single source of origin wrt to the package

* removed default imports from document editor

* all instances using index as key while mapping fixed

* Update Lite Text Editor package.json to remove @plane/editor-types as a dependency.

Update Lite Text Editor index.ts to update the import of IMentionSuggestion and IMentionHighlight from @plane/editor-types to @plane/editor-core.

Update Lite Text Editor ui/index.tsx to update the import of UploadImage, DeleteImage, IMentionSuggestion, and RestoreImage from @plane/editor-types to @plane/editor-core.

Update Lite Text Editor ui/menus/fixed-menu/index.tsx to update the import of UploadImage from @plane/editor-types to @plane/editor-core.

Update turbo.json to remove @plane/editor-types#build as a dependency for @plane/lite-text-editor#build, @plane/rich-text-editor#build, and @plane/document-editor#build.

* Remove deprecated import and adjust tippy.js usage in the slash-commands.tsx file of the editor extensions package.

* Update dependencies in `rich-text-editor/package.json`, remove `@plane/editor-types` and add `@plane/editor-core` in `rich-text-editor/src/index.ts`, and update imports in `rich-text-editor/src/ui/extensions/index.tsx` and `rich-text-editor/src/ui/index.tsx` to use `@plane/editor-core` instead of `@plane/editor-types`.

* Update package.json dependencies and add new types for image deletion, upload, restore, mention highlight, mention suggestion, and slash command item.

* Update import statements in various files to use the new package "@plane/editor-core" instead of "@plane/editor-types".

* fixed document editor to follow conventions

* Refactor imports in the Rich Text Editor package to use relative paths instead of absolute paths.

- Updated imports in `index.ts`, `ui/index.tsx`, and `ui/menus/bubble-menu/index.tsx` to use relative paths.
- Updated `tsconfig.json` to include the `baseUrl` compiler option and adjust the `include` and `exclude` paths.

* Refactor Lite Text Editor code to use relative import paths instead of absolute import paths.

* Added LucideIconType to the exports in index.ts for use in other files.
Created a new file lucide-icon.ts which contains the type LucideIconType.
Updated the icon type in HeadingOneItem in menu-items/index.tsx to use LucideIconType.
Updated the Icon type in AlertLabel in alert-label.tsx to use LucideIconType.
Updated the Icon type in VerticalDropdownItemProps in vertical-dropdown-menu.tsx to use LucideIconType.
Updated the Icon type in BubbleMenuItem in fixed-menu/index.tsx to use LucideIconType.
Deleted the file tooltip.tsx since it is no longer used.
Updated the Icon type in BubbleMenuItem in bubble-menu/index.tsx to use LucideIconType.

* ♻️ refactor: simplify rendering logic in slash-commands.tsx

The rendering logic in the file "slash-commands.tsx" has been simplified. Previously, the code used inline positioning for the popup, but it has now been removed. Instead of appending the popup to the document body, it is now appended to the element with the ID "tiptap-container". The "flip" option has also been removed. These changes have improved the readability and maintainability of the code.

* fixed build errors caused due to core's internal imports

* regression: fixed pages not saving issue and not duplicating with proper content issue

* build: Update @tiptap dependencies

Updated the @tiptap dependencies in the package.json files of `document-editor`, `extensions`, and `rich-text-editor` packages to version 2.1.13.

* 🚑 fix: Correct appendTo selector in slash-commands.tsx

Update the `appendTo` function call in `slash-commands.tsx` to use the correct selector `#editor-container` instead of `#tiptap-container`. This ensures that the component is appended to the appropriate container in the editor extension.

Note: The commit message assumes that the change is a fix for an issue or error. If it's not a fix, please provide more context so that an appropriate commit type can be determined.
2023-12-22 18:10:18 +05:30
Lakhan Baheti
aceee7d2e2 fix: bugs & improvements (#3189)
* fix: workspace invitation modal form values reset

* fix: profile sidebar avatar letter
2023-12-22 18:09:41 +05:30
Anmol Singh Bhatia
0a41eff435 chore: bug fixes & improvement (#3218)
* chore: draft issue validation added to prevent saving empty or whitespace title

* chore: resolve scrolling issue in page empty state

* chore: kanban layout quick add issue improvement
2023-12-22 18:09:41 +05:30
Prateek Shourya
5f681973a0 style: instance admin email settings ui & ux update. (#3186) 2023-12-22 18:09:41 +05:30
Hoang Luan
9c65657a66 fix - file size limit not work on plane.settings.production (#3160)
* fix - file size limit not work on plane.settings.production

* fix - file size limit not work on plane.settings.production

* fix - file size limit not work on plane.settings.production, move to common.py

---------

Co-authored-by: luanduongtel4vn <hoangluan@tel4vn.com>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2023-12-22 18:09:39 +05:30
Manish Gupta
6fab75f9ab fix: branch build fix (#3214)
* branch build fix for release-* in case of space,backend,proxy

* fixes
2023-12-22 18:08:36 +05:30
sriram veeraghanta
111070bdb9 Merge pull request #3231 from makeplane/release-0.14.2
fix: branch build custom docker repo with suffix (#3182)
2023-12-22 17:49:25 +05:30
David Chiang
d76840ff6f Fix spelling error in setting-label-list.tsx (#3210) 2023-12-22 17:24:02 +05:30
sriram veeraghanta
d4b6f4faf1 Merge pull request #3227 from makeplane/develop
fix: posthog events trigger in staging (#3220)
2023-12-22 14:05:51 +05:30
Ramesh Kumar Chandra
fe9519314a fix: posthog events trigger in staging (#3220)
* fix: posthog events trigger in staging

* refactor: cleared comment lines for warnings, added dependancy values

* refactor: removed unnecessary dependency
2023-12-22 14:05:17 +05:30
M. Palanikannan
e141091e99 fix: Resolved page not saving/not copying contents bug (#3203)
This commit adds a duplicate_page function to the PageDetailsPage component, which allows users to duplicate a page. If the current page does not have a description_html value, it will be set to the default value from the pageDetails object. The formData object is updated with the necessary values for duplication, including the new page name and description. Additionally, the handleSubmit dependency is included in the useEffect hook to ensure proper form submission.
2023-12-20 18:54:16 +05:30
Lakhan Baheti
40b8b0ac35 fix: role authorization for create project button in project empty state (#3204) 2023-12-20 18:53:35 +05:30
Manish Gupta
47d6b152a0 branch build custom docker repo with suffix (#3182) 2023-12-20 16:58:04 +05:30
sriram veeraghanta
216a7c8fda fix: adding additional headers to restrict iframe 2023-12-20 13:49:17 +05:30
sriram veeraghanta
e7468292c7 Merge pull request #3175 from makeplane/preview
release: moving changes from preview to master
2023-12-18 19:29:43 +05:30
Anmol Singh Bhatia
7bff8d2ec5 chore: cycle and module sidebar mutation fix (#3174)
* chore: issue action cycle sidebar mutation fix

* chore: issue action module sidebar mutation fix
2023-12-18 19:01:10 +05:30
sriram veeraghanta
6f2cce081f Merge pull request #3171 from makeplane/develop
Promote: develop change to preview
2023-12-18 15:37:53 +05:30
sriram veeraghanta
a86dafc11c Merge pull request #3170 from makeplane/preview
sync: moving contributions from preview to develop
2023-12-18 15:25:34 +05:30
Aaryan Khandelwal
81256d6373 chore: add tooltips to issue properties with no value (#3169)
* chore: add tolltips to properties with no value

* chore: update property types
2023-12-18 15:17:38 +05:30
Lakhan Baheti
c9792da4a1 fix: bug fixes & improvements (#3159)
* fix: on issue update updates_on not updating

* fix: on project change cycle update

* fix: update issue
2023-12-18 14:59:25 +05:30
Prateek Shourya
37df0bcdd8 fix: issue with peek view properties not editable and options not being shown in all workspace issues. (#3100)
* fix: issue with peek view properties not editable and options not being shown in `all workspace issues`..

* refactor: use projectId from props instead of router query.
fix: issue in add to module/ cycle not working properly.
2023-12-18 14:47:40 +05:30
Prateek Shourya
18c86bd8cc chore: add email from address in Instance Admin Email Settings. (#3155) 2023-12-18 13:36:09 +05:30
dependabot[bot]
0ee6c20272 chore(deps): bump cryptography in /apiserver/requirements (#3166)
Bumps [cryptography](https://github.com/pyca/cryptography) from 41.0.5 to 41.0.6.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/41.0.5...41.0.6)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-18 13:26:21 +05:30
Prateek Shourya
184db0156c User role validation across workspace and projects. (#3167)
* chore: remove `add link` button for guests & viewer in modules sidebar.

* chore: remove `+` (add view) icon for guests & viewer in `All Issues`.

* chore: remove `Start Project` button from Dashboard & Projects empty state for guests & viewers.

* chore: project level user role validation for empty states.
2023-12-18 13:25:03 +05:30
Nikhil
b7a0f3c693 fix: project cover list endpoint (#3168) 2023-12-18 13:19:04 +05:30
Ikko Eltociear Ashimine
e40f38e2e1 fix: typo in install.sh (#3136)
availabe -> available
2023-12-18 12:17:15 +05:30
Lakhan Baheti
05e7afab8d fix: issue peek-overview delete functionality (#3134)
* fix: peek-overview delete issue

* refactor: removed unused variables
2023-12-18 12:14:57 +05:30
Anmol Singh Bhatia
969a51f425 chore: issue click & peek overview improvement (#3157)
* improve issue popover to detect outside click

* chore: stopPropagation event added to prevent peekoverview triggering in action menu & issue properties

* chore: stopPropagation event added to prevent peekoverview triggering in issue properties

* chore: enable entire issue card clickability in list and kanban layout, introduce control-click functionality
to open issues in new tabs

* chore: build error fix and unused variable removed

* chore: build error fix

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2023-12-18 12:11:14 +05:30
sriram veeraghanta
849bc92aea Merge pull request #3121 from notpushkin/patch-1
Fix .vertical-lr in Firefox
2023-12-18 01:11:31 +05:30
Lakhan Baheti
a37dec45d9 chore: made project-identifier rule consistent (#3148) 2023-12-17 00:39:23 +05:30
Lakhan Baheti
e1793dda74 chore: role restriction to issue detail & peek-overview (#3146)
* chore: disabled interaction with title & description in peekview & issue detail

* chore: restriction to attachments delete for lower roles

* chore: kanban & calendar block drag restriction for lower roles

* fix: module/issue sidebar links access for lower roles

* fix: issue detail role validation

* fix: user role validation condition
2023-12-17 00:38:05 +05:30
Anmol Singh Bhatia
31fdaf2659 fix: project members fetch fix (#3145)
* chore: all issues assignee fetch fix

* chore: assignee fetch logic updated
2023-12-17 00:36:56 +05:30
Aaryan Khandelwal
8a1a6c6f62 chore: disable API calls for un-authorized users (#3144)
* fix: disable api calls if user doesn't have access

* chore: remove permissions from component level

* chore: rmeove console log and organize import statements
2023-12-17 00:35:53 +05:30
Prateek Shourya
3d83101f69 fix: issue with cycle and module sidebar filter implementation. (#3142) 2023-12-17 00:34:42 +05:30
Lakhan Baheti
d473ba9d0d fix: create issue project ID (#3131) 2023-12-17 00:32:00 +05:30
Aaryan Khandelwal
e5902152ab fix: overflow issues in the profile settings layout (#3163) 2023-12-17 00:21:03 +05:30
sriram veeraghanta
f03a9a6de8 fix: sync changes (#3156)
* fix: sync changes

* fix: sync changes

* fix: sync changes updated
2023-12-15 18:12:16 +05:30
Anmol Singh Bhatia
b7e2f1e57a chore: resolve priority sorting order (#3141) 2023-12-15 17:37:37 +05:30
sriram veeraghanta
885de6f679 fix: sync changes (#3154) 2023-12-15 17:36:26 +05:30
sriram veeraghanta
ce9714ff12 fix: sync pr changes (#3153) 2023-12-15 17:31:14 +05:30
sriram veeraghanta
74b141eea2 fix: sync changes (#3152) 2023-12-15 17:24:24 +05:30
sriram veeraghanta
08425c9614 fix: sync changes (#3151) 2023-12-15 17:21:06 +05:30
sriram veeraghanta
ecfcc03ef0 fix: removing unneccessary checks (#3150) 2023-12-15 17:12:57 +05:30
sriram veeraghanta
829c08f0ee fix: making changes to sync job (#3149) 2023-12-15 17:08:37 +05:30
Anmol Singh Bhatia
2edd2d947e chore: implement validation for accepting 'http://' and 'https://' url in link modal (#3147) 2023-12-15 16:39:32 +05:30
Aaryan Khandelwal
5b67f27345 fix: remove get requests from the catch block (#3135)
* fix: refetching project filters on error

* fix: get request in the catch block
2023-12-14 19:55:45 +05:30
Anmol Singh Bhatia
7684a2c091 chore: sidebar quick action improvement (#3133) 2023-12-14 17:24:42 +05:30
Lakhan Baheti
4e2bf24e8d chore: filter edit operation in views is disabled for lower roles (#3113)
* chore: edit/delete view options hidden for lower roles

* chore: project -> views access restriction for lower roles

* refactor: allowance condition
2023-12-14 16:49:36 +05:30
Anmol Singh Bhatia
aafac9ed1d chore: state sequence ordering (#3130) 2023-12-14 16:45:39 +05:30
Prateek Shourya
3adf48e429 chore: add new or existing issues validation for guest & viewers in cycles and modules empty state. (#3126) 2023-12-14 16:31:57 +05:30
Lakhan Baheti
1c546e3cc5 fix: cycle & module sidebar date range picker (#3127)
* fix: cycle sidebar date range picker

* chore: cycle sidebar hover pointer

* fix: module sidebar date range picker

* fix: module sidebar state select popover pointer
2023-12-14 16:31:39 +05:30
Nikhil
5c7382d894 fix: environment file missing for space (#3105)
* fix: environment file missing for space

* dev: remove container name from the docker-compose local

* dockerfile.dev modified for volumes

* local dev fixes

---------

Co-authored-by: Manish Gupta <manish@mgupta.me>
2023-12-14 16:18:02 +05:30
Bavisetti Narayan
1f8ae3a5ad chore: project member list (#3087) 2023-12-14 16:14:50 +05:30
Bavisetti Narayan
f1ed0c979c chore: workspace project reinvite (#3112)
* chore: user can rejoin a project by invite

* chore: workspace member leave
2023-12-14 16:14:12 +05:30
Bavisetti Narayan
78b29eb81b chore: resolved the use tls error (#3114) 2023-12-14 16:11:46 +05:30
Anmol Singh Bhatia
8d3a0a2eec fix: build error (#3128) 2023-12-14 16:07:53 +05:30
sriram veeraghanta
910d1a1de3 Merge branch 'preview' of github.com:makeplane/plane into develop 2023-12-13 23:18:09 +05:30
Ehtesham Siddiqui
6004f29bbc Fix: update self-host docs link in README (#3109)
* Fix: update self-host docs link

* fix: update readme.md to link to self-hosting docker compose
2023-12-13 23:11:48 +05:30
Lakhan Baheti
9d0056cfee fix: spreadsheet layout sub-issues property update (#3110)
* fix: spreadsheet subissues property update

* fix: hover effect for sub-issues

* refactor: mutate sub-issues
2023-12-13 23:07:26 +05:30
Lakhan Baheti
4bb99d5fbf fix: cycle delete & active cycle loading (#3088) 2023-12-13 23:06:38 +05:30
Anmol Singh Bhatia
6c61fbd102 chore: guest user profile access-related problem (#3089)
* fix: resolve guest user profile access-related problem

* chore: header and app sidebar permission validation added
2023-12-13 23:05:50 +05:30
Anmol Singh Bhatia
2605b938f0 chore: empty state action button validation (#3094) 2023-12-13 23:05:23 +05:30
Anmol Singh Bhatia
b4f51cb5af chore: cycle and module sidebar permission validation (#3095) 2023-12-13 23:05:02 +05:30
Anmol Singh Bhatia
fe80ca3e1c chore: issue sidebar and project view improvement and validation (#3098)
* chore: project view header button validation

* chore: copy view link option added in project view list item and role action validation added

* chore: issue sidebar improvement
2023-12-13 23:04:33 +05:30
Anmol Singh Bhatia
b78e83d81b chore: image file update, icon consistency, bug fixes and code refactor (#3107)
* chore: workspace view icon consistency

* chore: icon added in breadcrumb dropdown

* chore: svg image replaced with webp

* fix: build fix

* chore: unused variables removed

* chore: module sidebar copy module link fix
2023-12-13 23:03:54 +05:30
sriram veeraghanta
ee68c3ae86 Merge pull request #3111 from makeplane/chore/calendar_layout_improvement
chore: calendar layout improvement
2023-12-13 23:03:07 +05:30
sriram veeraghanta
239f68e260 Merge pull request #3117 from makeplane/chore/issue_related_bug_and_improvement
chore: issue details related bug fixes and improvement
2023-12-13 23:01:19 +05:30
sriram veeraghanta
f949d57fa0 Merge pull request #3119 from makeplane/chore/analytics_permission_validation
chore: analytics permission validation added
2023-12-13 23:00:05 +05:30
Alexander Pushkov
dadd2cf39b Fix .vertical-lr in Firefox 2023-12-13 19:04:12 +03:00
Anmol Singh Bhatia
13d7832d35 chore: app sidebar quick action permission validation added 2023-12-13 20:32:39 +05:30
Anmol Singh Bhatia
0f892d4670 chore: analytics permission validation added 2023-12-13 20:15:50 +05:30
Anmol Singh Bhatia
e8945f244d chore: remove parent option added in issue sidebar and peek overview 2023-12-13 19:29:49 +05:30
Anmol Singh Bhatia
a0588be405 chore: remove parent option added in issue sidebar and peek overview 2023-12-13 18:57:50 +05:30
Anmol Singh Bhatia
29a0ba4ddc chore: sub issue propery permission validation 2023-12-13 18:40:46 +05:30
Anmol Singh Bhatia
644073f063 chore: issue detail page title and indicator improvement 2023-12-13 18:32:21 +05:30
Anmol Singh Bhatia
63a15f2bf9 chore: calendar layout day tile improvement 2023-12-13 14:48:42 +05:30
sriram veeraghanta
936452758a sync: preview to master 2023-12-12 17:51:09 +05:30
sriram veeraghanta
4fdac437e9 Merge pull request #3097 from makeplane/develop
promote: develop to preview
2023-12-12 17:41:46 +05:30
Aaryan Khandelwal
48fe6f9b9a chore: update self hosted sign in form cta in the web and space apps (#3096)
* chore: update self hosted sign in form cta

* chore: update self hosted sign in form cta in the space app
2023-12-12 17:40:02 +05:30
sriram veeraghanta
05d675c138 Merge pull request #3086 from makeplane/fix/issue_layout_permission_validation
chore: issue layout quick add issue button permission validation
2023-12-12 17:36:07 +05:30
sriram veeraghanta
09b4f6dedd Merge pull request #3091 from makeplane/chore/project-member-leave
chore: guest and viewer can leave project
2023-12-12 17:35:14 +05:30
sriram veeraghanta
449ac06fd7 Merge pull request #3093 from makeplane/develop
promote: develop to preview
2023-12-12 17:01:57 +05:30
LAKHAN BAHETI
c7d50bb7ce fix: member role project leave 2023-12-12 17:00:59 +05:30
NarayanBavisetti
90de11c08d chore: guest and viewer can leave project 2023-12-12 16:22:33 +05:30
Anmol Singh Bhatia
24c02495aa chore: instance setup done image update (#3090) 2023-12-12 16:05:59 +05:30
Nikhil
1795916042 fix: debug toolbar import error when running on production setting with DEBUG on (#3085) 2023-12-12 11:54:52 +05:30
Anmol Singh Bhatia
43cbe44a35 chore: issue layout quick add issue button permission validation 2023-12-12 11:49:43 +05:30
Aaryan Khandelwal
361ee16567 fix: modal size not changing when switching between modes (#3082) 2023-12-12 00:44:58 +05:30
Nikhil
de24b02a0a fix: user login after account deactivation (#3073) 2023-12-11 22:44:37 +05:30
Anmol Singh Bhatia
1d3745157d style: cycle and module sidebar improvement (#3076)
* style: cycle and module sidebar improvement

* chore: issue attachment error message updated
2023-12-11 22:27:28 +05:30
Aaryan Khandelwal
07c15fcc1f fix: gantt issues not getting updated (#3075) 2023-12-11 22:26:57 +05:30
Anmol Singh Bhatia
c33cfeb227 chore: issue link mobx integration (#3072)
* chore: issue link mobx integration

* chore: issue link code refactor and build fix
2023-12-11 22:26:29 +05:30
Aaryan Khandelwal
f119d702c7 chore: set an expiry of 30 days for the access and refresh tokens (#3071) 2023-12-11 22:25:48 +05:30
Aaryan Khandelwal
0f752f93b6 fix: pre-fill cycle and module details when editing an issue (#3070)
* fix: prefill cycle and module when editing an issue

* chore: remove console.log
2023-12-11 22:25:14 +05:30
Lakhan Baheti
ae2e1a4b64 fix: delete issue mutation & toast (#3079)
* fix: delete issue mutation

* fix: toast on delete issue

* fix: activity to issue detail redirection
2023-12-11 22:24:41 +05:30
Lakhan Baheti
472a5d8047 fix: spreadsheet layout alignment (#3080) 2023-12-11 22:23:49 +05:30
Prateek Shourya
3b12332704 fix: issue with clear filter option not working for filter groups in create view modal. (#3081) 2023-12-11 22:23:20 +05:30
Aaryan Khandelwal
c7cad452ab fix: disable label combining for labels inside a group (#3069) 2023-12-11 17:31:22 +05:30
Anmol Singh Bhatia
82c0ee00a3 chore: profile setting bug fixes (#3068)
* chore: change password page redirection fix

* chore: profile activity profile redirection fix
2023-12-11 17:30:17 +05:30
Lakhan Baheti
8041b23a63 chore: access restriction for lower roles (#3067) 2023-12-11 17:29:43 +05:30
Anmol Singh Bhatia
f38278f465 chore: user permission related fix (#3066)
* chore: page action user permission validation

* chore: cycle & module action user permission validation

* chore: issue quick action user permission validation

* chore: spreadsheet layout improvement
2023-12-11 17:29:10 +05:30
Lakhan Baheti
73b58e91ee fix: spreadsheet layout sub issues overlapping (#3064)
* fix: spreadsheet layout sub issues overlapping

* fix: logs
2023-12-11 17:28:44 +05:30
Prateek Shourya
b515c0ffa6 fix: issue with GPT assistant response not updating on new requests. (#3063) 2023-12-11 17:27:52 +05:30
Aaryan Khandelwal
b629263bc2 fix: icon consistency for issue properties (#3065) 2023-12-11 17:27:29 +05:30
sriram veeraghanta
1bf064df15 sync: preview with develop 2023-12-11 16:26:11 +05:30
Nikhil
9918d5242c dev: fix oauth configuration check (#3062) 2023-12-11 16:17:11 +05:30
Aaryan Khandelwal
dd87bd0ee2 style: update OAuth buttons UI (#3061) 2023-12-11 15:52:21 +05:30
Lakhan Baheti
26d37fbd38 chore: error handling for user permission while issue drag & drop (#3060)
* filx: gantt chart quick add permission

* fix: permission toast on drag & drop issues
2023-12-11 15:51:40 +05:30
Nikhil
bf2c6e36ef chore: configurations (#3059)
* fix: encrypt and decrypt function to check for None case.

* fix: google and github oauth login check when one of them is not configured.

* remove: NEXT_PUBLIC_ENABLE_OAUTH variable.
2023-12-11 14:32:28 +05:30
sriram veeraghanta
62e66acc37 fix: upgrading lucide react dependency (#3057) 2023-12-10 17:54:36 +05:30
sriram veeraghanta
0f28008fa5 Merge pull request #3056 from makeplane/promote/develop-preview
Promote: Develop to Preview
2023-12-10 15:51:41 +05:30
sriram veeraghanta
5b0066140f chore: format all files in monorepo (#3054)
* chore: format all files in the project

* fix: removing @types/react from dependencies

* fix: adding prettier and eslint config

* chore: format files

* fix: upgrading turbo version

* chore: ignoring warnings and adding todos

* fix: updated the type of bubble menu item in the document editor

* chore: format files

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-12-10 15:50:45 +05:30
Aaryan Khandelwal
e5ae139178 chore: update lucide-react package to the latest version 2023-12-10 15:50:44 +05:30
Aaryan Khandelwal
02e2c6f848 style: update the ui of the slash command menu 2023-12-10 15:50:44 +05:30
sriram veeraghanta
8d15b9e7de chore: format all files in monorepo (#3054)
* chore: format all files in the project

* fix: removing @types/react from dependencies

* fix: adding prettier and eslint config

* chore: format files

* fix: upgrading turbo version

* chore: ignoring warnings and adding todos

* fix: updated the type of bubble menu item in the document editor

* chore: format files

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-12-10 15:48:10 +05:30
sriram veeraghanta
5eba682128 Merge pull request #3053 from makeplane/style/slash_command
style: update the ui of the slash command menu
2023-12-10 14:09:56 +05:30
Aaryan Khandelwal
457ed9bfe6 chore: update lucide-react package to the latest version 2023-12-10 01:10:59 +05:30
Aaryan Khandelwal
842048b2f2 style: update the ui of the slash command menu 2023-12-09 17:26:47 +05:30
sriram veeraghanta
d16a6402cd Merge pull request #3046 from makeplane/promote/dev-preview
promote: develop to preview
2023-12-08 19:53:57 +05:30
sriram veeraghanta
09253e3f55 Merge branch 'preview' of github.com:makeplane/plane into promote/dev-preview 2023-12-08 19:52:17 +05:30
sriram veeraghanta
9538d9ee9b Merge pull request #3039 from makeplane/promote/qa-preview
[FED-1116] bug: cycle and module change not working from the peek overview
2023-12-08 19:48:38 +05:30
Nikhil
b487e2defe fix: open api configuration (#3044)
* dev: fix openapi config default for docker setup

* dev: remove the default values from google client id and github client id

* dev: handle case for google and github client id
2023-12-08 19:46:46 +05:30
Henit Chobisa
fffcb2d947 fix: debounce logic in issue detail and description 2023-12-08 18:31:40 +05:30
Henit Chobisa
01874cb2db fix: added useCallback for controlling debounce function triggering on every rerendeer
Added a useCallback function, which helps keeping only one version of the debounce function, what happens is that when we type in the editor, the rerenders produces multiple debounce function, which all triggers together after the specifier amount of time, making many request to backend, consiquently failing the debounce. Now useCallback will give only one function even if the rerenders are happening which fixes the issue
2023-12-08 18:31:40 +05:30
Henit Chobisa
b82f04ea04 fix: document editor debounced 2023-12-08 18:31:40 +05:30
Palanikannan1437
29e8e6c997 added comments to prevent further changes 2023-12-08 18:31:25 +05:30
Palanikannan1437
c68ade11ec regression: peek overview rerendering 2023-12-08 18:31:25 +05:30
gurusainath
cb2577d259 fix: handled undefined issue_id in list layout 2023-12-08 17:43:24 +05:30
Anmol Singh Bhatia
5ac2c17e53 chore: release version update 2023-12-08 16:34:15 +05:30
Anmol Singh Bhatia
f198d14cc7 chore: release version update 2023-12-08 16:34:15 +05:30
Anmol Singh Bhatia
3400e31d0a chore: release version update 2023-12-08 16:34:15 +05:30
Aaryan Khandelwal
6c8c61c53b bug: cycle and module change not working from the peek overview (#3037) 2023-12-08 15:57:42 +05:30
pablohashescobar
dd9056c165 dev: take configuration value for Github client secret from get_configuration 2023-12-08 15:55:15 +05:30
pablohashescobar
89d4851ff5 dev: change default value for Github and Google client id if it is not present 2023-12-08 15:55:15 +05:30
pablohashescobar
49a4c466b7 dev: update dockerfile.dev to install local requirements 2023-12-08 15:55:15 +05:30
pablohashescobar
aebf8daf08 fix: user deactivation if instance admin and remove auto assigning of workspace and projects 2023-12-08 15:55:15 +05:30
gurusainath
d4a4046cd3 fix: Handled navigation dates in week view in calendar layout 2023-12-08 15:46:04 +05:30
gurusainath
39d1916ab2 fix: calendar filters and handled kanban issue empty state dnd error 2023-12-08 15:46:04 +05:30
Aaryan Khandelwal
d0d106901e bug: cycle and module change not working from the peek overview 2023-12-08 15:45:24 +05:30
sriram veeraghanta
9147b58b99 Merge pull request #3030 from makeplane/qa-merge-fixes
Promote: QA to Preview
2023-12-07 20:07:39 +05:30
sriram veeraghanta
4ff3a34a65 fix: merge conflicts resolved 2023-12-07 20:03:21 +05:30
Nikhil
74ca187659 chore: workspace roles (#3024)
* chore: workspace project roles for the current user

* dev: workspace and project member

* chore: store implementation for workspace user projects role

* view changes for the project roles

* connect workspace member's project roles to assigned

---------

Co-authored-by: gurusainath <gurusainath007@gmail.com>
Co-authored-by: rahulramesha <rahulramesham@gmail.com>
Co-authored-by: rahulramesha <71900764+rahulramesha@users.noreply.github.com>
2023-12-07 19:59:35 +05:30
M. Palanikannan
2a5ff3397f [REGRESSION]: Inbox issue editor initialization (#3025)
* fixed core editor package build error

* reverting back changes such that editor doesn't rerender randomly in inbox issues
2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
557fb2306b chore: deploy code refactor (#3019)
* chore: deploy code refactor

* fix: next_path redirection

* fix: sanitized pathname

---------

Co-authored-by: gurusainath <gurusainath007@gmail.com>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2023-12-07 19:59:35 +05:30
Lakhan Baheti
8409a84004 fix: kanban layout UI (#3023)
* fix: quick add placement

* fix: assignee avatar size in kanban header

* fix: issue detail sidebar scroll

* fix: extra margin around links

* formatting
2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
539c7a3455 refactor: issue peek overview (#3001)
* refactor: peek overview components

* fix: issue reactions

* chore: update comment types

* fix: access sepcifier value

* chore: remove unused vars

* fix: build errors

* build-error: build error resolved

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
Co-authored-by: gurusainath <gurusainath007@gmail.com>
2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
c455f03ced fix: sub-issue properties not rendering and other sub-issue bugs (#3020)
* fix: sub-issue properties not rendering

* fix: delete sub-issue

* fix: delete issue modal on command k and the issue details page
2023-12-07 19:59:35 +05:30
Lakhan Baheti
68dcfcd451 fix: deactivate account post loading (#3022) 2023-12-07 19:59:35 +05:30
Bavisetti Narayan
151ec259d8 chore: removed ce (#3021) 2023-12-07 19:59:35 +05:30
Nikhil
e755ce3272 dev: instance refactor (#3015)
* dev: remove license engine communication

* dev: remove license engine base url

* dev: update instance configuration function

* chore: removed the print statement

* chore: changed config variables

* chore: cleanup

* chore: added SKIP_ENV_VAR

* chore: changed the EMAIL_FROM

* dev: patch endpoint for workspace

* dev: custom port for takeoff script

* chore: changed my sequence

* fix: update operaton for member invitations in workspace

* clean-up: remove logs

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: gurusainath <gurusainath007@gmail.com>
2023-12-07 19:59:35 +05:30
sriram veeraghanta
9d16b39c15 fix: adding sentry configs on space app and updated docker ignore (#3018) 2023-12-07 19:59:35 +05:30
rahulramesha
9c5bf47ace Chore: Minify build for plane packages (#3017)
* minify @plane/ui build

* minify all the packages
2023-12-07 19:59:35 +05:30
Henit Chobisa
b5ac2f8078 [ FEATURE ] New Issue Widget for displaying issues inside document-editor (#2920)
* feat: added heading 3 in the editor summary markings

* feat: fixed editor and summary bar sizing

* feat: added `issue-embed` extension

* feat: exposed issue embed extension

* feat: added main embed config configuration to document editor body

* feat: added peek overview and issue embed fetch function

* feat: enabled slash commands to take additonal suggestions from editors

* chore: replaced `IssueEmbedWidget` into widget extension

* chore: removed issue embed from previous places

* feat: added issue embed suggestion extension

* feat: added issue embed suggestion renderer

* feat: added issue embed suggestions into extensions module

* feat: added issues in issueEmbedConfiguration in document editor

* chore: package fixes

* chore: removed log statements

* feat: added title updation logic into document editor

* fix: issue suggestion items, not rendering issue widget on enter

* feat: added error card for issue widget

* feat: improved focus logic for issue search and navigate

* feat: appended transactionid for issueWidgetTransaction

* chore: packages update

* feat: disabled editing of title in readonly mode

* feat: added issueEmbedConfig in readonly editor

* fix: issue suggestions not loading after structure changed to object

* feat: added toast messages for success/error messages from doc editor

* fix: issue suggestions sorting issue

* fix: formatting errors resolved

* fix: infinite reloading of the readonly document editor

* fix: css in avatar of issue widget card

* feat: added show alert on pages reload

* feat: added saving state for the pages editor

* fix: issue with heading 3 in side bar view

* style: updated issue suggestions dropdown ui

* fix: Pages intiliazation and mutation with updated MobX store

* fixed image uploads being cancelled on refocus due to swr

* fix: issue with same description rerendering empty content fixed

* fix: scroll in issue suggestion view

* fix: added submission prop

* fix: Updated the comment update to take issue id in inbox issues

* feat:changed date representation in IssueEmbedCard

* fix: page details mutation with optimistic updates using swr

* fix: menu options in read only editor with auth fixed

* fix: add error handling for title and page desc

* fixed yarn.lock

* fix: read-only editor title wrapping

* fix: build error with rich text editor

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
Co-authored-by: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com>
2023-12-07 19:59:35 +05:30
Lakhan Baheti
95c7403efc fix: spreadsheet layout bugs (#3016)
* fix: date picker z visibility

* fix: typo in empty issue screen

* fix: spread sheet column rightmost border
2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
92cb1834a5 fix: remove all unused variables and added dependecies to useEffect and useCallback (#3013) 2023-12-07 19:59:35 +05:30
sriram veeraghanta
691666e5e2 fix: upgrading types react package (#3014) 2023-12-07 19:59:35 +05:30
guru_sainath
e585255c4c fix: issue layouts bugs and ui fixes (#3012)
* fix: initial issue creation issue in the list layout

* fix kanban drag n drop and updating properties

* reduce z index of spreadsheet bottom row to not overlap with other elements

* fix state update by using state id instead of state detail's id

* fix add default use state for description

* add create issue button for project views to be at par with production

* save draft issues from modal

* chore: added save view button in all layouts applied filters

* use useEffect instead of swr for fetching issue details for peek overview

* fix: resolved kanban dnd

---------

Co-authored-by: rahulramesha <rahulramesham@gmail.com>
2023-12-07 19:59:35 +05:30
Bavisetti Narayan
55ce748aa1 chore: Page auth and other improvements (#3011)
* chore: project query optimised

* chore: page permissions changed
2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
c3f3578e8b chore: remove unused fields from the god mode (#3007) 2023-12-07 19:59:35 +05:30
Lakhan Baheti
c4602951c9 fix: kanban board block's menu & drop delete. (#2987)
* fix: kanban board block menu click

* fix: menu active/disable

* fix: drag n drop delete modal

* fix: quick action button in all the layouts

* chore: toast for drag & drop api
2023-12-07 19:59:35 +05:30
Henit Chobisa
7a96e12523 feat: added custom blockquote extension for resolving enter key behaviour (#2997) 2023-12-07 19:59:35 +05:30
Lakhan Baheti
4c53157b0e fix: custom analytic grouped bar tooltip value as ID (#3003)
* fix: tooltip value is coming as ID

* fix lint named module
2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
1f860312c6 chore: added authorization to pages (#3006)
* chore: updated pages authorization

* chore: updated pages empty state image
2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
13667d491b chore: plane logo without text updated (#3008) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
aba4592b73 fix: bug fixes (#3010)
* fix: project view modal auto close bug fix

* fix: issue peek overview label select permission validation added
2023-12-07 19:59:35 +05:30
Nikhil
c5cc706978 dev: user password reset management command (#3000) 2023-12-07 19:59:35 +05:30
Nikhil
b44dd26347 dev: remove unused packages (#3009)
* dev: remove unused packages

* dev: remove gunicorn config
2023-12-07 19:59:35 +05:30
Bavisetti Narayan
b35874e294 chore: posthog event for workspace invite (#2989)
* chore: posthog event for workspace invite

* chore: updated event names, added all the existing events to workspace metrics group

* chore: seperated workspace invite

* fix: workspace invite accept event updated

---------

Co-authored-by: Ramesh Kumar Chandra <rameshkumar2299@gmail.com>
2023-12-07 19:59:35 +05:30
Jorge
37c03ff239 Add CodeQL workflow (#1452) 2023-12-07 19:59:35 +05:30
Manish Gupta
8486983aa9 modified docker image repo names (#3004) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
24a28e44ff chore: updated plane deploy sign-in workflows for cloud and self-hosted instances (#2999)
* chore: deploy onboarding workflow

* chore: sign in workflow improvement

* fix: build error
2023-12-07 19:59:35 +05:30
guru_sainath
a56e7b17f1 clean-up: removed labels in the filters and handled redirection issue from peek overview and ui changes (#3002) 2023-12-07 19:59:35 +05:30
Lakhan Baheti
97bc153ef9 fix: bugs & improvements (#2998)
* fix: create more toggle in update issue modal

* fix: spreadsheet estimate column hide

* fix: flickering in all the layouts

* fix: logs
2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
be2cf2e842 chore: updated sign-in workflows for cloud and self-hosted instances (#2994)
* chore: update onboarding workflow

* dev: update user count tasks

* fix: forgot password endpoint

* dev: instance and onboarding updates

* chore: update sign-in workflow for cloud and self-hosted instances (#2993)

* chore: updated auth services

* chore: new signin workflow updated

* chore: updated content

* chore: instance admin setup

* dev: update instance verification task

* dev: run the instance verification task every 4 hours

* dev: update migrations

* chore: update latest features image

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
f481957818 fix: bug fixes and improvement (#2992)
* chore: issue sidebar permission bug fix and not authorized page redirection added

* chore: unauthorized project setting page improvement

* fix: build error fix
2023-12-07 19:59:35 +05:30
M. Palanikannan
1bddaf75b2 fix: Image Resizing and PR (#2996)
* added image min width and height programatically

* fixed editor initialization for peek view and inbox issues

* fixed ts issues with issue id in inbox
2023-12-07 19:59:35 +05:30
rahulramesha
91cb15c2e3 fix: bugs related to issues (#2995)
* hide properties in list and kanban with 0 or nil values

* module and cycle mutation from peek overlay

* fix peek over view title change while switching

* fix create issue fetching

* fix build errors by mutating the values as well
2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
a035cee165 [FED-1147] chore: module link mobx integration (#2990)
* chore: link type updated

* chore: mobx implementation for module link

* chore: update module mutation logic updated and toast alert added
2023-12-07 19:59:35 +05:30
guru_sainath
a4d7b2423e chore: workspace profile issues, kanabn DND upgrade, implemented filters in plaen deploy (#2991) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
e6eef7eb0b chore: space ui component revamp and bug fixes (#2980)
* chore: replace space ui component with plane ui component

* fix: space project icon and user pic bug

* chore: code refactor

* fix: profile section navbar fix
2023-12-07 19:59:35 +05:30
Lakhan Baheti
dbc8150852 chore: email invite accept validation (#2965)
* fix: empty state flickering on accepting only invitation

* fix: redirection from workspace-invitaion to onboarding

* chore: onboarding step 1 skip on accepting invite from email

* fix: dashboard redirection path
2023-12-07 19:59:35 +05:30
sabith-tu
de9c1a60e0 style: image picker, spreadsheet view title, icons (#2988)
* style: image picker, spreadsheet view title, icons

* fix: build error fix
2023-12-07 19:59:35 +05:30
Lakhan Baheti
1b51892489 chore: issue update status in peekview & detail (#2985) 2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
d12bd9507e fix: leave project mutation (#2976) 2023-12-07 19:59:35 +05:30
Nikhil
41f0d55dab fix: sentry dsn error (#2981) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
eda0b32b97 chore: draft issue layout and permission validation (#2982)
* chore: create draft issue option added in draft issue layout and permission validation added

* chore: create draft issue option added in draft issue list layout and permission validation added
2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
882cf91a91 chore: module and cycle sidebar date mutation fix (#2986) 2023-12-07 19:59:35 +05:30
Bavisetti Narayan
25b7e22b70 chore: html validation (#2970)
* chore: changed api serializers

* chore: state status code

* chore: removed sorted keys
2023-12-07 19:59:35 +05:30
rahulramesha
a36aa4d093 fix: Permission levels for project settings (#2978)
* fix add subgroup issue FED-1101

* fix subgroup by None assignee FED-1100

* fix grouping by asignee or labels FED-1096

* fix create view popup FED-1093

* fix subgroup exception in swimlanes

* fix show sub issue filter FED-1102

* use Enums instead of numbers

* fix Estimates setting permission for admin

* disable access to project settings for viewers and guests

* fix project unautorized flicker

* add observer to estimates

* add permissions to member list
2023-12-07 19:59:35 +05:30
rahulramesha
c346d82b0b fix: mutation on transfer issues from cycle (#2979)
* fix cycle issues mutation on transfering issues

* fix transfer issues from cycle
2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
054691d80e refactor: custom hook for sign in redirection (#2969) 2023-12-07 19:59:35 +05:30
Nikhil
59a1b6ca77 chore: instance (#2955) 2023-12-07 19:59:35 +05:30
rahulramesha
979e6fe383 fix: sub display filter params for fetching issues (#2972)
* fix add subgroup issue FED-1101

* fix subgroup by None assignee FED-1100

* fix grouping by asignee or labels FED-1096

* fix create view popup FED-1093

* fix subgroup exception in swimlanes

* fix show sub issue filter FED-1102
2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
6392a24098 chore: update instance admin sign in endpoint (#2973) 2023-12-07 19:59:35 +05:30
guru_sainath
f1b748947a fix: global issues properties updation issue resolved (#2974) 2023-12-07 19:59:35 +05:30
rahulramesha
7bc05b0bdc fix: V3 release blocker bugs (#2968)
* fix add subgroup issue FED-1101

* fix subgroup by None assignee FED-1100

* fix grouping by asignee or labels FED-1096

* fix create view popup FED-1093

* fix subgroup exception in swimlanes
2023-12-07 19:59:35 +05:30
sabith-tu
d7457ed5f4 style: empty state for analytics, views and pages (#2967) 2023-12-07 19:59:35 +05:30
guru_sainath
f969ed0662 fix: corrected rendering of workspace-level labels, members, and states in project view (#2966)
* fix: dynamic issue properties filters in project, workspace and profile level

* clean-up: removed logs from the project store
2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
1296b6af42 style: updated the UI of the instance admin setup and the sign in workflow (#2962)
* style: updated the UI of the signin and instance setups

* fix: form validations and mutations

* fix: updated Link tags in accordance to next v14

* chore: latest features image, reset password redirection
2023-12-07 19:59:35 +05:30
Lakhan Baheti
a515c59518 fix: create workspace form validation (#2958)
* fix: create workspace form validation

* fix: textfield placeholder typo

* fix: name field onchange
2023-12-07 19:59:35 +05:30
guru_sainath
a276bd2301 chore: workspace global issues (#2964)
* dev: global issues store

* build-error: all issues render

* build-error: build error resolved in global view store
2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
83026e8b2f chore: profile issue display filters content updated (#2963) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
1ef0a86c9d chore: create issue modal improvement (#2960) 2023-12-07 19:59:35 +05:30
Manish Gupta
f3f71f4f9e branch build fixes (#2961) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
43cd0554fb fix: view modal overlapping (#2956) 2023-12-07 19:59:35 +05:30
sriram veeraghanta
ff03f8badb fix: upgrading to nextjs 14 (#2959) 2023-12-07 19:59:35 +05:30
Manish Gupta
df647cc82a fix: branch build 2 (#2957)
* branch build fix

* removed quotes
2023-12-07 19:59:35 +05:30
Manish Gupta
a96edca190 branch build fix (#2954) 2023-12-07 19:59:35 +05:30
Bavisetti Narayan
abd6e32fca chore: removed django settings module (#2953) 2023-12-07 19:59:35 +05:30
Nikhil
46c7f98c9d dev: update email templates (#2948)
* dev: update magic link email

* dev: forgot password mail

* dev: workspace invitation update

* dev: update email templates and task

* dev: remove email verification template

* dev: change all conversation links to issues
2023-12-07 19:59:35 +05:30
rahulramesha
c95a6522ab fix: quick add positioning (#2949)
* fix quick add posutioning for kanban and spreadsheet

* fix kanban quick add project identifier
2023-12-07 19:59:35 +05:30
Nikhil
c598d458f6 chore: status code changed (#2947) 2023-12-07 19:59:35 +05:30
Nikhil
344ae5d551 dev: transactional emails (#2946) 2023-12-07 19:59:35 +05:30
Nikhil
5ccc226498 dev: instance registration (#2912)
* dev: remove auto script for registration

* dev: make all of the instance admins as owners when adding a instance admin

* dev: remove sign out endpoint

* dev: update takeoff script to register the instance

* dev:  reapply instance model

* dev: check none for instance configuration encryptions

* dev: encrypting secrets configuration

* dev: user workflow for registration in instances

* dev: add email automation configuration

* dev: remove unused imports

* dev: reallign migrations

* dev: reconfigure license engine registrations

* dev: move email check to background worker

* dev: add sign up

* chore: signup error message

* dev: updated onboarding workflows and instance setting

* dev: updated template for magic login

* chore: page migration changed

* dev: updated migrations and authentication for license and update template for workspace invite

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
fd5b7d20a8 dev: instance setup workflow (#2935)
* chore: instance type updated

* chore: instance not ready screen added

* chore: instance layout added

* chore: instance magic sign in endpoint and type added

* chore: instance admin password endpoint added

* chore: instance setup page added

* chore: instance setup form added

* chore: instance layout updated

* fix: instance admin workflow setup

* fix: admin workflow setup

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2023-12-07 19:59:35 +05:30
sriram veeraghanta
ee30eb0590 fix: removed unused packages and upgraded to next 14 (#2944)
* fix: upgrading next package and removed unused deps

* chore: unused variable removed

* chore: next image icon fix

* chore: unused component removed

* chore: next image icon fix

* chore: replace use-debounce with lodash debounce

* chore: unused component removed

* resolved: fixed issue with next link component

* fix: updates in next config

* fix: updating types pages

---------

Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so>
2023-12-07 19:59:35 +05:30
guru_sainath
804313413b chore: replaced v3 issues endpoints (#2945)
* chore: removed v3 endpoints

* chore: replace v3 issues to normal issues endpoints

* build-error: Bulid error is new issue structure

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
b2a948dcae chore: cycle and module status indicator improvement (#2942) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
468e117492 style: empty state improvement (#2943) 2023-12-07 19:59:35 +05:30
rahulramesha
90ca459b4a fix: v3 issues for the layouts (#2941)
* fix drag n drop exception error

* fix peek overlay close buttons

* fix project empty state view

* fix cycle and module empty state view

* add ai options to inbox issue creation

* fix inbox filters for viewers

* fix inbox filters for viewers for project

* disable editing permission for members and viewers

* define accurate types for drag and drop
2023-12-07 19:59:35 +05:30
Lakhan Baheti
f7fa4d8b65 style: deactivate acount modal (#2940) 2023-12-07 19:59:35 +05:30
Bavisetti Narayan
56fb9414f1 chore: v3 global issues (#2938) 2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
ffa74e21ac chore: updated sign in workflow (#2939)
* chore: new sign in workflow

* chore: request new code button added

* chore: create new password form added

* fix: build errors

* chore: remove unused components

* chore: update submitting state texts

* fix: oauth sign in process
2023-12-07 19:59:35 +05:30
Lakhan Baheti
c2b90df498 chore: redirection to profile after workpace delete/leave (#2937) 2023-12-07 19:59:35 +05:30
Manish Gupta
a477161fca fix: Branch Build and Self hosting fixes (#2930)
* Branch build yml modified to create preview and latest tags

* self host install modified to handle public image only

* testing update-docker

* testing

* wip

* rolled back to orignal

* selfhosting readme updated
2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
220389e74e chore: issue peek overview (#2918)
* chore: autorun for the issue detail store

* fix: labels mutation

* chore: remove old peek overview code

* chore: move add to cycle and module logic to store

* fix: build errors

* chore: add peekProjectId query param for the peek overview

* chore: update profile layout

* fix: multiple workspaces

* style: Issue activity and link design improvements in Peek overview.
* fix issue with labels not occupying full widht.
* fix links overflow issue.
* add tooltip in links to display entire link.
* add functionality to copy links to clipboard.

* chore: peek overview for all the layouts

* fix: build errors

---------

Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2023-12-07 19:59:35 +05:30
Lakhan Baheti
b30e41f324 fix: spreadsheet layout issue properties (#2936)
* fix: spredsheet layout state column state name & tootltip

* fix: label select dropdown first item auto active state

* fix: priority column padding & tooltip position
2023-12-07 19:59:35 +05:30
Bavisetti Narayan
53950f0684 chore: api rate limiting (#2933) 2023-12-07 19:59:35 +05:30
Bavisetti Narayan
e060a4dbf0 chore: api webhooks validation (#2928)
* chore: api webhooks update

* chore: webhooks signature validation
2023-12-07 19:59:35 +05:30
rahulramesha
926643e597 fix issue sorting and add sorting to other properties (#2931) 2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
7f6d59559d chore: update the content of webhooks form (#2932) 2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
d71ba47262 chore: update profile settings layout (#2925)
* chore: update profile layout

* fix: multiple workspaces

* chore: removed breadcrumbs

* chore: fix sidebar collapsed state
2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
44bc199385 style: workspace sidebar scroll fix and improvement (#2934) 2023-12-07 19:59:35 +05:30
Lakhan Baheti
621cf7b83d fix: google auth button content alignment (#2929) 2023-12-07 19:59:35 +05:30
Lakhan Baheti
c2c0dde824 style: switch or delete account modal (#2926)
* style: switch or delete account modal

* fix: popover text color

* fix: typo
2023-12-07 19:59:35 +05:30
guru_sainath
011db50da6 fix: drag and drop implementation in calendar layout and kanban layout (#2921)
* fix profile issue filters and kanban

* chore: calendar drag and drop

* chore: kanban drag and drop

* dev: remove issue from the kanban layout and resolved build errors

---------

Co-authored-by: rahulramesha <rahulramesham@gmail.com>
2023-12-07 19:59:35 +05:30
sabith-tu
e16e468b8f style: new empty state ui (#2923) 2023-12-07 19:59:35 +05:30
rahulramesha
d2a3d00e82 fix: all functionalities for profile, archived and draft issues (#2922)
* fix profile issue filters and kanban

* fix profile draft and archived issues
2023-12-07 19:59:35 +05:30
Lakhan Baheti
72b592b9ec style: member role visibility (#2919)
* style: member role visibility

* fix: build errors
2023-12-07 19:59:35 +05:30
Bavisetti Narayan
18587395c9 chore: deactivated user workflow change (#2888)
* chore: deactivated user workflow change

* chore: removed archived and draft from v3 by default

* chore: draft and archive update

* chore: bool field

* chore: fall back workspace updated

* chore: workspace member active
2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
eb366887d7 fix: workspace settings pages authorization (#2915)
* fix: workspace settings pages authorization

* chore: user cannot add a member with a higher role than theirs

* chore: update workspace general settings auth
2023-12-07 19:59:35 +05:30
rahulramesha
03387848fe add functionality for addition of existing issues to modules and cycles (#2913)
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2023-12-07 19:59:35 +05:30
Manish Gupta
accdd02ce7 migration script added (#2914) 2023-12-07 19:59:35 +05:30
M. Palanikannan
e01ca97fc9 fix: Image restoration fixed (marks/unmarks an image to be deleted after a week) (#2859)
* image restoration fixed (marks an image to be deleted after a week)

* removed clgs

* added image constraints

* formatted editor-core package using yarn format

* lite-text-editor nothing to format

* rich-text-editor nothing to format

* formatted document-editor with prettier

* modified file service to follow api change

* fixed more formatting in document editor

* fixed all instances of types with that from the package

* fixed delete to work consistently (minor optimizations turned off)

* stop duplicate images inside editor

* restore image on editor creation

say if user A deletes image number 2, user B was also in the same issue and in their screen the image was there, if user B makes certain changes and that gets saved in backend, according to user B image 2 should exist but since user A deleted it, it'll not get restored and get deleted in 7 days, hence I've added a check such that whenever a issue loads we restore all images by default

* added restore image function with types

* replaced all instances to have restore image logic

* fixed issue detail for peek view

* disabled option to insert table inside a table
2023-12-07 19:59:35 +05:30
Manish Gupta
0fcadca53a removed container names for selfhosting (#2907) 2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
ad22ff222f chore: update get invitation details endpoint (#2902) 2023-12-07 19:59:35 +05:30
Prateek Shourya
c4fb543372 Fix: bug fixes and UI / UX improvements (#2906)
* Fix: issue with project publish modal data not updating immediately.

* fix: issue with workspace list not scrollable in profile settings.

* fix: update redirect workspace slug logic to redirect to prev workspace instead of `/`.

* style: update API tokens and webhooks empty state designs.
2023-12-07 19:59:35 +05:30
sriram veeraghanta
d84e043c93 fix: adding ai assistance to pages (#2905)
* fix: adding ai modal to pages

* fix: pages overflow

* chore: update pages UI

* fix: updating page description while using ai assistance

* fix: gpt assistant modal height and position

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-12-07 19:59:35 +05:30
M. Palanikannan
10cde58363 image can't be inserted inside table (#2904)
* image can't be inserted inside table

Now we've diabled image icon from showing up if the cursor is inside a table node or if a table cell is selected

* added drag drop support for document editor

* fixed missing dependencies
2023-12-07 19:59:35 +05:30
Prateek Shourya
041c3af35a refactor: Instance admin setting and UI updates. (#2889)
* refactor: shift instance admin restriction content to seperate component.
fix: instance components export logic.

* style: fix sidebar dropdown `God Mode` icon padding.

* style: update profile settings user dropdown menu width.

* fix: update input type to `password` for Client Secret and API/ Access Key fields.

* style: update loader design for all forms.

* fix: typo

* style: ui updates.

* chore: add show/ hide button for all password fields.
2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
3a1b722d31 refactor: keyboard shortcuts modal (#2822)
* refactor: keyboard shortcuts modal

* chore: updated search logic

* refactor: divided the modal component into granular components
2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
2e3476ab3c fix: workspace dropdown scroll (#2900) 2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
27478ee4bd fix: profile layout flicker (#2898)
* fix: user profile layout flicker

* chore: update import statements
2023-12-07 19:59:35 +05:30
Lakhan Baheti
bdc85ae10d fix: progress panel default open (#2894) 2023-12-07 19:59:35 +05:30
Lakhan Baheti
983b0debcd chore: signup removed (#2890) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
8062800bd9 [FED-1018] chore: workspace dropdown improvement (#2891)
* fix: workspace dropdown improvement

* style: sidebar workspace icon alignment
2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
21e32ce863 [FED-1054] fix: join project mutation (#2892)
* fix: join project mutation

* chore: code refactor
2023-12-07 19:59:35 +05:30
srinivas pendem
0887d35596 Update README.md (#2893)
./setup.sh removed

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
726f4668e0 refactor: webhooks (#2896)
* refactor: webhooks workflow

* chore: update delete modal content
2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
6e940399cb chore: instance admins endpoint added and ui/ux improvement (#2895)
* style: sidebar improvement

* style: header height consistency

* chore: layout consistency and general page improvement

* chore: layout, email form and image form improvement

* chore: instance admins endpoint intergrated and code refactor

* chore: code refactor

* chore: google client secret section removed
2023-12-07 19:59:35 +05:30
guru_sainath
f79bd9df60 issues rendering in all issue layouts fir profile and project issues and global issues store implementation (#2886)
* dev: draft and archived issue store

* connect draft and archived issues

* kanban for draft issues

* fix filter store for calendar and kanban

* dev: profile issues store and draft issues filters in header

* disble issue creation for draft issues

* dev: profile issues store filters

* disable kanban properties in draft issues

* dev: profile issues store filters

* dev: seperated adding issues to the cycle and module as seperate methds in cycle and module store

* dev: workspace profile issues store

* dev: sub group issues in the swimlanes

* profile issues and create issue connection

* fix profile issues

* fix spreadsheet issues

* fix dissapearing project from create issue modal

* page level modifications

* fix additional bugs

* dev: issues profile and global iisues and filters update

* fix issue related bugs

* fix project views for list and kanban

* fix build errors

---------

Co-authored-by: rahulramesha <rahulramesham@gmail.com>
2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
70994d1da7 fix: resolve modal overlapping issue (#2885) 2023-12-07 19:59:35 +05:30
Lakhan Baheti
6d46771109 fix: bug fixes & UI improvements (#2884)
* chore: access restriction for api tokens

* fix: on create module total issues undefined

* fix: cycle board card typo

* chore: fetch modules after creation

* fix: peek module on delete

* fix: peek cycle on delete

* fix: cycle detail sidebar copy link toast

* chore: router replace -> push
2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
7ad0360920 chore: revamp the API tokens workflow (#2880)
* chore: added getLayout method to api tokens pages

* revamp: api tokens workflow

* chore: add title validation and update types

* chore: minor UI updates

* chore: update route
2023-12-07 19:59:35 +05:30
Lakhan Baheti
7b5eea8722 fix: user state after logout (#2849)
* fix: user state after logout

* chore: user state handle with mobx

* chore: signout update for profile setting

* fix: minor fixes

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2023-12-07 19:59:35 +05:30
sriram veeraghanta
a7e446f134 fix: remove slack notification on build branch workflow (#2881) 2023-12-07 19:59:35 +05:30
Lakhan Baheti
1c29f0b0a9 fix: workspace & user avatar tooltip (#2851)
* fix: workspace & user avatar tooltip

* chore: user name update while typing on top right avatar

* chore: imports placement

* fix: rendering condition

* chore: component re-arrangement

* fix: imports
2023-12-07 19:59:35 +05:30
Bavisetti Narayan
acc6b5ed5c chore: seperated delete endpoint for file upload (#2870) 2023-12-07 19:59:35 +05:30
Ramesh Kumar Chandra
20fe27e086 fix: track events updated, extra parameters added, added events for issues, pages, states, cycles (#2875)
* fix: event tracking method updated to store, chore: updated and added events for workspace, projects and create issue

* fix: posthog auth event tracking

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2023-12-07 19:59:35 +05:30
Prateek Shourya
398f35d36d Feat: God Mode UI Updates and More Config Settings (#2877)
* feat: Images in Plane config screen.
* feat: Enable/ Disable Magic Login config toggle.
* style: UX copy and design updates across all screens.
* style: SSO and OAuth Screen revamp.
* style: Enter God Mode button for Profile Settings sidebar.
* fix: update input type to password for password fields.
2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
bf060cc8eb dev: badge component added in planu ui package (#2876) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
a9ea5b6d90 fix: issue peek overview state select dropdown overflow fix (#2873) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
1c2761000a fix: module sidebar date select fix and code refactor (#2872) 2023-12-07 19:59:35 +05:30
sriram veeraghanta
ecea744657 fix: remove slack notify (#2871)
* fix: remove slack notifications on workflows

* fix: bugfix
2023-12-07 19:59:35 +05:30
Nikhil
1bd38ad4c7 refactor: image upload modals, file size limit added to config (#2868)
* chore: add file size limit as config in the config api

* refactor: image upload modals

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-12-07 19:59:35 +05:30
Manish Gupta
af3267ac5a Updated the slack notification message to PR Title (#2869) 2023-12-07 19:59:35 +05:30
Lakhan Baheti
192fe9b057 chore: added error toast for invitation (#2853) 2023-12-07 19:59:35 +05:30
Bavisetti Narayan
0669dab1c4 chore: user deactivation and login restriction (#2855)
* chore: user deactivation

* chore: deactivation and login disabled

* chore: added get configuration value

* chore: serializer message change

* chore: instance admin passowrd change

* chore: removed triage

* chore: v3 endpoint for user profile

* chore: added enable signin

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2023-12-07 19:59:35 +05:30
Nikhil
34e6ef0d8d chore: api and webhook refactor (#2861)
* chore: bug fix

* dev: changes in api endpoints for invitations and inbox

* chore: improvements

* dev: update webhook send

* dev: webhook validation and fix webhook flow for app

* dev: error messages for deactivation

* chore: api fixes

* dev: update webhook and workspace leave

* chore: issue comment

* dev: default values for environment variables

* dev: make the user active if he was already part of project member

* chore: webhook cycle and module event

* dev: disable ssl for emails

* dev: webhooks restructuring

* dev: updated webhook configuration

* dev: webhooks

* dev: state get object

* dev: update workspace slug validation

* dev: remove deactivation flag if max retries exceeded

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2023-12-07 19:59:35 +05:30
sriram veeraghanta
c305cf2c72 fix: adding slack notification when build is failed to upload to docker (#2862)
* fix: removing logs

* fix: adding slack notification when build is failed to upload to docker

* minor changes

---------

Co-authored-by: Manish Gupta <59428681+manishg3@users.noreply.github.com>
2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
267cf75004 chore: update profile and God mode routes (#2860)
* chore: update profile and god mode routes

* fix: profile activity loader

* chore: update profile route in the change password page
2023-12-07 19:59:35 +05:30
Bavisetti Narayan
86de38d3a0 chore: user activity in profile page (#2856)
* chore: user activity endpoint change

* chore: added workspace detail in activity serializer
2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
910bd11e86 fix: view date filter select fix (#2858) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
77af7311ba style: module ui improvement (#2838) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
f56067f372 style: issue activity section improvement (#2836) 2023-12-07 19:59:35 +05:30
Ramesh Kumar Chandra
0530410201 feat: change password page (#2847) 2023-12-07 19:59:35 +05:30
sabith-tu
1e104e85a4 style: removing extra options heading and drop down icon (#2852) 2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
dfffa63151 fix: page scroll area (#2850) 2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
bf525aa2c4 dev: added tailwind merge helper function (#2844) 2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
fc523c6485 fix: archived issues infinite call (#2848) 2023-12-07 19:59:35 +05:30
sriram veeraghanta
8c1f9e720a fix: updated document editor package in web and space apps (#2846) 2023-12-07 19:59:35 +05:30
sriram veeraghanta
fa8ae6b8ce chore: optimizations and file name changes (#2845)
* fix: deepsource antipatterns

* fix: deepsource exclude file patterns

* chore: file name changes and removed unwanted variables

* fix: changing version number for editor
2023-12-07 19:59:35 +05:30
guru_sainath
d6abb87a3a chore: implemented new store and issue layouts for issues and updated new data structure for issues (#2843)
* fix: Implemented new workflow in the issue store and updated the quick add workflow in list layout

* fix: initial load and mutaion of issues in list layout

* dev: implemented the new project issues store with grouped, subGrouped and unGrouped issue computed functions

* dev: default display properties data made as a function

* conflict: merge conflict resolved

* dev: implemented quick add logic in kanban

* chore: implemented quick add logic in calendar and spreadsheet layout

* fix: spreadsheet layout quick add fix

* dev: optimised the issues workflow and handled the issues order_by filter

* dev: project issue CRUD operations in new issue store architecture

* dev: issues filtering in calendar layout

* fix: build error

* dev/issue_filters_store

* chore: updated filters computed structure

* conflict: merge conflicts resolved in project issues

* dev: implemented gantt chart for project issues using the new mobx store

* dev: initialized cycle and module issue filters store

* dev: issue store and list layout store updates

* dev: quick add and update, delete issue in the list

* refactor list root changes

* dev: store new structure

* refactor spreadsheet and gnatt project roots

* fix errors for base gantt and spreadsheet roots

* connect Calendar project view

* minor house keeping

* connect Kanban View to th enew store

* generalise base calendar issue actions

* dev: store project issues and issue filters

* dev: store project issues and filters

* dev: updated undefined with displayFilters in project issue store

* Add Quick add to all the layouts

* connect module views to store

* dev: Rendering list issues in project issues

* dev: removed console log

* dev: module filters store

* fix errors and connect modules list and quick add for list

* dev: module issue store

* dev: modle filter store issue fixed and updates cycle issue filters

* minor house keeping changes

* dev: cycle issues and cycle filters

* connecty cycles to teh store

* dev: project view issues and issue filtrs

* connect project views

* dev: updated applied filters in layouts

* dev: replaced project id with view id in project views

* dev: in cycle and module store made cycledId and moduleId as optional

* fix minor issues and build errots

* dev: project draft and archived issues store and filters

---------

Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
Co-authored-by: rahulramesha <rahulramesham@gmail.com>
2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
db75eced0a chore: deactivate user option added (#2841)
* dev: deactivate user option added

* chore: new layout for profile settings

* fix: build errors

* fix: user profile activity
2023-12-07 19:59:35 +05:30
Lakhan Baheti
3c89ef8cc3 fix: onboarding bugs & improvements (#2839)
* fix: terms & condition alignment

* fix: onboarding page scrolling

* fix: create workspace name clear

* fix: setup profile sidebar workspace name

* fix: invite team screen button text

* fix: inner div min height

* fix: allow single invite also in invite member

* fix: UI clipping in invite members

* fix: signin screen scroll

* fix: sidebar notification icon

* fix: sidebar project name & icon

* fix: user detail bottom image alignment

* fix: step indicator in invite member

* fix: try different account modal state

* fix: setup profile remove image

* fix: workspace slug clear

* fix: invite member UI & focus

* fix: step indicator size

* fix: inner div placement

* fix: invite member validation logic

* fix: cuurent user data persistency

* fix: sidebar animation colors

* feat: signup & resend

* fix: sign out theme persist from popover

* fix: imports

* chore: signin responsiveness

* fix: sign-in, sign-up top padding
2023-12-07 19:59:35 +05:30
Bavisetti Narayan
f9590929dc chore: change password endpoint (#2842) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
7169463ee7 [FED-888] fix: parent issue select modal improvement (#2837)
This PR include improvement for parent issue select modal.
2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
152a920788 fix: project setting ui consistency (#2835) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
a1a9015df2 fix: profile setting overflow (#2834) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
37559fd69f fix: cycle and module create/update modal fix (#2833) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
1559822a1b fix: module and cycle sidebar loading state (#2831) 2023-12-07 19:59:35 +05:30
sabith-tu
9215134e32 style: new empty project screen (#2832) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
51cf93c9a8 fix: custom analytics project dropdown fix (#2828) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
3dd3499b3e style: project card improvement (#2827) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
20f3d7ce09 fix: module sidebar link section (#2830) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
b106e15268 chore: dashboard redirection fix (#2826) 2023-12-07 19:59:35 +05:30
sriram veeraghanta
666d46de58 fix: AI Assistance hide/unhide depending on the configuration (#2825)
* fix: gpt error handlijng

* fix: enabling ai assistance only when it is configured.
2023-12-07 19:59:35 +05:30
Prateek Shourya
734f27122b Style: UI improvements (#2824)
* style: update notification Read status toast alert description.

* style: update issue subscribe button design.

* fix: remove group_by `none` display filter from the kanban view in profile and draft issues.

* style: design improvement in members settings.
* style: add display name for all user role.
* style: remove email for user roles other than admin.
* style: fix border color as per designs.
2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
d43db7fc88 style: revamped page details UI (#2823)
* style: revamp page details UI

* chore: updated the info popover date format

* fix: page actions mutation

* style: made the page content responsive
2023-12-07 19:59:35 +05:30
Bavisetti Narayan
e57b95f99e chore: file asset update (#2816)
* chore: endpoint to update file asset

* chore: aws storage endpoint change
2023-12-07 19:59:35 +05:30
Lakhan Baheti
e21acf1341 fix: sidebar project section hover (#2818)
* fix: sidebar project section hover

* fix: icons alignment
2023-12-07 19:59:35 +05:30
Lakhan Baheti
7825dd7f77 fix: bug fixes & UI improvements (#2819)
* fix: profile setting fields border

* fix: webhooks empty state UI

* fix: cycle delete redirection from cycle detail

* fix: integration access restriction
2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
cfbb4c9579 chore: update join project endpoint (#2821) 2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
7200cbf58e fix: complete pages editor not clickable, recent pages calculation logic (#2820)
* fix: whole editor not clickable

* fix: recent pages calculation

* chore: update older pages calculation logic in recent pages list

* fix: archived pages computed function

* chore: add type for older pages
2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
aea9a40a73 refactor: command k modal (#2803)
* refactor: command palette file structure

* fix: identifier search
2023-12-07 19:59:35 +05:30
Nikhil
d14ca3a141 dev: change url for the license engine instance registration (#2810) 2023-12-07 19:59:35 +05:30
Bavisetti Narayan
88ef24788e chore: removed DOCKERIZED value and changed REDIS_SSL (#2813)
* chore: removed DOCKERIZED value

* chore: changed redis ssl
2023-12-07 19:59:35 +05:30
Henit Chobisa
4416419c9b feat: New Pages with Enhanced Document Editor Packages made over Editor Core 📝 (#2784)
* fix: page transaction model

* fix: page transaction model

* feat: updated ui for page route

* chore: initailized `document-editor` package for plane

* fix: format persistence while pasting markdown in editor

* feat: Inititalized Document-Editor and Editor with Ref

* feat: added tooltip component and slash command for editor

* feat: added `document-editor` extensions

* feat: added custom search component for embedding labels

* feat: added top bar menu component

* feat: created document-editor exposed components

* feat: integrated `document-editor` in `pages` route

* chore: updated dependencies

* feat: merge conflict resolution

* chore: modified configuration for document editor

* feat: added content browser menu for document editor summary

* feat: added fixed menu and editor instances

* feat: added document edittor instances and summary table

* feat: implemented document-editor in PageDetail

* chore: css and export fixes

* fix: migration and optimisation

* fix: added `on_create` hook in the core editor

* feat: added conditional menu bar action in document-editor

* feat: added menu actions from single page view

* feat: added services for archiving, unarchiving and retriving archived pages

* feat: added services for page archives

* feat: implemented page archives in page list view

* feat: implemented page archives in document-editor

* feat: added editor marking hook

* chore: seperated editor header from the main content

* chore: seperated editor summary utilities from the main editor

* chore: refactored necessary components from the document editor

* chore: removed summary sidebar component from the main content editor

* chore: removed scrollSummaryDependency from Header and Sidebar

* feat: seperated page renderer as a seperate component

* chore: seperated page_renderer and sidebar as component from index

* feat: added locked property to IPage type

* feat: added lock/unlock services in page service

* chore: seperated DocumentDetails as exported interface from index

* feat: seperated document editor configs as seperate interfaces

* chore: seperated menu options from the editor header component

* fix: fixed page_lock performing lock/unlock operation on queryset instead of single instance

* fix: css positioning changes

* feat: added archive/lock alert labels

* feat: added boolean props in menu-actions/options

* feat: added lock/unlock & archive/unarchive services

* feat: added on update mutations for archived pages in page-view

* feat: added archive/lock on_update mutations in single page vieq

* feat: exported readonly editor for locked pages

* chore: seperated kanban menu props and saved over passing redundant data

* fix: readonly editor not generating markings on first render

* fix: cheveron overflowing from editor-header

* chore: removed unused utility actions

* fix: enabled sidebar view by default

* feat: removed locking on pages in archived state

* feat: added indentation in heading component

* fix: button classnames in vertical dropdowns

* feat: added `last_archived_at` and `last_edited_at` details in editor-header

* feat: changed types for archived updates and document last updates

* feat: updated editor and header props

* feat: updated queryset according to new page query format

* feat: added parameters in page view for shared / private pages

* feat: updated other-page-view to shared page view && same with private pages

* feat: added page-view as shared / private

* fix: replaced deleting to archiving for pages

* feat: handle restoring of page from archived section from list view

* feat: made previledge based option render for pages

* feat: removed layout view for page list view

* feat: linting changes

* fix: adding mobx changes to pages

* fix: removed uneccessary migrations

* fix: mobx store changes

* fix: adding date-fns pacakge

* fix: updating yarn lock

* fix: removing unneccessary method params

* chore: added access specifier to the create/update page modal

* fix: tab view layout changes

* chore: delete endpoint for page

* fix: page actions, including- archive, favorite, access control, delete

* chore: remove archive page modal

* fix: build errors

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-12-07 19:59:35 +05:30
Prateek Shourya
2a2e504ebb feat: Instance Admin Panel: Configuration Settings (#2800)
* feat: Instance Admin Panel: Configuration Settings

* refactor: seprate Google and Github form into independent components.

* feat: add admin auth wrapper and access denied page.

* style: design updates.
2023-12-07 19:59:35 +05:30
sabith-tu
7978c8277c style: changing profile screen title (#2814) 2023-12-07 19:59:35 +05:30
sriram veeraghanta
28c4703bf4 fix: minor fix (#2815) 2023-12-07 19:59:35 +05:30
Lakhan Baheti
6512b8205f chore: onboarding (#2790)
* style: onboarding light version

* style: dark mode

* fix: onboarding gradient

* refactor: imports

* chore: add use case field in users api

* feat: delete account

* fix: delete modal points alignment

* feat: usecase in profile

* fix: build error

* fix: typos & hardcoded strings

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
2023-12-07 19:59:35 +05:30
Prashant Indurkar
9d5f835bea Fixed: while creating new Add Labels the field should be auto focus #2437 (#2438)
* bug:fix recent page hiding last item on scroll #1468

* bug:fix recent page hiding last item on scroll #1468 (#2411)

* fixed add label autofocuse

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2023-12-07 19:59:35 +05:30
Lakhan Baheti
3ea926a908 fix: bug fixes & ui improvements. (#2772)
* fix: create project modal member select

* fix: overflow in workspace activity

* fix: memeber selected state
2023-12-07 19:59:35 +05:30
Bavisetti Narayan
8e9f9cf6df chore: ams url name changed (#2808) 2023-12-07 19:59:35 +05:30
Nikhil
97c50b2957 dev: open ai configuration (#2807) 2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
af8804eb12 refactor: project estimates store (#2801)
* refactor: remove estimates from project store

* chore: update all the instances of the old store

* chore: update store declaration structure
2023-12-07 19:59:35 +05:30
Nikhil
d3c85b1336 dev: external apis (#2806)
* dev: new proxy api setup

* dev: updated endpoints with serializers and structure

* dev: external apis for cycles, modules and inbox
issue

* dev: order by for all the apis

* dev: enable webhooks for external apis

* dev: fields and expand for the apis

* dev: move authentication to proxy middleware

* dev: fix imports

* dev: api serializer updates and paginator

* dev: renamed api to app

* dev: renamed proxy to api

* dev: validation for project, issues, modules and cycles

* dev: remove favourites from project apis

* dev: states api

* dev: rewrite the url endpoints

* dev: exception handling for the apis

* dev: merge updated structure

* dev: remove attachment apis

* dev: issue activities endpoints
2023-12-07 19:59:35 +05:30
Aaryan Khandelwal
a5ee049692 chore: update exception detected screen action button (#2805) 2023-12-07 19:59:35 +05:30
Bavisetti Narayan
2f75611662 fix: file asset delete (#2804) 2023-12-07 19:59:35 +05:30
onFire(Abhi)
b210fc8032 fix: newly added cycle doesnt appear unlelss the page is manually reloaded (#2673)
* fix: newly added cycle doesnt appear unlelss the page is manually reloaded

* Delete \

* Delete web/layouts/profile-layout/profile-sidebar.tsx

* Update cycles.store.ts

* fix: remove duplicate type declaration

---------

Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
2023-12-07 19:59:35 +05:30
sriram veeraghanta
78fee22fec feat: event tracking using posthog and created application provider to render multiple wrappers (#2757)
* fix: event tracker changes

* fix: App provider implementation using wrappers

* fix: updating packages

* fix: handling warning

* fix: wrapper fixes and minor optimization changes

* fix: chore app-provider clearnup

* fix: cleanup

* fix: removing jitsu tracking

* fix: minor updates

* fix: adding event to posthog event tracker (#2802)

* dev: posthog event tracker update intitiate

* fix: adding events for posthog integration

* fix: event payload

---------

Co-authored-by: Ramesh Kumar Chandra <31303617+rameshkumarchandra@users.noreply.github.com>
2023-12-07 19:59:35 +05:30
Dakshesh Jain
33be52792f fix: archive issue bugs (#2712)
* fix: blur on side/modal peek view

* fix: delete archive not working on list layout with group by is none

* fix: show empty group has no effect

* fix: filter/display options same as production

* fix: disabling full-screen peek-overview for archive issues

* fix: truncate in calendar view
2023-12-07 19:59:35 +05:30
Nikhil
bd5ebc2760 fix: self hosted instance (#2795)
* dev: update create bucket script

* dev: update patch endpoint for instance configuration

* dev: add google client secret and default values for ADMIN_EMAIL and LICENSE_ENGINE_BASE_URL
2023-12-07 19:59:35 +05:30
Dakshesh Jain
2abc5eb68c fix: delete issues in spreadsheet doesn't work (#2718)
Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
2023-12-07 19:59:35 +05:30
Bavisetti Narayan
ced5bfd930 fix: file structuring (#2797)
* fix: file structure changes

* fix: pages update

* fix: license imports changed
2023-12-07 19:59:35 +05:30
Nikhil
728213e3fd dev: environment settings (#2794)
* dev: update environment configuration

* dev: update the takeoff script for instance registration
2023-12-07 19:59:35 +05:30
Lakhan Baheti
63b6150b9c fix: Labels delete & reordering (#2729)
* fix: Labels reordering inconsistency

* fix: Delete child labels

* feat: multi-select while grouping labels

* refactor: label sorting in mobx computed function

* feat: drag & drop label grouping, un-grouping

* chore: removed label select modal

* fix: moving labels from project store to project label store

* fix: typo changes and build tree function added

* labels feature

* disable dropping group into a group

* fix build errors

* fix more issues

* chore: added combining state UI, fixed scroll issue for label groups

* chore: group icon for label groups

* fix: group cannot be dropped in another group

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
Co-authored-by: rahulramesha <rahulramesham@gmail.com>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-12-07 19:59:35 +05:30
Bavisetti Narayan
d933c73343 feat: v3 endpoint for module and cycle (#2786)
* feat: v3 endpoint for module and cycle

* fix: removed the str
2023-12-07 19:59:35 +05:30
M. Palanikannan
2cca0b1e76 fix: Task List Behaviour in Editor (#2789)
* better variable names and comments

* drag drop migrated

* custom horizontal rule created

* init transaction hijack

* fixed code block with better contrast, keyboard tripple enter press disabled and syntax highlighting

* fixed link selector closing on open behaviour

* added better keymaps and syntax highlights

* made drag and drop working for code blocks

* fixed drag drop for code blocks

* moved drag drop only to rich text editor

* fixed drag and drop only for description

* enabled drag handles for peek overview and main issues

* got images to old state

* fixed task lists to be smaller

* removed validate image functions and uncessary imports

* table icons svg attributes fixed

* custom list keymap extension added

* more uncessary imports of validate image removed

* removed console logs

* fixed drag-handle styles

* space styles updated for the editor

* removed showing quotes from blockquotes

* removed validateImage for now

* added better comments and improved redundant renders

* removed uncessary console logs

* created util for creating the drag handle element

* fixed file names
2023-12-07 19:59:35 +05:30
Nikhil
3d8da99eec chore: user onboarding workflow (#2791) 2023-12-07 19:59:35 +05:30
sriram veeraghanta
eb53876af3 feat: Instance Registration and Configuration (#2793)
* dev: remove default user

* dev: initiate licensing

* dev: remove migration file 0046

* feat: self hosted licensing initialize

* dev: instance licenses

* dev: change license response structure

* dev: add default properties and issue mention migration

* dev: reset migrations

* dev: instance configuration

* dev: instance configuration migration

* dev: update instance configuration model to take null and empty values

* dev: instance configuration variables

* dev: set default values

* dev: update instance configuration load

* dev: email configuration settings moved to database

* dev: instance configuration on instance bootup

* dev: auto instance registration script

* dev: instance admin

* dev: enable instance configuration and instance admin roles

* dev: instance owner fix

* dev: instance configuration values

* dev: fix instance permissions and serializer

* dev: fix email senders

* dev: remove deprecated variables

* dev: fix current site domain registration

* dev: update cors setup and local settings

* dev: migrate instance registration and configuration to manage commands

* dev: check email validity

* dev: update script to use manage command

* dev: default bucket creation script

* dev: instance admin routes and initial set of screens

* dev: admin api to check if the current user is admin

* dev: instance admin unique constraints

* dev: check magic link login

* dev: fix email sending for ssl

* dev: create instance activation route if the instance is not activated during startup

* dev: removed DJANGO_SETTINGS_MODULE from environment files and deleted auto bucket create script

* dev: environment configuration for backend

* dev: fix access token variable error

* feat: Instance Admin Panel: General Settings (#2792)

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
2023-12-07 19:59:35 +05:30
M. Palanikannan
34ab188a99 [feat]: Drag and Drop Handles for all Data Structures (#2745)
* better variable names and comments

* drag drop migrated

* custom horizontal rule created

* init transaction hijack

* fixed code block with better contrast, keyboard tripple enter press disabled and syntax highlighting

* fixed link selector closing on open behaviour

* added better keymaps and syntax highlights

* made drag and drop working for code blocks

* fixed drag drop for code blocks

* moved drag drop only to rich text editor

* fixed drag and drop only for description

* enabled drag handles for peek overview and main issues

* got images to old state
2023-12-07 19:59:35 +05:30
Manish Gupta
1f61ad141e dev: Self Hosting with private repo fixes (#2787)
* fixes to self hosting

* self hosting fixes

* removed .temp

* wip

* wip

* self install private repo

* folder change

* fix

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2023-12-07 19:59:35 +05:30
Manish Gupta
3a1b64d8f8 Dev/mg selfhosting fix (#2782)
* fixes to self hosting

* self hosting fixes

* removed .temp

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2023-12-07 19:59:35 +05:30
Bavisetti Narayan
eb6809c015 fix: pages revamping (#2760)
* fix: page transaction model

* fix: page transaction model

* fix: migration and optimisation

* fix: back migration of page blocks

* fix: added issue embed

* fix: migration fixes

* fix: resolved changes
2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
ca899b2ac7 style: workspace sidebar dropdown improvement (#2783) 2023-12-07 19:59:35 +05:30
Nikhil
eead64ba49 dev: squashed migrations (#2779)
* dev: migration squash

* dev: migrations squashed for apis and webhooks

* dev: packages updated and  move dj-database-url for local settings

* dev: update package changes
2023-12-07 19:59:35 +05:30
Bavisetti Narayan
870c4403e4 feat: api webhooks (#2543)
* dev: initiate external apis

* dev: external api

* dev: external public api implementation

* dev: add prefix to all api tokens

* dev: flag to enable disable api token api access

* dev: webhook model create and apis

* dev: webhook settings

* fix: webhook logs

* chore: removed drf spectacular

* dev: remove retry_count and fix api logging for get requests

* dev: refactor webhook logic

* fix: celery retry mechanism

* chore: event and action change

* chore: migrations changes

* dev: proxy setup for apis

* chore: changed retry time and cleanup

* chore: added issue comment and inbox issue api endpoints

* fix: migration files

* fix: added env variables

* fix: removed issue attachment from proxy

* fix: added new migration file

* fix: restricted wehbook access

* chore: changed urls

* chore: fixed porject serializer

* fix: set expire for api token

* fix: retrive endpoint for api token

* feat: Api Token screens & api integration

* dev: webhook endpoint changes

* dev: add fields for webhook updates

* feat: Download Api secret key

* chore: removed BASE API URL

* feat: revoke token access

* dev: migration fixes

* feat: workspace webhooks (#2748)

* feat: workspace webhook store, services integeration and rendered webhook list and create

* chore: handled webhook update and rengenerate token in workspace webhooks

* feat: regenerate key and delete functionality

---------

Co-authored-by: Ramesh Kumar <rameshkumar@rameshs-MacBook-Pro.local>
Co-authored-by: gurusainath <gurusainath007@gmail.com>
Co-authored-by: Ramesh Kumar Chandra <rameshkumar2299@gmail.com>

* fix: url validation added

* fix: seperated env for webhook and api

* Web hooks refactoring

* add show option for generated hook key

* Api token restructure

* webhook minor fixes

* fix build errors

* chore: improvements in file structring

* dev: rate limiting the open apis

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: LAKHAN BAHETI <lakhanbaheti9@gmail.com>
Co-authored-by: rahulramesha <71900764+rahulramesha@users.noreply.github.com>
Co-authored-by: Ramesh Kumar <rameshkumar@rameshs-MacBook-Pro.local>
Co-authored-by: gurusainath <gurusainath007@gmail.com>
Co-authored-by: Ramesh Kumar Chandra <rameshkumar2299@gmail.com>
Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
Co-authored-by: rahulramesha <rahulramesham@gmail.com>
2023-12-07 19:59:35 +05:30
Nikhil
20fd57b793 dev: update bucket script to make the bucket public (#2767)
* dev: update bucket script to make the bucket public

* dev: remove auto bucket script from docker compose
2023-12-07 19:59:35 +05:30
Nikhil
bdbdacd68c chore: user workflow (#2762)
* dev: workspace member deactivation and leave endpoints and filters

* dev: deactivated for project members

* dev: project members leave

* dev: project member check on workspace deactivation

* dev: project member queryset update and remove leave project endpoint

* dev: rename is_deactivated to is_active and user deactivation apis

* dev: check if the user is already part of workspace then make them active

* dev: workspace and project save

* dev: update project members to make them active

* dev: project invitation

* dev: automatic user workspace and project member create when user sign in/up

* dev: fix member invites

* dev: rename deactivation variable

* dev: update project member invitation

* dev: additional permission layer for workspace

* dev: update the url for  workspace invitations

* dev: remove invitation urls from users

* dev: cleanup workspace invitation workflow

* dev: workspace and project invitation
2023-12-07 19:59:35 +05:30
sabith-tu
1f904e88e1 fix: Delete estimate popup is not closing automatically (#2777) 2023-12-07 19:59:35 +05:30
Nikhil
a675cd5755 dev: API settings (#2594)
* dev: update settings file structure and added extra settings for CORS

* dev: remove WEB_URL variable and add celery integration for sentry

* dev: aws and minio settings

* dev: add cors origins to env

* dev: update settings
2023-12-07 19:59:35 +05:30
Nikhil
ad3e511328 enhancement: label sort order (#2763)
* chore: label sort ordering

* dev: ordering

* fix: sort order

* fix: save of labels

* dev: remove ordering by name

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2023-12-07 19:59:35 +05:30
Prateek Shourya
d158fe8193 Fix: Custom menu item not automatically closing, affecting delete popup behavior. (#2771) 2023-12-07 19:59:35 +05:30
Ankush Deshmukh
2d1536e44d Standarding priority icons across the platform (#2776) 2023-12-07 19:59:35 +05:30
Prateek Shourya
002dc7a5f3 style: text overflow fix and border color update (#2769)
* style: fix text overflow in:
* Issue activity
* Cycle and Module Select in Create Issue form
* Delete Module modal
* Join Project modal

* style: update assignee select border as per design.
2023-12-07 19:59:35 +05:30
Dakshesh Jain
e96f059f65 fix: bugs (#2761)
* fix: semicolon on estimate settings page

* refactor: project settings automations store implementation

* fix: active cycle stuck on infinite loading

* fix: removed delete project option from sidebar

* fix: discloser not opening when navigating to project

* fix: clear filter not working & filter appearing even if nothing is selected

* refactor: select label store implementation

* refactor: select state store implementation
2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
964e880fc4 style: create update view modal consistency (#2775) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
93abff5a4b chore: no lead option added in lead select dropdown (#2774) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
4fcc66fd54 chore: spreadsheet layout column responsiveness (#2768) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
4c09e46de7 chore: create update issue modal improvement (#2765) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
4e37916616 fix: breadcrumb project icon improvement (#2764) 2023-12-07 19:59:35 +05:30
sabith-tu
48ed439523 style: ui fixes for pages and views (#2770) 2023-12-07 19:59:35 +05:30
Nikhil
794bfd6e3b dev: create bucket through application (#2720) 2023-12-07 19:59:35 +05:30
Prateek Shourya
16292de8d3 style: ui improvements and bug fixes (#2758)
* style: add transition to favorite projects dropdown.

* style: update project integration settings borders.

* style: fix text overflow issue in project views.

* fix: issue with non-functional cancel button in leave project modal.
2023-12-07 19:59:35 +05:30
Dakshesh Jain
21988e8528 fix: workspace settings bugs (#2743)
* fix: double layout in exports

* fix: typo in jira email address section

* fix: workspace members not mutating

* fix: removed un-used variable

* fix: workspace members can't be filtered using email

* fix: autocomplete in workspace delete

* fix: autocomplete in project delete modal

* fix: update member function in store

* fix: sidebar link not active when in github/jira

* style: margin top & icon inconsistency

* fix: typo in create workspace

* fix: workspace leave flow

* fix: redirection to delete issue

* fix: autocomplete off in jira api token

* refactor: reduced api call, added optional chaining & removed variable with low scope
2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
4d35c931cd fix: peek overview comment ordering and comment icon alignment fix (#2753) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
5cbec23d5e fix: cycle card title responsiveness added (#2752) 2023-12-07 19:59:35 +05:30
Anmol Singh Bhatia
942323f81c fix: app sidebar dropdown fix (#2751) 2023-12-07 19:59:35 +05:30
sriram veeraghanta
ee2ad400ba Merge pull request #2754 from makeplane/develop
sync: develop to release
2023-11-10 19:06:22 +05:30
sriram veeraghanta
0ceb9974f6 dev: hub compose file update (#2376) (#2444) (#2445)
* docker-compose-hub modified for envs

* bug:fix recent page hiding last item on scroll #1468 (#2411)

* wip

* fixed the AMD build on ARM

---------

Co-authored-by: Manish Gupta <59428681+manishg3@users.noreply.github.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
2023-10-16 13:00:58 +05:30
Prashant Indurkar
e0fcc0c876 bug:fix recent page hiding last item on scroll #1468 (#2411) 2023-10-12 16:35:00 +05:30
2162 changed files with 118197 additions and 70632 deletions

View File

@@ -1,5 +1,11 @@
version = 1
exclude_patterns = [
"bin/**",
"**/node_modules/",
"**/*.min.js"
]
[[analyzers]]
name = "shell"

View File

@@ -2,5 +2,16 @@
*.pyc
.env
venv
node_modules
npm-debug.log
node_modules/
**/node_modules/
npm-debug.log
.next/
**/.next/
.turbo/
**/.turbo/
build/
**/build/
out/
**/out/
dist/
**/dist/

View File

@@ -1,14 +1,12 @@
# Database Settings
PGUSER="plane"
PGPASSWORD="plane"
PGHOST="plane-db"
PGDATABASE="plane"
DATABASE_URL=postgresql://${PGUSER}:${PGPASSWORD}@${PGHOST}/${PGDATABASE}
POSTGRES_USER="plane"
POSTGRES_PASSWORD="plane"
POSTGRES_DB="plane"
PGDATA="/var/lib/postgresql/data"
# Redis Settings
REDIS_HOST="plane-redis"
REDIS_PORT="6379"
REDIS_URL="redis://${REDIS_HOST}:6379/"
# AWS Settings
AWS_REGION=""
@@ -21,15 +19,15 @@ AWS_S3_BUCKET_NAME="uploads"
FILE_SIZE_LIMIT=5242880
# GPT settings
OPENAI_API_BASE="https://api.openai.com/v1" # change if using a custom endpoint
OPENAI_API_KEY="sk-" # add your openai key here
GPT_ENGINE="gpt-3.5-turbo" # use "gpt-4" if you have access
OPENAI_API_BASE="https://api.openai.com/v1" # deprecated
OPENAI_API_KEY="sk-" # deprecated
GPT_ENGINE="gpt-3.5-turbo" # deprecated
# Settings related to Docker
DOCKERIZED=1
DOCKERIZED=1 # deprecated
# set to 1 If using the pre-configured minio setup
USE_MINIO=1
# Nginx Configuration
NGINX_PORT=80

View File

@@ -1,7 +1,8 @@
name: Bug report
description: Create a bug report to help us improve Plane
title: "[bug]: "
labels: [bug, need testing]
labels: [🐛bug]
assignees: [srinivaspendem, pushya-plane]
body:
- type: markdown
attributes:

View File

@@ -1,7 +1,8 @@
name: Feature request
description: Suggest a feature to improve Plane
title: "[feature]: "
labels: [feature]
labels: [feature]
assignees: [srinivaspendem, pushya-plane]
body:
- type: markdown
attributes:

View File

@@ -1,120 +1,91 @@
name: Branch Build
on:
pull_request:
types:
- closed
branches:
workflow_dispatch:
push:
branches:
- master
- release
- qa
- preview
- develop
release:
types: [released, prereleased]
env:
TARGET_BRANCH: ${{ github.event.pull_request.base.ref }}
TARGET_BRANCH: ${{ github.ref_name || github.event.release.target_commitish }}
jobs:
branch_build_and_push:
if: ${{ (github.event_name == 'pull_request' && github.event.action =='closed' && github.event.pull_request.merged == true) }}
branch_build_setup:
name: Build-Push Web/Space/API/Proxy Docker Image
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
outputs:
gh_branch_name: ${{ steps.set_env_variables.outputs.TARGET_BRANCH }}
gh_buildx_driver: ${{ steps.set_env_variables.outputs.BUILDX_DRIVER }}
gh_buildx_version: ${{ steps.set_env_variables.outputs.BUILDX_VERSION }}
gh_buildx_platforms: ${{ steps.set_env_variables.outputs.BUILDX_PLATFORMS }}
gh_buildx_endpoint: ${{ steps.set_env_variables.outputs.BUILDX_ENDPOINT }}
steps:
- name: Check out the repo
uses: actions/checkout@v3.3.0
# - name: Set Target Branch Name on PR close
# if: ${{ github.event_name == 'pull_request' && github.event.action =='closed' }}
# run: echo "TARGET_BRANCH=${{ github.event.pull_request.base.ref }}" >> $GITHUB_ENV
# - name: Set Target Branch Name on other than PR close
# if: ${{ github.event_name == 'push' }}
# run: echo "TARGET_BRANCH=${{ github.ref_name }}" >> $GITHUB_ENV
- uses: ASzc/change-string-case-action@v2
id: gh_branch_upper_lower
with:
string: ${{env.TARGET_BRANCH}}
- uses: mad9000/actions-find-and-replace-string@2
id: gh_branch_replace_slash
with:
source: ${{ steps.gh_branch_upper_lower.outputs.lowercase }}
find: '/'
replace: '-'
- uses: mad9000/actions-find-and-replace-string@2
id: gh_branch_replace_dot
with:
source: ${{ steps.gh_branch_replace_slash.outputs.value }}
find: '.'
replace: ''
- uses: mad9000/actions-find-and-replace-string@2
id: gh_branch_clean
with:
source: ${{ steps.gh_branch_replace_dot.outputs.value }}
find: '_'
replace: ''
- name: Uploading Proxy Source
uses: actions/upload-artifact@v3
with:
name: proxy-src-code
path: ./nginx
- name: Uploading Backend Source
uses: actions/upload-artifact@v3
with:
name: backend-src-code
path: ./apiserver
- name: Uploading Web Source
uses: actions/upload-artifact@v3
with:
name: web-src-code
path: |
./
!./apiserver
!./nginx
!./deploy
!./space
- name: Uploading Space Source
uses: actions/upload-artifact@v3
with:
name: space-src-code
path: |
./
!./apiserver
!./nginx
!./deploy
!./web
outputs:
gh_branch_name: ${{ steps.gh_branch_clean.outputs.value }}
- id: set_env_variables
name: Set Environment Variables
run: |
if [ "${{ env.TARGET_BRANCH }}" == "master" ]; then
echo "BUILDX_DRIVER=cloud" >> $GITHUB_OUTPUT
echo "BUILDX_VERSION=lab:latest" >> $GITHUB_OUTPUT
echo "BUILDX_PLATFORMS=linux/amd64,linux/arm64" >> $GITHUB_OUTPUT
echo "BUILDX_ENDPOINT=makeplane/plane-dev" >> $GITHUB_OUTPUT
else
echo "BUILDX_DRIVER=docker-container" >> $GITHUB_OUTPUT
echo "BUILDX_VERSION=latest" >> $GITHUB_OUTPUT
echo "BUILDX_PLATFORMS=linux/amd64" >> $GITHUB_OUTPUT
echo "BUILDX_ENDPOINT=" >> $GITHUB_OUTPUT
fi
echo "TARGET_BRANCH=${{ env.TARGET_BRANCH }}" >> $GITHUB_OUTPUT
branch_build_push_frontend:
runs-on: ubuntu-20.04
needs: [ branch_build_and_push ]
needs: [branch_build_setup]
env:
FRONTEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend:${{ needs.branch_build_setup.outputs.gh_branch_name }}
TARGET_BRANCH: ${{ needs.branch_build_setup.outputs.gh_branch_name }}
BUILDX_DRIVER: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
BUILDX_VERSION: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
BUILDX_PLATFORMS: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }}
BUILDX_ENDPOINT: ${{ needs.branch_build_setup.outputs.gh_buildx_endpoint }}
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.5.0
- name: Set Frontend Docker Tag
run: |
if [ "${{ env.TARGET_BRANCH }}" == "master" ] && [ "${{ github.event_name }}" == "release" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend:${{ github.event.release.tag_name }}
elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend:stable
else
TAG=${{ env.FRONTEND_TAG }}
fi
echo "FRONTEND_TAG=${TAG}" >> $GITHUB_ENV
- name: Login to Docker Hub
uses: docker/login-action@v2.1.0
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Downloading Web Source Code
uses: actions/download-artifact@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
name: web-src-code
driver: ${{ env.BUILDX_DRIVER }}
version: ${{ env.BUILDX_VERSION }}
endpoint: ${{ env.BUILDX_ENDPOINT }}
- name: Check out the repo
uses: actions/checkout@v4.1.1
- name: Build and Push Frontend to Docker Container Registry
uses: docker/build-push-action@v4.0.0
uses: docker/build-push-action@v5.1.0
with:
context: .
file: ./web/Dockerfile.web
platforms: linux/amd64
tags: ${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend-private:${{ needs.branch_build_and_push.outputs.gh_branch_name }}
platforms: ${{ env.BUILDX_PLATFORMS }}
tags: ${{ env.FRONTEND_TAG }}
push: true
env:
DOCKER_BUILDKIT: 1
@@ -123,28 +94,49 @@ jobs:
branch_build_push_space:
runs-on: ubuntu-20.04
needs: [ branch_build_and_push ]
needs: [branch_build_setup]
env:
SPACE_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-space:${{ needs.branch_build_setup.outputs.gh_branch_name }}
TARGET_BRANCH: ${{ needs.branch_build_setup.outputs.gh_branch_name }}
BUILDX_DRIVER: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
BUILDX_VERSION: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
BUILDX_PLATFORMS: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }}
BUILDX_ENDPOINT: ${{ needs.branch_build_setup.outputs.gh_buildx_endpoint }}
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.5.0
- name: Set Space Docker Tag
run: |
if [ "${{ env.TARGET_BRANCH }}" == "master" ] && [ "${{ github.event_name }}" == "release" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-space:${{ github.event.release.tag_name }}
elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space:stable
else
TAG=${{ env.SPACE_TAG }}
fi
echo "SPACE_TAG=${TAG}" >> $GITHUB_ENV
- name: Login to Docker Hub
uses: docker/login-action@v2.1.0
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Downloading Space Source Code
uses: actions/download-artifact@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
name: space-src-code
driver: ${{ env.BUILDX_DRIVER }}
version: ${{ env.BUILDX_VERSION }}
endpoint: ${{ env.BUILDX_ENDPOINT }}
- name: Check out the repo
uses: actions/checkout@v4.1.1
- name: Build and Push Space to Docker Hub
uses: docker/build-push-action@v4.0.0
uses: docker/build-push-action@v5.1.0
with:
context: .
file: ./space/Dockerfile.space
platforms: linux/amd64
tags: ${{ secrets.DOCKERHUB_USERNAME }}/plane-space-private:${{ needs.branch_build_and_push.outputs.gh_branch_name }}
platforms: ${{ env.BUILDX_PLATFORMS }}
tags: ${{ env.SPACE_TAG }}
push: true
env:
DOCKER_BUILDKIT: 1
@@ -153,61 +145,104 @@ jobs:
branch_build_push_backend:
runs-on: ubuntu-20.04
needs: [ branch_build_and_push ]
needs: [branch_build_setup]
env:
BACKEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-backend:${{ needs.branch_build_setup.outputs.gh_branch_name }}
TARGET_BRANCH: ${{ needs.branch_build_setup.outputs.gh_branch_name }}
BUILDX_DRIVER: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
BUILDX_VERSION: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
BUILDX_PLATFORMS: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }}
BUILDX_ENDPOINT: ${{ needs.branch_build_setup.outputs.gh_buildx_endpoint }}
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.5.0
- name: Set Backend Docker Tag
run: |
if [ "${{ env.TARGET_BRANCH }}" == "master" ] && [ "${{ github.event_name }}" == "release" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-backend:${{ github.event.release.tag_name }}
elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend:stable
else
TAG=${{ env.BACKEND_TAG }}
fi
echo "BACKEND_TAG=${TAG}" >> $GITHUB_ENV
- name: Login to Docker Hub
uses: docker/login-action@v2.1.0
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Downloading Backend Source Code
uses: actions/download-artifact@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
name: backend-src-code
driver: ${{ env.BUILDX_DRIVER }}
version: ${{ env.BUILDX_VERSION }}
endpoint: ${{ env.BUILDX_ENDPOINT }}
- name: Check out the repo
uses: actions/checkout@v4.1.1
- name: Build and Push Backend to Docker Hub
uses: docker/build-push-action@v4.0.0
uses: docker/build-push-action@v5.1.0
with:
context: .
file: ./Dockerfile.api
platforms: linux/amd64
context: ./apiserver
file: ./apiserver/Dockerfile.api
platforms: ${{ env.BUILDX_PLATFORMS }}
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-private:${{ needs.branch_build_and_push.outputs.gh_branch_name }}
tags: ${{ env.BACKEND_TAG }}
env:
DOCKER_BUILDKIT: 1
DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
branch_build_push_proxy:
runs-on: ubuntu-20.04
needs: [ branch_build_and_push ]
needs: [branch_build_setup]
env:
PROXY_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy:${{ needs.branch_build_setup.outputs.gh_branch_name }}
TARGET_BRANCH: ${{ needs.branch_build_setup.outputs.gh_branch_name }}
BUILDX_DRIVER: ${{ needs.branch_build_setup.outputs.gh_buildx_driver }}
BUILDX_VERSION: ${{ needs.branch_build_setup.outputs.gh_buildx_version }}
BUILDX_PLATFORMS: ${{ needs.branch_build_setup.outputs.gh_buildx_platforms }}
BUILDX_ENDPOINT: ${{ needs.branch_build_setup.outputs.gh_buildx_endpoint }}
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.5.0
- name: Set Proxy Docker Tag
run: |
if [ "${{ env.TARGET_BRANCH }}" == "master" ] && [ "${{ github.event_name }}" == "release" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy:${{ github.event.release.tag_name }}
elif [ "${{ env.TARGET_BRANCH }}" == "master" ]; then
TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy:stable
else
TAG=${{ env.PROXY_TAG }}
fi
echo "PROXY_TAG=${TAG}" >> $GITHUB_ENV
- name: Login to Docker Hub
uses: docker/login-action@v2.1.0
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Downloading Proxy Source Code
uses: actions/download-artifact@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
name: proxy-src-code
driver: ${{ env.BUILDX_DRIVER }}
version: ${{ env.BUILDX_VERSION }}
endpoint: ${{ env.BUILDX_ENDPOINT }}
- name: Check out the repo
uses: actions/checkout@v4.1.1
- name: Build and Push Plane-Proxy to Docker Hub
uses: docker/build-push-action@v4.0.0
uses: docker/build-push-action@v5.1.0
with:
context: .
file: ./Dockerfile
platforms: linux/amd64
tags: ${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy-private:${{ needs.branch_build_and_push.outputs.gh_branch_name }}
context: ./nginx
file: ./nginx/Dockerfile
platforms: ${{ env.BUILDX_PLATFORMS }}
tags: ${{ env.PROXY_TAG }}
push: true
env:
DOCKER_BUILDKIT: 1
DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}

View File

@@ -1,6 +1,6 @@
name: Build Pull Request Contents
on:
on:
pull_request:
types: ["opened", "synchronize"]
@@ -14,16 +14,18 @@ jobs:
steps:
- name: Checkout Repository to Actions
uses: actions/checkout@v3.3.0
with:
token: ${{ secrets.ACCESS_TOKEN }}
- name: Setup Node.js 18.x
uses: actions/setup-node@v2
with:
node-version: 18.x
cache: 'yarn'
cache: "yarn"
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v38
uses: tj-actions/changed-files@v41
with:
files_yaml: |
apiserver:
@@ -44,5 +46,3 @@ jobs:
run: |
yarn
yarn build --filter=space

65
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,65 @@
name: "CodeQL"
on:
push:
branches: [ 'develop', 'preview', 'master' ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ 'develop', 'preview', 'master' ]
schedule:
- cron: '53 19 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'python', 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Use only 'java' to analyze code written in Java, Kotlin or both
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"

View File

@@ -1,42 +1,28 @@
name: Create PR in Plane EE Repository to sync the changes
name: Create Sync Action
on:
pull_request:
workflow_dispatch:
push:
branches:
- master
types:
- closed
- preview
env:
SOURCE_BRANCH_NAME: ${{ github.ref_name }}
jobs:
create_pr:
# Only run the job when a PR is merged
if: github.event.pull_request.merged == true
sync_changes:
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
steps:
- name: Check SOURCE_REPO
id: check_repo
env:
SOURCE_REPO: ${{ secrets.SOURCE_REPO_NAME }}
run: |
echo "::set-output name=is_correct_repo::$(if [[ "$SOURCE_REPO" == "makeplane/plane" ]]; then echo 'true'; else echo 'false'; fi)"
- name: Checkout Code
if: steps.check_repo.outputs.is_correct_repo == 'true'
uses: actions/checkout@v2
uses: actions/checkout@v4.1.1
with:
persist-credentials: false
fetch-depth: 0
- name: Set up Branch Name
if: steps.check_repo.outputs.is_correct_repo == 'true'
run: |
echo "SOURCE_BRANCH_NAME=${{ github.head_ref }}" >> $GITHUB_ENV
- name: Setup GH CLI
if: steps.check_repo.outputs.is_correct_repo == 'true'
run: |
type -p curl >/dev/null || (sudo apt update && sudo apt install curl -y)
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
@@ -45,35 +31,14 @@ jobs:
sudo apt update
sudo apt install gh -y
- name: Create Pull Request
if: steps.check_repo.outputs.is_correct_repo == 'true'
- name: Push Changes to Target Repo
env:
GH_TOKEN: ${{ secrets.ACCESS_TOKEN }}
run: |
TARGET_REPO="${{ secrets.TARGET_REPO_NAME }}"
TARGET_BRANCH="${{ secrets.TARGET_REPO_BRANCH }}"
TARGET_REPO="${{ secrets.SYNC_TARGET_REPO_NAME }}"
TARGET_BRANCH="${{ secrets.SYNC_TARGET_BRANCH_NAME }}"
SOURCE_BRANCH="${{ env.SOURCE_BRANCH_NAME }}"
git checkout $SOURCE_BRANCH
git remote add target "https://$GH_TOKEN@github.com/$TARGET_REPO.git"
git push target $SOURCE_BRANCH:$SOURCE_BRANCH
PR_TITLE="${{ github.event.pull_request.title }}"
PR_BODY="${{ github.event.pull_request.body }}"
# Remove double quotes
PR_TITLE_CLEANED="${PR_TITLE//\"/}"
PR_BODY_CLEANED="${PR_BODY//\"/}"
# Construct PR_BODY_CONTENT using a here-document
PR_BODY_CONTENT=$(cat <<EOF
$PR_BODY_CLEANED
EOF
)
gh pr create \
--base $TARGET_BRANCH \
--head $SOURCE_BRANCH \
--title "[SYNC] $PR_TITLE_CLEANED" \
--body "$PR_BODY_CONTENT" \
--repo $TARGET_REPO
git remote add target-origin "https://$GH_TOKEN@github.com/$TARGET_REPO.git"
git push target-origin $SOURCE_BRANCH:$TARGET_BRANCH

View File

@@ -1,107 +0,0 @@
name: Update Docker Images for Plane on Release
on:
release:
types: [released, prereleased]
jobs:
build_push_backend:
name: Build and Push Api Server Docker Image
runs-on: ubuntu-20.04
steps:
- name: Check out the repo
uses: actions/checkout@v3.3.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.5.0
- name: Login to Docker Hub
uses: docker/login-action@v2.1.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker (Docker Hub) from Github Release
id: metaFrontend
uses: docker/metadata-action@v4.3.0
with:
images: ${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend
tags: |
type=ref,event=tag
- name: Extract metadata (tags, labels) for Docker (Docker Hub) from Github Release
id: metaBackend
uses: docker/metadata-action@v4.3.0
with:
images: ${{ secrets.DOCKERHUB_USERNAME }}/plane-backend
tags: |
type=ref,event=tag
- name: Extract metadata (tags, labels) for Docker (Docker Hub) from Github Release
id: metaSpace
uses: docker/metadata-action@v4.3.0
with:
images: ${{ secrets.DOCKERHUB_USERNAME }}/plane-space
tags: |
type=ref,event=tag
- name: Extract metadata (tags, labels) for Docker (Docker Hub) from Github Release
id: metaProxy
uses: docker/metadata-action@v4.3.0
with:
images: ${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy
tags: |
type=ref,event=tag
- name: Build and Push Frontend to Docker Container Registry
uses: docker/build-push-action@v4.0.0
with:
context: .
file: ./web/Dockerfile.web
platforms: linux/amd64
tags: ${{ steps.metaFrontend.outputs.tags }}
push: true
env:
DOCKER_BUILDKIT: 1
DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKET_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and Push Backend to Docker Hub
uses: docker/build-push-action@v4.0.0
with:
context: ./apiserver
file: ./apiserver/Dockerfile.api
platforms: linux/amd64
push: true
tags: ${{ steps.metaBackend.outputs.tags }}
env:
DOCKER_BUILDKIT: 1
DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKET_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and Push Plane-Deploy to Docker Hub
uses: docker/build-push-action@v4.0.0
with:
context: .
file: ./space/Dockerfile.space
platforms: linux/amd64
push: true
tags: ${{ steps.metaSpace.outputs.tags }}
env:
DOCKER_BUILDKIT: 1
DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKET_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and Push Plane-Proxy to Docker Hub
uses: docker/build-push-action@v4.0.0
with:
context: ./nginx
file: ./nginx/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ steps.metaProxy.outputs.tags }}
env:
DOCKER_BUILDKIT: 1
DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKET_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}

1
.gitignore vendored
View File

@@ -79,3 +79,4 @@ pnpm-workspace.yaml
tmp/
## packages
dist
.temp/

View File

@@ -33,8 +33,8 @@ The backend is a django project which is kept inside apiserver
1. Clone the repo
```bash
git clone https://github.com/makeplane/plane
cd plane
git clone https://github.com/makeplane/plane.git [folder-name]
cd [folder-name]
chmod +x setup.sh
```
@@ -44,33 +44,12 @@ chmod +x setup.sh
./setup.sh
```
3. Define `NEXT_PUBLIC_API_BASE_URL=http://localhost` in **web/.env** and **space/.env** file
3. Start the containers
```bash
echo "\nNEXT_PUBLIC_API_BASE_URL=http://localhost\n" >> ./web/.env
docker compose -f docker-compose-local.yml up
```
```bash
echo "\nNEXT_PUBLIC_API_BASE_URL=http://localhost\n" >> ./space/.env
```
4. Run Docker compose up
```bash
docker compose up -d
```
5. Install dependencies
```bash
yarn install
```
6. Run the web app in development mode
```bash
yarn dev
```
## Missing a Feature?

View File

@@ -43,8 +43,6 @@ FROM python:3.11.1-alpine3.17 AS backend
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
ENV DJANGO_SETTINGS_MODULE plane.settings.production
ENV DOCKERIZED 1
WORKDIR /code
@@ -81,7 +79,6 @@ COPY apiserver/manage.py manage.py
COPY apiserver/plane plane/
COPY apiserver/templates templates/
COPY apiserver/gunicorn.config.py ./
RUN apk --no-cache add "bash~=5.2"
COPY apiserver/bin ./bin/

View File

@@ -31,12 +31,10 @@ AWS_S3_BUCKET_NAME="uploads"
FILE_SIZE_LIMIT=5242880
# GPT settings
OPENAI_API_BASE="https://api.openai.com/v1" # change if using a custom endpoint
OPENAI_API_KEY="sk-" # add your openai key here
GPT_ENGINE="gpt-3.5-turbo" # use "gpt-4" if you have access
OPENAI_API_BASE="https://api.openai.com/v1" # deprecated
OPENAI_API_KEY="sk-" # deprecated
GPT_ENGINE="gpt-3.5-turbo" # deprecated
# Settings related to Docker
DOCKERIZED=1
# set to 1 If using the pre-configured minio setup
USE_MINIO=1
@@ -51,24 +49,10 @@ NGINX_PORT=80
```
# Enable/Disable OAUTH - default 0 for selfhosted instance
NEXT_PUBLIC_ENABLE_OAUTH=0
# Public boards deploy URL
NEXT_PUBLIC_DEPLOY_URL="http://localhost/spaces"
```
## {PROJECT_FOLDER}/spaces/.env.example
```
# Flag to toggle OAuth
NEXT_PUBLIC_ENABLE_OAUTH=0
```
## {PROJECT_FOLDER}/apiserver/.env
@@ -78,7 +62,6 @@ NEXT_PUBLIC_ENABLE_OAUTH=0
# Backend
# Debug value for api server use it as 0 for production use
DEBUG=0
DJANGO_SETTINGS_MODULE="plane.settings.selfhosted"
# Error logs
SENTRY_DSN=""
@@ -115,24 +98,22 @@ AWS_S3_BUCKET_NAME="uploads"
FILE_SIZE_LIMIT=5242880
# GPT settings
OPENAI_API_BASE="https://api.openai.com/v1" # change if using a custom endpoint
OPENAI_API_KEY="sk-" # add your openai key here
GPT_ENGINE="gpt-3.5-turbo" # use "gpt-4" if you have access
OPENAI_API_BASE="https://api.openai.com/v1" # deprecated
OPENAI_API_KEY="sk-" # deprecated
GPT_ENGINE="gpt-3.5-turbo" # deprecated
# Settings related to Docker
DOCKERIZED=1 # Deprecated
# Github
GITHUB_CLIENT_SECRET="" # For fetching release notes
# Settings related to Docker
DOCKERIZED=1
# set to 1 If using the pre-configured minio setup
USE_MINIO=1
# Nginx Configuration
NGINX_PORT=80
# Default Creds
DEFAULT_EMAIL="captain@plane.so"
DEFAULT_PASSWORD="password123"
# SignUps
ENABLE_SIGNUP="1"

View File

@@ -37,7 +37,7 @@ Meet [Plane](https://plane.so). An open-source software development tool to mana
> Plane is still in its early days, not everything will be perfect yet, and hiccups may happen. Please let us know of any suggestions, ideas, or bugs that you encounter on our [Discord](https://discord.com/invite/A92xrEGCge) or GitHub issues, and we will use your feedback to improve on our upcoming releases.
The easiest way to get started with Plane is by creating a [Plane Cloud](https://app.plane.so) account. Plane Cloud offers a hosted solution for Plane. If you prefer to self-host Plane, please refer to our [deployment documentation](https://docs.plane.so/self-hosting).
The easiest way to get started with Plane is by creating a [Plane Cloud](https://app.plane.so) account. Plane Cloud offers a hosted solution for Plane. If you prefer to self-host Plane, please refer to our [deployment documentation](https://docs.plane.so/docker-compose).
## ⚡️ Contributors Quick Start
@@ -57,17 +57,13 @@ Setting up local environment is extremely easy and straight forward. Follow the
1. Review the `.env` files available in various folders. Visit [Environment Setup](./ENV_SETUP.md) to know about various environment variables used in system
1. Run the docker command to initiate various services `docker compose -f docker-compose-local.yml up -d`
```bash
./setup.sh
```
You are ready to make changes to the code. Do not forget to refresh the browser (in case id does not auto-reload)
Thats it!
## 🍙 Self Hosting
For self hosting environment setup, visit the [Self Hosting](https://docs.plane.so/self-hosting) documentation page
For self hosting environment setup, visit the [Self Hosting](https://docs.plane.so/docker-compose) documentation page
## 🚀 Features

View File

@@ -1,32 +1,29 @@
# Backend
# Debug value for api server use it as 0 for production use
DEBUG=0
DJANGO_SETTINGS_MODULE="plane.settings.production"
CORS_ALLOWED_ORIGINS=""
# Error logs
SENTRY_DSN=""
SENTRY_ENVIRONMENT="development"
# Database Settings
PGUSER="plane"
PGPASSWORD="plane"
PGHOST="plane-db"
PGDATABASE="plane"
DATABASE_URL=postgresql://${PGUSER}:${PGPASSWORD}@${PGHOST}/${PGDATABASE}
POSTGRES_USER="plane"
POSTGRES_PASSWORD="plane"
POSTGRES_HOST="plane-db"
POSTGRES_DB="plane"
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}/${POSTGRES_DB}
# Oauth variables
GOOGLE_CLIENT_ID=""
GITHUB_CLIENT_ID=""
GITHUB_CLIENT_SECRET=""
# Redis Settings
REDIS_HOST="plane-redis"
REDIS_PORT="6379"
REDIS_URL="redis://${REDIS_HOST}:6379/"
# Email Settings
EMAIL_HOST=""
EMAIL_HOST_USER=""
EMAIL_HOST_PASSWORD=""
EMAIL_PORT=587
EMAIL_FROM="Team Plane <team@mailer.plane.so>"
EMAIL_USE_TLS="1"
EMAIL_USE_SSL="0"
# AWS Settings
AWS_REGION=""
AWS_ACCESS_KEY_ID="access-key"
@@ -38,29 +35,23 @@ AWS_S3_BUCKET_NAME="uploads"
FILE_SIZE_LIMIT=5242880
# GPT settings
OPENAI_API_BASE="https://api.openai.com/v1" # change if using a custom endpoint
OPENAI_API_KEY="sk-" # add your openai key here
GPT_ENGINE="gpt-3.5-turbo" # use "gpt-4" if you have access
# Github
GITHUB_CLIENT_SECRET="" # For fetching release notes
OPENAI_API_BASE="https://api.openai.com/v1" # deprecated
OPENAI_API_KEY="sk-" # deprecated
GPT_ENGINE="gpt-3.5-turbo" # deprecated
# Settings related to Docker
DOCKERIZED=1
DOCKERIZED=1 # deprecated
# set to 1 If using the pre-configured minio setup
USE_MINIO=1
# Nginx Configuration
NGINX_PORT=80
# Default Creds
DEFAULT_EMAIL="captain@plane.so"
DEFAULT_PASSWORD="password123"
# SignUps
ENABLE_SIGNUP="1"
# Enable Email/Password Signup
ENABLE_EMAIL_PASSWORD="1"
@@ -70,6 +61,6 @@ ENABLE_MAGIC_LINK_LOGIN="0"
# Email redirections and minio domain settings
WEB_URL="http://localhost"
# Gunicorn Workers
GUNICORN_WORKERS=2

View File

@@ -43,8 +43,7 @@ USER captain
COPY manage.py manage.py
COPY plane plane/
COPY templates templates/
COPY gunicorn.config.py ./
COPY package.json package.json
USER root
RUN apk --no-cache add "bash~=5.2"
COPY ./bin ./bin/

View File

@@ -27,20 +27,16 @@ WORKDIR /code
COPY requirements.txt ./requirements.txt
ADD requirements ./requirements
RUN pip install -r requirements.txt --compile --no-cache-dir
# Install the local development settings
RUN pip install -r requirements/local.txt --compile --no-cache-dir
RUN addgroup -S plane && \
adduser -S captain -G plane
RUN chown captain.plane /code
COPY . .
USER captain
# Add in Django deps and generate Django's static files
USER root
# RUN chmod +x ./bin/takeoff ./bin/worker ./bin/beat
RUN chown -R captain.plane /code
RUN chmod -R +x /code/bin
RUN chmod -R 777 /code
USER captain
@@ -48,5 +44,5 @@ USER captain
# Expose container port and run entry point script
EXPOSE 8000
# CMD [ "./bin/takeoff" ]
CMD [ "./bin/takeoff.local" ]

View File

@@ -26,7 +26,9 @@ def update_description():
updated_issues.append(issue)
Issue.objects.bulk_update(
updated_issues, ["description_html", "description_stripped"], batch_size=100
updated_issues,
["description_html", "description_stripped"],
batch_size=100,
)
print("Success")
except Exception as e:
@@ -40,7 +42,9 @@ def update_comments():
updated_issue_comments = []
for issue_comment in issue_comments:
issue_comment.comment_html = f"<p>{issue_comment.comment_stripped}</p>"
issue_comment.comment_html = (
f"<p>{issue_comment.comment_stripped}</p>"
)
updated_issue_comments.append(issue_comment)
IssueComment.objects.bulk_update(
@@ -99,7 +103,9 @@ def updated_issue_sort_order():
issue.sort_order = issue.sequence_id * random.randint(100, 500)
updated_issues.append(issue)
Issue.objects.bulk_update(updated_issues, ["sort_order"], batch_size=100)
Issue.objects.bulk_update(
updated_issues, ["sort_order"], batch_size=100
)
print("Success")
except Exception as e:
print(e)
@@ -137,7 +143,9 @@ def update_project_cover_images():
project.cover_image = project_cover_images[random.randint(0, 19)]
updated_projects.append(project)
Project.objects.bulk_update(updated_projects, ["cover_image"], batch_size=100)
Project.objects.bulk_update(
updated_projects, ["cover_image"], batch_size=100
)
print("Success")
except Exception as e:
print(e)
@@ -186,7 +194,9 @@ def update_label_color():
def create_slack_integration():
try:
_ = Integration.objects.create(provider="slack", network=2, title="Slack")
_ = Integration.objects.create(
provider="slack", network=2, title="Slack"
)
print("Success")
except Exception as e:
print(e)
@@ -212,12 +222,16 @@ def update_integration_verified():
def update_start_date():
try:
issues = Issue.objects.filter(state__group__in=["started", "completed"])
issues = Issue.objects.filter(
state__group__in=["started", "completed"]
)
updated_issues = []
for issue in issues:
issue.start_date = issue.created_at.date()
updated_issues.append(issue)
Issue.objects.bulk_update(updated_issues, ["start_date"], batch_size=500)
Issue.objects.bulk_update(
updated_issues, ["start_date"], batch_size=500
)
print("Success")
except Exception as e:
print(e)

3
apiserver/bin/beat Normal file → Executable file
View File

@@ -2,4 +2,7 @@
set -e
python manage.py wait_for_db
# Wait for migrations
python manage.py wait_for_migrations
# Run the processes
celery -A plane beat -l info

View File

@@ -1,9 +1,31 @@
#!/bin/bash
set -e
python manage.py wait_for_db
python manage.py migrate
# Wait for migrations
python manage.py wait_for_migrations
# Create a Default User
python bin/user_script.py
# Create the default bucket
#!/bin/bash
exec gunicorn -w $GUNICORN_WORKERS -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile -
# Collect system information
HOSTNAME=$(hostname)
MAC_ADDRESS=$(ip link show | awk '/ether/ {print $2}' | head -n 1)
CPU_INFO=$(cat /proc/cpuinfo)
MEMORY_INFO=$(free -h)
DISK_INFO=$(df -h)
# Concatenate information and compute SHA-256 hash
SIGNATURE=$(echo "$HOSTNAME$MAC_ADDRESS$CPU_INFO$MEMORY_INFO$DISK_INFO" | sha256sum | awk '{print $1}')
# Export the variables
export MACHINE_SIGNATURE=$SIGNATURE
# Register instance
python manage.py register_instance $MACHINE_SIGNATURE
# Load the configuration variable
python manage.py configure_instance
# Create the default bucket
python manage.py create_bucket
exec gunicorn -w $GUNICORN_WORKERS -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:${PORT:-8000} --max-requests 1200 --max-requests-jitter 1000 --access-logfile -

32
apiserver/bin/takeoff.local Executable file
View File

@@ -0,0 +1,32 @@
#!/bin/bash
set -e
python manage.py wait_for_db
# Wait for migrations
python manage.py wait_for_migrations
# Create the default bucket
#!/bin/bash
# Collect system information
HOSTNAME=$(hostname)
MAC_ADDRESS=$(ip link show | awk '/ether/ {print $2}' | head -n 1)
CPU_INFO=$(cat /proc/cpuinfo)
MEMORY_INFO=$(free -h)
DISK_INFO=$(df -h)
# Concatenate information and compute SHA-256 hash
SIGNATURE=$(echo "$HOSTNAME$MAC_ADDRESS$CPU_INFO$MEMORY_INFO$DISK_INFO" | sha256sum | awk '{print $1}')
# Export the variables
export MACHINE_SIGNATURE=$SIGNATURE
# Register instance
python manage.py register_instance $MACHINE_SIGNATURE
# Load the configuration variable
python manage.py configure_instance
# Create the default bucket
python manage.py create_bucket
python manage.py runserver 0.0.0.0:8000 --settings=plane.settings.local

View File

@@ -1,28 +0,0 @@
import os, sys
import uuid
sys.path.append("/code")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "plane.settings.production")
import django
django.setup()
from plane.db.models import User
def populate():
default_email = os.environ.get("DEFAULT_EMAIL", "captain@plane.so")
default_password = os.environ.get("DEFAULT_PASSWORD", "password123")
if not User.objects.filter(email=default_email).exists():
user = User.objects.create(email=default_email, username=uuid.uuid4().hex)
user.set_password(default_password)
user.save()
print(f"User created with an email: {default_email}")
else:
print(f"User already exists with the default email: {default_email}")
if __name__ == "__main__":
populate()

View File

@@ -2,4 +2,7 @@
set -e
python manage.py wait_for_db
# Wait for migrations
python manage.py wait_for_migrations
# Run the processes
celery -A plane worker -l info

0
apiserver/file.txt Normal file
View File

View File

@@ -1,6 +0,0 @@
from psycogreen.gevent import patch_psycopg
def post_fork(server, worker):
patch_psycopg()
worker.log.info("Made Psycopg2 Green")

View File

@@ -2,10 +2,10 @@
import os
import sys
if __name__ == '__main__':
if __name__ == "__main__":
os.environ.setdefault(
'DJANGO_SETTINGS_MODULE',
'plane.settings.production')
"DJANGO_SETTINGS_MODULE", "plane.settings.production"
)
try:
from django.core.management import execute_from_command_line
except ImportError as exc:

4
apiserver/package.json Normal file
View File

@@ -0,0 +1,4 @@
{
"name": "plane-api",
"version": "0.15.0"
}

View File

@@ -1,3 +1,3 @@
from .celery import app as celery_app
__all__ = ('celery_app',)
__all__ = ("celery_app",)

View File

@@ -2,4 +2,4 @@ from django.apps import AppConfig
class AnalyticsConfig(AppConfig):
name = 'plane.analytics'
name = "plane.analytics"

View File

@@ -0,0 +1,50 @@
# Django imports
from django.utils import timezone
from django.db.models import Q
# Third party imports
from rest_framework import authentication
from rest_framework.exceptions import AuthenticationFailed
# Module imports
from plane.db.models import APIToken
class APIKeyAuthentication(authentication.BaseAuthentication):
"""
Authentication with an API Key
"""
www_authenticate_realm = "api"
media_type = "application/json"
auth_header_name = "X-Api-Key"
def get_api_token(self, request):
return request.headers.get(self.auth_header_name)
def validate_api_token(self, token):
try:
api_token = APIToken.objects.get(
Q(
Q(expired_at__gt=timezone.now())
| Q(expired_at__isnull=True)
),
token=token,
is_active=True,
)
except APIToken.DoesNotExist:
raise AuthenticationFailed("Given API token is not valid")
# save api token last used
api_token.last_used = timezone.now()
api_token.save(update_fields=["last_used"])
return (api_token.user, api_token.token)
def authenticate(self, request):
token = self.get_api_token(request=request)
if not token:
return None
# Validate the API token
user, token = self.validate_api_token(token)
return user, token

View File

@@ -1,2 +0,0 @@
from .workspace import WorkSpaceBasePermission, WorkSpaceAdminPermission, WorkspaceEntityPermission, WorkspaceViewerPermission
from .project import ProjectBasePermission, ProjectEntityPermission, ProjectMemberPermission, ProjectLitePermission

View File

@@ -0,0 +1,42 @@
from rest_framework.throttling import SimpleRateThrottle
class ApiKeyRateThrottle(SimpleRateThrottle):
scope = "api_key"
rate = "60/minute"
def get_cache_key(self, request, view):
# Retrieve the API key from the request header
api_key = request.headers.get("X-Api-Key")
if not api_key:
return None # Allow the request if there's no API key
# Use the API key as part of the cache key
return f"{self.scope}:{api_key}"
def allow_request(self, request, view):
allowed = super().allow_request(request, view)
if allowed:
now = self.timer()
# Calculate the remaining limit and reset time
history = self.cache.get(self.key, [])
# Remove old histories
while history and history[-1] <= now - self.duration:
history.pop()
# Calculate the requests
num_requests = len(history)
# Check available requests
available = self.num_requests - num_requests
# Unix timestamp for when the rate limit will reset
reset_time = int(now + self.duration)
# Add headers
request.META["X-RateLimit-Remaining"] = max(0, available)
request.META["X-RateLimit-Reset"] = reset_time
return allowed

View File

@@ -1,102 +1,19 @@
from .base import BaseSerializer
from .user import (
UserSerializer,
UserLiteSerializer,
ChangePasswordSerializer,
ResetPasswordSerializer,
UserAdminLiteSerializer,
UserMeSerializer,
UserMeSettingsSerializer,
)
from .workspace import (
WorkSpaceSerializer,
WorkSpaceMemberSerializer,
TeamSerializer,
WorkSpaceMemberInviteSerializer,
WorkspaceLiteSerializer,
WorkspaceThemeSerializer,
WorkspaceMemberAdminSerializer,
WorkspaceMemberMeSerializer,
)
from .project import (
ProjectSerializer,
ProjectListSerializer,
ProjectDetailSerializer,
ProjectMemberSerializer,
ProjectMemberInviteSerializer,
ProjectIdentifierSerializer,
ProjectFavoriteSerializer,
ProjectLiteSerializer,
ProjectMemberLiteSerializer,
ProjectDeployBoardSerializer,
ProjectMemberAdminSerializer,
ProjectPublicMemberSerializer,
)
from .state import StateSerializer, StateLiteSerializer
from .view import GlobalViewSerializer, IssueViewSerializer, IssueViewFavoriteSerializer
from .cycle import (
CycleSerializer,
CycleIssueSerializer,
CycleFavoriteSerializer,
CycleWriteSerializer,
)
from .asset import FileAssetSerializer
from .user import UserLiteSerializer
from .workspace import WorkspaceLiteSerializer
from .project import ProjectSerializer, ProjectLiteSerializer
from .issue import (
IssueCreateSerializer,
IssueActivitySerializer,
IssueCommentSerializer,
IssuePropertySerializer,
IssueAssigneeSerializer,
LabelSerializer,
IssueSerializer,
IssueFlatSerializer,
IssueStateSerializer,
LabelSerializer,
IssueLinkSerializer,
IssueLiteSerializer,
IssueAttachmentSerializer,
IssueSubscriberSerializer,
IssueReactionSerializer,
CommentReactionSerializer,
IssueVoteSerializer,
IssueRelationSerializer,
RelatedIssueSerializer,
IssuePublicSerializer,
IssueCommentSerializer,
IssueActivitySerializer,
IssueExpandSerializer,
)
from .state import StateLiteSerializer, StateSerializer
from .cycle import CycleSerializer, CycleIssueSerializer, CycleLiteSerializer
from .module import (
ModuleWriteSerializer,
ModuleSerializer,
ModuleIssueSerializer,
ModuleLinkSerializer,
ModuleFavoriteSerializer,
ModuleLiteSerializer,
)
from .api_token import APITokenSerializer
from .integration import (
IntegrationSerializer,
WorkspaceIntegrationSerializer,
GithubIssueSyncSerializer,
GithubRepositorySerializer,
GithubRepositorySyncSerializer,
GithubCommentSyncSerializer,
SlackProjectSyncSerializer,
)
from .importer import ImporterSerializer
from .page import PageSerializer, PageBlockSerializer, PageFavoriteSerializer
from .estimate import (
EstimateSerializer,
EstimatePointSerializer,
EstimateReadSerializer,
)
from .inbox import InboxSerializer, InboxIssueSerializer, IssueStateInboxSerializer
from .analytic import AnalyticViewSerializer
from .notification import NotificationSerializer
from .exporter import ExporterHistorySerializer
from .inbox import InboxIssueSerializer

View File

@@ -1,14 +0,0 @@
from .base import BaseSerializer
from plane.db.models import APIToken
class APITokenSerializer(BaseSerializer):
class Meta:
model = APIToken
fields = [
"label",
"user",
"user_type",
"workspace",
"created_at",
]

View File

@@ -1,22 +1,22 @@
# Third party imports
from rest_framework import serializers
class BaseSerializer(serializers.ModelSerializer):
id = serializers.PrimaryKeyRelatedField(read_only=True)
class DynamicBaseSerializer(BaseSerializer):
def __init__(self, *args, **kwargs):
# If 'fields' is provided in the arguments, remove it and store it separately.
# This is done so as not to pass this custom argument up to the superclass.
fields = kwargs.pop("fields", None)
fields = kwargs.pop("fields", [])
self.expand = kwargs.pop("expand", []) or []
# Call the initialization of the superclass.
super().__init__(*args, **kwargs)
# If 'fields' was provided, filter the fields of the serializer accordingly.
if fields is not None:
self.fields = self._filter_fields(fields)
if fields:
self.fields = self._filter_fields(fields=fields)
def _filter_fields(self, fields):
"""
@@ -31,7 +31,7 @@ class DynamicBaseSerializer(BaseSerializer):
# loop through its keys and values.
if isinstance(field_name, dict):
for key, value in field_name.items():
# If the value of this nested field is a list,
# If the value of this nested field is a list,
# perform a recursive filter on it.
if isinstance(value, list):
self._filter_fields(self.fields[key], value)
@@ -52,7 +52,56 @@ class DynamicBaseSerializer(BaseSerializer):
allowed = set(allowed)
# Remove fields from the serializer that aren't in the 'allowed' list.
for field_name in (existing - allowed):
for field_name in existing - allowed:
self.fields.pop(field_name)
return self.fields
def to_representation(self, instance):
response = super().to_representation(instance)
# Ensure 'expand' is iterable before processing
if self.expand:
for expand in self.expand:
if expand in self.fields:
# Import all the expandable serializers
from . import (
WorkspaceLiteSerializer,
ProjectLiteSerializer,
UserLiteSerializer,
StateLiteSerializer,
IssueSerializer,
)
# Expansion mapper
expansion = {
"user": UserLiteSerializer,
"workspace": WorkspaceLiteSerializer,
"project": ProjectLiteSerializer,
"default_assignee": UserLiteSerializer,
"project_lead": UserLiteSerializer,
"state": StateLiteSerializer,
"created_by": UserLiteSerializer,
"issue": IssueSerializer,
"actor": UserLiteSerializer,
"owned_by": UserLiteSerializer,
"members": UserLiteSerializer,
}
# Check if field in expansion then expand the field
if expand in expansion:
if isinstance(response.get(expand), list):
exp_serializer = expansion[expand](
getattr(instance, expand), many=True
)
else:
exp_serializer = expansion[expand](
getattr(instance, expand)
)
response[expand] = exp_serializer.data
else:
# You might need to handle this case differently
response[expand] = getattr(
instance, f"{expand}_id", None
)
return response

View File

@@ -3,43 +3,19 @@ from rest_framework import serializers
# Module imports
from .base import BaseSerializer
from .user import UserLiteSerializer
from .issue import IssueStateSerializer
from .workspace import WorkspaceLiteSerializer
from .project import ProjectLiteSerializer
from plane.db.models import Cycle, CycleIssue, CycleFavorite
class CycleWriteSerializer(BaseSerializer):
def validate(self, data):
if (
data.get("start_date", None) is not None
and data.get("end_date", None) is not None
and data.get("start_date", None) > data.get("end_date", None)
):
raise serializers.ValidationError("Start date cannot exceed end date")
return data
class Meta:
model = Cycle
fields = "__all__"
from plane.db.models import Cycle, CycleIssue
class CycleSerializer(BaseSerializer):
owned_by = UserLiteSerializer(read_only=True)
is_favorite = serializers.BooleanField(read_only=True)
total_issues = serializers.IntegerField(read_only=True)
cancelled_issues = serializers.IntegerField(read_only=True)
completed_issues = serializers.IntegerField(read_only=True)
started_issues = serializers.IntegerField(read_only=True)
unstarted_issues = serializers.IntegerField(read_only=True)
backlog_issues = serializers.IntegerField(read_only=True)
assignees = serializers.SerializerMethodField(read_only=True)
total_estimates = serializers.IntegerField(read_only=True)
completed_estimates = serializers.IntegerField(read_only=True)
started_estimates = serializers.IntegerField(read_only=True)
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
def validate(self, data):
if (
@@ -47,33 +23,20 @@ class CycleSerializer(BaseSerializer):
and data.get("end_date", None) is not None
and data.get("start_date", None) > data.get("end_date", None)
):
raise serializers.ValidationError("Start date cannot exceed end date")
raise serializers.ValidationError(
"Start date cannot exceed end date"
)
return data
def get_assignees(self, obj):
members = [
{
"avatar": assignee.avatar,
"display_name": assignee.display_name,
"id": assignee.id,
}
for issue_cycle in obj.issue_cycle.prefetch_related(
"issue__assignees"
).all()
for assignee in issue_cycle.issue.assignees.all()
]
# Use a set comprehension to return only the unique objects
unique_objects = {frozenset(item.items()) for item in members}
# Convert the set back to a list of dictionaries
unique_list = [dict(item) for item in unique_objects]
return unique_list
class Meta:
model = Cycle
fields = "__all__"
read_only_fields = [
"id",
"created_at",
"updated_at",
"created_by",
"updated_by",
"workspace",
"project",
"owned_by",
@@ -81,7 +44,6 @@ class CycleSerializer(BaseSerializer):
class CycleIssueSerializer(BaseSerializer):
issue_detail = IssueStateSerializer(read_only=True, source="issue")
sub_issues_count = serializers.IntegerField(read_only=True)
class Meta:
@@ -94,14 +56,7 @@ class CycleIssueSerializer(BaseSerializer):
]
class CycleFavoriteSerializer(BaseSerializer):
cycle_detail = CycleSerializer(source="cycle", read_only=True)
class CycleLiteSerializer(BaseSerializer):
class Meta:
model = CycleFavorite
model = Cycle
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"user",
]

View File

@@ -1,57 +1,19 @@
# Third party frameworks
from rest_framework import serializers
# Module imports
# Module improts
from .base import BaseSerializer
from .issue import IssueFlatSerializer, LabelLiteSerializer
from .project import ProjectLiteSerializer
from .state import StateLiteSerializer
from .user import UserLiteSerializer
from plane.db.models import Inbox, InboxIssue, Issue
class InboxSerializer(BaseSerializer):
project_detail = ProjectLiteSerializer(source="project", read_only=True)
pending_issue_count = serializers.IntegerField(read_only=True)
class Meta:
model = Inbox
fields = "__all__"
read_only_fields = [
"project",
"workspace",
]
from plane.db.models import InboxIssue
class InboxIssueSerializer(BaseSerializer):
issue_detail = IssueFlatSerializer(source="issue", read_only=True)
project_detail = ProjectLiteSerializer(source="project", read_only=True)
class Meta:
model = InboxIssue
fields = "__all__"
read_only_fields = [
"project",
"id",
"workspace",
"project",
"issue",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
class InboxIssueLiteSerializer(BaseSerializer):
class Meta:
model = InboxIssue
fields = ["id", "status", "duplicate_to", "snoozed_till", "source"]
read_only_fields = fields
class IssueStateInboxSerializer(BaseSerializer):
state_detail = StateLiteSerializer(read_only=True, source="state")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
label_details = LabelLiteSerializer(read_only=True, source="labels", many=True)
assignee_details = UserLiteSerializer(read_only=True, source="assignees", many=True)
sub_issues_count = serializers.IntegerField(read_only=True)
bridge_id = serializers.UUIDField(read_only=True)
issue_inbox = InboxIssueLiteSerializer(read_only=True, many=True)
class Meta:
model = Issue
fields = "__all__"

View File

@@ -1,95 +1,53 @@
from lxml import html
# Django imports
from django.utils import timezone
# Third Party imports
# Third party imports
from rest_framework import serializers
# Module imports
from .base import BaseSerializer, DynamicBaseSerializer
from .user import UserLiteSerializer
from .state import StateSerializer, StateLiteSerializer
from .project import ProjectLiteSerializer
from .workspace import WorkspaceLiteSerializer
from plane.db.models import (
User,
Issue,
IssueActivity,
IssueComment,
IssueProperty,
State,
IssueAssignee,
IssueSubscriber,
IssueLabel,
Label,
CycleIssue,
Cycle,
Module,
ModuleIssue,
IssueLabel,
IssueLink,
IssueAttachment,
IssueReaction,
CommentReaction,
IssueVote,
IssueRelation,
IssueComment,
IssueActivity,
ProjectMember,
)
from .base import BaseSerializer
from .cycle import CycleSerializer, CycleLiteSerializer
from .module import ModuleSerializer, ModuleLiteSerializer
from .user import UserLiteSerializer
from .state import StateLiteSerializer
class IssueFlatSerializer(BaseSerializer):
## Contain only flat fields
class Meta:
model = Issue
fields = [
"id",
"name",
"description",
"description_html",
"priority",
"start_date",
"target_date",
"sequence_id",
"sort_order",
"is_draft",
]
class IssueProjectLiteSerializer(BaseSerializer):
project_detail = ProjectLiteSerializer(source="project", read_only=True)
class Meta:
model = Issue
fields = [
"id",
"project_detail",
"name",
"sequence_id",
]
read_only_fields = fields
##TODO: Find a better way to write this serializer
## Find a better approach to save manytomany?
class IssueCreateSerializer(BaseSerializer):
state_detail = StateSerializer(read_only=True, source="state")
created_by_detail = UserLiteSerializer(read_only=True, source="created_by")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
class IssueSerializer(BaseSerializer):
assignees = serializers.ListField(
child=serializers.PrimaryKeyRelatedField(queryset=User.objects.all()),
child=serializers.PrimaryKeyRelatedField(
queryset=User.objects.values_list("id", flat=True)
),
write_only=True,
required=False,
)
labels = serializers.ListField(
child=serializers.PrimaryKeyRelatedField(queryset=Label.objects.all()),
child=serializers.PrimaryKeyRelatedField(
queryset=Label.objects.values_list("id", flat=True)
),
write_only=True,
required=False,
)
class Meta:
model = Issue
fields = "__all__"
read_only_fields = [
"id",
"workspace",
"project",
"created_by",
@@ -97,12 +55,10 @@ class IssueCreateSerializer(BaseSerializer):
"created_at",
"updated_at",
]
def to_representation(self, instance):
data = super().to_representation(instance)
data['assignees'] = [str(assignee.id) for assignee in instance.assignees.all()]
data['labels'] = [str(label.id) for label in instance.labels.all()]
return data
exclude = [
"description",
"description_stripped",
]
def validate(self, data):
if (
@@ -110,7 +66,58 @@ class IssueCreateSerializer(BaseSerializer):
and data.get("target_date", None) is not None
and data.get("start_date", None) > data.get("target_date", None)
):
raise serializers.ValidationError("Start date cannot exceed target date")
raise serializers.ValidationError(
"Start date cannot exceed target date"
)
try:
if data.get("description_html", None) is not None:
parsed = html.fromstring(data["description_html"])
parsed_str = html.tostring(parsed, encoding="unicode")
data["description_html"] = parsed_str
except Exception as e:
raise serializers.ValidationError(f"Invalid HTML: {str(e)}")
# Validate assignees are from project
if data.get("assignees", []):
data["assignees"] = ProjectMember.objects.filter(
project_id=self.context.get("project_id"),
is_active=True,
member_id__in=data["assignees"],
).values_list("member_id", flat=True)
# Validate labels are from project
if data.get("labels", []):
data["labels"] = Label.objects.filter(
project_id=self.context.get("project_id"),
id__in=data["labels"],
).values_list("id", flat=True)
# Check state is from the project only else raise validation error
if (
data.get("state")
and not State.objects.filter(
project_id=self.context.get("project_id"),
pk=data.get("state").id,
).exists()
):
raise serializers.ValidationError(
"State is not valid please pass a valid state_id"
)
# Check parent issue is from workspace as it can be cross workspace
if (
data.get("parent")
and not Issue.objects.filter(
workspace_id=self.context.get("workspace_id"),
pk=data.get("parent").id,
).exists()
):
raise serializers.ValidationError(
"Parent is not valid issue_id please pass a valid issue_id"
)
return data
def create(self, validated_data):
@@ -131,14 +138,14 @@ class IssueCreateSerializer(BaseSerializer):
IssueAssignee.objects.bulk_create(
[
IssueAssignee(
assignee=user,
assignee_id=assignee_id,
issue=issue,
project_id=project_id,
workspace_id=workspace_id,
created_by_id=created_by_id,
updated_by_id=updated_by_id,
)
for user in assignees
for assignee_id in assignees
],
batch_size=10,
)
@@ -158,14 +165,14 @@ class IssueCreateSerializer(BaseSerializer):
IssueLabel.objects.bulk_create(
[
IssueLabel(
label=label,
label_id=label_id,
issue=issue,
project_id=project_id,
workspace_id=workspace_id,
created_by_id=created_by_id,
updated_by_id=updated_by_id,
)
for label in labels
for label_id in labels
],
batch_size=10,
)
@@ -187,14 +194,14 @@ class IssueCreateSerializer(BaseSerializer):
IssueAssignee.objects.bulk_create(
[
IssueAssignee(
assignee=user,
assignee_id=assignee_id,
issue=instance,
project_id=project_id,
workspace_id=workspace_id,
created_by_id=created_by_id,
updated_by_id=updated_by_id,
)
for user in assignees
for assignee_id in assignees
],
batch_size=10,
)
@@ -204,14 +211,14 @@ class IssueCreateSerializer(BaseSerializer):
IssueLabel.objects.bulk_create(
[
IssueLabel(
label=label,
label_id=label_id,
issue=instance,
project_id=project_id,
workspace_id=workspace_id,
created_by_id=created_by_id,
updated_by_id=updated_by_id,
)
for label in labels
for label_id in labels
],
batch_size=10,
)
@@ -220,39 +227,130 @@ class IssueCreateSerializer(BaseSerializer):
instance.updated_at = timezone.now()
return super().update(instance, validated_data)
def to_representation(self, instance):
data = super().to_representation(instance)
if "assignees" in self.fields:
if "assignees" in self.expand:
from .user import UserLiteSerializer
class IssueActivitySerializer(BaseSerializer):
actor_detail = UserLiteSerializer(read_only=True, source="actor")
issue_detail = IssueFlatSerializer(read_only=True, source="issue")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
data["assignees"] = UserLiteSerializer(
instance.assignees.all(), many=True
).data
else:
data["assignees"] = [
str(assignee.id) for assignee in instance.assignees.all()
]
if "labels" in self.fields:
if "labels" in self.expand:
data["labels"] = LabelSerializer(
instance.labels.all(), many=True
).data
else:
data["labels"] = [
str(label.id) for label in instance.labels.all()
]
class Meta:
model = IssueActivity
fields = "__all__"
class IssuePropertySerializer(BaseSerializer):
class Meta:
model = IssueProperty
fields = "__all__"
read_only_fields = [
"user",
"workspace",
"project",
]
return data
class LabelSerializer(BaseSerializer):
workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
project_detail = ProjectLiteSerializer(source="project", read_only=True)
class Meta:
model = Label
fields = "__all__"
read_only_fields = [
"id",
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
class IssueLinkSerializer(BaseSerializer):
class Meta:
model = IssueLink
fields = "__all__"
read_only_fields = [
"id",
"workspace",
"project",
"issue",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
# Validation if url already exists
def create(self, validated_data):
if IssueLink.objects.filter(
url=validated_data.get("url"),
issue_id=validated_data.get("issue_id"),
).exists():
raise serializers.ValidationError(
{"error": "URL already exists for this Issue"}
)
return IssueLink.objects.create(**validated_data)
class IssueCommentSerializer(BaseSerializer):
is_member = serializers.BooleanField(read_only=True)
class Meta:
model = IssueComment
read_only_fields = [
"id",
"workspace",
"project",
"issue",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
exclude = [
"comment_stripped",
"comment_json",
]
def validate(self, data):
try:
if data.get("comment_html", None) is not None:
parsed = html.fromstring(data["comment_html"])
parsed_str = html.tostring(parsed, encoding="unicode")
data["comment_html"] = parsed_str
except Exception as e:
raise serializers.ValidationError(f"Invalid HTML: {str(e)}")
return data
class IssueActivitySerializer(BaseSerializer):
class Meta:
model = IssueActivity
exclude = [
"created_by",
"updated_by",
]
class CycleIssueSerializer(BaseSerializer):
cycle = CycleSerializer(read_only=True)
class Meta:
fields = [
"cycle",
]
class ModuleIssueSerializer(BaseSerializer):
module = ModuleSerializer(read_only=True)
class Meta:
fields = [
"module",
]
@@ -266,279 +364,18 @@ class LabelLiteSerializer(BaseSerializer):
]
class IssueLabelSerializer(BaseSerializer):
class IssueExpandSerializer(BaseSerializer):
cycle = CycleLiteSerializer(source="issue_cycle.cycle", read_only=True)
module = ModuleLiteSerializer(source="issue_module.module", read_only=True)
labels = LabelLiteSerializer(read_only=True, many=True)
assignees = UserLiteSerializer(read_only=True, many=True)
state = StateLiteSerializer(read_only=True)
class Meta:
model = IssueLabel
model = Issue
fields = "__all__"
read_only_fields = [
"workspace",
"project",
]
class IssueRelationSerializer(BaseSerializer):
issue_detail = IssueProjectLiteSerializer(read_only=True, source="related_issue")
class Meta:
model = IssueRelation
fields = [
"issue_detail",
"relation_type",
"related_issue",
"issue",
"id"
]
read_only_fields = [
"workspace",
"project",
]
class RelatedIssueSerializer(BaseSerializer):
issue_detail = IssueProjectLiteSerializer(read_only=True, source="issue")
class Meta:
model = IssueRelation
fields = [
"issue_detail",
"relation_type",
"related_issue",
"issue",
"id"
]
read_only_fields = [
"workspace",
"project",
]
class IssueAssigneeSerializer(BaseSerializer):
assignee_details = UserLiteSerializer(read_only=True, source="assignee")
class Meta:
model = IssueAssignee
fields = "__all__"
class CycleBaseSerializer(BaseSerializer):
class Meta:
model = Cycle
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
class IssueCycleDetailSerializer(BaseSerializer):
cycle_detail = CycleBaseSerializer(read_only=True, source="cycle")
class Meta:
model = CycleIssue
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
class ModuleBaseSerializer(BaseSerializer):
class Meta:
model = Module
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
class IssueModuleDetailSerializer(BaseSerializer):
module_detail = ModuleBaseSerializer(read_only=True, source="module")
class Meta:
model = ModuleIssue
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
class IssueLinkSerializer(BaseSerializer):
created_by_detail = UserLiteSerializer(read_only=True, source="created_by")
class Meta:
model = IssueLink
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
"issue",
]
# Validation if url already exists
def create(self, validated_data):
if IssueLink.objects.filter(
url=validated_data.get("url"), issue_id=validated_data.get("issue_id")
).exists():
raise serializers.ValidationError(
{"error": "URL already exists for this Issue"}
)
return IssueLink.objects.create(**validated_data)
class IssueAttachmentSerializer(BaseSerializer):
class Meta:
model = IssueAttachment
fields = "__all__"
read_only_fields = [
"created_by",
"updated_by",
"created_at",
"updated_at",
"workspace",
"project",
"issue",
]
class IssueReactionSerializer(BaseSerializer):
actor_detail = UserLiteSerializer(read_only=True, source="actor")
class Meta:
model = IssueReaction
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"issue",
"actor",
]
class CommentReactionLiteSerializer(BaseSerializer):
actor_detail = UserLiteSerializer(read_only=True, source="actor")
class Meta:
model = CommentReaction
fields = [
"id",
"reaction",
"comment",
"actor_detail",
]
class CommentReactionSerializer(BaseSerializer):
class Meta:
model = CommentReaction
fields = "__all__"
read_only_fields = ["workspace", "project", "comment", "actor"]
class IssueVoteSerializer(BaseSerializer):
actor_detail = UserLiteSerializer(read_only=True, source="actor")
class Meta:
model = IssueVote
fields = ["issue", "vote", "workspace", "project", "actor", "actor_detail"]
read_only_fields = fields
class IssueCommentSerializer(BaseSerializer):
actor_detail = UserLiteSerializer(read_only=True, source="actor")
issue_detail = IssueFlatSerializer(read_only=True, source="issue")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
comment_reactions = CommentReactionLiteSerializer(read_only=True, many=True)
is_member = serializers.BooleanField(read_only=True)
class Meta:
model = IssueComment
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"issue",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
class IssueStateFlatSerializer(BaseSerializer):
state_detail = StateLiteSerializer(read_only=True, source="state")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
class Meta:
model = Issue
fields = [
"id",
"sequence_id",
"name",
"state_detail",
"project_detail",
]
# Issue Serializer with state details
class IssueStateSerializer(BaseSerializer):
label_details = LabelLiteSerializer(read_only=True, source="labels", many=True)
state_detail = StateLiteSerializer(read_only=True, source="state")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
assignee_details = UserLiteSerializer(read_only=True, source="assignees", many=True)
sub_issues_count = serializers.IntegerField(read_only=True)
bridge_id = serializers.UUIDField(read_only=True)
attachment_count = serializers.IntegerField(read_only=True)
link_count = serializers.IntegerField(read_only=True)
class Meta:
model = Issue
fields = "__all__"
class IssueSerializer(BaseSerializer):
project_detail = ProjectLiteSerializer(read_only=True, source="project")
state_detail = StateSerializer(read_only=True, source="state")
parent_detail = IssueStateFlatSerializer(read_only=True, source="parent")
label_details = LabelSerializer(read_only=True, source="labels", many=True)
assignee_details = UserLiteSerializer(read_only=True, source="assignees", many=True)
related_issues = IssueRelationSerializer(read_only=True, source="issue_relation", many=True)
issue_relations = RelatedIssueSerializer(read_only=True, source="issue_related", many=True)
issue_cycle = IssueCycleDetailSerializer(read_only=True)
issue_module = IssueModuleDetailSerializer(read_only=True)
issue_link = IssueLinkSerializer(read_only=True, many=True)
issue_attachment = IssueAttachmentSerializer(read_only=True, many=True)
sub_issues_count = serializers.IntegerField(read_only=True)
issue_reactions = IssueReactionSerializer(read_only=True, many=True)
class Meta:
model = Issue
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"created_by",
@@ -546,70 +383,3 @@ class IssueSerializer(BaseSerializer):
"created_at",
"updated_at",
]
class IssueLiteSerializer(DynamicBaseSerializer):
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
state_detail = StateLiteSerializer(read_only=True, source="state")
label_details = LabelLiteSerializer(read_only=True, source="labels", many=True)
assignee_details = UserLiteSerializer(read_only=True, source="assignees", many=True)
sub_issues_count = serializers.IntegerField(read_only=True)
cycle_id = serializers.UUIDField(read_only=True)
module_id = serializers.UUIDField(read_only=True)
attachment_count = serializers.IntegerField(read_only=True)
link_count = serializers.IntegerField(read_only=True)
issue_reactions = IssueReactionSerializer(read_only=True, many=True)
class Meta:
model = Issue
fields = "__all__"
read_only_fields = [
"start_date",
"target_date",
"completed_at",
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
class IssuePublicSerializer(BaseSerializer):
project_detail = ProjectLiteSerializer(read_only=True, source="project")
state_detail = StateLiteSerializer(read_only=True, source="state")
reactions = IssueReactionSerializer(read_only=True, many=True, source="issue_reactions")
votes = IssueVoteSerializer(read_only=True, many=True)
class Meta:
model = Issue
fields = [
"id",
"name",
"description_html",
"sequence_id",
"state",
"state_detail",
"project",
"project_detail",
"workspace",
"priority",
"target_date",
"reactions",
"votes",
]
read_only_fields = fields
class IssueSubscriberSerializer(BaseSerializer):
class Meta:
model = IssueSubscriber
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"issue",
]

View File

@@ -1,36 +1,38 @@
# Third Party imports
# Third party imports
from rest_framework import serializers
# Module imports
from .base import BaseSerializer
from .user import UserLiteSerializer
from .project import ProjectLiteSerializer
from .workspace import WorkspaceLiteSerializer
from plane.db.models import (
User,
Module,
ModuleLink,
ModuleMember,
ModuleIssue,
ModuleLink,
ModuleFavorite,
ProjectMember,
)
class ModuleWriteSerializer(BaseSerializer):
class ModuleSerializer(BaseSerializer):
members = serializers.ListField(
child=serializers.PrimaryKeyRelatedField(queryset=User.objects.all()),
child=serializers.PrimaryKeyRelatedField(
queryset=User.objects.values_list("id", flat=True)
),
write_only=True,
required=False,
)
project_detail = ProjectLiteSerializer(source="project", read_only=True)
workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
total_issues = serializers.IntegerField(read_only=True)
cancelled_issues = serializers.IntegerField(read_only=True)
completed_issues = serializers.IntegerField(read_only=True)
started_issues = serializers.IntegerField(read_only=True)
unstarted_issues = serializers.IntegerField(read_only=True)
backlog_issues = serializers.IntegerField(read_only=True)
class Meta:
model = Module
fields = "__all__"
read_only_fields = [
"id",
"workspace",
"project",
"created_by",
@@ -38,32 +40,45 @@ class ModuleWriteSerializer(BaseSerializer):
"created_at",
"updated_at",
]
def to_representation(self, instance):
data = super().to_representation(instance)
data['members'] = [str(member.id) for member in instance.members.all()]
data["members"] = [str(member.id) for member in instance.members.all()]
return data
def validate(self, data):
if data.get("start_date", None) is not None and data.get("target_date", None) is not None and data.get("start_date", None) > data.get("target_date", None):
raise serializers.ValidationError("Start date cannot exceed target date")
return data
if (
data.get("start_date", None) is not None
and data.get("target_date", None) is not None
and data.get("start_date", None) > data.get("target_date", None)
):
raise serializers.ValidationError(
"Start date cannot exceed target date"
)
if data.get("members", []):
data["members"] = ProjectMember.objects.filter(
project_id=self.context.get("project_id"),
member_id__in=data["members"],
).values_list("member_id", flat=True)
return data
def create(self, validated_data):
members = validated_data.pop("members", None)
project = self.context["project"]
module = Module.objects.create(**validated_data, project=project)
project_id = self.context["project_id"]
workspace_id = self.context["workspace_id"]
module = Module.objects.create(**validated_data, project_id=project_id)
if members is not None:
ModuleMember.objects.bulk_create(
[
ModuleMember(
module=module,
member=member,
project=project,
workspace=project.workspace,
member_id=str(member),
project_id=project_id,
workspace_id=workspace_id,
created_by=module.created_by,
updated_by=module.updated_by,
)
@@ -84,7 +99,7 @@ class ModuleWriteSerializer(BaseSerializer):
[
ModuleMember(
module=instance,
member=member,
member_id=str(member),
project=instance.project,
workspace=instance.project.workspace,
created_by=instance.created_by,
@@ -99,23 +114,7 @@ class ModuleWriteSerializer(BaseSerializer):
return super().update(instance, validated_data)
class ModuleFlatSerializer(BaseSerializer):
class Meta:
model = Module
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
class ModuleIssueSerializer(BaseSerializer):
module_detail = ModuleFlatSerializer(read_only=True, source="module")
issue_detail = ProjectLiteSerializer(read_only=True, source="issue")
sub_issues_count = serializers.IntegerField(read_only=True)
class Meta:
@@ -133,8 +132,6 @@ class ModuleIssueSerializer(BaseSerializer):
class ModuleLinkSerializer(BaseSerializer):
created_by_detail = UserLiteSerializer(read_only=True, source="created_by")
class Meta:
model = ModuleLink
fields = "__all__"
@@ -151,7 +148,8 @@ class ModuleLinkSerializer(BaseSerializer):
# Validation if url already exists
def create(self, validated_data):
if ModuleLink.objects.filter(
url=validated_data.get("url"), module_id=validated_data.get("module_id")
url=validated_data.get("url"),
module_id=validated_data.get("module_id"),
).exists():
raise serializers.ValidationError(
{"error": "URL already exists for this Issue"}
@@ -159,40 +157,7 @@ class ModuleLinkSerializer(BaseSerializer):
return ModuleLink.objects.create(**validated_data)
class ModuleSerializer(BaseSerializer):
project_detail = ProjectLiteSerializer(read_only=True, source="project")
lead_detail = UserLiteSerializer(read_only=True, source="lead")
members_detail = UserLiteSerializer(read_only=True, many=True, source="members")
link_module = ModuleLinkSerializer(read_only=True, many=True)
is_favorite = serializers.BooleanField(read_only=True)
total_issues = serializers.IntegerField(read_only=True)
cancelled_issues = serializers.IntegerField(read_only=True)
completed_issues = serializers.IntegerField(read_only=True)
started_issues = serializers.IntegerField(read_only=True)
unstarted_issues = serializers.IntegerField(read_only=True)
backlog_issues = serializers.IntegerField(read_only=True)
class ModuleLiteSerializer(BaseSerializer):
class Meta:
model = Module
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
class ModuleFavoriteSerializer(BaseSerializer):
module_detail = ModuleFlatSerializer(source="module", read_only=True)
class Meta:
model = ModuleFavorite
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"user",
]

View File

@@ -1,12 +0,0 @@
# Module imports
from .base import BaseSerializer
from .user import UserLiteSerializer
from plane.db.models import Notification
class NotificationSerializer(BaseSerializer):
triggered_by_details = UserLiteSerializer(read_only=True, source="triggered_by")
class Meta:
model = Notification
fields = "__all__"

View File

@@ -2,39 +2,79 @@
from rest_framework import serializers
# Module imports
from .base import BaseSerializer, DynamicBaseSerializer
from plane.api.serializers.workspace import WorkSpaceSerializer, WorkspaceLiteSerializer
from plane.api.serializers.user import UserLiteSerializer, UserAdminLiteSerializer
from plane.db.models import (
Project,
ProjectMember,
ProjectMemberInvite,
ProjectIdentifier,
ProjectFavorite,
ProjectDeployBoard,
ProjectPublicMember,
WorkspaceMember,
State,
Estimate,
)
from .base import BaseSerializer
class ProjectSerializer(BaseSerializer):
workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
total_members = serializers.IntegerField(read_only=True)
total_cycles = serializers.IntegerField(read_only=True)
total_modules = serializers.IntegerField(read_only=True)
is_member = serializers.BooleanField(read_only=True)
sort_order = serializers.FloatField(read_only=True)
member_role = serializers.IntegerField(read_only=True)
is_deployed = serializers.BooleanField(read_only=True)
class Meta:
model = Project
fields = "__all__"
read_only_fields = [
"id",
"emoji",
"workspace",
"created_at",
"updated_at",
"created_by",
"updated_by",
]
def validate(self, data):
# Check project lead should be a member of the workspace
if (
data.get("project_lead", None) is not None
and not WorkspaceMember.objects.filter(
workspace_id=self.context["workspace_id"],
member_id=data.get("project_lead"),
).exists()
):
raise serializers.ValidationError(
"Project lead should be a user in the workspace"
)
# Check default assignee should be a member of the workspace
if (
data.get("default_assignee", None) is not None
and not WorkspaceMember.objects.filter(
workspace_id=self.context["workspace_id"],
member_id=data.get("default_assignee"),
).exists()
):
raise serializers.ValidationError(
"Default assignee should be a user in the workspace"
)
return data
def create(self, validated_data):
identifier = validated_data.get("identifier", "").strip().upper()
if identifier == "":
raise serializers.ValidationError(detail="Project Identifier is required")
raise serializers.ValidationError(
detail="Project Identifier is required"
)
if ProjectIdentifier.objects.filter(
name=identifier, workspace_id=self.context["workspace_id"]
).exists():
raise serializers.ValidationError(detail="Project Identifier is taken")
raise serializers.ValidationError(
detail="Project Identifier is taken"
)
project = Project.objects.create(
**validated_data, workspace_id=self.context["workspace_id"]
)
@@ -45,36 +85,6 @@ class ProjectSerializer(BaseSerializer):
)
return project
def update(self, instance, validated_data):
identifier = validated_data.get("identifier", "").strip().upper()
# If identifier is not passed update the project and return
if identifier == "":
project = super().update(instance, validated_data)
return project
# If no Project Identifier is found create it
project_identifier = ProjectIdentifier.objects.filter(
name=identifier, workspace_id=instance.workspace_id
).first()
if project_identifier is None:
project = super().update(instance, validated_data)
project_identifier = ProjectIdentifier.objects.filter(
project=project
).first()
if project_identifier is not None:
project_identifier.name = identifier
project_identifier.save()
return project
# If found check if the project_id to be updated and identifier project id is same
if project_identifier.project_id == instance.id:
# If same pass update
project = super().update(instance, validated_data)
return project
# If not same fail update
raise serializers.ValidationError(detail="Project Identifier is already taken")
class ProjectLiteSerializer(BaseSerializer):
class Meta:
@@ -89,126 +99,3 @@ class ProjectLiteSerializer(BaseSerializer):
"description",
]
read_only_fields = fields
class ProjectListSerializer(DynamicBaseSerializer):
is_favorite = serializers.BooleanField(read_only=True)
total_members = serializers.IntegerField(read_only=True)
total_cycles = serializers.IntegerField(read_only=True)
total_modules = serializers.IntegerField(read_only=True)
is_member = serializers.BooleanField(read_only=True)
sort_order = serializers.FloatField(read_only=True)
member_role = serializers.IntegerField(read_only=True)
is_deployed = serializers.BooleanField(read_only=True)
members = serializers.SerializerMethodField()
def get_members(self, obj):
project_members = ProjectMember.objects.filter(project_id=obj.id).values(
"id",
"member_id",
"member__display_name",
"member__avatar",
)
return project_members
class Meta:
model = Project
fields = "__all__"
class ProjectDetailSerializer(BaseSerializer):
# workspace = WorkSpaceSerializer(read_only=True)
default_assignee = UserLiteSerializer(read_only=True)
project_lead = UserLiteSerializer(read_only=True)
is_favorite = serializers.BooleanField(read_only=True)
total_members = serializers.IntegerField(read_only=True)
total_cycles = serializers.IntegerField(read_only=True)
total_modules = serializers.IntegerField(read_only=True)
is_member = serializers.BooleanField(read_only=True)
sort_order = serializers.FloatField(read_only=True)
member_role = serializers.IntegerField(read_only=True)
is_deployed = serializers.BooleanField(read_only=True)
class Meta:
model = Project
fields = "__all__"
class ProjectMemberSerializer(BaseSerializer):
workspace = WorkspaceLiteSerializer(read_only=True)
project = ProjectLiteSerializer(read_only=True)
member = UserLiteSerializer(read_only=True)
class Meta:
model = ProjectMember
fields = "__all__"
class ProjectMemberAdminSerializer(BaseSerializer):
workspace = WorkspaceLiteSerializer(read_only=True)
project = ProjectLiteSerializer(read_only=True)
member = UserAdminLiteSerializer(read_only=True)
class Meta:
model = ProjectMember
fields = "__all__"
class ProjectMemberInviteSerializer(BaseSerializer):
project = ProjectLiteSerializer(read_only=True)
workspace = WorkspaceLiteSerializer(read_only=True)
class Meta:
model = ProjectMemberInvite
fields = "__all__"
class ProjectIdentifierSerializer(BaseSerializer):
class Meta:
model = ProjectIdentifier
fields = "__all__"
class ProjectFavoriteSerializer(BaseSerializer):
class Meta:
model = ProjectFavorite
fields = "__all__"
read_only_fields = [
"workspace",
"user",
]
class ProjectMemberLiteSerializer(BaseSerializer):
member = UserLiteSerializer(read_only=True)
is_subscribed = serializers.BooleanField(read_only=True)
class Meta:
model = ProjectMember
fields = ["member", "id", "is_subscribed"]
read_only_fields = fields
class ProjectDeployBoardSerializer(BaseSerializer):
project_details = ProjectLiteSerializer(read_only=True, source="project")
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
class Meta:
model = ProjectDeployBoard
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"anchor",
]
class ProjectPublicMemberSerializer(BaseSerializer):
class Meta:
model = ProjectPublicMember
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"member",
]

View File

@@ -1,17 +1,26 @@
# Module imports
from .base import BaseSerializer
from .workspace import WorkspaceLiteSerializer
from .project import ProjectLiteSerializer
from plane.db.models import State
class StateSerializer(BaseSerializer):
def validate(self, data):
# If the default is being provided then make all other states default False
if data.get("default", False):
State.objects.filter(
project_id=self.context.get("project_id")
).update(default=False)
return data
class Meta:
model = State
fields = "__all__"
read_only_fields = [
"id",
"created_by",
"updated_by",
"created_at",
"updated_at",
"workspace",
"project",
]

View File

@@ -1,111 +1,6 @@
# Third party imports
from rest_framework import serializers
# Module import
# Module imports
from plane.db.models import User
from .base import BaseSerializer
from plane.db.models import User, Workspace, WorkspaceMemberInvite
class UserSerializer(BaseSerializer):
class Meta:
model = User
fields = "__all__"
read_only_fields = [
"id",
"created_at",
"updated_at",
"is_superuser",
"is_staff",
"last_active",
"last_login_time",
"last_logout_time",
"last_login_ip",
"last_logout_ip",
"last_login_uagent",
"token_updated_at",
"is_onboarded",
"is_bot",
]
extra_kwargs = {"password": {"write_only": True}}
# If the user has already filled first name or last name then he is onboarded
def get_is_onboarded(self, obj):
return bool(obj.first_name) or bool(obj.last_name)
class UserMeSerializer(BaseSerializer):
class Meta:
model = User
fields = [
"id",
"avatar",
"cover_image",
"date_joined",
"display_name",
"email",
"first_name",
"last_name",
"is_active",
"is_bot",
"is_email_verified",
"is_managed",
"is_onboarded",
"is_tour_completed",
"mobile_number",
"role",
"onboarding_step",
"user_timezone",
"username",
"theme",
"last_workspace_id",
]
read_only_fields = fields
class UserMeSettingsSerializer(BaseSerializer):
workspace = serializers.SerializerMethodField()
class Meta:
model = User
fields = [
"id",
"email",
"workspace",
]
read_only_fields = fields
def get_workspace(self, obj):
workspace_invites = WorkspaceMemberInvite.objects.filter(
email=obj.email
).count()
if obj.last_workspace_id is not None:
workspace = Workspace.objects.filter(
pk=obj.last_workspace_id, workspace_member__member=obj.id
).first()
return {
"last_workspace_id": obj.last_workspace_id,
"last_workspace_slug": workspace.slug if workspace is not None else "",
"fallback_workspace_id": obj.last_workspace_id,
"fallback_workspace_slug": workspace.slug if workspace is not None else "",
"invites": workspace_invites,
}
else:
fallback_workspace = (
Workspace.objects.filter(workspace_member__member_id=obj.id)
.order_by("created_at")
.first()
)
return {
"last_workspace_id": None,
"last_workspace_slug": None,
"fallback_workspace_id": fallback_workspace.id
if fallback_workspace is not None
else None,
"fallback_workspace_slug": fallback_workspace.slug
if fallback_workspace is not None
else None,
"invites": workspace_invites,
}
class UserLiteSerializer(BaseSerializer):
@@ -116,48 +11,6 @@ class UserLiteSerializer(BaseSerializer):
"first_name",
"last_name",
"avatar",
"is_bot",
"display_name",
]
read_only_fields = [
"id",
"is_bot",
]
class UserAdminLiteSerializer(BaseSerializer):
class Meta:
model = User
fields = [
"id",
"first_name",
"last_name",
"avatar",
"is_bot",
"display_name",
"email",
]
read_only_fields = [
"id",
"is_bot",
]
class ChangePasswordSerializer(serializers.Serializer):
model = User
"""
Serializer for password change endpoint.
"""
old_password = serializers.CharField(required=True)
new_password = serializers.CharField(required=True)
class ResetPasswordSerializer(serializers.Serializer):
model = User
"""
Serializer for password change endpoint.
"""
new_password = serializers.CharField(required=True)
confirm_password = serializers.CharField(required=True)
read_only_fields = fields

View File

@@ -1,39 +1,11 @@
# Third party imports
from rest_framework import serializers
# Module imports
from plane.db.models import Workspace
from .base import BaseSerializer
from .user import UserLiteSerializer, UserAdminLiteSerializer
from plane.db.models import (
User,
Workspace,
WorkspaceMember,
Team,
TeamMember,
WorkspaceMemberInvite,
WorkspaceTheme,
)
class WorkSpaceSerializer(BaseSerializer):
owner = UserLiteSerializer(read_only=True)
total_members = serializers.IntegerField(read_only=True)
total_issues = serializers.IntegerField(read_only=True)
class Meta:
model = Workspace
fields = "__all__"
read_only_fields = [
"id",
"created_by",
"updated_by",
"created_at",
"updated_at",
"owner",
]
class WorkspaceLiteSerializer(BaseSerializer):
"""Lite serializer with only required fields"""
class Meta:
model = Workspace
fields = [
@@ -42,95 +14,3 @@ class WorkspaceLiteSerializer(BaseSerializer):
"id",
]
read_only_fields = fields
class WorkSpaceMemberSerializer(BaseSerializer):
member = UserLiteSerializer(read_only=True)
workspace = WorkspaceLiteSerializer(read_only=True)
class Meta:
model = WorkspaceMember
fields = "__all__"
class WorkspaceMemberMeSerializer(BaseSerializer):
class Meta:
model = WorkspaceMember
fields = "__all__"
class WorkspaceMemberAdminSerializer(BaseSerializer):
member = UserAdminLiteSerializer(read_only=True)
workspace = WorkspaceLiteSerializer(read_only=True)
class Meta:
model = WorkspaceMember
fields = "__all__"
class WorkSpaceMemberInviteSerializer(BaseSerializer):
workspace = WorkSpaceSerializer(read_only=True)
total_members = serializers.IntegerField(read_only=True)
created_by_detail = UserLiteSerializer(read_only=True, source="created_by")
class Meta:
model = WorkspaceMemberInvite
fields = "__all__"
class TeamSerializer(BaseSerializer):
members_detail = UserLiteSerializer(read_only=True, source="members", many=True)
members = serializers.ListField(
child=serializers.PrimaryKeyRelatedField(queryset=User.objects.all()),
write_only=True,
required=False,
)
class Meta:
model = Team
fields = "__all__"
read_only_fields = [
"workspace",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
def create(self, validated_data, **kwargs):
if "members" in validated_data:
members = validated_data.pop("members")
workspace = self.context["workspace"]
team = Team.objects.create(**validated_data, workspace=workspace)
team_members = [
TeamMember(member=member, team=team, workspace=workspace)
for member in members
]
TeamMember.objects.bulk_create(team_members, batch_size=10)
return team
team = Team.objects.create(**validated_data)
return team
def update(self, instance, validated_data):
if "members" in validated_data:
members = validated_data.pop("members")
TeamMember.objects.filter(team=instance).delete()
team_members = [
TeamMember(member=member, team=instance, workspace=instance.workspace)
for member in members
]
TeamMember.objects.bulk_create(team_members, batch_size=10)
return super().update(instance, validated_data)
return super().update(instance, validated_data)
class WorkspaceThemeSerializer(BaseSerializer):
class Meta:
model = WorkspaceTheme
fields = "__all__"
read_only_fields = [
"workspace",
"actor",
]

View File

@@ -1,46 +1,15 @@
from .analytic import urlpatterns as analytic_urls
from .asset import urlpatterns as asset_urls
from .authentication import urlpatterns as authentication_urls
from .config import urlpatterns as configuration_urls
from .cycle import urlpatterns as cycle_urls
from .estimate import urlpatterns as estimate_urls
from .external import urlpatterns as external_urls
from .importer import urlpatterns as importer_urls
from .inbox import urlpatterns as inbox_urls
from .integration import urlpatterns as integration_urls
from .issue import urlpatterns as issue_urls
from .module import urlpatterns as module_urls
from .notification import urlpatterns as notification_urls
from .page import urlpatterns as page_urls
from .project import urlpatterns as project_urls
from .public_board import urlpatterns as public_board_urls
from .search import urlpatterns as search_urls
from .state import urlpatterns as state_urls
from .user import urlpatterns as user_urls
from .views import urlpatterns as view_urls
from .workspace import urlpatterns as workspace_urls
from .project import urlpatterns as project_patterns
from .state import urlpatterns as state_patterns
from .issue import urlpatterns as issue_patterns
from .cycle import urlpatterns as cycle_patterns
from .module import urlpatterns as module_patterns
from .inbox import urlpatterns as inbox_patterns
urlpatterns = [
*analytic_urls,
*asset_urls,
*authentication_urls,
*configuration_urls,
*cycle_urls,
*estimate_urls,
*external_urls,
*importer_urls,
*inbox_urls,
*integration_urls,
*issue_urls,
*module_urls,
*notification_urls,
*page_urls,
*project_urls,
*public_board_urls,
*search_urls,
*state_urls,
*user_urls,
*view_urls,
*workspace_urls,
*project_patterns,
*state_patterns,
*issue_patterns,
*cycle_patterns,
*module_patterns,
*inbox_patterns,
]

View File

@@ -1,12 +0,0 @@
from django.urls import path
from plane.api.views import ConfigurationEndpoint
urlpatterns = [
path(
"configs/",
ConfigurationEndpoint.as_view(),
name="configuration",
),
]

View File

@@ -1,87 +1,35 @@
from django.urls import path
from plane.api.views import (
CycleViewSet,
CycleIssueViewSet,
CycleDateCheckEndpoint,
CycleFavoriteViewSet,
TransferCycleIssueEndpoint,
from plane.api.views.cycle import (
CycleAPIEndpoint,
CycleIssueAPIEndpoint,
TransferCycleIssueAPIEndpoint,
)
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/",
CycleViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-cycle",
CycleAPIEndpoint.as_view(),
name="cycles",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/<uuid:pk>/",
CycleViewSet.as_view(
{
"get": "retrieve",
"put": "update",
"patch": "partial_update",
"delete": "destroy",
}
),
name="project-cycle",
CycleAPIEndpoint.as_view(),
name="cycles",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/<uuid:cycle_id>/cycle-issues/",
CycleIssueViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-issue-cycle",
CycleIssueAPIEndpoint.as_view(),
name="cycle-issues",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/<uuid:cycle_id>/cycle-issues/<uuid:pk>/",
CycleIssueViewSet.as_view(
{
"get": "retrieve",
"put": "update",
"patch": "partial_update",
"delete": "destroy",
}
),
name="project-issue-cycle",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/date-check/",
CycleDateCheckEndpoint.as_view(),
name="project-cycle-date",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-cycles/",
CycleFavoriteViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="user-favorite-cycle",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-cycles/<uuid:cycle_id>/",
CycleFavoriteViewSet.as_view(
{
"delete": "destroy",
}
),
name="user-favorite-cycle",
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/<uuid:cycle_id>/cycle-issues/<uuid:issue_id>/",
CycleIssueAPIEndpoint.as_view(),
name="cycle-issues",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/<uuid:cycle_id>/transfer-issues/",
TransferCycleIssueEndpoint.as_view(),
TransferCycleIssueAPIEndpoint.as_view(),
name="transfer-issues",
),
]

View File

@@ -1,53 +1,17 @@
from django.urls import path
from plane.api.views import (
InboxViewSet,
InboxIssueViewSet,
)
from plane.api.views import InboxIssueAPIEndpoint
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/inboxes/",
InboxViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="inbox",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/inboxes/<uuid:pk>/",
InboxViewSet.as_view(
{
"get": "retrieve",
"patch": "partial_update",
"delete": "destroy",
}
),
name="inbox",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/inboxes/<uuid:inbox_id>/inbox-issues/",
InboxIssueViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
"workspaces/<str:slug>/projects/<uuid:project_id>/inbox-issues/",
InboxIssueAPIEndpoint.as_view(),
name="inbox-issue",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/inboxes/<uuid:inbox_id>/inbox-issues/<uuid:pk>/",
InboxIssueViewSet.as_view(
{
"get": "retrieve",
"patch": "partial_update",
"delete": "destroy",
}
),
"workspaces/<str:slug>/projects/<uuid:project_id>/inbox-issues/<uuid:issue_id>/",
InboxIssueAPIEndpoint.as_view(),
name="inbox-issue",
),
]

View File

@@ -1,327 +1,62 @@
from django.urls import path
from plane.api.views import (
IssueViewSet,
IssueListEndpoint,
IssueListGroupedEndpoint,
LabelViewSet,
BulkCreateIssueLabelsEndpoint,
BulkDeleteIssuesEndpoint,
BulkImportIssuesEndpoint,
UserWorkSpaceIssues,
SubIssuesEndpoint,
IssueLinkViewSet,
IssueAttachmentEndpoint,
ExportIssuesEndpoint,
IssueActivityEndpoint,
IssueCommentViewSet,
IssueSubscriberViewSet,
IssueReactionViewSet,
CommentReactionViewSet,
IssueUserDisplayPropertyEndpoint,
IssueArchiveViewSet,
IssueRelationViewSet,
IssueDraftViewSet,
IssueAPIEndpoint,
LabelAPIEndpoint,
IssueLinkAPIEndpoint,
IssueCommentAPIEndpoint,
IssueActivityAPIEndpoint,
)
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/",
IssueViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-issue",
),
path(
"v2/workspaces/<str:slug>/projects/<uuid:project_id>/issues/",
IssueListEndpoint.as_view(),
name="project-issue",
),
path(
"v3/workspaces/<str:slug>/projects/<uuid:project_id>/issues/",
IssueListGroupedEndpoint.as_view(),
name="project-issue",
IssueAPIEndpoint.as_view(),
name="issue",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:pk>/",
IssueViewSet.as_view(
{
"get": "retrieve",
"put": "update",
"patch": "partial_update",
"delete": "destroy",
}
),
name="project-issue",
IssueAPIEndpoint.as_view(),
name="issue",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issue-labels/",
LabelViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-issue-labels",
"workspaces/<str:slug>/projects/<uuid:project_id>/labels/",
LabelAPIEndpoint.as_view(),
name="label",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issue-labels/<uuid:pk>/",
LabelViewSet.as_view(
{
"get": "retrieve",
"put": "update",
"patch": "partial_update",
"delete": "destroy",
}
),
name="project-issue-labels",
"workspaces/<str:slug>/projects/<uuid:project_id>/labels/<uuid:pk>/",
LabelAPIEndpoint.as_view(),
name="label",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/bulk-create-labels/",
BulkCreateIssueLabelsEndpoint.as_view(),
name="project-bulk-labels",
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/links/",
IssueLinkAPIEndpoint.as_view(),
name="link",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/bulk-delete-issues/",
BulkDeleteIssuesEndpoint.as_view(),
name="project-issues-bulk",
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/links/<uuid:pk>/",
IssueLinkAPIEndpoint.as_view(),
name="link",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/bulk-import-issues/<str:service>/",
BulkImportIssuesEndpoint.as_view(),
name="project-issues-bulk",
),
path(
"workspaces/<str:slug>/my-issues/",
UserWorkSpaceIssues.as_view(),
name="workspace-issues",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/sub-issues/",
SubIssuesEndpoint.as_view(),
name="sub-issues",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-links/",
IssueLinkViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-issue-links",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-links/<uuid:pk>/",
IssueLinkViewSet.as_view(
{
"get": "retrieve",
"put": "update",
"patch": "partial_update",
"delete": "destroy",
}
),
name="project-issue-links",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-attachments/",
IssueAttachmentEndpoint.as_view(),
name="project-issue-attachments",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-attachments/<uuid:pk>/",
IssueAttachmentEndpoint.as_view(),
name="project-issue-attachments",
),
path(
"workspaces/<str:slug>/export-issues/",
ExportIssuesEndpoint.as_view(),
name="export-issues",
),
## End Issues
## Issue Activity
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/history/",
IssueActivityEndpoint.as_view(),
name="project-issue-history",
),
## Issue Activity
## IssueComments
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/comments/",
IssueCommentViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-issue-comment",
IssueCommentAPIEndpoint.as_view(),
name="comment",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/comments/<uuid:pk>/",
IssueCommentViewSet.as_view(
{
"get": "retrieve",
"put": "update",
"patch": "partial_update",
"delete": "destroy",
}
),
name="project-issue-comment",
),
## End IssueComments
# Issue Subscribers
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-subscribers/",
IssueSubscriberViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-issue-subscribers",
IssueCommentAPIEndpoint.as_view(),
name="comment",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-subscribers/<uuid:subscriber_id>/",
IssueSubscriberViewSet.as_view({"delete": "destroy"}),
name="project-issue-subscribers",
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/activities/",
IssueActivityAPIEndpoint.as_view(),
name="activity",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/subscribe/",
IssueSubscriberViewSet.as_view(
{
"get": "subscription_status",
"post": "subscribe",
"delete": "unsubscribe",
}
),
name="project-issue-subscribers",
),
## End Issue Subscribers
# Issue Reactions
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/reactions/",
IssueReactionViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-issue-reactions",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/reactions/<str:reaction_code>/",
IssueReactionViewSet.as_view(
{
"delete": "destroy",
}
),
name="project-issue-reactions",
),
## End Issue Reactions
# Comment Reactions
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/comments/<uuid:comment_id>/reactions/",
CommentReactionViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-issue-comment-reactions",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/comments/<uuid:comment_id>/reactions/<str:reaction_code>/",
CommentReactionViewSet.as_view(
{
"delete": "destroy",
}
),
name="project-issue-comment-reactions",
),
## End Comment Reactions
## IssueProperty
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issue-display-properties/",
IssueUserDisplayPropertyEndpoint.as_view(),
name="project-issue-display-properties",
),
## IssueProperty End
## Issue Archives
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/archived-issues/",
IssueArchiveViewSet.as_view(
{
"get": "list",
}
),
name="project-issue-archive",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/archived-issues/<uuid:pk>/",
IssueArchiveViewSet.as_view(
{
"get": "retrieve",
"delete": "destroy",
}
),
name="project-issue-archive",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/unarchive/<uuid:pk>/",
IssueArchiveViewSet.as_view(
{
"post": "unarchive",
}
),
name="project-issue-archive",
),
## End Issue Archives
## Issue Relation
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-relation/",
IssueRelationViewSet.as_view(
{
"post": "create",
}
),
name="issue-relation",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-relation/<uuid:pk>/",
IssueRelationViewSet.as_view(
{
"delete": "destroy",
}
),
name="issue-relation",
),
## End Issue Relation
## Issue Drafts
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issue-drafts/",
IssueDraftViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-issue-draft",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issue-drafts/<uuid:pk>/",
IssueDraftViewSet.as_view(
{
"get": "retrieve",
"patch": "partial_update",
"delete": "destroy",
}
),
name="project-issue-draft",
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/activities/<uuid:pk>/",
IssueActivityAPIEndpoint.as_view(),
name="activity",
),
]

View File

@@ -1,104 +1,26 @@
from django.urls import path
from plane.api.views import (
ModuleViewSet,
ModuleIssueViewSet,
ModuleLinkViewSet,
ModuleFavoriteViewSet,
BulkImportModulesEndpoint,
)
from plane.api.views import ModuleAPIEndpoint, ModuleIssueAPIEndpoint
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/",
ModuleViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-modules",
ModuleAPIEndpoint.as_view(),
name="modules",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:pk>/",
ModuleViewSet.as_view(
{
"get": "retrieve",
"put": "update",
"patch": "partial_update",
"delete": "destroy",
}
),
name="project-modules",
ModuleAPIEndpoint.as_view(),
name="modules",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:module_id>/module-issues/",
ModuleIssueViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-module-issues",
ModuleIssueAPIEndpoint.as_view(),
name="module-issues",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:module_id>/module-issues/<uuid:pk>/",
ModuleIssueViewSet.as_view(
{
"get": "retrieve",
"put": "update",
"patch": "partial_update",
"delete": "destroy",
}
),
name="project-module-issues",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:module_id>/module-links/",
ModuleLinkViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-issue-module-links",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:module_id>/module-links/<uuid:pk>/",
ModuleLinkViewSet.as_view(
{
"get": "retrieve",
"put": "update",
"patch": "partial_update",
"delete": "destroy",
}
),
name="project-issue-module-links",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-modules/",
ModuleFavoriteViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="user-favorite-module",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-modules/<uuid:module_id>/",
ModuleFavoriteViewSet.as_view(
{
"delete": "destroy",
}
),
name="user-favorite-module",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/bulk-import-modules/<str:service>/",
BulkImportModulesEndpoint.as_view(),
name="bulk-modules-create",
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:module_id>/module-issues/<uuid:issue_id>/",
ModuleIssueAPIEndpoint.as_view(),
name="module-issues",
),
]

View File

@@ -1,79 +0,0 @@
from django.urls import path
from plane.api.views import (
PageViewSet,
PageBlockViewSet,
PageFavoriteViewSet,
CreateIssueFromPageBlockEndpoint,
)
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/",
PageViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-pages",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:pk>/",
PageViewSet.as_view(
{
"get": "retrieve",
"patch": "partial_update",
"delete": "destroy",
}
),
name="project-pages",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/page-blocks/",
PageBlockViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-page-blocks",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/page-blocks/<uuid:pk>/",
PageBlockViewSet.as_view(
{
"get": "retrieve",
"patch": "partial_update",
"delete": "destroy",
}
),
name="project-page-blocks",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-pages/",
PageFavoriteViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="user-favorite-pages",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-pages/<uuid:page_id>/",
PageFavoriteViewSet.as_view(
{
"delete": "destroy",
}
),
name="user-favorite-pages",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/page-blocks/<uuid:page_block_id>/issues/",
CreateIssueFromPageBlockEndpoint.as_view(),
name="page-block-issues",
),
]

View File

@@ -1,132 +1,16 @@
from django.urls import path
from plane.api.views import (
ProjectViewSet,
InviteProjectEndpoint,
ProjectMemberViewSet,
ProjectMemberInvitationsViewset,
ProjectMemberUserEndpoint,
ProjectJoinEndpoint,
AddTeamToProjectEndpoint,
ProjectUserViewsEndpoint,
ProjectIdentifierEndpoint,
ProjectFavoritesViewSet,
LeaveProjectEndpoint,
ProjectPublicCoverImagesEndpoint,
)
from plane.api.views import ProjectAPIEndpoint
urlpatterns = [
path(
"workspaces/<str:slug>/projects/",
ProjectViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
ProjectAPIEndpoint.as_view(),
name="project",
),
path(
"workspaces/<str:slug>/projects/<uuid:pk>/",
ProjectViewSet.as_view(
{
"get": "retrieve",
"put": "update",
"patch": "partial_update",
"delete": "destroy",
}
),
"workspaces/<str:slug>/projects/<uuid:project_id>/",
ProjectAPIEndpoint.as_view(),
name="project",
),
path(
"workspaces/<str:slug>/project-identifiers/",
ProjectIdentifierEndpoint.as_view(),
name="project-identifiers",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/invite/",
InviteProjectEndpoint.as_view(),
name="invite-project",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/members/",
ProjectMemberViewSet.as_view({"get": "list", "post": "create"}),
name="project-member",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/members/<uuid:pk>/",
ProjectMemberViewSet.as_view(
{
"get": "retrieve",
"patch": "partial_update",
"delete": "destroy",
}
),
name="project-member",
),
path(
"workspaces/<str:slug>/projects/join/",
ProjectJoinEndpoint.as_view(),
name="project-join",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/team-invite/",
AddTeamToProjectEndpoint.as_view(),
name="projects",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/invitations/",
ProjectMemberInvitationsViewset.as_view({"get": "list"}),
name="project-member-invite",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/invitations/<uuid:pk>/",
ProjectMemberInvitationsViewset.as_view(
{
"get": "retrieve",
"delete": "destroy",
}
),
name="project-member-invite",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/project-views/",
ProjectUserViewsEndpoint.as_view(),
name="project-view",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/project-members/me/",
ProjectMemberUserEndpoint.as_view(),
name="project-member-view",
),
path(
"workspaces/<str:slug>/user-favorite-projects/",
ProjectFavoritesViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-favorite",
),
path(
"workspaces/<str:slug>/user-favorite-projects/<uuid:project_id>/",
ProjectFavoritesViewSet.as_view(
{
"delete": "destroy",
}
),
name="project-favorite",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/members/leave/",
LeaveProjectEndpoint.as_view(),
name="leave-project",
),
path(
"project-covers/",
ProjectPublicCoverImagesEndpoint.as_view(),
name="project-covers",
),
]

View File

@@ -1,151 +0,0 @@
from django.urls import path
from plane.api.views import (
ProjectDeployBoardViewSet,
ProjectDeployBoardPublicSettingsEndpoint,
ProjectIssuesPublicEndpoint,
IssueRetrievePublicEndpoint,
IssueCommentPublicViewSet,
IssueReactionPublicViewSet,
CommentReactionPublicViewSet,
InboxIssuePublicViewSet,
IssueVotePublicViewSet,
WorkspaceProjectDeployBoardEndpoint,
)
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/project-deploy-boards/",
ProjectDeployBoardViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-deploy-board",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/project-deploy-boards/<uuid:pk>/",
ProjectDeployBoardViewSet.as_view(
{
"get": "retrieve",
"patch": "partial_update",
"delete": "destroy",
}
),
name="project-deploy-board",
),
path(
"public/workspaces/<str:slug>/project-boards/<uuid:project_id>/settings/",
ProjectDeployBoardPublicSettingsEndpoint.as_view(),
name="project-deploy-board-settings",
),
path(
"public/workspaces/<str:slug>/project-boards/<uuid:project_id>/issues/",
ProjectIssuesPublicEndpoint.as_view(),
name="project-deploy-board",
),
path(
"public/workspaces/<str:slug>/project-boards/<uuid:project_id>/issues/<uuid:issue_id>/",
IssueRetrievePublicEndpoint.as_view(),
name="workspace-project-boards",
),
path(
"public/workspaces/<str:slug>/project-boards/<uuid:project_id>/issues/<uuid:issue_id>/comments/",
IssueCommentPublicViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="issue-comments-project-board",
),
path(
"public/workspaces/<str:slug>/project-boards/<uuid:project_id>/issues/<uuid:issue_id>/comments/<uuid:pk>/",
IssueCommentPublicViewSet.as_view(
{
"get": "retrieve",
"patch": "partial_update",
"delete": "destroy",
}
),
name="issue-comments-project-board",
),
path(
"public/workspaces/<str:slug>/project-boards/<uuid:project_id>/issues/<uuid:issue_id>/reactions/",
IssueReactionPublicViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="issue-reactions-project-board",
),
path(
"public/workspaces/<str:slug>/project-boards/<uuid:project_id>/issues/<uuid:issue_id>/reactions/<str:reaction_code>/",
IssueReactionPublicViewSet.as_view(
{
"delete": "destroy",
}
),
name="issue-reactions-project-board",
),
path(
"public/workspaces/<str:slug>/project-boards/<uuid:project_id>/comments/<uuid:comment_id>/reactions/",
CommentReactionPublicViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="comment-reactions-project-board",
),
path(
"public/workspaces/<str:slug>/project-boards/<uuid:project_id>/comments/<uuid:comment_id>/reactions/<str:reaction_code>/",
CommentReactionPublicViewSet.as_view(
{
"delete": "destroy",
}
),
name="comment-reactions-project-board",
),
path(
"public/workspaces/<str:slug>/project-boards/<uuid:project_id>/inboxes/<uuid:inbox_id>/inbox-issues/",
InboxIssuePublicViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="inbox-issue",
),
path(
"public/workspaces/<str:slug>/project-boards/<uuid:project_id>/inboxes/<uuid:inbox_id>/inbox-issues/<uuid:pk>/",
InboxIssuePublicViewSet.as_view(
{
"get": "retrieve",
"patch": "partial_update",
"delete": "destroy",
}
),
name="inbox-issue",
),
path(
"public/workspaces/<str:slug>/project-boards/<uuid:project_id>/issues/<uuid:issue_id>/votes/",
IssueVotePublicViewSet.as_view(
{
"get": "list",
"post": "create",
"delete": "destroy",
}
),
name="issue-vote-project-board",
),
path(
"public/workspaces/<str:slug>/project-boards/",
WorkspaceProjectDeployBoardEndpoint.as_view(),
name="workspace-project-boards",
),
]

View File

@@ -1,38 +1,16 @@
from django.urls import path
from plane.api.views import StateViewSet
from plane.api.views import StateAPIEndpoint
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/states/",
StateViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-states",
StateAPIEndpoint.as_view(),
name="states",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/states/<uuid:pk>/",
StateViewSet.as_view(
{
"get": "retrieve",
"patch": "partial_update",
"delete": "destroy",
}
),
name="project-state",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/states/<uuid:pk>/mark-default/",
StateViewSet.as_view(
{
"post": "mark_as_default",
}
),
name="project-state",
"workspaces/<str:slug>/projects/<uuid:project_id>/states/<uuid:state_id>/",
StateAPIEndpoint.as_view(),
name="states",
),
]

View File

@@ -1,180 +1,21 @@
from .project import (
ProjectViewSet,
ProjectMemberViewSet,
UserProjectInvitationsViewset,
InviteProjectEndpoint,
AddTeamToProjectEndpoint,
ProjectMemberInvitationsViewset,
ProjectMemberInviteDetailViewSet,
ProjectIdentifierEndpoint,
ProjectJoinEndpoint,
ProjectUserViewsEndpoint,
ProjectMemberUserEndpoint,
ProjectFavoritesViewSet,
ProjectDeployBoardViewSet,
ProjectDeployBoardPublicSettingsEndpoint,
WorkspaceProjectDeployBoardEndpoint,
LeaveProjectEndpoint,
ProjectPublicCoverImagesEndpoint,
)
from .user import (
UserEndpoint,
UpdateUserOnBoardedEndpoint,
UpdateUserTourCompletedEndpoint,
UserActivityEndpoint,
)
from .project import ProjectAPIEndpoint
from .oauth import OauthEndpoint
from .state import StateAPIEndpoint
from .base import BaseAPIView, BaseViewSet
from .workspace import (
WorkSpaceViewSet,
UserWorkSpacesEndpoint,
WorkSpaceAvailabilityCheckEndpoint,
InviteWorkspaceEndpoint,
JoinWorkspaceEndpoint,
WorkSpaceMemberViewSet,
TeamMemberViewSet,
WorkspaceInvitationsViewset,
UserWorkspaceInvitationsEndpoint,
UserWorkspaceInvitationEndpoint,
UserLastProjectWithWorkspaceEndpoint,
WorkspaceMemberUserEndpoint,
WorkspaceMemberUserViewsEndpoint,
UserActivityGraphEndpoint,
UserIssueCompletedGraphEndpoint,
UserWorkspaceDashboardEndpoint,
WorkspaceThemeViewSet,
WorkspaceUserProfileStatsEndpoint,
WorkspaceUserActivityEndpoint,
WorkspaceUserProfileEndpoint,
WorkspaceUserProfileIssuesEndpoint,
WorkspaceLabelsEndpoint,
LeaveWorkspaceEndpoint,
)
from .state import StateViewSet
from .view import (
GlobalViewViewSet,
GlobalViewIssuesViewSet,
IssueViewViewSet,
IssueViewFavoriteViewSet,
)
from .cycle import (
CycleViewSet,
CycleIssueViewSet,
CycleDateCheckEndpoint,
CycleFavoriteViewSet,
TransferCycleIssueEndpoint,
)
from .asset import FileAssetEndpoint, UserAssetsEndpoint
from .issue import (
IssueViewSet,
IssueListEndpoint,
IssueListGroupedEndpoint,
WorkSpaceIssuesEndpoint,
IssueActivityEndpoint,
IssueCommentViewSet,
IssueUserDisplayPropertyEndpoint,
LabelViewSet,
BulkDeleteIssuesEndpoint,
UserWorkSpaceIssues,
SubIssuesEndpoint,
IssueLinkViewSet,
BulkCreateIssueLabelsEndpoint,
IssueAttachmentEndpoint,
IssueArchiveViewSet,
IssueSubscriberViewSet,
IssueCommentPublicViewSet,
CommentReactionViewSet,
IssueReactionViewSet,
IssueReactionPublicViewSet,
CommentReactionPublicViewSet,
IssueVotePublicViewSet,
IssueRelationViewSet,
IssueRetrievePublicEndpoint,
ProjectIssuesPublicEndpoint,
IssueDraftViewSet,
IssueAPIEndpoint,
LabelAPIEndpoint,
IssueLinkAPIEndpoint,
IssueCommentAPIEndpoint,
IssueActivityAPIEndpoint,
)
from .auth_extended import (
VerifyEmailEndpoint,
RequestEmailVerificationEndpoint,
ForgotPasswordEndpoint,
ResetPasswordEndpoint,
ChangePasswordEndpoint,
from .cycle import (
CycleAPIEndpoint,
CycleIssueAPIEndpoint,
TransferCycleIssueAPIEndpoint,
)
from .module import ModuleAPIEndpoint, ModuleIssueAPIEndpoint
from .authentication import (
SignUpEndpoint,
SignInEndpoint,
SignOutEndpoint,
MagicSignInEndpoint,
MagicSignInGenerateEndpoint,
)
from .module import (
ModuleViewSet,
ModuleIssueViewSet,
ModuleLinkViewSet,
ModuleFavoriteViewSet,
)
from .api_token import ApiTokenEndpoint
from .integration import (
WorkspaceIntegrationViewSet,
IntegrationViewSet,
GithubIssueSyncViewSet,
GithubRepositorySyncViewSet,
GithubCommentSyncViewSet,
GithubRepositoriesEndpoint,
BulkCreateGithubIssueSyncEndpoint,
SlackProjectSyncViewSet,
)
from .importer import (
ServiceIssueImportSummaryEndpoint,
ImportServiceEndpoint,
UpdateServiceImportStatusEndpoint,
BulkImportIssuesEndpoint,
BulkImportModulesEndpoint,
)
from .page import (
PageViewSet,
PageBlockViewSet,
PageFavoriteViewSet,
CreateIssueFromPageBlockEndpoint,
)
from .search import GlobalSearchEndpoint, IssueSearchEndpoint
from .external import GPTIntegrationEndpoint, ReleaseNotesEndpoint, UnsplashEndpoint
from .estimate import (
ProjectEstimatePointEndpoint,
BulkEstimatePointEndpoint,
)
from .inbox import InboxViewSet, InboxIssueViewSet, InboxIssuePublicViewSet
from .analytic import (
AnalyticsEndpoint,
AnalyticViewViewset,
SavedAnalyticEndpoint,
ExportAnalyticsEndpoint,
DefaultAnalyticsEndpoint,
)
from .notification import (
NotificationViewSet,
UnreadNotificationEndpoint,
MarkAllReadNotificationViewSet,
)
from .exporter import ExportIssuesEndpoint
from .config import ConfigurationEndpoint
from .inbox import InboxIssueAPIEndpoint

View File

@@ -1,47 +0,0 @@
# Python import
from uuid import uuid4
# Third party
from rest_framework.response import Response
from rest_framework import status
from sentry_sdk import capture_exception
# Module import
from .base import BaseAPIView
from plane.db.models import APIToken
from plane.api.serializers import APITokenSerializer
class ApiTokenEndpoint(BaseAPIView):
def post(self, request):
label = request.data.get("label", str(uuid4().hex))
workspace = request.data.get("workspace", False)
if not workspace:
return Response(
{"error": "Workspace is required"}, status=status.HTTP_200_OK
)
api_token = APIToken.objects.create(
label=label, user=request.user, workspace_id=workspace
)
serializer = APITokenSerializer(api_token)
# Token will be only vissible while creating
return Response(
{"api_token": serializer.data, "token": api_token.token},
status=status.HTTP_201_CREATED,
)
def get(self, request):
api_tokens = APIToken.objects.filter(user=request.user)
serializer = APITokenSerializer(api_tokens, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def delete(self, request, pk):
api_token = APIToken.objects.get(pk=pk)
api_token.delete()
return Response(status=status.HTTP_204_NO_CONTENT)

View File

@@ -1,75 +0,0 @@
# Third party imports
from rest_framework import status
from rest_framework.response import Response
from rest_framework.parsers import MultiPartParser, FormParser
from sentry_sdk import capture_exception
from django.conf import settings
# Module imports
from .base import BaseAPIView
from plane.db.models import FileAsset, Workspace
from plane.api.serializers import FileAssetSerializer
class FileAssetEndpoint(BaseAPIView):
parser_classes = (MultiPartParser, FormParser)
"""
A viewset for viewing and editing task instances.
"""
def get(self, request, workspace_id, asset_key):
asset_key = str(workspace_id) + "/" + asset_key
files = FileAsset.objects.filter(asset=asset_key)
if files.exists():
serializer = FileAssetSerializer(files, context={"request": request}, many=True)
return Response({"data": serializer.data, "status": True}, status=status.HTTP_200_OK)
else:
return Response({"error": "Asset key does not exist", "status": False}, status=status.HTTP_200_OK)
def post(self, request, slug):
serializer = FileAssetSerializer(data=request.data)
if serializer.is_valid():
# Get the workspace
workspace = Workspace.objects.get(slug=slug)
serializer.save(workspace_id=workspace.id)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, workspace_id, asset_key):
asset_key = str(workspace_id) + "/" + asset_key
file_asset = FileAsset.objects.get(asset=asset_key)
# Delete the file from storage
file_asset.asset.delete(save=False)
# Delete the file object
file_asset.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
class UserAssetsEndpoint(BaseAPIView):
parser_classes = (MultiPartParser, FormParser)
def get(self, request, asset_key):
files = FileAsset.objects.filter(asset=asset_key, created_by=request.user)
if files.exists():
serializer = FileAssetSerializer(files, context={"request": request})
return Response({"data": serializer.data, "status": True}, status=status.HTTP_200_OK)
else:
return Response({"error": "Asset key does not exist", "status": False}, status=status.HTTP_200_OK)
def post(self, request):
serializer = FileAssetSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, asset_key):
file_asset = FileAsset.objects.get(asset=asset_key, created_by=request.user)
# Delete the file from storage
file_asset.asset.delete(save=False)
# Delete the file object
file_asset.delete()
return Response(status=status.HTTP_204_NO_CONTENT)

View File

@@ -1,151 +0,0 @@
## Python imports
import jwt
## Django imports
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.utils.encoding import (
smart_str,
smart_bytes,
DjangoUnicodeDecodeError,
)
from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
from django.conf import settings
## Third Party Imports
from rest_framework import status
from rest_framework.response import Response
from rest_framework import permissions
from rest_framework_simplejwt.tokens import RefreshToken
from sentry_sdk import capture_exception
## Module imports
from . import BaseAPIView
from plane.api.serializers import (
ChangePasswordSerializer,
ResetPasswordSerializer,
)
from plane.db.models import User
from plane.bgtasks.email_verification_task import email_verification
from plane.bgtasks.forgot_password_task import forgot_password
class RequestEmailVerificationEndpoint(BaseAPIView):
def get(self, request):
token = RefreshToken.for_user(request.user).access_token
current_site = settings.WEB_URL
email_verification.delay(
request.user.first_name, request.user.email, token, current_site
)
return Response(
{"message": "Email sent successfully"}, status=status.HTTP_200_OK
)
class VerifyEmailEndpoint(BaseAPIView):
def get(self, request):
token = request.GET.get("token")
try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithms="HS256")
user = User.objects.get(id=payload["user_id"])
if not user.is_email_verified:
user.is_email_verified = True
user.save()
return Response(
{"email": "Successfully activated"}, status=status.HTTP_200_OK
)
except jwt.ExpiredSignatureError as _indentifier:
return Response(
{"email": "Activation expired"}, status=status.HTTP_400_BAD_REQUEST
)
except jwt.exceptions.DecodeError as _indentifier:
return Response(
{"email": "Invalid token"}, status=status.HTTP_400_BAD_REQUEST
)
class ForgotPasswordEndpoint(BaseAPIView):
permission_classes = [permissions.AllowAny]
def post(self, request):
email = request.data.get("email")
if User.objects.filter(email=email).exists():
user = User.objects.get(email=email)
uidb64 = urlsafe_base64_encode(smart_bytes(user.id))
token = PasswordResetTokenGenerator().make_token(user)
current_site = settings.WEB_URL
forgot_password.delay(
user.first_name, user.email, uidb64, token, current_site
)
return Response(
{"message": "Check your email to reset your password"},
status=status.HTTP_200_OK,
)
return Response(
{"error": "Please check the email"}, status=status.HTTP_400_BAD_REQUEST
)
class ResetPasswordEndpoint(BaseAPIView):
permission_classes = [permissions.AllowAny]
def post(self, request, uidb64, token):
try:
id = smart_str(urlsafe_base64_decode(uidb64))
user = User.objects.get(id=id)
if not PasswordResetTokenGenerator().check_token(user, token):
return Response(
{"error": "token is not valid, please check the new one"},
status=status.HTTP_401_UNAUTHORIZED,
)
serializer = ResetPasswordSerializer(data=request.data)
if serializer.is_valid():
# set_password also hashes the password that the user will get
user.set_password(serializer.data.get("new_password"))
user.save()
response = {
"status": "success",
"code": status.HTTP_200_OK,
"message": "Password updated successfully",
}
return Response(response)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except DjangoUnicodeDecodeError as indentifier:
return Response(
{"error": "token is not valid, please check the new one"},
status=status.HTTP_401_UNAUTHORIZED,
)
class ChangePasswordEndpoint(BaseAPIView):
def post(self, request):
serializer = ChangePasswordSerializer(data=request.data)
user = User.objects.get(pk=request.user.id)
if serializer.is_valid():
# Check old password
if not user.object.check_password(serializer.data.get("old_password")):
return Response(
{"old_password": ["Wrong password."]},
status=status.HTTP_400_BAD_REQUEST,
)
# set_password also hashes the password that the user will get
self.object.set_password(serializer.data.get("new_password"))
self.object.save()
response = {
"status": "success",
"code": status.HTTP_200_OK,
"message": "Password updated successfully",
}
return Response(response)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

View File

@@ -1,397 +0,0 @@
# Python imports
import uuid
import random
import string
import json
import requests
# Django imports
from django.utils import timezone
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.conf import settings
from django.contrib.auth.hashers import make_password
# Third party imports
from rest_framework.response import Response
from rest_framework.permissions import AllowAny
from rest_framework import status
from rest_framework_simplejwt.tokens import RefreshToken
from sentry_sdk import capture_exception, capture_message
# Module imports
from . import BaseAPIView
from plane.db.models import User
from plane.api.serializers import UserSerializer
from plane.settings.redis import redis_instance
from plane.bgtasks.magic_link_code_task import magic_link
def get_tokens_for_user(user):
refresh = RefreshToken.for_user(user)
return (
str(refresh.access_token),
str(refresh),
)
class SignUpEndpoint(BaseAPIView):
permission_classes = (AllowAny,)
def post(self, request):
if not settings.ENABLE_SIGNUP:
return Response(
{
"error": "New account creation is disabled. Please contact your site administrator"
},
status=status.HTTP_400_BAD_REQUEST,
)
email = request.data.get("email", False)
password = request.data.get("password", False)
## Raise exception if any of the above are missing
if not email or not password:
return Response(
{"error": "Both email and password are required"},
status=status.HTTP_400_BAD_REQUEST,
)
email = email.strip().lower()
try:
validate_email(email)
except ValidationError as e:
return Response(
{"error": "Please provide a valid email address."},
status=status.HTTP_400_BAD_REQUEST,
)
# Check if the user already exists
if User.objects.filter(email=email).exists():
return Response(
{"error": "User with this email already exists"},
status=status.HTTP_400_BAD_REQUEST,
)
user = User.objects.create(email=email, username=uuid.uuid4().hex)
user.set_password(password)
# settings last actives for the user
user.last_active = timezone.now()
user.last_login_time = timezone.now()
user.last_login_ip = request.META.get("REMOTE_ADDR")
user.last_login_uagent = request.META.get("HTTP_USER_AGENT")
user.token_updated_at = timezone.now()
user.save()
access_token, refresh_token = get_tokens_for_user(user)
data = {
"access_token": access_token,
"refresh_token": refresh_token,
}
# Send Analytics
if settings.ANALYTICS_BASE_API:
_ = requests.post(
settings.ANALYTICS_BASE_API,
headers={
"Content-Type": "application/json",
"X-Auth-Token": settings.ANALYTICS_SECRET_KEY,
},
json={
"event_id": uuid.uuid4().hex,
"event_data": {
"medium": "email",
},
"user": {"email": email, "id": str(user.id)},
"device_ctx": {
"ip": request.META.get("REMOTE_ADDR"),
"user_agent": request.META.get("HTTP_USER_AGENT"),
},
"event_type": "SIGN_UP",
},
)
return Response(data, status=status.HTTP_200_OK)
class SignInEndpoint(BaseAPIView):
permission_classes = (AllowAny,)
def post(self, request):
email = request.data.get("email", False)
password = request.data.get("password", False)
## Raise exception if any of the above are missing
if not email or not password:
return Response(
{"error": "Both email and password are required"},
status=status.HTTP_400_BAD_REQUEST,
)
email = email.strip().lower()
try:
validate_email(email)
except ValidationError as e:
return Response(
{"error": "Please provide a valid email address."},
status=status.HTTP_400_BAD_REQUEST,
)
user = User.objects.filter(email=email).first()
if user is None:
return Response(
{
"error": "Sorry, we could not find a user with the provided credentials. Please try again."
},
status=status.HTTP_403_FORBIDDEN,
)
# Sign up Process
if not user.check_password(password):
return Response(
{
"error": "Sorry, we could not find a user with the provided credentials. Please try again."
},
status=status.HTTP_403_FORBIDDEN,
)
if not user.is_active:
return Response(
{
"error": "Your account has been deactivated. Please contact your site administrator."
},
status=status.HTTP_403_FORBIDDEN,
)
# settings last active for the user
user.last_active = timezone.now()
user.last_login_time = timezone.now()
user.last_login_ip = request.META.get("REMOTE_ADDR")
user.last_login_uagent = request.META.get("HTTP_USER_AGENT")
user.token_updated_at = timezone.now()
user.save()
access_token, refresh_token = get_tokens_for_user(user)
# Send Analytics
if settings.ANALYTICS_BASE_API:
_ = requests.post(
settings.ANALYTICS_BASE_API,
headers={
"Content-Type": "application/json",
"X-Auth-Token": settings.ANALYTICS_SECRET_KEY,
},
json={
"event_id": uuid.uuid4().hex,
"event_data": {
"medium": "email",
},
"user": {"email": email, "id": str(user.id)},
"device_ctx": {
"ip": request.META.get("REMOTE_ADDR"),
"user_agent": request.META.get("HTTP_USER_AGENT"),
},
"event_type": "SIGN_IN",
},
)
data = {
"access_token": access_token,
"refresh_token": refresh_token,
}
return Response(data, status=status.HTTP_200_OK)
class SignOutEndpoint(BaseAPIView):
def post(self, request):
refresh_token = request.data.get("refresh_token", False)
if not refresh_token:
capture_message("No refresh token provided")
return Response(
{"error": "No refresh token provided"},
status=status.HTTP_400_BAD_REQUEST,
)
user = User.objects.get(pk=request.user.id)
user.last_logout_time = timezone.now()
user.last_logout_ip = request.META.get("REMOTE_ADDR")
user.save()
token = RefreshToken(refresh_token)
token.blacklist()
return Response({"message": "success"}, status=status.HTTP_200_OK)
class MagicSignInGenerateEndpoint(BaseAPIView):
permission_classes = [
AllowAny,
]
def post(self, request):
email = request.data.get("email", False)
if not email:
return Response(
{"error": "Please provide a valid email address"},
status=status.HTTP_400_BAD_REQUEST,
)
# Clean up
email = email.strip().lower()
validate_email(email)
## Generate a random token
token = (
"".join(random.choices(string.ascii_lowercase, k=4))
+ "-"
+ "".join(random.choices(string.ascii_lowercase, k=4))
+ "-"
+ "".join(random.choices(string.ascii_lowercase, k=4))
)
ri = redis_instance()
key = "magic_" + str(email)
# Check if the key already exists in python
if ri.exists(key):
data = json.loads(ri.get(key))
current_attempt = data["current_attempt"] + 1
if data["current_attempt"] > 2:
return Response(
{"error": "Max attempts exhausted. Please try again later."},
status=status.HTTP_400_BAD_REQUEST,
)
value = {
"current_attempt": current_attempt,
"email": email,
"token": token,
}
expiry = 600
ri.set(key, json.dumps(value), ex=expiry)
else:
value = {"current_attempt": 0, "email": email, "token": token}
expiry = 600
ri.set(key, json.dumps(value), ex=expiry)
current_site = settings.WEB_URL
magic_link.delay(email, key, token, current_site)
return Response({"key": key}, status=status.HTTP_200_OK)
class MagicSignInEndpoint(BaseAPIView):
permission_classes = [
AllowAny,
]
def post(self, request):
user_token = request.data.get("token", "").strip()
key = request.data.get("key", False).strip().lower()
if not key or user_token == "":
return Response(
{"error": "User token and key are required"},
status=status.HTTP_400_BAD_REQUEST,
)
ri = redis_instance()
if ri.exists(key):
data = json.loads(ri.get(key))
token = data["token"]
email = data["email"]
if str(token) == str(user_token):
if User.objects.filter(email=email).exists():
user = User.objects.get(email=email)
# Send event to Jitsu for tracking
if settings.ANALYTICS_BASE_API:
_ = requests.post(
settings.ANALYTICS_BASE_API,
headers={
"Content-Type": "application/json",
"X-Auth-Token": settings.ANALYTICS_SECRET_KEY,
},
json={
"event_id": uuid.uuid4().hex,
"event_data": {
"medium": "code",
},
"user": {"email": email, "id": str(user.id)},
"device_ctx": {
"ip": request.META.get("REMOTE_ADDR"),
"user_agent": request.META.get("HTTP_USER_AGENT"),
},
"event_type": "SIGN_IN",
},
)
else:
user = User.objects.create(
email=email,
username=uuid.uuid4().hex,
password=make_password(uuid.uuid4().hex),
is_password_autoset=True,
)
# Send event to Jitsu for tracking
if settings.ANALYTICS_BASE_API:
_ = requests.post(
settings.ANALYTICS_BASE_API,
headers={
"Content-Type": "application/json",
"X-Auth-Token": settings.ANALYTICS_SECRET_KEY,
},
json={
"event_id": uuid.uuid4().hex,
"event_data": {
"medium": "code",
},
"user": {"email": email, "id": str(user.id)},
"device_ctx": {
"ip": request.META.get("REMOTE_ADDR"),
"user_agent": request.META.get("HTTP_USER_AGENT"),
},
"event_type": "SIGN_UP",
},
)
user.last_active = timezone.now()
user.last_login_time = timezone.now()
user.last_login_ip = request.META.get("REMOTE_ADDR")
user.last_login_uagent = request.META.get("HTTP_USER_AGENT")
user.token_updated_at = timezone.now()
user.save()
access_token, refresh_token = get_tokens_for_user(user)
data = {
"access_token": access_token,
"refresh_token": refresh_token,
}
return Response(data, status=status.HTTP_200_OK)
else:
return Response(
{"error": "Your login code was incorrect. Please try again."},
status=status.HTTP_400_BAD_REQUEST,
)
else:
return Response(
{"error": "The magic code/link has expired please try again"},
status=status.HTTP_400_BAD_REQUEST,
)

View File

@@ -1,27 +1,27 @@
# Python imports
import zoneinfo
import json
from urllib.parse import urlparse
# Django imports
from django.urls import resolve
from django.conf import settings
from django.utils import timezone
from django.db import IntegrityError
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.utils import timezone
# Third part imports
from rest_framework import status
from rest_framework import status
from rest_framework.viewsets import ModelViewSet
from rest_framework.response import Response
from rest_framework.exceptions import APIException
# Third party imports
from rest_framework.views import APIView
from rest_framework.filters import SearchFilter
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from rest_framework import status
from sentry_sdk import capture_exception
from django_filters.rest_framework import DjangoFilterBackend
# Module imports
from plane.api.middleware.api_authentication import APIKeyAuthentication
from plane.api.rate_limit import ApiKeyRateThrottle
from plane.utils.paginator import BasePaginator
from plane.bgtasks.webhook_task import send_webhook
class TimezoneMixin:
@@ -29,6 +29,7 @@ class TimezoneMixin:
This enables timezone conversion according
to the user set timezone
"""
def initial(self, request, *args, **kwargs):
super().initial(request, *args, **kwargs)
if request.user.is_authenticated:
@@ -37,109 +38,58 @@ class TimezoneMixin:
timezone.deactivate()
class BaseViewSet(TimezoneMixin, ModelViewSet, BasePaginator):
class WebhookMixin:
webhook_event = None
bulk = False
model = None
def finalize_response(self, request, response, *args, **kwargs):
response = super().finalize_response(
request, response, *args, **kwargs
)
permission_classes = [
IsAuthenticated,
]
# Check for the case should webhook be sent
if (
self.webhook_event
and self.request.method in ["POST", "PATCH", "DELETE"]
and response.status_code in [200, 201, 204]
):
url = request.build_absolute_uri()
parsed_url = urlparse(url)
# Extract the scheme and netloc
scheme = parsed_url.scheme
netloc = parsed_url.netloc
# Push the object to delay
send_webhook.delay(
event=self.webhook_event,
payload=response.data,
kw=self.kwargs,
action=self.request.method,
slug=self.workspace_slug,
bulk=self.bulk,
current_site=f"{scheme}://{netloc}",
)
filter_backends = (
DjangoFilterBackend,
SearchFilter,
)
filterset_fields = []
search_fields = []
def get_queryset(self):
try:
return self.model.objects.all()
except Exception as e:
capture_exception(e)
raise APIException("Please check the view", status.HTTP_400_BAD_REQUEST)
def handle_exception(self, exc):
"""
Handle any exception that occurs, by returning an appropriate response,
or re-raising the error.
"""
try:
response = super().handle_exception(exc)
return response
except Exception as e:
if isinstance(e, IntegrityError):
return Response({"error": "The payload is not valid"}, status=status.HTTP_400_BAD_REQUEST)
if isinstance(e, ValidationError):
return Response({"error": "Please provide valid detail"}, status=status.HTTP_400_BAD_REQUEST)
if isinstance(e, ObjectDoesNotExist):
model_name = str(exc).split(" matching query does not exist.")[0]
return Response({"error": f"{model_name} does not exist."}, status=status.HTTP_404_NOT_FOUND)
if isinstance(e, KeyError):
capture_exception(e)
return Response({"error": f"key {e} does not exist"}, status=status.HTTP_400_BAD_REQUEST)
print(e) if settings.DEBUG else print("Server Error")
capture_exception(e)
return Response({"error": "Something went wrong please try again later"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def dispatch(self, request, *args, **kwargs):
try:
response = super().dispatch(request, *args, **kwargs)
if settings.DEBUG:
from django.db import connection
print(
f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}"
)
return response
except Exception as exc:
response = self.handle_exception(exc)
return exc
@property
def workspace_slug(self):
return self.kwargs.get("slug", None)
@property
def project_id(self):
project_id = self.kwargs.get("project_id", None)
if project_id:
return project_id
if resolve(self.request.path_info).url_name == "project":
return self.kwargs.get("pk", None)
return response
class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
authentication_classes = [
APIKeyAuthentication,
]
permission_classes = [
IsAuthenticated,
]
filter_backends = (
DjangoFilterBackend,
SearchFilter,
)
filterset_fields = []
search_fields = []
throttle_classes = [
ApiKeyRateThrottle,
]
def filter_queryset(self, queryset):
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
def handle_exception(self, exc):
"""
Handle any exception that occurs, by returning an appropriate response,
@@ -150,27 +100,42 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
return response
except Exception as e:
if isinstance(e, IntegrityError):
return Response({"error": "The payload is not valid"}, status=status.HTTP_400_BAD_REQUEST)
if isinstance(e, ValidationError):
return Response({"error": "Please provide valid detail"}, status=status.HTTP_400_BAD_REQUEST)
if isinstance(e, ObjectDoesNotExist):
model_name = str(exc).split(" matching query does not exist.")[0]
return Response({"error": f"{model_name} does not exist."}, status=status.HTTP_404_NOT_FOUND)
if isinstance(e, KeyError):
return Response({"error": f"key {e} does not exist"}, status=status.HTTP_400_BAD_REQUEST)
print(e) if settings.DEBUG else print("Server Error")
capture_exception(e)
return Response({"error": "Something went wrong please try again later"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response(
{"error": "The payload is not valid"},
status=status.HTTP_400_BAD_REQUEST,
)
if isinstance(e, ValidationError):
return Response(
{
"error": "The provided payload is not valid please try with a valid payload"
},
status=status.HTTP_400_BAD_REQUEST,
)
if isinstance(e, ObjectDoesNotExist):
return Response(
{"error": f"The required object does not exist."},
status=status.HTTP_404_NOT_FOUND,
)
if isinstance(e, KeyError):
return Response(
{"error": f" The required key does not exist."},
status=status.HTTP_400_BAD_REQUEST,
)
if settings.DEBUG:
print(e)
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
def dispatch(self, request, *args, **kwargs):
try:
response = super().dispatch(request, *args, **kwargs)
if settings.DEBUG:
from django.db import connection
@@ -178,11 +143,27 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}"
)
return response
except Exception as exc:
response = self.handle_exception(exc)
return exc
def finalize_response(self, request, response, *args, **kwargs):
# Call super to get the default response
response = super().finalize_response(
request, response, *args, **kwargs
)
# Add custom headers if they exist in the request META
ratelimit_remaining = request.META.get("X-RateLimit-Remaining")
if ratelimit_remaining is not None:
response["X-RateLimit-Remaining"] = ratelimit_remaining
ratelimit_reset = request.META.get("X-RateLimit-Reset")
if ratelimit_reset is not None:
response["X-RateLimit-Reset"] = ratelimit_reset
return response
@property
def workspace_slug(self):
return self.kwargs.get("slug", None)
@@ -190,3 +171,21 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
@property
def project_id(self):
return self.kwargs.get("project_id", None)
@property
def fields(self):
fields = [
field
for field in self.request.GET.get("fields", "").split(",")
if field
]
return fields if fields else None
@property
def expand(self):
expand = [
expand
for expand in self.request.GET.get("expand", "").split(",")
if expand
]
return expand if expand else None

View File

@@ -1,37 +0,0 @@
# Python imports
import os
# Django imports
from django.conf import settings
# Third party imports
from rest_framework.permissions import AllowAny
from rest_framework import status
from rest_framework.response import Response
from sentry_sdk import capture_exception
# Module imports
from .base import BaseAPIView
class ConfigurationEndpoint(BaseAPIView):
permission_classes = [
AllowAny,
]
def get(self, request):
data = {}
data["google_client_id"] = os.environ.get("GOOGLE_CLIENT_ID", None)
data["github_client_id"] = os.environ.get("GITHUB_CLIENT_ID", None)
data["github_app_name"] = os.environ.get("GITHUB_APP_NAME", None)
data["magic_login"] = (
bool(settings.EMAIL_HOST_USER) and bool(settings.EMAIL_HOST_PASSWORD)
) and os.environ.get("ENABLE_MAGIC_LINK_LOGIN", "0") == "1"
data["email_password_login"] = (
os.environ.get("ENABLE_EMAIL_PASSWORD", "0") == "1"
)
data["slack_client_id"] = os.environ.get("SLACK_CLIENT_ID", None)
data["posthog_api_key"] = os.environ.get("POSTHOG_API_KEY", None)
data["posthog_host"] = os.environ.get("POSTHOG_HOST", None)
data["has_unsplash_configured"] = bool(settings.UNSPLASH_ACCESS_KEY)
return Response(data, status=status.HTTP_200_OK)

View File

@@ -2,81 +2,53 @@
import json
# Django imports
from django.db.models import (
Func,
F,
Q,
Exists,
OuterRef,
Count,
Prefetch,
Sum,
)
from django.core import serializers
from django.db.models import Q, Count, Sum, Prefetch, F, OuterRef, Func
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views.decorators.gzip import gzip_page
from django.core import serializers
# Third party imports
from rest_framework.response import Response
from rest_framework import status
from sentry_sdk import capture_exception
# Module imports
from . import BaseViewSet, BaseAPIView
from .base import BaseAPIView, WebhookMixin
from plane.db.models import (
Cycle,
Issue,
CycleIssue,
IssueLink,
FileAsset,
)
from plane.app.permissions import ProjectEntityPermission
from plane.api.serializers import (
CycleSerializer,
CycleIssueSerializer,
CycleFavoriteSerializer,
IssueStateSerializer,
CycleWriteSerializer,
)
from plane.api.permissions import ProjectEntityPermission
from plane.db.models import (
User,
Cycle,
CycleIssue,
Issue,
CycleFavorite,
IssueLink,
IssueAttachment,
Label,
)
from plane.bgtasks.issue_activites_task import issue_activity
from plane.utils.grouper import group_results
from plane.utils.issue_filters import issue_filters
from plane.utils.analytics_plot import burndown_plot
class CycleViewSet(BaseViewSet):
class CycleAPIEndpoint(WebhookMixin, BaseAPIView):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions related to cycle.
"""
serializer_class = CycleSerializer
model = Cycle
webhook_event = "cycle"
permission_classes = [
ProjectEntityPermission,
]
def perform_create(self, serializer):
serializer.save(
project_id=self.kwargs.get("project_id"), owned_by=self.request.user
)
def get_queryset(self):
subquery = CycleFavorite.objects.filter(
user=self.request.user,
cycle_id=OuterRef("pk"),
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
)
return self.filter_queryset(
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
return (
Cycle.objects.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(project__project_projectmember__member=self.request.user)
.select_related("project")
.select_related("workspace")
.select_related("owned_by")
.annotate(is_favorite=Exists(subquery))
.annotate(
total_issues=Count(
"issue_cycle",
@@ -136,7 +108,9 @@ class CycleViewSet(BaseViewSet):
),
)
)
.annotate(total_estimates=Sum("issue_cycle__issue__estimate_point"))
.annotate(
total_estimates=Sum("issue_cycle__issue__estimate_point")
)
.annotate(
completed_estimates=Sum(
"issue_cycle__issue__estimate_point",
@@ -157,142 +131,62 @@ class CycleViewSet(BaseViewSet):
),
)
)
.prefetch_related(
Prefetch(
"issue_cycle__issue__assignees",
queryset=User.objects.only("avatar", "first_name", "id").distinct(),
)
)
.prefetch_related(
Prefetch(
"issue_cycle__issue__labels",
queryset=Label.objects.only("name", "color", "id").distinct(),
)
)
.order_by("-is_favorite", "name")
.order_by(self.kwargs.get("order_by", "-created_at"))
.distinct()
)
def list(self, request, slug, project_id):
def get(self, request, slug, project_id, pk=None):
if pk:
queryset = self.get_queryset().get(pk=pk)
data = CycleSerializer(
queryset,
fields=self.fields,
expand=self.expand,
).data
return Response(
data,
status=status.HTTP_200_OK,
)
queryset = self.get_queryset()
cycle_view = request.GET.get("cycle_view", "all")
queryset = queryset.order_by("-is_favorite","-created_at")
# Current Cycle
if cycle_view == "current":
queryset = queryset.filter(
start_date__lte=timezone.now(),
end_date__gte=timezone.now(),
)
data = CycleSerializer(queryset, many=True).data
if len(data):
assignee_distribution = (
Issue.objects.filter(
issue_cycle__cycle_id=data[0]["id"],
workspace__slug=slug,
project_id=project_id,
)
.annotate(display_name=F("assignees__display_name"))
.annotate(assignee_id=F("assignees__id"))
.annotate(avatar=F("assignees__avatar"))
.values("display_name", "assignee_id", "avatar")
.annotate(
total_issues=Count(
"assignee_id",
filter=Q(archived_at__isnull=True, is_draft=False),
),
)
.annotate(
completed_issues=Count(
"assignee_id",
filter=Q(
completed_at__isnull=False,
archived_at__isnull=True,
is_draft=False,
),
)
)
.annotate(
pending_issues=Count(
"assignee_id",
filter=Q(
completed_at__isnull=True,
archived_at__isnull=True,
is_draft=False,
),
)
)
.order_by("display_name")
)
label_distribution = (
Issue.objects.filter(
issue_cycle__cycle_id=data[0]["id"],
workspace__slug=slug,
project_id=project_id,
)
.annotate(label_name=F("labels__name"))
.annotate(color=F("labels__color"))
.annotate(label_id=F("labels__id"))
.values("label_name", "color", "label_id")
.annotate(
total_issues=Count(
"label_id",
filter=Q(archived_at__isnull=True, is_draft=False),
)
)
.annotate(
completed_issues=Count(
"label_id",
filter=Q(
completed_at__isnull=False,
archived_at__isnull=True,
is_draft=False,
),
)
)
.annotate(
pending_issues=Count(
"label_id",
filter=Q(
completed_at__isnull=True,
archived_at__isnull=True,
is_draft=False,
),
)
)
.order_by("label_name")
)
data[0]["distribution"] = {
"assignees": assignee_distribution,
"labels": label_distribution,
"completion_chart": {},
}
if data[0]["start_date"] and data[0]["end_date"]:
data[0]["distribution"]["completion_chart"] = burndown_plot(
queryset=queryset.first(),
slug=slug,
project_id=project_id,
cycle_id=data[0]["id"],
)
data = CycleSerializer(
queryset, many=True, fields=self.fields, expand=self.expand
).data
return Response(data, status=status.HTTP_200_OK)
# Upcoming Cycles
if cycle_view == "upcoming":
queryset = queryset.filter(start_date__gt=timezone.now())
return Response(
CycleSerializer(queryset, many=True).data, status=status.HTTP_200_OK
return self.paginate(
request=request,
queryset=(queryset),
on_results=lambda cycles: CycleSerializer(
cycles,
many=True,
fields=self.fields,
expand=self.expand,
).data,
)
# Completed Cycles
if cycle_view == "completed":
queryset = queryset.filter(end_date__lt=timezone.now())
return Response(
CycleSerializer(queryset, many=True).data, status=status.HTTP_200_OK
return self.paginate(
request=request,
queryset=(queryset),
on_results=lambda cycles: CycleSerializer(
cycles,
many=True,
fields=self.fields,
expand=self.expand,
).data,
)
# Draft Cycles
@@ -301,26 +195,45 @@ class CycleViewSet(BaseViewSet):
end_date=None,
start_date=None,
)
return Response(
CycleSerializer(queryset, many=True).data, status=status.HTTP_200_OK
return self.paginate(
request=request,
queryset=(queryset),
on_results=lambda cycles: CycleSerializer(
cycles,
many=True,
fields=self.fields,
expand=self.expand,
).data,
)
# Incomplete Cycles
if cycle_view == "incomplete":
queryset = queryset.filter(
Q(end_date__gte=timezone.now().date()) | Q(end_date__isnull=True),
Q(end_date__gte=timezone.now().date())
| Q(end_date__isnull=True),
)
return Response(
CycleSerializer(queryset, many=True).data, status=status.HTTP_200_OK
return self.paginate(
request=request,
queryset=(queryset),
on_results=lambda cycles: CycleSerializer(
cycles,
many=True,
fields=self.fields,
expand=self.expand,
).data,
)
# If no matching view is found return all cycles
return Response(
CycleSerializer(queryset, many=True).data, status=status.HTTP_200_OK
return self.paginate(
request=request,
queryset=(queryset),
on_results=lambda cycles: CycleSerializer(
cycles,
many=True,
fields=self.fields,
expand=self.expand,
).data,
)
def create(self, request, slug, project_id):
def post(self, request, slug, project_id):
if (
request.data.get("start_date", None) is None
and request.data.get("end_date", None) is None
@@ -330,12 +243,39 @@ class CycleViewSet(BaseViewSet):
):
serializer = CycleSerializer(data=request.data)
if serializer.is_valid():
if (
request.data.get("external_id")
and request.data.get("external_source")
and Cycle.objects.filter(
project_id=project_id,
workspace__slug=slug,
external_source=request.data.get("external_source"),
external_id=request.data.get("external_id"),
).exists()
):
cycle = Cycle.objects.filter(
workspace__slug=slug,
project_id=project_id,
external_source=request.data.get("external_source"),
external_id=request.data.get("external_id"),
).first()
return Response(
{
"error": "Cycle with the same external id and external source already exists",
"id": str(cycle.id),
},
status=status.HTTP_409_CONFLICT,
)
serializer.save(
project_id=project_id,
owned_by=request.user,
)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
return Response(
serializer.data, status=status.HTTP_201_CREATED
)
return Response(
serializer.errors, status=status.HTTP_400_BAD_REQUEST
)
else:
return Response(
{
@@ -344,16 +284,23 @@ class CycleViewSet(BaseViewSet):
status=status.HTTP_400_BAD_REQUEST,
)
def partial_update(self, request, slug, project_id, pk):
cycle = Cycle.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
def patch(self, request, slug, project_id, pk):
cycle = Cycle.objects.get(
workspace__slug=slug, project_id=project_id, pk=pk
)
request_data = request.data
if cycle.end_date is not None and cycle.end_date < timezone.now().date():
if (
cycle.end_date is not None
and cycle.end_date < timezone.now().date()
):
if "sort_order" in request_data:
# Can only change sort order
request_data = {
"sort_order": request_data.get("sort_order", cycle.sort_order)
"sort_order": request_data.get(
"sort_order", cycle.sort_order
)
}
else:
return Response(
@@ -363,121 +310,38 @@ class CycleViewSet(BaseViewSet):
status=status.HTTP_400_BAD_REQUEST,
)
serializer = CycleWriteSerializer(cycle, data=request.data, partial=True)
serializer = CycleSerializer(cycle, data=request.data, partial=True)
if serializer.is_valid():
if (
request.data.get("external_id")
and (cycle.external_id != request.data.get("external_id"))
and Cycle.objects.filter(
project_id=project_id,
workspace__slug=slug,
external_source=request.data.get("external_source", cycle.external_source),
external_id=request.data.get("external_id"),
).exists()
):
return Response(
{
"error": "Cycle with the same external id and external source already exists",
"id": str(cycle.id),
},
status=status.HTTP_409_CONFLICT,
)
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def retrieve(self, request, slug, project_id, pk):
queryset = self.get_queryset().get(pk=pk)
# Assignee Distribution
assignee_distribution = (
Issue.objects.filter(
issue_cycle__cycle_id=pk,
workspace__slug=slug,
project_id=project_id,
)
.annotate(first_name=F("assignees__first_name"))
.annotate(last_name=F("assignees__last_name"))
.annotate(assignee_id=F("assignees__id"))
.annotate(avatar=F("assignees__avatar"))
.annotate(display_name=F("assignees__display_name"))
.values("first_name", "last_name", "assignee_id", "avatar", "display_name")
.annotate(
total_issues=Count(
"assignee_id",
filter=Q(archived_at__isnull=True, is_draft=False),
),
)
.annotate(
completed_issues=Count(
"assignee_id",
filter=Q(
completed_at__isnull=False,
archived_at__isnull=True,
is_draft=False,
),
)
)
.annotate(
pending_issues=Count(
"assignee_id",
filter=Q(
completed_at__isnull=True,
archived_at__isnull=True,
is_draft=False,
),
)
)
.order_by("first_name", "last_name")
)
# Label Distribution
label_distribution = (
Issue.objects.filter(
issue_cycle__cycle_id=pk,
workspace__slug=slug,
project_id=project_id,
)
.annotate(label_name=F("labels__name"))
.annotate(color=F("labels__color"))
.annotate(label_id=F("labels__id"))
.values("label_name", "color", "label_id")
.annotate(
total_issues=Count(
"label_id",
filter=Q(archived_at__isnull=True, is_draft=False),
),
)
.annotate(
completed_issues=Count(
"label_id",
filter=Q(
completed_at__isnull=False,
archived_at__isnull=True,
is_draft=False,
),
)
)
.annotate(
pending_issues=Count(
"label_id",
filter=Q(
completed_at__isnull=True,
archived_at__isnull=True,
is_draft=False,
),
)
)
.order_by("label_name")
)
data = CycleSerializer(queryset).data
data["distribution"] = {
"assignees": assignee_distribution,
"labels": label_distribution,
"completion_chart": {},
}
if queryset.start_date and queryset.end_date:
data["distribution"]["completion_chart"] = burndown_plot(
queryset=queryset, slug=slug, project_id=project_id, cycle_id=pk
)
return Response(
data,
status=status.HTTP_200_OK,
)
def destroy(self, request, slug, project_id, pk):
def delete(self, request, slug, project_id, pk):
cycle_issues = list(
CycleIssue.objects.filter(cycle_id=self.kwargs.get("pk")).values_list(
"issue", flat=True
)
CycleIssue.objects.filter(
cycle_id=self.kwargs.get("pk")
).values_list("issue", flat=True)
)
cycle = Cycle.objects.get(
workspace__slug=slug, project_id=project_id, pk=pk
)
cycle = Cycle.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
issue_activity.delay(
type="cycle.activity.deleted",
@@ -489,7 +353,7 @@ class CycleViewSet(BaseViewSet):
}
),
actor_id=str(request.user.id),
issue_id=str(pk),
issue_id=None,
project_id=str(project_id),
current_instance=None,
epoch=int(timezone.now().timestamp()),
@@ -499,25 +363,27 @@ class CycleViewSet(BaseViewSet):
return Response(status=status.HTTP_204_NO_CONTENT)
class CycleIssueViewSet(BaseViewSet):
class CycleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
"""
This viewset automatically provides `list`, `create`,
and `destroy` actions related to cycle issues.
"""
serializer_class = CycleIssueSerializer
model = CycleIssue
webhook_event = "cycle_issue"
bulk = True
permission_classes = [
ProjectEntityPermission,
]
filterset_fields = [
"issue__labels__id",
"issue__assignees__id",
]
def get_queryset(self):
return self.filter_queryset(
super()
.get_queryset()
.annotate(
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("issue_id"))
return (
CycleIssue.objects.annotate(
sub_issues_count=Issue.issue_objects.filter(
parent=OuterRef("issue_id")
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
@@ -531,19 +397,18 @@ class CycleIssueViewSet(BaseViewSet):
.select_related("cycle")
.select_related("issue", "issue__state", "issue__project")
.prefetch_related("issue__assignees", "issue__labels")
.order_by(self.kwargs.get("order_by", "-created_at"))
.distinct()
)
@method_decorator(gzip_page)
def list(self, request, slug, project_id, cycle_id):
def get(self, request, slug, project_id, cycle_id):
order_by = request.GET.get("order_by", "created_at")
group_by = request.GET.get("group_by", False)
sub_group_by = request.GET.get("sub_group_by", False)
filters = issue_filters(request.query_params, "GET")
issues = (
Issue.issue_objects.filter(issue_cycle__cycle_id=cycle_id)
.annotate(
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
sub_issues_count=Issue.issue_objects.filter(
parent=OuterRef("id")
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
@@ -558,7 +423,6 @@ class CycleIssueViewSet(BaseViewSet):
.prefetch_related("assignees")
.prefetch_related("labels")
.order_by(order_by)
.filter(**filters)
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
@@ -566,45 +430,44 @@ class CycleIssueViewSet(BaseViewSet):
.values("count")
)
.annotate(
attachment_count=IssueAttachment.objects.filter(issue=OuterRef("id"))
attachment_count=FileAsset.objects.filter(
entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
)
issues_data = IssueStateSerializer(issues, many=True).data
if sub_group_by and sub_group_by == group_by:
return Response(
{"error": "Group by and sub group by cannot be same"},
status=status.HTTP_400_BAD_REQUEST,
)
if group_by:
grouped_results = group_results(issues_data, group_by, sub_group_by)
return Response(
grouped_results,
status=status.HTTP_200_OK,
)
return Response(
issues_data, status=status.HTTP_200_OK
return self.paginate(
request=request,
queryset=(issues),
on_results=lambda issues: CycleSerializer(
issues,
many=True,
fields=self.fields,
expand=self.expand,
).data,
)
def create(self, request, slug, project_id, cycle_id):
def post(self, request, slug, project_id, cycle_id):
issues = request.data.get("issues", [])
if not len(issues):
if not issues:
return Response(
{"error": "Issues are required"}, status=status.HTTP_400_BAD_REQUEST
{"error": "Issues are required"},
status=status.HTTP_400_BAD_REQUEST,
)
cycle = Cycle.objects.get(
workspace__slug=slug, project_id=project_id, pk=cycle_id
)
if cycle.end_date is not None and cycle.end_date < timezone.now().date():
if (
cycle.end_date is not None
and cycle.end_date < timezone.now().date()
):
return Response(
{
"error": "The Cycle has already been completed so no new issues can be added"
@@ -612,6 +475,10 @@ class CycleIssueViewSet(BaseViewSet):
status=status.HTTP_400_BAD_REQUEST,
)
issues = Issue.objects.filter(
pk__in=issues, workspace__slug=slug, project_id=project_id
).values_list("id", flat=True)
# Get all CycleIssues already created
cycle_issues = list(CycleIssue.objects.filter(issue_id__in=issues))
update_cycle_issue_activity = []
@@ -662,7 +529,7 @@ class CycleIssueViewSet(BaseViewSet):
# Capture Issue Activity
issue_activity.delay(
type="cycle.activity.created",
requested_data=json.dumps({"cycles_list": issues}),
requested_data=json.dumps({"cycles_list": str(issues)}),
actor_id=str(self.request.user.id),
issue_id=None,
project_id=str(self.kwargs.get("project_id", None)),
@@ -683,9 +550,12 @@ class CycleIssueViewSet(BaseViewSet):
status=status.HTTP_200_OK,
)
def destroy(self, request, slug, project_id, cycle_id, pk):
def delete(self, request, slug, project_id, cycle_id, issue_id):
cycle_issue = CycleIssue.objects.get(
pk=pk, workspace__slug=slug, project_id=project_id, cycle_id=cycle_id
issue_id=issue_id,
workspace__slug=slug,
project_id=project_id,
cycle_id=cycle_id,
)
issue_id = cycle_issue.issue_id
cycle_issue.delete()
@@ -698,7 +568,7 @@ class CycleIssueViewSet(BaseViewSet):
}
),
actor_id=str(self.request.user.id),
issue_id=str(self.kwargs.get("pk", None)),
issue_id=str(issue_id),
project_id=str(self.kwargs.get("project_id", None)),
current_instance=None,
epoch=int(timezone.now().timestamp()),
@@ -706,74 +576,12 @@ class CycleIssueViewSet(BaseViewSet):
return Response(status=status.HTTP_204_NO_CONTENT)
class CycleDateCheckEndpoint(BaseAPIView):
permission_classes = [
ProjectEntityPermission,
]
class TransferCycleIssueAPIEndpoint(BaseAPIView):
"""
This viewset provides `create` actions for transfering the issues into a particular cycle.
def post(self, request, slug, project_id):
start_date = request.data.get("start_date", False)
end_date = request.data.get("end_date", False)
cycle_id = request.data.get("cycle_id")
if not start_date or not end_date:
return Response(
{"error": "Start date and end date both are required"},
status=status.HTTP_400_BAD_REQUEST,
)
"""
cycles = Cycle.objects.filter(
Q(workspace__slug=slug)
& Q(project_id=project_id)
& (
Q(start_date__lte=start_date, end_date__gte=start_date)
| Q(start_date__lte=end_date, end_date__gte=end_date)
| Q(start_date__gte=start_date, end_date__lte=end_date)
)
).exclude(pk=cycle_id)
if cycles.exists():
return Response(
{
"error": "You have a cycle already on the given dates, if you want to create a draft cycle you can do that by removing dates",
"status": False,
}
)
else:
return Response({"status": True}, status=status.HTTP_200_OK)
class CycleFavoriteViewSet(BaseViewSet):
serializer_class = CycleFavoriteSerializer
model = CycleFavorite
def get_queryset(self):
return self.filter_queryset(
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(user=self.request.user)
.select_related("cycle", "cycle__owned_by")
)
def create(self, request, slug, project_id):
serializer = CycleFavoriteSerializer(data=request.data)
if serializer.is_valid():
serializer.save(user=request.user, project_id=project_id)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def destroy(self, request, slug, project_id, cycle_id):
cycle_favorite = CycleFavorite.objects.get(
project=project_id,
user=request.user,
workspace__slug=slug,
cycle_id=cycle_id,
)
cycle_favorite.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
class TransferCycleIssueEndpoint(BaseAPIView):
permission_classes = [
ProjectEntityPermission,
]

View File

@@ -1,83 +1,37 @@
# Python imports
import json
# Django import
# Django improts
from django.utils import timezone
from django.db.models import Q, Count, OuterRef, Func, F, Prefetch
from django.db.models import Q
from django.core.serializers.json import DjangoJSONEncoder
# Third party imports
from rest_framework import status
from rest_framework.response import Response
from sentry_sdk import capture_exception
# Module imports
from .base import BaseViewSet
from plane.api.permissions import ProjectBasePermission, ProjectLitePermission
from .base import BaseAPIView
from plane.app.permissions import ProjectLitePermission
from plane.api.serializers import InboxIssueSerializer, IssueSerializer
from plane.db.models import (
Inbox,
InboxIssue,
Issue,
State,
IssueLink,
IssueAttachment,
ProjectMember,
ProjectDeployBoard,
Project,
Inbox,
)
from plane.api.serializers import (
IssueSerializer,
InboxSerializer,
InboxIssueSerializer,
IssueCreateSerializer,
IssueStateInboxSerializer,
)
from plane.utils.issue_filters import issue_filters
from plane.bgtasks.issue_activites_task import issue_activity
class InboxViewSet(BaseViewSet):
permission_classes = [
ProjectBasePermission,
]
class InboxIssueAPIEndpoint(BaseAPIView):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions related to inbox issues.
serializer_class = InboxSerializer
model = Inbox
"""
def get_queryset(self):
return (
super()
.get_queryset()
.filter(
workspace__slug=self.kwargs.get("slug"),
project_id=self.kwargs.get("project_id"),
)
.annotate(
pending_issue_count=Count(
"issue_inbox",
filter=Q(issue_inbox__status=-2),
)
)
.select_related("workspace", "project")
)
def perform_create(self, serializer):
serializer.save(project_id=self.kwargs.get("project_id"))
def destroy(self, request, slug, project_id, pk):
inbox = Inbox.objects.get(
workspace__slug=slug, project_id=project_id, pk=pk
)
# Handle default inbox delete
if inbox.is_default:
return Response(
{"error": "You cannot delete the default inbox"},
status=status.HTTP_400_BAD_REQUEST,
)
inbox.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
class InboxIssueViewSet(BaseViewSet):
permission_classes = [
ProjectLitePermission,
]
@@ -90,71 +44,78 @@ class InboxIssueViewSet(BaseViewSet):
]
def get_queryset(self):
return self.filter_queryset(
super()
.get_queryset()
.filter(
Q(snoozed_till__gte=timezone.now()) | Q(snoozed_till__isnull=True),
inbox = Inbox.objects.filter(
workspace__slug=self.kwargs.get("slug"),
project_id=self.kwargs.get("project_id"),
).first()
project = Project.objects.get(
workspace__slug=self.kwargs.get("slug"),
pk=self.kwargs.get("project_id"),
)
if inbox is None and not project.inbox_view:
return InboxIssue.objects.none()
return (
InboxIssue.objects.filter(
Q(snoozed_till__gte=timezone.now())
| Q(snoozed_till__isnull=True),
workspace__slug=self.kwargs.get("slug"),
project_id=self.kwargs.get("project_id"),
inbox_id=self.kwargs.get("inbox_id"),
inbox_id=inbox.id,
)
.select_related("issue", "workspace", "project")
.order_by(self.kwargs.get("order_by", "-created_at"))
)
def list(self, request, slug, project_id, inbox_id):
filters = issue_filters(request.query_params, "GET")
issues = (
Issue.objects.filter(
issue_inbox__inbox_id=inbox_id,
workspace__slug=slug,
project_id=project_id,
def get(self, request, slug, project_id, issue_id=None):
if issue_id:
inbox_issue_queryset = self.get_queryset().get(issue_id=issue_id)
inbox_issue_data = InboxIssueSerializer(
inbox_issue_queryset,
fields=self.fields,
expand=self.expand,
).data
return Response(
inbox_issue_data,
status=status.HTTP_200_OK,
)
.filter(**filters)
.annotate(bridge_id=F("issue_inbox__id"))
.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels")
.order_by("issue_inbox__snoozed_till", "issue_inbox__status")
.annotate(
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
attachment_count=IssueAttachment.objects.filter(
issue=OuterRef("id")
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.prefetch_related(
Prefetch(
"issue_inbox",
queryset=InboxIssue.objects.only(
"status", "duplicate_to", "snoozed_till", "source"
),
)
)
)
issues_data = IssueStateInboxSerializer(issues, many=True).data
return Response(
issues_data,
status=status.HTTP_200_OK,
issue_queryset = self.get_queryset()
return self.paginate(
request=request,
queryset=(issue_queryset),
on_results=lambda inbox_issues: InboxIssueSerializer(
inbox_issues,
many=True,
fields=self.fields,
expand=self.expand,
).data,
)
def create(self, request, slug, project_id, inbox_id):
def post(self, request, slug, project_id):
if not request.data.get("issue", {}).get("name", False):
return Response(
{"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST
{"error": "Name is required"},
status=status.HTTP_400_BAD_REQUEST,
)
inbox = Inbox.objects.filter(
workspace__slug=slug, project_id=project_id
).first()
project = Project.objects.get(
workspace__slug=slug,
pk=project_id,
)
# Inbox view
if inbox is None and not project.inbox_view:
return Response(
{
"error": "Inbox is not enabled for this project enable it through the project's api"
},
status=status.HTTP_400_BAD_REQUEST,
)
# Check for valid priority
@@ -166,7 +127,8 @@ class InboxIssueViewSet(BaseViewSet):
"none",
]:
return Response(
{"error": "Invalid priority"}, status=status.HTTP_400_BAD_REQUEST
{"error": "Invalid priority"},
status=status.HTTP_400_BAD_REQUEST,
)
# Create or get state
@@ -198,46 +160,85 @@ class InboxIssueViewSet(BaseViewSet):
issue_id=str(issue.id),
project_id=str(project_id),
current_instance=None,
epoch=int(timezone.now().timestamp())
epoch=int(timezone.now().timestamp()),
)
# create an inbox issue
InboxIssue.objects.create(
inbox_id=inbox_id,
inbox_issue = InboxIssue.objects.create(
inbox_id=inbox.id,
project_id=project_id,
issue=issue,
source=request.data.get("source", "in-app"),
)
serializer = IssueStateInboxSerializer(issue)
serializer = InboxIssueSerializer(inbox_issue)
return Response(serializer.data, status=status.HTTP_200_OK)
def partial_update(self, request, slug, project_id, inbox_id, pk):
inbox_issue = InboxIssue.objects.get(
pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id
def patch(self, request, slug, project_id, issue_id):
inbox = Inbox.objects.filter(
workspace__slug=slug, project_id=project_id
).first()
project = Project.objects.get(
workspace__slug=slug,
pk=project_id,
)
# Inbox view
if inbox is None and not project.inbox_view:
return Response(
{
"error": "Inbox is not enabled for this project enable it through the project's api"
},
status=status.HTTP_400_BAD_REQUEST,
)
# Get the inbox issue
inbox_issue = InboxIssue.objects.get(
issue_id=issue_id,
workspace__slug=slug,
project_id=project_id,
inbox_id=inbox.id,
)
# Get the project member
project_member = ProjectMember.objects.get(workspace__slug=slug, project_id=project_id, member=request.user)
project_member = ProjectMember.objects.get(
workspace__slug=slug,
project_id=project_id,
member=request.user,
is_active=True,
)
# Only project members admins and created_by users can access this endpoint
if project_member.role <= 10 and str(inbox_issue.created_by_id) != str(request.user.id):
return Response({"error": "You cannot edit inbox issues"}, status=status.HTTP_400_BAD_REQUEST)
if project_member.role <= 10 and str(inbox_issue.created_by_id) != str(
request.user.id
):
return Response(
{"error": "You cannot edit inbox issues"},
status=status.HTTP_400_BAD_REQUEST,
)
# Get issue data
issue_data = request.data.pop("issue", False)
if bool(issue_data):
issue = Issue.objects.get(
pk=inbox_issue.issue_id, workspace__slug=slug, project_id=project_id
pk=issue_id, workspace__slug=slug, project_id=project_id
)
# Only allow guests and viewers to edit name and description
if project_member.role <= 10:
# viewers and guests since only viewers and guests
# viewers and guests since only viewers and guests
issue_data = {
"name": issue_data.get("name", issue.name),
"description_html": issue_data.get("description_html", issue.description_html),
"description": issue_data.get("description", issue.description)
"description_html": issue_data.get(
"description_html", issue.description_html
),
"description": issue_data.get(
"description", issue.description
),
}
issue_serializer = IssueCreateSerializer(
issue_serializer = IssueSerializer(
issue, data=issue_data, partial=True
)
@@ -250,13 +251,13 @@ class InboxIssueViewSet(BaseViewSet):
type="issue.activity.updated",
requested_data=requested_data,
actor_id=str(request.user.id),
issue_id=str(issue.id),
issue_id=str(issue_id),
project_id=str(project_id),
current_instance=json.dumps(
IssueSerializer(current_instance).data,
cls=DjangoJSONEncoder,
),
epoch=int(timezone.now().timestamp())
epoch=int(timezone.now().timestamp()),
)
issue_serializer.save()
else:
@@ -275,12 +276,14 @@ class InboxIssueViewSet(BaseViewSet):
# Update the issue state if the issue is rejected or marked as duplicate
if serializer.data["status"] in [-1, 2]:
issue = Issue.objects.get(
pk=inbox_issue.issue_id,
pk=issue_id,
workspace__slug=slug,
project_id=project_id,
)
state = State.objects.filter(
group="cancelled", workspace__slug=slug, project_id=project_id
group="cancelled",
workspace__slug=slug,
project_id=project_id,
).first()
if state is not None:
issue.state = state
@@ -289,7 +292,7 @@ class InboxIssueViewSet(BaseViewSet):
# Update the issue state if it is accepted
if serializer.data["status"] in [1]:
issue = Issue.objects.get(
pk=inbox_issue.issue_id,
pk=issue_id,
workspace__slug=slug,
project_id=project_id,
)
@@ -298,262 +301,74 @@ class InboxIssueViewSet(BaseViewSet):
if issue.state.name == "Triage":
# Move to default state
state = State.objects.filter(
workspace__slug=slug, project_id=project_id, default=True
workspace__slug=slug,
project_id=project_id,
default=True,
).first()
if state is not None:
issue.state = state
issue.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
return Response(
serializer.errors, status=status.HTTP_400_BAD_REQUEST
)
else:
return Response(InboxIssueSerializer(inbox_issue).data, status=status.HTTP_200_OK)
return Response(
InboxIssueSerializer(inbox_issue).data,
status=status.HTTP_200_OK,
)
def retrieve(self, request, slug, project_id, inbox_id, pk):
inbox_issue = InboxIssue.objects.get(
pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id
)
issue = Issue.objects.get(
pk=inbox_issue.issue_id, workspace__slug=slug, project_id=project_id
)
serializer = IssueStateInboxSerializer(issue)
return Response(serializer.data, status=status.HTTP_200_OK)
def delete(self, request, slug, project_id, issue_id):
inbox = Inbox.objects.filter(
workspace__slug=slug, project_id=project_id
).first()
def destroy(self, request, slug, project_id, inbox_id, pk):
inbox_issue = InboxIssue.objects.get(
pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id
project = Project.objects.get(
workspace__slug=slug,
pk=project_id,
)
# Inbox view
if inbox is None and not project.inbox_view:
return Response(
{
"error": "Inbox is not enabled for this project enable it through the project's api"
},
status=status.HTTP_400_BAD_REQUEST,
)
# Get the inbox issue
inbox_issue = InboxIssue.objects.get(
issue_id=issue_id,
workspace__slug=slug,
project_id=project_id,
inbox_id=inbox.id,
)
# Get the project member
project_member = ProjectMember.objects.get(workspace__slug=slug, project_id=project_id, member=request.user)
project_member = ProjectMember.objects.get(
workspace__slug=slug,
project_id=project_id,
member=request.user,
is_active=True,
)
if project_member.role <= 10 and str(inbox_issue.created_by_id) != str(request.user.id):
return Response({"error": "You cannot delete inbox issue"}, status=status.HTTP_400_BAD_REQUEST)
# Check the inbox issue created
if project_member.role <= 10 and str(inbox_issue.created_by_id) != str(
request.user.id
):
return Response(
{"error": "You cannot delete inbox issue"},
status=status.HTTP_400_BAD_REQUEST,
)
# Check the issue status
if inbox_issue.status in [-2, -1, 0, 2]:
# Delete the issue also
Issue.objects.filter(workspace__slug=slug, project_id=project_id, pk=inbox_issue.issue_id).delete()
inbox_issue.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
class InboxIssuePublicViewSet(BaseViewSet):
serializer_class = InboxIssueSerializer
model = InboxIssue
filterset_fields = [
"status",
]
def get_queryset(self):
project_deploy_board = ProjectDeployBoard.objects.get(workspace__slug=self.kwargs.get("slug"), project_id=self.kwargs.get("project_id"))
if project_deploy_board is not None:
return self.filter_queryset(
super()
.get_queryset()
.filter(
Q(snoozed_till__gte=timezone.now()) | Q(snoozed_till__isnull=True),
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
inbox_id=self.kwargs.get("inbox_id"),
)
.select_related("issue", "workspace", "project")
)
return InboxIssue.objects.none()
def list(self, request, slug, project_id, inbox_id):
project_deploy_board = ProjectDeployBoard.objects.get(workspace__slug=slug, project_id=project_id)
if project_deploy_board.inbox is None:
return Response({"error": "Inbox is not enabled for this Project Board"}, status=status.HTTP_400_BAD_REQUEST)
filters = issue_filters(request.query_params, "GET")
issues = (
Issue.objects.filter(
issue_inbox__inbox_id=inbox_id,
workspace__slug=slug,
project_id=project_id,
)
.filter(**filters)
.annotate(bridge_id=F("issue_inbox__id"))
.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels")
.order_by("issue_inbox__snoozed_till", "issue_inbox__status")
.annotate(
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
attachment_count=IssueAttachment.objects.filter(
issue=OuterRef("id")
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.prefetch_related(
Prefetch(
"issue_inbox",
queryset=InboxIssue.objects.only(
"status", "duplicate_to", "snoozed_till", "source"
),
)
)
)
issues_data = IssueStateInboxSerializer(issues, many=True).data
return Response(
issues_data,
status=status.HTTP_200_OK,
)
def create(self, request, slug, project_id, inbox_id):
project_deploy_board = ProjectDeployBoard.objects.get(workspace__slug=slug, project_id=project_id)
if project_deploy_board.inbox is None:
return Response({"error": "Inbox is not enabled for this Project Board"}, status=status.HTTP_400_BAD_REQUEST)
if not request.data.get("issue", {}).get("name", False):
return Response(
{"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST
)
# Check for valid priority
if not request.data.get("issue", {}).get("priority", "none") in [
"low",
"medium",
"high",
"urgent",
"none",
]:
return Response(
{"error": "Invalid priority"}, status=status.HTTP_400_BAD_REQUEST
)
# Create or get state
state, _ = State.objects.get_or_create(
name="Triage",
group="backlog",
description="Default state for managing all Inbox Issues",
project_id=project_id,
color="#ff7700",
)
# create an issue
issue = Issue.objects.create(
name=request.data.get("issue", {}).get("name"),
description=request.data.get("issue", {}).get("description", {}),
description_html=request.data.get("issue", {}).get(
"description_html", "<p></p>"
),
priority=request.data.get("issue", {}).get("priority", "low"),
project_id=project_id,
state=state,
)
# Create an Issue Activity
issue_activity.delay(
type="issue.activity.created",
requested_data=json.dumps(request.data, cls=DjangoJSONEncoder),
actor_id=str(request.user.id),
issue_id=str(issue.id),
project_id=str(project_id),
current_instance=None,
epoch=int(timezone.now().timestamp())
)
# create an inbox issue
InboxIssue.objects.create(
inbox_id=inbox_id,
project_id=project_id,
issue=issue,
source=request.data.get("source", "in-app"),
)
serializer = IssueStateInboxSerializer(issue)
return Response(serializer.data, status=status.HTTP_200_OK)
def partial_update(self, request, slug, project_id, inbox_id, pk):
project_deploy_board = ProjectDeployBoard.objects.get(workspace__slug=slug, project_id=project_id)
if project_deploy_board.inbox is None:
return Response({"error": "Inbox is not enabled for this Project Board"}, status=status.HTTP_400_BAD_REQUEST)
inbox_issue = InboxIssue.objects.get(
pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id
)
# Get the project member
if str(inbox_issue.created_by_id) != str(request.user.id):
return Response({"error": "You cannot edit inbox issues"}, status=status.HTTP_400_BAD_REQUEST)
# Get issue data
issue_data = request.data.pop("issue", False)
issue = Issue.objects.get(
pk=inbox_issue.issue_id, workspace__slug=slug, project_id=project_id
)
# viewers and guests since only viewers and guests
issue_data = {
"name": issue_data.get("name", issue.name),
"description_html": issue_data.get("description_html", issue.description_html),
"description": issue_data.get("description", issue.description)
}
issue_serializer = IssueCreateSerializer(
issue, data=issue_data, partial=True
)
if issue_serializer.is_valid():
current_instance = issue
# Log all the updates
requested_data = json.dumps(issue_data, cls=DjangoJSONEncoder)
if issue is not None:
issue_activity.delay(
type="issue.activity.updated",
requested_data=requested_data,
actor_id=str(request.user.id),
issue_id=str(issue.id),
project_id=str(project_id),
current_instance=json.dumps(
IssueSerializer(current_instance).data,
cls=DjangoJSONEncoder,
),
epoch=int(timezone.now().timestamp())
)
issue_serializer.save()
return Response(issue_serializer.data, status=status.HTTP_200_OK)
return Response(issue_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def retrieve(self, request, slug, project_id, inbox_id, pk):
project_deploy_board = ProjectDeployBoard.objects.get(workspace__slug=slug, project_id=project_id)
if project_deploy_board.inbox is None:
return Response({"error": "Inbox is not enabled for this Project Board"}, status=status.HTTP_400_BAD_REQUEST)
inbox_issue = InboxIssue.objects.get(
pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id
)
issue = Issue.objects.get(
pk=inbox_issue.issue_id, workspace__slug=slug, project_id=project_id
)
serializer = IssueStateInboxSerializer(issue)
return Response(serializer.data, status=status.HTTP_200_OK)
def destroy(self, request, slug, project_id, inbox_id, pk):
project_deploy_board = ProjectDeployBoard.objects.get(workspace__slug=slug, project_id=project_id)
if project_deploy_board.inbox is None:
return Response({"error": "Inbox is not enabled for this Project Board"}, status=status.HTTP_400_BAD_REQUEST)
inbox_issue = InboxIssue.objects.get(
pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id
)
if str(inbox_issue.created_by_id) != str(request.user.id):
return Response({"error": "You cannot delete inbox issue"}, status=status.HTTP_400_BAD_REQUEST)
workspace__slug=slug, project_id=project_id, pk=issue_id
).delete()
inbox_issue.delete()
return Response(status=status.HTTP_204_NO_CONTENT)

File diff suppressed because it is too large Load Diff

View File

@@ -1,73 +1,53 @@
# Python imports
import json
# Django Imports
# Django imports
from django.db.models import Count, Prefetch, Q, F, Func, OuterRef
from django.utils import timezone
from django.db import IntegrityError
from django.db.models import Prefetch, F, OuterRef, Func, Exists, Count, Q
from django.core import serializers
from django.utils.decorators import method_decorator
from django.views.decorators.gzip import gzip_page
# Third party imports
from rest_framework.response import Response
from rest_framework import status
from sentry_sdk import capture_exception
from rest_framework.response import Response
# Module imports
from . import BaseViewSet
from .base import BaseAPIView, WebhookMixin
from plane.app.permissions import ProjectEntityPermission
from plane.db.models import (
Project,
Module,
ModuleLink,
Issue,
ModuleIssue,
IssueLink,
FileAsset,
)
from plane.api.serializers import (
ModuleWriteSerializer,
ModuleSerializer,
ModuleIssueSerializer,
ModuleLinkSerializer,
ModuleFavoriteSerializer,
IssueStateSerializer,
)
from plane.api.permissions import ProjectEntityPermission
from plane.db.models import (
Module,
ModuleIssue,
Project,
Issue,
ModuleLink,
ModuleFavorite,
IssueLink,
IssueAttachment,
IssueSerializer,
)
from plane.bgtasks.issue_activites_task import issue_activity
from plane.utils.grouper import group_results
from plane.utils.issue_filters import issue_filters
from plane.utils.analytics_plot import burndown_plot
class ModuleViewSet(BaseViewSet):
class ModuleAPIEndpoint(WebhookMixin, BaseAPIView):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions related to module.
"""
model = Module
permission_classes = [
ProjectEntityPermission,
]
def get_serializer_class(self):
return (
ModuleWriteSerializer
if self.action in ["create", "update", "partial_update"]
else ModuleSerializer
)
serializer_class = ModuleSerializer
webhook_event = "module"
def get_queryset(self):
subquery = ModuleFavorite.objects.filter(
user=self.request.user,
module_id=OuterRef("pk"),
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
)
return (
super()
.get_queryset()
.filter(project_id=self.kwargs.get("project_id"))
Module.objects.filter(project_id=self.kwargs.get("project_id"))
.filter(workspace__slug=self.kwargs.get("slug"))
.annotate(is_favorite=Exists(subquery))
.select_related("project")
.select_related("workspace")
.select_related("lead")
@@ -75,7 +55,9 @@ class ModuleViewSet(BaseViewSet):
.prefetch_related(
Prefetch(
"link_module",
queryset=ModuleLink.objects.select_related("module", "created_by"),
queryset=ModuleLink.objects.select_related(
"module", "created_by"
),
)
)
.annotate(
@@ -137,133 +119,111 @@ class ModuleViewSet(BaseViewSet):
),
)
)
.order_by("-is_favorite","-created_at")
.order_by(self.kwargs.get("order_by", "-created_at"))
)
def create(self, request, slug, project_id):
project = Project.objects.get(workspace__slug=slug, pk=project_id)
serializer = ModuleWriteSerializer(
data=request.data, context={"project": project}
def post(self, request, slug, project_id):
project = Project.objects.get(pk=project_id, workspace__slug=slug)
serializer = ModuleSerializer(
data=request.data,
context={
"project_id": project_id,
"workspace_id": project.workspace_id,
},
)
if serializer.is_valid():
if (
request.data.get("external_id")
and request.data.get("external_source")
and Module.objects.filter(
project_id=project_id,
workspace__slug=slug,
external_source=request.data.get("external_source"),
external_id=request.data.get("external_id"),
).exists()
):
module = Module.objects.filter(
project_id=project_id,
workspace__slug=slug,
external_source=request.data.get("external_source"),
external_id=request.data.get("external_id"),
).first()
return Response(
{
"error": "Module with the same external id and external source already exists",
"id": str(module.id),
},
status=status.HTTP_409_CONFLICT,
)
serializer.save()
module = Module.objects.get(pk=serializer.data["id"])
serializer = ModuleSerializer(module)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def retrieve(self, request, slug, project_id, pk):
queryset = self.get_queryset().get(pk=pk)
def patch(self, request, slug, project_id, pk):
module = Module.objects.get(
pk=pk, project_id=project_id, workspace__slug=slug
)
serializer = ModuleSerializer(
module,
data=request.data,
context={"project_id": project_id},
partial=True,
)
if serializer.is_valid():
if (
request.data.get("external_id")
and (module.external_id != request.data.get("external_id"))
and Module.objects.filter(
project_id=project_id,
workspace__slug=slug,
external_source=request.data.get("external_source", module.external_source),
external_id=request.data.get("external_id"),
).exists()
):
return Response(
{
"error": "Module with the same external id and external source already exists",
"id": str(module.id),
},
status=status.HTTP_409_CONFLICT,
)
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
assignee_distribution = (
Issue.objects.filter(
issue_module__module_id=pk,
workspace__slug=slug,
project_id=project_id,
def get(self, request, slug, project_id, pk=None):
if pk:
queryset = self.get_queryset().get(pk=pk)
data = ModuleSerializer(
queryset,
fields=self.fields,
expand=self.expand,
).data
return Response(
data,
status=status.HTTP_200_OK,
)
.annotate(first_name=F("assignees__first_name"))
.annotate(last_name=F("assignees__last_name"))
.annotate(assignee_id=F("assignees__id"))
.annotate(display_name=F("assignees__display_name"))
.annotate(avatar=F("assignees__avatar"))
.values("first_name", "last_name", "assignee_id", "avatar", "display_name")
.annotate(
total_issues=Count(
"assignee_id",
filter=Q(
archived_at__isnull=True,
is_draft=False,
),
)
)
.annotate(
completed_issues=Count(
"assignee_id",
filter=Q(
completed_at__isnull=False,
archived_at__isnull=True,
is_draft=False,
),
)
)
.annotate(
pending_issues=Count(
"assignee_id",
filter=Q(
completed_at__isnull=True,
archived_at__isnull=True,
is_draft=False,
),
)
)
.order_by("first_name", "last_name")
return self.paginate(
request=request,
queryset=(self.get_queryset()),
on_results=lambda modules: ModuleSerializer(
modules,
many=True,
fields=self.fields,
expand=self.expand,
).data,
)
label_distribution = (
Issue.objects.filter(
issue_module__module_id=pk,
workspace__slug=slug,
project_id=project_id,
)
.annotate(label_name=F("labels__name"))
.annotate(color=F("labels__color"))
.annotate(label_id=F("labels__id"))
.values("label_name", "color", "label_id")
.annotate(
total_issues=Count(
"label_id",
filter=Q(
archived_at__isnull=True,
is_draft=False,
),
),
)
.annotate(
completed_issues=Count(
"label_id",
filter=Q(
completed_at__isnull=False,
archived_at__isnull=True,
is_draft=False,
),
)
)
.annotate(
pending_issues=Count(
"label_id",
filter=Q(
completed_at__isnull=True,
archived_at__isnull=True,
is_draft=False,
),
)
)
.order_by("label_name")
def delete(self, request, slug, project_id, pk):
module = Module.objects.get(
workspace__slug=slug, project_id=project_id, pk=pk
)
data = ModuleSerializer(queryset).data
data["distribution"] = {
"assignees": assignee_distribution,
"labels": label_distribution,
"completion_chart": {},
}
if queryset.start_date and queryset.target_date:
data["distribution"]["completion_chart"] = burndown_plot(
queryset=queryset, slug=slug, project_id=project_id, module_id=pk
)
return Response(
data,
status=status.HTTP_200_OK,
)
def destroy(self, request, slug, project_id, pk):
module = Module.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
module_issues = list(
ModuleIssue.objects.filter(module_id=pk).values_list("issue", flat=True)
ModuleIssue.objects.filter(module_id=pk).values_list(
"issue", flat=True
)
)
issue_activity.delay(
type="module.activity.deleted",
@@ -275,7 +235,7 @@ class ModuleViewSet(BaseViewSet):
}
),
actor_id=str(request.user.id),
issue_id=str(pk),
issue_id=None,
project_id=str(project_id),
current_instance=None,
epoch=int(timezone.now().timestamp()),
@@ -284,25 +244,28 @@ class ModuleViewSet(BaseViewSet):
return Response(status=status.HTTP_204_NO_CONTENT)
class ModuleIssueViewSet(BaseViewSet):
class ModuleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions related to module issues.
"""
serializer_class = ModuleIssueSerializer
model = ModuleIssue
filterset_fields = [
"issue__labels__id",
"issue__assignees__id",
]
webhook_event = "module_issue"
bulk = True
permission_classes = [
ProjectEntityPermission,
]
def get_queryset(self):
return self.filter_queryset(
super()
.get_queryset()
.annotate(
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("issue"))
return (
ModuleIssue.objects.annotate(
sub_issues_count=Issue.issue_objects.filter(
parent=OuterRef("issue")
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
@@ -317,19 +280,18 @@ class ModuleIssueViewSet(BaseViewSet):
.select_related("issue", "issue__state", "issue__project")
.prefetch_related("issue__assignees", "issue__labels")
.prefetch_related("module__members")
.order_by(self.kwargs.get("order_by", "-created_at"))
.distinct()
)
@method_decorator(gzip_page)
def list(self, request, slug, project_id, module_id):
def get(self, request, slug, project_id, module_id):
order_by = request.GET.get("order_by", "created_at")
group_by = request.GET.get("group_by", False)
sub_group_by = request.GET.get("sub_group_by", False)
filters = issue_filters(request.query_params, "GET")
issues = (
Issue.issue_objects.filter(issue_module__module_id=module_id)
.annotate(
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
sub_issues_count=Issue.issue_objects.filter(
parent=OuterRef("id")
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
@@ -344,7 +306,6 @@ class ModuleIssueViewSet(BaseViewSet):
.prefetch_related("assignees")
.prefetch_related("labels")
.order_by(order_by)
.filter(**filters)
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
@@ -352,41 +313,41 @@ class ModuleIssueViewSet(BaseViewSet):
.values("count")
)
.annotate(
attachment_count=IssueAttachment.objects.filter(issue=OuterRef("id"))
attachment_count=FileAsset.objects.filter(
entity_identifier=OuterRef("id"),
entity_type="issue_attachment",
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
)
issues_data = IssueStateSerializer(issues, many=True).data
if sub_group_by and sub_group_by == group_by:
return Response(
{"error": "Group by and sub group by cannot be same"},
status=status.HTTP_400_BAD_REQUEST,
)
if group_by:
grouped_results = group_results(issues_data, group_by, sub_group_by)
return Response(
grouped_results,
status=status.HTTP_200_OK,
)
return Response(
issues_data, status=status.HTTP_200_OK
return self.paginate(
request=request,
queryset=(issues),
on_results=lambda issues: IssueSerializer(
issues,
many=True,
fields=self.fields,
expand=self.expand,
).data,
)
def create(self, request, slug, project_id, module_id):
def post(self, request, slug, project_id, module_id):
issues = request.data.get("issues", [])
if not len(issues):
return Response(
{"error": "Issues are required"}, status=status.HTTP_400_BAD_REQUEST
{"error": "Issues are required"},
status=status.HTTP_400_BAD_REQUEST,
)
module = Module.objects.get(
workspace__slug=slug, project_id=project_id, pk=module_id
)
issues = Issue.objects.filter(
workspace__slug=slug, project_id=project_id, pk__in=issues
).values_list("id", flat=True)
module_issues = list(ModuleIssue.objects.filter(issue_id__in=issues))
update_module_issue_activity = []
@@ -438,7 +399,7 @@ class ModuleIssueViewSet(BaseViewSet):
# Capture Issue Activity
issue_activity.delay(
type="module.activity.created",
requested_data=json.dumps({"modules_list": issues}),
requested_data=json.dumps({"modules_list": str(issues)}),
actor_id=str(self.request.user.id),
issue_id=None,
project_id=str(self.kwargs.get("project_id", None)),
@@ -458,9 +419,12 @@ class ModuleIssueViewSet(BaseViewSet):
status=status.HTTP_200_OK,
)
def destroy(self, request, slug, project_id, module_id, pk):
def delete(self, request, slug, project_id, module_id, issue_id):
module_issue = ModuleIssue.objects.get(
workspace__slug=slug, project_id=project_id, module_id=module_id, pk=pk
workspace__slug=slug,
project_id=project_id,
module_id=module_id,
issue_id=issue_id,
)
module_issue.delete()
issue_activity.delay(
@@ -472,67 +436,9 @@ class ModuleIssueViewSet(BaseViewSet):
}
),
actor_id=str(request.user.id),
issue_id=str(pk),
issue_id=str(issue_id),
project_id=str(project_id),
current_instance=None,
epoch=int(timezone.now().timestamp()),
)
return Response(status=status.HTTP_204_NO_CONTENT)
class ModuleLinkViewSet(BaseViewSet):
permission_classes = [
ProjectEntityPermission,
]
model = ModuleLink
serializer_class = ModuleLinkSerializer
def perform_create(self, serializer):
serializer.save(
project_id=self.kwargs.get("project_id"),
module_id=self.kwargs.get("module_id"),
)
def get_queryset(self):
return (
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(module_id=self.kwargs.get("module_id"))
.filter(project__project_projectmember__member=self.request.user)
.order_by("-created_at")
.distinct()
)
class ModuleFavoriteViewSet(BaseViewSet):
serializer_class = ModuleFavoriteSerializer
model = ModuleFavorite
def get_queryset(self):
return self.filter_queryset(
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(user=self.request.user)
.select_related("module")
)
def create(self, request, slug, project_id):
serializer = ModuleFavoriteSerializer(data=request.data)
if serializer.is_valid():
serializer.save(user=request.user, project_id=project_id)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def destroy(self, request, slug, project_id, module_id):
module_favorite = ModuleFavorite.objects.get(
project=project_id,
user=request.user,
workspace__slug=slug,
module_id=module_id,
)
module_favorite.delete()
return Response(status=status.HTTP_204_NO_CONTENT)

View File

@@ -1,255 +0,0 @@
# Python imports
from datetime import timedelta, date
# Django imports
from django.db.models import Exists, OuterRef, Q, Prefetch
from django.utils import timezone
# Third party imports
from rest_framework import status
from rest_framework.response import Response
from sentry_sdk import capture_exception
# Module imports
from .base import BaseViewSet, BaseAPIView
from plane.api.permissions import ProjectEntityPermission
from plane.db.models import (
Page,
PageBlock,
PageFavorite,
Issue,
IssueAssignee,
IssueActivity,
)
from plane.api.serializers import (
PageSerializer,
PageBlockSerializer,
PageFavoriteSerializer,
IssueLiteSerializer,
)
class PageViewSet(BaseViewSet):
serializer_class = PageSerializer
model = Page
permission_classes = [
ProjectEntityPermission,
]
search_fields = [
"name",
]
def get_queryset(self):
subquery = PageFavorite.objects.filter(
user=self.request.user,
page_id=OuterRef("pk"),
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
)
return self.filter_queryset(
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(project__project_projectmember__member=self.request.user)
.filter(Q(owned_by=self.request.user) | Q(access=0))
.select_related("project")
.select_related("workspace")
.select_related("owned_by")
.annotate(is_favorite=Exists(subquery))
.order_by(self.request.GET.get("order_by", "-created_at"))
.prefetch_related("labels")
.order_by("name", "-is_favorite")
.prefetch_related(
Prefetch(
"blocks",
queryset=PageBlock.objects.select_related(
"page", "issue", "workspace", "project"
),
)
)
.distinct()
)
def perform_create(self, serializer):
serializer.save(
project_id=self.kwargs.get("project_id"), owned_by=self.request.user
)
def create(self, request, slug, project_id):
serializer = PageSerializer(
data=request.data,
context={"project_id": project_id, "owned_by_id": request.user.id},
)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def partial_update(self, request, slug, project_id, pk):
page = Page.objects.get(pk=pk, workspace__slug=slug, project_id=project_id)
# Only update access if the page owner is the requesting user
if (
page.access != request.data.get("access", page.access)
and page.owned_by_id != request.user.id
):
return Response(
{
"error": "Access cannot be updated since this page is owned by someone else"
},
status=status.HTTP_400_BAD_REQUEST,
)
serializer = PageSerializer(page, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def list(self, request, slug, project_id):
queryset = self.get_queryset()
page_view = request.GET.get("page_view", False)
if not page_view:
return Response({"error": "Page View parameter is required"}, status=status.HTTP_400_BAD_REQUEST)
# All Pages
if page_view == "all":
return Response(PageSerializer(queryset, many=True).data, status=status.HTTP_200_OK)
# Recent pages
if page_view == "recent":
current_time = date.today()
day_before = current_time - timedelta(days=1)
todays_pages = queryset.filter(updated_at__date=date.today())
yesterdays_pages = queryset.filter(updated_at__date=day_before)
earlier_this_week = queryset.filter( updated_at__date__range=(
(timezone.now() - timedelta(days=7)),
(timezone.now() - timedelta(days=2)),
))
return Response(
{
"today": PageSerializer(todays_pages, many=True).data,
"yesterday": PageSerializer(yesterdays_pages, many=True).data,
"earlier_this_week": PageSerializer(earlier_this_week, many=True).data,
},
status=status.HTTP_200_OK,
)
# Favorite Pages
if page_view == "favorite":
queryset = queryset.filter(is_favorite=True)
return Response(PageSerializer(queryset, many=True).data, status=status.HTTP_200_OK)
# My pages
if page_view == "created_by_me":
queryset = queryset.filter(owned_by=request.user)
return Response(PageSerializer(queryset, many=True).data, status=status.HTTP_200_OK)
# Created by other Pages
if page_view == "created_by_other":
queryset = queryset.filter(~Q(owned_by=request.user), access=0)
return Response(PageSerializer(queryset, many=True).data, status=status.HTTP_200_OK)
return Response({"error": "No matching view found"}, status=status.HTTP_400_BAD_REQUEST)
class PageBlockViewSet(BaseViewSet):
serializer_class = PageBlockSerializer
model = PageBlock
permission_classes = [
ProjectEntityPermission,
]
def get_queryset(self):
return self.filter_queryset(
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(page_id=self.kwargs.get("page_id"))
.filter(project__project_projectmember__member=self.request.user)
.select_related("project")
.select_related("workspace")
.select_related("page")
.select_related("issue")
.order_by("sort_order")
.distinct()
)
def perform_create(self, serializer):
serializer.save(
project_id=self.kwargs.get("project_id"),
page_id=self.kwargs.get("page_id"),
)
class PageFavoriteViewSet(BaseViewSet):
permission_classes = [
ProjectEntityPermission,
]
serializer_class = PageFavoriteSerializer
model = PageFavorite
def get_queryset(self):
return self.filter_queryset(
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(user=self.request.user)
.select_related("page", "page__owned_by")
)
def create(self, request, slug, project_id):
serializer = PageFavoriteSerializer(data=request.data)
if serializer.is_valid():
serializer.save(user=request.user, project_id=project_id)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def destroy(self, request, slug, project_id, page_id):
page_favorite = PageFavorite.objects.get(
project=project_id,
user=request.user,
workspace__slug=slug,
page_id=page_id,
)
page_favorite.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
class CreateIssueFromPageBlockEndpoint(BaseAPIView):
permission_classes = [
ProjectEntityPermission,
]
def post(self, request, slug, project_id, page_id, page_block_id):
page_block = PageBlock.objects.get(
pk=page_block_id,
workspace__slug=slug,
project_id=project_id,
page_id=page_id,
)
issue = Issue.objects.create(
name=page_block.name,
project_id=project_id,
description=page_block.description,
description_html=page_block.description_html,
description_stripped=page_block.description_stripped,
)
_ = IssueAssignee.objects.create(
issue=issue, assignee=request.user, project_id=project_id
)
_ = IssueActivity.objects.create(
issue=issue,
actor=request.user,
project_id=project_id,
comment=f"created the issue from {page_block.name} block",
verb="created",
)
page_block.issue = issue
page_block.save()
return Response(IssueLiteSerializer(issue).data, status=status.HTTP_200_OK)

File diff suppressed because it is too large Load Diff

View File

@@ -7,30 +7,24 @@ from django.db.models import Q
# Third party imports
from rest_framework.response import Response
from rest_framework import status
from sentry_sdk import capture_exception
# Module imports
from . import BaseViewSet, BaseAPIView
from .base import BaseAPIView
from plane.api.serializers import StateSerializer
from plane.api.permissions import ProjectEntityPermission
from plane.app.permissions import ProjectEntityPermission
from plane.db.models import State, Issue
class StateViewSet(BaseViewSet):
class StateAPIEndpoint(BaseAPIView):
serializer_class = StateSerializer
model = State
permission_classes = [
ProjectEntityPermission,
]
def perform_create(self, serializer):
serializer.save(project_id=self.kwargs.get("project_id"))
def get_queryset(self):
return self.filter_queryset(
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
return (
State.objects.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(project__project_projectmember__member=self.request.user)
.filter(~Q(name="Triage"))
@@ -39,55 +33,105 @@ class StateViewSet(BaseViewSet):
.distinct()
)
def create(self, request, slug, project_id):
serializer = StateSerializer(data=request.data)
def post(self, request, slug, project_id):
serializer = StateSerializer(
data=request.data, context={"project_id": project_id}
)
if serializer.is_valid():
if (
request.data.get("external_id")
and request.data.get("external_source")
and State.objects.filter(
project_id=project_id,
workspace__slug=slug,
external_source=request.data.get("external_source"),
external_id=request.data.get("external_id"),
).exists()
):
state = State.objects.filter(
workspace__slug=slug,
project_id=project_id,
external_id=request.data.get("external_id"),
external_source=request.data.get("external_source"),
).first()
return Response(
{
"error": "State with the same external id and external source already exists",
"id": str(state.id),
},
status=status.HTTP_409_CONFLICT,
)
serializer.save(project_id=project_id)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def list(self, request, slug, project_id):
states = StateSerializer(self.get_queryset(), many=True).data
grouped = request.GET.get("grouped", False)
if grouped == "true":
state_dict = {}
for key, value in groupby(
sorted(states, key=lambda state: state["group"]),
lambda state: state.get("group"),
):
state_dict[str(key)] = list(value)
return Response(state_dict, status=status.HTTP_200_OK)
return Response(states, status=status.HTTP_200_OK)
def get(self, request, slug, project_id, state_id=None):
if state_id:
serializer = StateSerializer(self.get_queryset().get(pk=state_id))
return Response(serializer.data, status=status.HTTP_200_OK)
return self.paginate(
request=request,
queryset=(self.get_queryset()),
on_results=lambda states: StateSerializer(
states,
many=True,
fields=self.fields,
expand=self.expand,
).data,
)
def mark_as_default(self, request, slug, project_id, pk):
# Select all the states which are marked as default
_ = State.objects.filter(
workspace__slug=slug, project_id=project_id, default=True
).update(default=False)
_ = State.objects.filter(
workspace__slug=slug, project_id=project_id, pk=pk
).update(default=True)
return Response(status=status.HTTP_204_NO_CONTENT)
def destroy(self, request, slug, project_id, pk):
def delete(self, request, slug, project_id, state_id):
state = State.objects.get(
~Q(name="Triage"),
pk=pk,
pk=state_id,
project_id=project_id,
workspace__slug=slug,
)
if state.default:
return Response({"error": "Default state cannot be deleted"}, status=False)
return Response(
{"error": "Default state cannot be deleted"},
status=status.HTTP_400_BAD_REQUEST,
)
# Check for any issues in the state
issue_exist = Issue.issue_objects.filter(state=pk).exists()
issue_exist = Issue.issue_objects.filter(state=state_id).exists()
if issue_exist:
return Response(
{"error": "The state is not empty, only empty states can be deleted"},
{
"error": "The state is not empty, only empty states can be deleted"
},
status=status.HTTP_400_BAD_REQUEST,
)
state.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
def patch(self, request, slug, project_id, state_id=None):
state = State.objects.get(
workspace__slug=slug, project_id=project_id, pk=state_id
)
serializer = StateSerializer(state, data=request.data, partial=True)
if serializer.is_valid():
if (
str(request.data.get("external_id"))
and (state.external_id != str(request.data.get("external_id")))
and State.objects.filter(
project_id=project_id,
workspace__slug=slug,
external_source=request.data.get("external_source", state.external_source),
external_id=request.data.get("external_id"),
).exists()
):
return Response(
{
"error": "State with the same external id and external source already exists",
"id": str(state.id),
},
status=status.HTTP_409_CONFLICT,
)
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

View File

@@ -1,73 +0,0 @@
# Third party imports
from rest_framework.response import Response
from rest_framework import status
from sentry_sdk import capture_exception
# Module imports
from plane.api.serializers import (
UserSerializer,
IssueActivitySerializer,
UserMeSerializer,
UserMeSettingsSerializer,
)
from plane.api.views.base import BaseViewSet, BaseAPIView
from plane.db.models import (
User,
Workspace,
WorkspaceMemberInvite,
Issue,
IssueActivity,
)
from plane.utils.paginator import BasePaginator
class UserEndpoint(BaseViewSet):
serializer_class = UserSerializer
model = User
def get_object(self):
return self.request.user
def retrieve(self, request):
serialized_data = UserMeSerializer(request.user).data
return Response(
serialized_data,
status=status.HTTP_200_OK,
)
def retrieve_user_settings(self, request):
serialized_data = UserMeSettingsSerializer(request.user).data
return Response(serialized_data, status=status.HTTP_200_OK)
class UpdateUserOnBoardedEndpoint(BaseAPIView):
def patch(self, request):
user = User.objects.get(pk=request.user.id)
user.is_onboarded = request.data.get("is_onboarded", False)
user.save()
return Response({"message": "Updated successfully"}, status=status.HTTP_200_OK)
class UpdateUserTourCompletedEndpoint(BaseAPIView):
def patch(self, request):
user = User.objects.get(pk=request.user.id)
user.is_tour_completed = request.data.get("is_tour_completed", False)
user.save()
return Response({"message": "Updated successfully"}, status=status.HTTP_200_OK)
class UserActivityEndpoint(BaseAPIView, BasePaginator):
def get(self, request, slug):
queryset = IssueActivity.objects.filter(
actor=request.user, workspace__slug=slug
).select_related("actor", "workspace", "issue", "project")
return self.paginate(
request=request,
queryset=queryset,
on_results=lambda issue_activities: IssueActivitySerializer(
issue_activities, many=True
).data,
)

View File

View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class AppApiConfig(AppConfig):
name = "plane.app"

View File

@@ -0,0 +1,50 @@
# Django imports
from django.utils import timezone
from django.db.models import Q
# Third party imports
from rest_framework import authentication
from rest_framework.exceptions import AuthenticationFailed
# Module imports
from plane.db.models import APIToken
class APIKeyAuthentication(authentication.BaseAuthentication):
"""
Authentication with an API Key
"""
www_authenticate_realm = "api"
media_type = "application/json"
auth_header_name = "X-Api-Key"
def get_api_token(self, request):
return request.headers.get(self.auth_header_name)
def validate_api_token(self, token):
try:
api_token = APIToken.objects.get(
Q(
Q(expired_at__gt=timezone.now())
| Q(expired_at__isnull=True)
),
token=token,
is_active=True,
)
except APIToken.DoesNotExist:
raise AuthenticationFailed("Given API token is not valid")
# save api token last used
api_token.last_used = timezone.now()
api_token.save(update_fields=["last_used"])
return (api_token.user, api_token.token)
def authenticate(self, request):
token = self.get_api_token(request=request)
if not token:
return None
# Validate the API token
user, token = self.validate_api_token(token)
return user, token

View File

@@ -0,0 +1,14 @@
from .workspace import (
WorkSpaceBasePermission,
WorkspaceOwnerPermission,
WorkSpaceAdminPermission,
WorkspaceEntityPermission,
WorkspaceViewerPermission,
WorkspaceUserPermission,
)
from .project import (
ProjectBasePermission,
ProjectEntityPermission,
ProjectMemberPermission,
ProjectLitePermission,
)

View File

@@ -13,14 +13,15 @@ Guest = 5
class ProjectBasePermission(BasePermission):
def has_permission(self, request, view):
if request.user.is_anonymous:
return False
## Safe Methods -> Handle the filtering logic in queryset
if request.method in SAFE_METHODS:
return WorkspaceMember.objects.filter(
workspace__slug=view.workspace_slug, member=request.user
workspace__slug=view.workspace_slug,
member=request.user,
is_active=True,
).exists()
## Only workspace owners or admins can create the projects
@@ -29,6 +30,7 @@ class ProjectBasePermission(BasePermission):
workspace__slug=view.workspace_slug,
member=request.user,
role__in=[Admin, Member],
is_active=True,
).exists()
## Only Project Admins can update project attributes
@@ -37,19 +39,21 @@ class ProjectBasePermission(BasePermission):
member=request.user,
role=Admin,
project_id=view.project_id,
is_active=True,
).exists()
class ProjectMemberPermission(BasePermission):
def has_permission(self, request, view):
if request.user.is_anonymous:
return False
## Safe Methods -> Handle the filtering logic in queryset
if request.method in SAFE_METHODS:
return ProjectMember.objects.filter(
workspace__slug=view.workspace_slug, member=request.user
workspace__slug=view.workspace_slug,
member=request.user,
is_active=True,
).exists()
## Only workspace owners or admins can create the projects
if request.method == "POST":
@@ -57,6 +61,7 @@ class ProjectMemberPermission(BasePermission):
workspace__slug=view.workspace_slug,
member=request.user,
role__in=[Admin, Member],
is_active=True,
).exists()
## Only Project Admins can update project attributes
@@ -65,12 +70,12 @@ class ProjectMemberPermission(BasePermission):
member=request.user,
role__in=[Admin, Member],
project_id=view.project_id,
is_active=True,
).exists()
class ProjectEntityPermission(BasePermission):
def has_permission(self, request, view):
if request.user.is_anonymous:
return False
@@ -80,6 +85,7 @@ class ProjectEntityPermission(BasePermission):
workspace__slug=view.workspace_slug,
member=request.user,
project_id=view.project_id,
is_active=True,
).exists()
## Only project members or admins can create and edit the project attributes
@@ -88,17 +94,18 @@ class ProjectEntityPermission(BasePermission):
member=request.user,
role__in=[Admin, Member],
project_id=view.project_id,
is_active=True,
).exists()
class ProjectLitePermission(BasePermission):
def has_permission(self, request, view):
if request.user.is_anonymous:
return False
return ProjectMember.objects.filter(
workspace__slug=view.workspace_slug,
member=request.user,
project_id=view.project_id,
is_active=True,
).exists()

View File

@@ -32,15 +32,31 @@ class WorkSpaceBasePermission(BasePermission):
member=request.user,
workspace__slug=view.workspace_slug,
role__in=[Owner, Admin],
is_active=True,
).exists()
# allow only owner to delete the workspace
if request.method == "DELETE":
return WorkspaceMember.objects.filter(
member=request.user, workspace__slug=view.workspace_slug, role=Owner
member=request.user,
workspace__slug=view.workspace_slug,
role=Owner,
is_active=True,
).exists()
class WorkspaceOwnerPermission(BasePermission):
def has_permission(self, request, view):
if request.user.is_anonymous:
return False
return WorkspaceMember.objects.filter(
workspace__slug=view.workspace_slug,
member=request.user,
role=Owner,
).exists()
class WorkSpaceAdminPermission(BasePermission):
def has_permission(self, request, view):
if request.user.is_anonymous:
@@ -50,6 +66,7 @@ class WorkSpaceAdminPermission(BasePermission):
member=request.user,
workspace__slug=view.workspace_slug,
role__in=[Owner, Admin],
is_active=True,
).exists()
@@ -63,12 +80,14 @@ class WorkspaceEntityPermission(BasePermission):
return WorkspaceMember.objects.filter(
workspace__slug=view.workspace_slug,
member=request.user,
is_active=True,
).exists()
return WorkspaceMember.objects.filter(
member=request.user,
workspace__slug=view.workspace_slug,
role__in=[Owner, Admin],
is_active=True,
).exists()
@@ -78,5 +97,19 @@ class WorkspaceViewerPermission(BasePermission):
return False
return WorkspaceMember.objects.filter(
member=request.user, workspace__slug=view.workspace_slug, role__gte=10
member=request.user,
workspace__slug=view.workspace_slug,
is_active=True,
).exists()
class WorkspaceUserPermission(BasePermission):
def has_permission(self, request, view):
if request.user.is_anonymous:
return False
return WorkspaceMember.objects.filter(
member=request.user,
workspace__slug=view.workspace_slug,
is_active=True,
).exists()

View File

@@ -0,0 +1,124 @@
from .base import BaseSerializer
from .user import (
UserSerializer,
UserLiteSerializer,
ChangePasswordSerializer,
ResetPasswordSerializer,
UserAdminLiteSerializer,
UserMeSerializer,
UserMeSettingsSerializer,
)
from .workspace import (
WorkSpaceSerializer,
WorkSpaceMemberSerializer,
TeamSerializer,
WorkSpaceMemberInviteSerializer,
WorkspaceLiteSerializer,
WorkspaceThemeSerializer,
WorkspaceMemberAdminSerializer,
WorkspaceMemberMeSerializer,
WorkspaceUserPropertiesSerializer,
)
from .project import (
ProjectSerializer,
ProjectListSerializer,
ProjectDetailSerializer,
ProjectMemberSerializer,
ProjectMemberInviteSerializer,
ProjectIdentifierSerializer,
ProjectFavoriteSerializer,
ProjectLiteSerializer,
ProjectMemberLiteSerializer,
ProjectDeployBoardSerializer,
ProjectMemberAdminSerializer,
ProjectPublicMemberSerializer,
ProjectMemberRoleSerializer,
)
from .state import StateSerializer, StateLiteSerializer
from .view import (
GlobalViewSerializer,
IssueViewSerializer,
IssueViewFavoriteSerializer,
)
from .cycle import (
CycleSerializer,
CycleIssueSerializer,
CycleFavoriteSerializer,
CycleWriteSerializer,
CycleUserPropertiesSerializer,
)
from .asset import FileAssetSerializer
from .issue import (
IssueCreateSerializer,
IssueActivitySerializer,
IssueCommentSerializer,
IssuePropertySerializer,
IssueAssigneeSerializer,
LabelSerializer,
IssueSerializer,
IssueFlatSerializer,
IssueStateSerializer,
IssueLinkSerializer,
IssueLiteSerializer,
IssueSubscriberSerializer,
IssueReactionSerializer,
CommentReactionSerializer,
IssueVoteSerializer,
IssueRelationSerializer,
RelatedIssueSerializer,
IssuePublicSerializer,
)
from .module import (
ModuleWriteSerializer,
ModuleSerializer,
ModuleIssueSerializer,
ModuleLinkSerializer,
ModuleFavoriteSerializer,
ModuleUserPropertiesSerializer,
)
from .api import APITokenSerializer, APITokenReadSerializer
from .integration import (
IntegrationSerializer,
WorkspaceIntegrationSerializer,
GithubIssueSyncSerializer,
GithubRepositorySerializer,
GithubRepositorySyncSerializer,
GithubCommentSyncSerializer,
SlackProjectSyncSerializer,
)
from .importer import ImporterSerializer
from .page import (
PageSerializer,
PageLogSerializer,
SubPageSerializer,
PageFavoriteSerializer,
)
from .estimate import (
EstimateSerializer,
EstimatePointSerializer,
EstimateReadSerializer,
WorkspaceEstimateSerializer,
)
from .inbox import (
InboxSerializer,
InboxIssueSerializer,
IssueStateInboxSerializer,
InboxIssueLiteSerializer,
)
from .analytic import AnalyticViewSerializer
from .notification import NotificationSerializer, UserNotificationPreferenceSerializer
from .exporter import ExporterHistorySerializer
from .webhook import WebhookSerializer, WebhookLogSerializer
from .dashboard import DashboardSerializer, WidgetSerializer

View File

@@ -0,0 +1,28 @@
from .base import BaseSerializer
from plane.db.models import APIToken, APIActivityLog
class APITokenSerializer(BaseSerializer):
class Meta:
model = APIToken
fields = "__all__"
read_only_fields = [
"token",
"expired_at",
"created_at",
"updated_at",
"workspace",
"user",
]
class APITokenReadSerializer(BaseSerializer):
class Meta:
model = APIToken
exclude = ("token",)
class APIActivityLogSerializer(BaseSerializer):
class Meta:
model = APIActivityLog
fields = "__all__"

View File

@@ -1,8 +1,9 @@
from .base import BaseSerializer
from .base import BaseFileSerializer
from plane.db.models import FileAsset
class FileAssetSerializer(BaseSerializer):
class FileAssetSerializer(BaseFileSerializer):
class Meta:
model = FileAsset
fields = "__all__"

View File

@@ -0,0 +1,180 @@
from rest_framework import serializers
from plane.settings.storage import S3PrivateBucketStorage
class BaseSerializer(serializers.ModelSerializer):
id = serializers.PrimaryKeyRelatedField(read_only=True)
class DynamicBaseSerializer(BaseSerializer):
def __init__(self, *args, **kwargs):
# If 'fields' is provided in the arguments, remove it and store it separately.
# This is done so as not to pass this custom argument up to the superclass.
fields = kwargs.pop("fields", [])
self.expand = kwargs.pop("expand", []) or []
fields = self.expand
# Call the initialization of the superclass.
super().__init__(*args, **kwargs)
# If 'fields' was provided, filter the fields of the serializer accordingly.
if fields is not None:
self.fields = self._filter_fields(fields)
def _filter_fields(self, fields):
"""
Adjust the serializer's fields based on the provided 'fields' list.
:param fields: List or dictionary specifying which fields to include in the serializer.
:return: The updated fields for the serializer.
"""
# Check each field_name in the provided fields.
for field_name in fields:
# If the field is a dictionary (indicating nested fields),
# loop through its keys and values.
if isinstance(field_name, dict):
for key, value in field_name.items():
# If the value of this nested field is a list,
# perform a recursive filter on it.
if isinstance(value, list):
self._filter_fields(self.fields[key], value)
# Create a list to store allowed fields.
allowed = []
for item in fields:
# If the item is a string, it directly represents a field's name.
if isinstance(item, str):
allowed.append(item)
# If the item is a dictionary, it represents a nested field.
# Add the key of this dictionary to the allowed list.
elif isinstance(item, dict):
allowed.append(list(item.keys())[0])
for field in allowed:
if field not in self.fields:
from . import (
WorkspaceLiteSerializer,
ProjectLiteSerializer,
UserLiteSerializer,
StateLiteSerializer,
IssueSerializer,
LabelSerializer,
CycleIssueSerializer,
IssueFlatSerializer,
IssueRelationSerializer,
InboxIssueLiteSerializer,
)
# Expansion mapper
expansion = {
"user": UserLiteSerializer,
"workspace": WorkspaceLiteSerializer,
"project": ProjectLiteSerializer,
"default_assignee": UserLiteSerializer,
"project_lead": UserLiteSerializer,
"state": StateLiteSerializer,
"created_by": UserLiteSerializer,
"issue": IssueSerializer,
"actor": UserLiteSerializer,
"owned_by": UserLiteSerializer,
"members": UserLiteSerializer,
"assignees": UserLiteSerializer,
"labels": LabelSerializer,
"issue_cycle": CycleIssueSerializer,
"parent": IssueSerializer,
"issue_relation": IssueRelationSerializer,
"issue_inbox": InboxIssueLiteSerializer,
}
self.fields[field] = expansion[field](
many=True
if field
in [
"members",
"assignees",
"labels",
"issue_cycle",
"issue_relation",
"issue_inbox",
]
else False
)
return self.fields
def to_representation(self, instance):
response = super().to_representation(instance)
# Ensure 'expand' is iterable before processing
if self.expand:
for expand in self.expand:
if expand in self.fields:
# Import all the expandable serializers
from . import (
WorkspaceLiteSerializer,
ProjectLiteSerializer,
UserLiteSerializer,
StateLiteSerializer,
IssueSerializer,
LabelSerializer,
CycleIssueSerializer,
IssueRelationSerializer,
InboxIssueLiteSerializer,
)
# Expansion mapper
expansion = {
"user": UserLiteSerializer,
"workspace": WorkspaceLiteSerializer,
"project": ProjectLiteSerializer,
"default_assignee": UserLiteSerializer,
"project_lead": UserLiteSerializer,
"state": StateLiteSerializer,
"created_by": UserLiteSerializer,
"issue": IssueSerializer,
"actor": UserLiteSerializer,
"owned_by": UserLiteSerializer,
"members": UserLiteSerializer,
"assignees": UserLiteSerializer,
"labels": LabelSerializer,
"issue_cycle": CycleIssueSerializer,
"parent": IssueSerializer,
"issue_relation": IssueRelationSerializer,
"issue_inbox": InboxIssueLiteSerializer,
}
# Check if field in expansion then expand the field
if expand in expansion:
if isinstance(response.get(expand), list):
exp_serializer = expansion[expand](
getattr(instance, expand), many=True
)
else:
exp_serializer = expansion[expand](
getattr(instance, expand)
)
response[expand] = exp_serializer.data
else:
# You might need to handle this case differently
response[expand] = getattr(
instance, f"{expand}_id", None
)
return response
class BaseFileSerializer(DynamicBaseSerializer):
class Meta:
abstract = True # Make this serializer abstract
def to_representation(self, instance):
"""
Object instance -> Dict of primitive datatypes.
"""
response = super().to_representation(instance)
response[
"asset"
] = (
instance.asset.name
) # Ensure 'asset' field is consistently serialized
# Apply custom method to get download URL
return response

View File

@@ -0,0 +1,129 @@
# Third party imports
from rest_framework import serializers
# Module imports
from .base import BaseSerializer
from .user import UserLiteSerializer
from .issue import IssueStateSerializer
from .workspace import WorkspaceLiteSerializer
from .project import ProjectLiteSerializer
from plane.db.models import (
Cycle,
CycleIssue,
CycleFavorite,
CycleUserProperties,
)
class CycleWriteSerializer(BaseSerializer):
def validate(self, data):
if (
data.get("start_date", None) is not None
and data.get("end_date", None) is not None
and data.get("start_date", None) > data.get("end_date", None)
):
raise serializers.ValidationError(
"Start date cannot exceed end date"
)
return data
class Meta:
model = Cycle
fields = "__all__"
class CycleSerializer(BaseSerializer):
is_favorite = serializers.BooleanField(read_only=True)
total_issues = serializers.IntegerField(read_only=True)
cancelled_issues = serializers.IntegerField(read_only=True)
completed_issues = serializers.IntegerField(read_only=True)
started_issues = serializers.IntegerField(read_only=True)
unstarted_issues = serializers.IntegerField(read_only=True)
backlog_issues = serializers.IntegerField(read_only=True)
assignees = serializers.SerializerMethodField(read_only=True)
total_estimates = serializers.IntegerField(read_only=True)
completed_estimates = serializers.IntegerField(read_only=True)
started_estimates = serializers.IntegerField(read_only=True)
workspace_detail = WorkspaceLiteSerializer(
read_only=True, source="workspace"
)
project_detail = ProjectLiteSerializer(read_only=True, source="project")
status = serializers.CharField(read_only=True)
def validate(self, data):
if (
data.get("start_date", None) is not None
and data.get("end_date", None) is not None
and data.get("start_date", None) > data.get("end_date", None)
):
raise serializers.ValidationError(
"Start date cannot exceed end date"
)
return data
def get_assignees(self, obj):
members = [
{
"avatar": assignee.avatar,
"display_name": assignee.display_name,
"id": assignee.id,
}
for issue_cycle in obj.issue_cycle.prefetch_related(
"issue__assignees"
).all()
for assignee in issue_cycle.issue.assignees.all()
]
# Use a set comprehension to return only the unique objects
unique_objects = {frozenset(item.items()) for item in members}
# Convert the set back to a list of dictionaries
unique_list = [dict(item) for item in unique_objects]
return unique_list
class Meta:
model = Cycle
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"owned_by",
]
class CycleIssueSerializer(BaseSerializer):
issue_detail = IssueStateSerializer(read_only=True, source="issue")
sub_issues_count = serializers.IntegerField(read_only=True)
class Meta:
model = CycleIssue
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"cycle",
]
class CycleFavoriteSerializer(BaseSerializer):
cycle_detail = CycleSerializer(source="cycle", read_only=True)
class Meta:
model = CycleFavorite
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"user",
]
class CycleUserPropertiesSerializer(BaseSerializer):
class Meta:
model = CycleUserProperties
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"cycle" "user",
]

View File

@@ -0,0 +1,26 @@
# Module imports
from .base import BaseSerializer
from plane.db.models import Dashboard, Widget
# Third party frameworks
from rest_framework import serializers
class DashboardSerializer(BaseSerializer):
class Meta:
model = Dashboard
fields = "__all__"
class WidgetSerializer(BaseSerializer):
is_visible = serializers.BooleanField(read_only=True)
widget_filters = serializers.JSONField(read_only=True)
class Meta:
model = Widget
fields = [
"id",
"key",
"is_visible",
"widget_filters"
]

View File

@@ -2,11 +2,18 @@
from .base import BaseSerializer
from plane.db.models import Estimate, EstimatePoint
from plane.api.serializers import WorkspaceLiteSerializer, ProjectLiteSerializer
from plane.app.serializers import (
WorkspaceLiteSerializer,
ProjectLiteSerializer,
)
from rest_framework import serializers
class EstimateSerializer(BaseSerializer):
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
workspace_detail = WorkspaceLiteSerializer(
read_only=True, source="workspace"
)
project_detail = ProjectLiteSerializer(read_only=True, source="project")
class Meta:
@@ -19,6 +26,16 @@ class EstimateSerializer(BaseSerializer):
class EstimatePointSerializer(BaseSerializer):
def validate(self, data):
if not data:
raise serializers.ValidationError("Estimate points are required")
value = data.get("value")
if value and len(value) > 20:
raise serializers.ValidationError(
"Value can't be more than 20 characters"
)
return data
class Meta:
model = EstimatePoint
fields = "__all__"
@@ -31,7 +48,9 @@ class EstimatePointSerializer(BaseSerializer):
class EstimateReadSerializer(BaseSerializer):
points = EstimatePointSerializer(read_only=True, many=True)
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
workspace_detail = WorkspaceLiteSerializer(
read_only=True, source="workspace"
)
project_detail = ProjectLiteSerializer(read_only=True, source="project")
class Meta:
@@ -42,3 +61,18 @@ class EstimateReadSerializer(BaseSerializer):
"name",
"description",
]
class WorkspaceEstimateSerializer(BaseSerializer):
points = EstimatePointSerializer(read_only=True, many=True)
class Meta:
model = Estimate
fields = "__all__"
read_only_fields = [
"points",
"name",
"description",
]

View File

@@ -5,7 +5,9 @@ from .user import UserLiteSerializer
class ExporterHistorySerializer(BaseSerializer):
initiated_by_detail = UserLiteSerializer(source="initiated_by", read_only=True)
initiated_by_detail = UserLiteSerializer(
source="initiated_by", read_only=True
)
class Meta:
model = ExporterHistory

View File

@@ -7,9 +7,13 @@ from plane.db.models import Importer
class ImporterSerializer(BaseSerializer):
initiated_by_detail = UserLiteSerializer(source="initiated_by", read_only=True)
initiated_by_detail = UserLiteSerializer(
source="initiated_by", read_only=True
)
project_detail = ProjectLiteSerializer(source="project", read_only=True)
workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
workspace_detail = WorkspaceLiteSerializer(
source="workspace", read_only=True
)
class Meta:
model = Importer

View File

@@ -0,0 +1,60 @@
# Third party frameworks
from rest_framework import serializers
# Module imports
from .base import BaseSerializer
from .issue import IssueFlatSerializer, LabelLiteSerializer
from .project import ProjectLiteSerializer
from .state import StateLiteSerializer
from .user import UserLiteSerializer
from plane.db.models import Inbox, InboxIssue, Issue
class InboxSerializer(BaseSerializer):
project_detail = ProjectLiteSerializer(source="project", read_only=True)
pending_issue_count = serializers.IntegerField(read_only=True)
class Meta:
model = Inbox
fields = "__all__"
read_only_fields = [
"project",
"workspace",
]
class InboxIssueSerializer(BaseSerializer):
issue_detail = IssueFlatSerializer(source="issue", read_only=True)
project_detail = ProjectLiteSerializer(source="project", read_only=True)
class Meta:
model = InboxIssue
fields = "__all__"
read_only_fields = [
"project",
"workspace",
]
class InboxIssueLiteSerializer(BaseSerializer):
class Meta:
model = InboxIssue
fields = ["id", "status", "duplicate_to", "snoozed_till", "source"]
read_only_fields = fields
class IssueStateInboxSerializer(BaseSerializer):
state_detail = StateLiteSerializer(read_only=True, source="state")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
label_details = LabelLiteSerializer(
read_only=True, source="labels", many=True
)
assignee_details = UserLiteSerializer(
read_only=True, source="assignees", many=True
)
sub_issues_count = serializers.IntegerField(read_only=True)
issue_inbox = InboxIssueLiteSerializer(read_only=True, many=True)
class Meta:
model = Issue
fields = "__all__"

View File

@@ -1,5 +1,5 @@
# Module imports
from plane.api.serializers import BaseSerializer
from plane.app.serializers import BaseSerializer
from plane.db.models import Integration, WorkspaceIntegration
@@ -13,7 +13,9 @@ class IntegrationSerializer(BaseSerializer):
class WorkspaceIntegrationSerializer(BaseSerializer):
integration_detail = IntegrationSerializer(read_only=True, source="integration")
integration_detail = IntegrationSerializer(
read_only=True, source="integration"
)
class Meta:
model = WorkspaceIntegration

View File

@@ -1,5 +1,5 @@
# Module imports
from plane.api.serializers import BaseSerializer
from plane.app.serializers import BaseSerializer
from plane.db.models import (
GithubIssueSync,
GithubRepository,

View File

@@ -1,5 +1,5 @@
# Module imports
from plane.api.serializers import BaseSerializer
from plane.app.serializers import BaseSerializer
from plane.db.models import SlackProjectSync

View File

@@ -0,0 +1,678 @@
# Django imports
from django.utils import timezone
# Third Party imports
from rest_framework import serializers
# Module imports
from .base import BaseSerializer, DynamicBaseSerializer, BaseFileSerializer
from .user import UserLiteSerializer
from .state import StateSerializer, StateLiteSerializer
from .project import ProjectLiteSerializer
from .workspace import WorkspaceLiteSerializer
from plane.db.models import (
User,
Issue,
IssueActivity,
IssueComment,
IssueProperty,
IssueAssignee,
IssueSubscriber,
IssueLabel,
Label,
CycleIssue,
Cycle,
Module,
ModuleIssue,
IssueLink,
FileAsset,
IssueReaction,
CommentReaction,
IssueVote,
IssueRelation,
State,
Project,
)
class IssueFlatSerializer(BaseSerializer):
## Contain only flat fields
class Meta:
model = Issue
fields = [
"id",
"name",
"description",
"description_html",
"priority",
"start_date",
"target_date",
"sequence_id",
"sort_order",
"is_draft",
]
class IssueProjectLiteSerializer(BaseSerializer):
project_detail = ProjectLiteSerializer(source="project", read_only=True)
class Meta:
model = Issue
fields = [
"id",
"project_detail",
"name",
"sequence_id",
]
read_only_fields = fields
##TODO: Find a better way to write this serializer
## Find a better approach to save manytomany?
class IssueCreateSerializer(BaseSerializer):
# ids
state_id = serializers.PrimaryKeyRelatedField(
source="state",
queryset=State.objects.all(),
required=False,
allow_null=True,
)
parent_id = serializers.PrimaryKeyRelatedField(
source="parent",
queryset=Issue.objects.all(),
required=False,
allow_null=True,
)
label_ids = serializers.ListField(
child=serializers.PrimaryKeyRelatedField(queryset=Label.objects.all()),
write_only=True,
required=False,
)
assignee_ids = serializers.ListField(
child=serializers.PrimaryKeyRelatedField(queryset=User.objects.all()),
write_only=True,
required=False,
)
class Meta:
model = Issue
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
def to_representation(self, instance):
data = super().to_representation(instance)
assignee_ids = self.initial_data.get("assignee_ids")
data["assignee_ids"] = assignee_ids if assignee_ids else []
label_ids = self.initial_data.get("label_ids")
data["label_ids"] = label_ids if label_ids else []
return data
def validate(self, data):
if (
data.get("start_date", None) is not None
and data.get("target_date", None) is not None
and data.get("start_date", None) > data.get("target_date", None)
):
raise serializers.ValidationError(
"Start date cannot exceed target date"
)
return data
def create(self, validated_data):
assignees = validated_data.pop("assignee_ids", None)
labels = validated_data.pop("label_ids", None)
project_id = self.context["project_id"]
workspace_id = self.context["workspace_id"]
default_assignee_id = self.context["default_assignee_id"]
issue = Issue.objects.create(**validated_data, project_id=project_id)
# Issue Audit Users
created_by_id = issue.created_by_id
updated_by_id = issue.updated_by_id
if assignees is not None and len(assignees):
IssueAssignee.objects.bulk_create(
[
IssueAssignee(
assignee=user,
issue=issue,
project_id=project_id,
workspace_id=workspace_id,
created_by_id=created_by_id,
updated_by_id=updated_by_id,
)
for user in assignees
],
batch_size=10,
)
else:
# Then assign it to default assignee
if default_assignee_id is not None:
IssueAssignee.objects.create(
assignee_id=default_assignee_id,
issue=issue,
project_id=project_id,
workspace_id=workspace_id,
created_by_id=created_by_id,
updated_by_id=updated_by_id,
)
if labels is not None and len(labels):
IssueLabel.objects.bulk_create(
[
IssueLabel(
label=label,
issue=issue,
project_id=project_id,
workspace_id=workspace_id,
created_by_id=created_by_id,
updated_by_id=updated_by_id,
)
for label in labels
],
batch_size=10,
)
return issue
def update(self, instance, validated_data):
assignees = validated_data.pop("assignee_ids", None)
labels = validated_data.pop("label_ids", None)
# Related models
project_id = instance.project_id
workspace_id = instance.workspace_id
created_by_id = instance.created_by_id
updated_by_id = instance.updated_by_id
if assignees is not None:
IssueAssignee.objects.filter(issue=instance).delete()
IssueAssignee.objects.bulk_create(
[
IssueAssignee(
assignee=user,
issue=instance,
project_id=project_id,
workspace_id=workspace_id,
created_by_id=created_by_id,
updated_by_id=updated_by_id,
)
for user in assignees
],
batch_size=10,
)
if labels is not None:
IssueLabel.objects.filter(issue=instance).delete()
IssueLabel.objects.bulk_create(
[
IssueLabel(
label=label,
issue=instance,
project_id=project_id,
workspace_id=workspace_id,
created_by_id=created_by_id,
updated_by_id=updated_by_id,
)
for label in labels
],
batch_size=10,
)
# Time updation occues even when other related models are updated
instance.updated_at = timezone.now()
return super().update(instance, validated_data)
class IssueActivitySerializer(BaseSerializer):
actor_detail = UserLiteSerializer(read_only=True, source="actor")
issue_detail = IssueFlatSerializer(read_only=True, source="issue")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
workspace_detail = WorkspaceLiteSerializer(
read_only=True, source="workspace"
)
class Meta:
model = IssueActivity
fields = "__all__"
class IssuePropertySerializer(BaseSerializer):
class Meta:
model = IssueProperty
fields = "__all__"
read_only_fields = [
"user",
"workspace",
"project",
]
class LabelSerializer(BaseSerializer):
class Meta:
model = Label
fields = [
"parent",
"name",
"color",
"id",
"project_id",
"workspace_id",
"sort_order",
]
read_only_fields = [
"workspace",
"project",
]
class LabelLiteSerializer(BaseSerializer):
class Meta:
model = Label
fields = [
"id",
"name",
"color",
]
class IssueLabelSerializer(BaseSerializer):
class Meta:
model = IssueLabel
fields = "__all__"
read_only_fields = [
"workspace",
"project",
]
class IssueRelationSerializer(BaseSerializer):
id = serializers.UUIDField(source="related_issue.id", read_only=True)
project_id = serializers.PrimaryKeyRelatedField(
source="related_issue.project_id", read_only=True
)
sequence_id = serializers.IntegerField(
source="related_issue.sequence_id", read_only=True
)
name = serializers.CharField(source="related_issue.name", read_only=True)
relation_type = serializers.CharField(read_only=True)
class Meta:
model = IssueRelation
fields = [
"id",
"project_id",
"sequence_id",
"relation_type",
"name",
]
read_only_fields = [
"workspace",
"project",
]
class RelatedIssueSerializer(BaseSerializer):
id = serializers.UUIDField(source="issue.id", read_only=True)
project_id = serializers.PrimaryKeyRelatedField(
source="issue.project_id", read_only=True
)
sequence_id = serializers.IntegerField(
source="issue.sequence_id", read_only=True
)
name = serializers.CharField(source="issue.name", read_only=True)
relation_type = serializers.CharField(read_only=True)
class Meta:
model = IssueRelation
fields = [
"id",
"project_id",
"sequence_id",
"relation_type",
"name",
]
read_only_fields = [
"workspace",
"project",
]
class IssueAssigneeSerializer(BaseSerializer):
assignee_details = UserLiteSerializer(read_only=True, source="assignee")
class Meta:
model = IssueAssignee
fields = "__all__"
class CycleBaseSerializer(BaseSerializer):
class Meta:
model = Cycle
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
class IssueCycleDetailSerializer(BaseSerializer):
cycle_detail = CycleBaseSerializer(read_only=True, source="cycle")
class Meta:
model = CycleIssue
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
class ModuleBaseSerializer(BaseSerializer):
class Meta:
model = Module
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
class IssueModuleDetailSerializer(BaseSerializer):
module_detail = ModuleBaseSerializer(read_only=True, source="module")
class Meta:
model = ModuleIssue
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
class IssueLinkSerializer(BaseSerializer):
created_by_detail = UserLiteSerializer(read_only=True, source="created_by")
class Meta:
model = IssueLink
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
"issue",
]
# Validation if url already exists
def create(self, validated_data):
if IssueLink.objects.filter(
url=validated_data.get("url"),
issue_id=validated_data.get("issue_id"),
).exists():
raise serializers.ValidationError(
{"error": "URL already exists for this Issue"}
)
return IssueLink.objects.create(**validated_data)
class IssueReactionSerializer(BaseSerializer):
actor_detail = UserLiteSerializer(read_only=True, source="actor")
class Meta:
model = IssueReaction
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"issue",
"actor",
]
class CommentReactionSerializer(BaseSerializer):
class Meta:
model = CommentReaction
fields = "__all__"
read_only_fields = ["workspace", "project", "comment", "actor"]
class IssueVoteSerializer(BaseSerializer):
actor_detail = UserLiteSerializer(read_only=True, source="actor")
class Meta:
model = IssueVote
fields = [
"issue",
"vote",
"workspace",
"project",
"actor",
"actor_detail",
]
read_only_fields = fields
class IssueCommentSerializer(BaseSerializer):
actor_detail = UserLiteSerializer(read_only=True, source="actor")
issue_detail = IssueFlatSerializer(read_only=True, source="issue")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
workspace_detail = WorkspaceLiteSerializer(
read_only=True, source="workspace"
)
comment_reactions = CommentReactionSerializer(read_only=True, many=True)
is_member = serializers.BooleanField(read_only=True)
class Meta:
model = IssueComment
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"issue",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
class IssueStateFlatSerializer(BaseSerializer):
state_detail = StateLiteSerializer(read_only=True, source="state")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
class Meta:
model = Issue
fields = [
"id",
"sequence_id",
"name",
"state_detail",
"project_detail",
]
# Issue Serializer with state details
class IssueStateSerializer(DynamicBaseSerializer):
label_details = LabelLiteSerializer(
read_only=True, source="labels", many=True
)
state_detail = StateLiteSerializer(read_only=True, source="state")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
assignee_details = UserLiteSerializer(
read_only=True, source="assignees", many=True
)
sub_issues_count = serializers.IntegerField(read_only=True)
attachment_count = serializers.IntegerField(read_only=True)
link_count = serializers.IntegerField(read_only=True)
class Meta:
model = Issue
fields = "__all__"
class IssueSerializer(DynamicBaseSerializer):
# ids
project_id = serializers.PrimaryKeyRelatedField(read_only=True)
state_id = serializers.PrimaryKeyRelatedField(read_only=True)
parent_id = serializers.PrimaryKeyRelatedField(read_only=True)
cycle_id = serializers.PrimaryKeyRelatedField(read_only=True)
module_ids = serializers.SerializerMethodField()
# Many to many
label_ids = serializers.PrimaryKeyRelatedField(
read_only=True, many=True, source="labels"
)
assignee_ids = serializers.PrimaryKeyRelatedField(
read_only=True, many=True, source="assignees"
)
# Count items
sub_issues_count = serializers.IntegerField(read_only=True)
attachment_count = serializers.IntegerField(read_only=True)
link_count = serializers.IntegerField(read_only=True)
# is_subscribed
is_subscribed = serializers.BooleanField(read_only=True)
class Meta:
model = Issue
fields = [
"id",
"name",
"state_id",
"description_html",
"sort_order",
"completed_at",
"estimate_point",
"priority",
"start_date",
"target_date",
"sequence_id",
"project_id",
"parent_id",
"cycle_id",
"module_ids",
"label_ids",
"assignee_ids",
"sub_issues_count",
"created_at",
"updated_at",
"created_by",
"updated_by",
"attachment_count",
"link_count",
"is_subscribed",
"is_draft",
"archived_at",
]
read_only_fields = fields
def get_module_ids(self, obj):
# Access the prefetched modules and extract module IDs
return [
module
for module in obj.issue_module.values_list("module_id", flat=True)
]
class IssueLiteSerializer(DynamicBaseSerializer):
workspace_detail = WorkspaceLiteSerializer(
read_only=True, source="workspace"
)
project_detail = ProjectLiteSerializer(read_only=True, source="project")
state_detail = StateLiteSerializer(read_only=True, source="state")
label_details = LabelLiteSerializer(
read_only=True, source="labels", many=True
)
assignee_details = UserLiteSerializer(
read_only=True, source="assignees", many=True
)
sub_issues_count = serializers.IntegerField(read_only=True)
cycle_id = serializers.UUIDField(read_only=True)
module_id = serializers.UUIDField(read_only=True)
attachment_count = serializers.IntegerField(read_only=True)
link_count = serializers.IntegerField(read_only=True)
issue_reactions = IssueReactionSerializer(read_only=True, many=True)
class Meta:
model = Issue
fields = "__all__"
read_only_fields = [
"start_date",
"target_date",
"completed_at",
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
class IssuePublicSerializer(BaseSerializer):
project_detail = ProjectLiteSerializer(read_only=True, source="project")
state_detail = StateLiteSerializer(read_only=True, source="state")
reactions = IssueReactionSerializer(
read_only=True, many=True, source="issue_reactions"
)
votes = IssueVoteSerializer(read_only=True, many=True)
class Meta:
model = Issue
fields = [
"id",
"name",
"description_html",
"sequence_id",
"state",
"state_detail",
"project",
"project_detail",
"workspace",
"priority",
"target_date",
"reactions",
"votes",
]
read_only_fields = fields
class IssueSubscriberSerializer(BaseSerializer):
class Meta:
model = IssueSubscriber
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"issue",
]

View File

@@ -0,0 +1,217 @@
# Third Party imports
from rest_framework import serializers
# Module imports
from .base import BaseSerializer, DynamicBaseSerializer
from .user import UserLiteSerializer
from .project import ProjectLiteSerializer
from .workspace import WorkspaceLiteSerializer
from plane.db.models import (
User,
Module,
ModuleMember,
ModuleIssue,
ModuleLink,
ModuleFavorite,
ModuleUserProperties,
)
class ModuleWriteSerializer(BaseSerializer):
members = serializers.ListField(
child=serializers.PrimaryKeyRelatedField(queryset=User.objects.all()),
write_only=True,
required=False,
)
project_detail = ProjectLiteSerializer(source="project", read_only=True)
workspace_detail = WorkspaceLiteSerializer(
source="workspace", read_only=True
)
class Meta:
model = Module
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
def to_representation(self, instance):
data = super().to_representation(instance)
data["members"] = [str(member.id) for member in instance.members.all()]
return data
def validate(self, data):
if (
data.get("start_date", None) is not None
and data.get("target_date", None) is not None
and data.get("start_date", None) > data.get("target_date", None)
):
raise serializers.ValidationError(
"Start date cannot exceed target date"
)
return data
def create(self, validated_data):
members = validated_data.pop("members", None)
project = self.context["project"]
module = Module.objects.create(**validated_data, project=project)
if members is not None:
ModuleMember.objects.bulk_create(
[
ModuleMember(
module=module,
member=member,
project=project,
workspace=project.workspace,
created_by=module.created_by,
updated_by=module.updated_by,
)
for member in members
],
batch_size=10,
ignore_conflicts=True,
)
return module
def update(self, instance, validated_data):
members = validated_data.pop("members", None)
if members is not None:
ModuleMember.objects.filter(module=instance).delete()
ModuleMember.objects.bulk_create(
[
ModuleMember(
module=instance,
member=member,
project=instance.project,
workspace=instance.project.workspace,
created_by=instance.created_by,
updated_by=instance.updated_by,
)
for member in members
],
batch_size=10,
ignore_conflicts=True,
)
return super().update(instance, validated_data)
class ModuleFlatSerializer(BaseSerializer):
class Meta:
model = Module
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
class ModuleIssueSerializer(BaseSerializer):
module_detail = ModuleFlatSerializer(read_only=True, source="module")
issue_detail = ProjectLiteSerializer(read_only=True, source="issue")
sub_issues_count = serializers.IntegerField(read_only=True)
class Meta:
model = ModuleIssue
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
"module",
]
class ModuleLinkSerializer(BaseSerializer):
created_by_detail = UserLiteSerializer(read_only=True, source="created_by")
class Meta:
model = ModuleLink
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
"module",
]
# Validation if url already exists
def create(self, validated_data):
if ModuleLink.objects.filter(
url=validated_data.get("url"),
module_id=validated_data.get("module_id"),
).exists():
raise serializers.ValidationError(
{"error": "URL already exists for this Issue"}
)
return ModuleLink.objects.create(**validated_data)
class ModuleSerializer(DynamicBaseSerializer):
project_detail = ProjectLiteSerializer(read_only=True, source="project")
lead_detail = UserLiteSerializer(read_only=True, source="lead")
members_detail = UserLiteSerializer(
read_only=True, many=True, source="members"
)
link_module = ModuleLinkSerializer(read_only=True, many=True)
is_favorite = serializers.BooleanField(read_only=True)
total_issues = serializers.IntegerField(read_only=True)
cancelled_issues = serializers.IntegerField(read_only=True)
completed_issues = serializers.IntegerField(read_only=True)
started_issues = serializers.IntegerField(read_only=True)
unstarted_issues = serializers.IntegerField(read_only=True)
backlog_issues = serializers.IntegerField(read_only=True)
class Meta:
model = Module
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
class ModuleFavoriteSerializer(BaseSerializer):
module_detail = ModuleFlatSerializer(source="module", read_only=True)
class Meta:
model = ModuleFavorite
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"user",
]
class ModuleUserPropertiesSerializer(BaseSerializer):
class Meta:
model = ModuleUserProperties
fields = "__all__"
read_only_fields = ["workspace", "project", "module", "user"]

View File

@@ -0,0 +1,21 @@
# Module imports
from .base import BaseSerializer
from .user import UserLiteSerializer
from plane.db.models import Notification, UserNotificationPreference
class NotificationSerializer(BaseSerializer):
triggered_by_details = UserLiteSerializer(
read_only=True, source="triggered_by"
)
class Meta:
model = Notification
fields = "__all__"
class UserNotificationPreferenceSerializer(BaseSerializer):
class Meta:
model = UserNotificationPreference
fields = "__all__"

View File

@@ -6,41 +6,31 @@ from .base import BaseSerializer
from .issue import IssueFlatSerializer, LabelLiteSerializer
from .workspace import WorkspaceLiteSerializer
from .project import ProjectLiteSerializer
from plane.db.models import Page, PageBlock, PageFavorite, PageLabel, Label
class PageBlockSerializer(BaseSerializer):
issue_detail = IssueFlatSerializer(source="issue", read_only=True)
project_detail = ProjectLiteSerializer(source="project", read_only=True)
workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
class Meta:
model = PageBlock
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"page",
]
class PageBlockLiteSerializer(BaseSerializer):
class Meta:
model = PageBlock
fields = "__all__"
from plane.db.models import (
Page,
PageLog,
PageFavorite,
PageLabel,
Label,
Issue,
Module,
)
class PageSerializer(BaseSerializer):
is_favorite = serializers.BooleanField(read_only=True)
label_details = LabelLiteSerializer(read_only=True, source="labels", many=True)
label_details = LabelLiteSerializer(
read_only=True, source="labels", many=True
)
labels = serializers.ListField(
child=serializers.PrimaryKeyRelatedField(queryset=Label.objects.all()),
write_only=True,
required=False,
)
blocks = PageBlockLiteSerializer(read_only=True, many=True)
project_detail = ProjectLiteSerializer(source="project", read_only=True)
workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
workspace_detail = WorkspaceLiteSerializer(
source="workspace", read_only=True
)
class Meta:
model = Page
@@ -50,9 +40,10 @@ class PageSerializer(BaseSerializer):
"project",
"owned_by",
]
def to_representation(self, instance):
data = super().to_representation(instance)
data['labels'] = [str(label.id) for label in instance.labels.all()]
data["labels"] = [str(label.id) for label in instance.labels.all()]
return data
def create(self, validated_data):
@@ -102,6 +93,40 @@ class PageSerializer(BaseSerializer):
return super().update(instance, validated_data)
class SubPageSerializer(BaseSerializer):
entity_details = serializers.SerializerMethodField()
class Meta:
model = PageLog
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"page",
]
def get_entity_details(self, obj):
entity_name = obj.entity_name
if entity_name == "forward_link" or entity_name == "back_link":
try:
page = Page.objects.get(pk=obj.entity_identifier)
return PageSerializer(page).data
except Page.DoesNotExist:
return None
return None
class PageLogSerializer(BaseSerializer):
class Meta:
model = PageLog
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"page",
]
class PageFavoriteSerializer(BaseSerializer):
page_detail = PageSerializer(source="page", read_only=True)

View File

@@ -0,0 +1,239 @@
# Third party imports
from rest_framework import serializers
# Module imports
from .base import BaseSerializer, DynamicBaseSerializer
from plane.app.serializers.workspace import WorkspaceLiteSerializer
from plane.app.serializers.user import (
UserLiteSerializer,
UserAdminLiteSerializer,
)
from plane.db.models import (
Project,
ProjectMember,
ProjectMemberInvite,
ProjectIdentifier,
ProjectFavorite,
ProjectDeployBoard,
ProjectPublicMember,
)
class ProjectSerializer(BaseSerializer):
workspace_detail = WorkspaceLiteSerializer(
source="workspace", read_only=True
)
class Meta:
model = Project
fields = "__all__"
read_only_fields = [
"workspace",
]
def create(self, validated_data):
identifier = validated_data.get("identifier", "").strip().upper()
if identifier == "":
raise serializers.ValidationError(
detail="Project Identifier is required"
)
if ProjectIdentifier.objects.filter(
name=identifier, workspace_id=self.context["workspace_id"]
).exists():
raise serializers.ValidationError(
detail="Project Identifier is taken"
)
project = Project.objects.create(
**validated_data, workspace_id=self.context["workspace_id"]
)
_ = ProjectIdentifier.objects.create(
name=project.identifier,
project=project,
workspace_id=self.context["workspace_id"],
)
return project
def update(self, instance, validated_data):
identifier = validated_data.get("identifier", "").strip().upper()
# If identifier is not passed update the project and return
if identifier == "":
project = super().update(instance, validated_data)
return project
# If no Project Identifier is found create it
project_identifier = ProjectIdentifier.objects.filter(
name=identifier, workspace_id=instance.workspace_id
).first()
if project_identifier is None:
project = super().update(instance, validated_data)
project_identifier = ProjectIdentifier.objects.filter(
project=project
).first()
if project_identifier is not None:
project_identifier.name = identifier
project_identifier.save()
return project
# If found check if the project_id to be updated and identifier project id is same
if project_identifier.project_id == instance.id:
# If same pass update
project = super().update(instance, validated_data)
return project
# If not same fail update
raise serializers.ValidationError(
detail="Project Identifier is already taken"
)
class ProjectLiteSerializer(BaseSerializer):
class Meta:
model = Project
fields = [
"id",
"identifier",
"name",
"cover_image",
"icon_prop",
"emoji",
"description",
]
read_only_fields = fields
class ProjectListSerializer(DynamicBaseSerializer):
is_favorite = serializers.BooleanField(read_only=True)
total_members = serializers.IntegerField(read_only=True)
total_cycles = serializers.IntegerField(read_only=True)
total_modules = serializers.IntegerField(read_only=True)
is_member = serializers.BooleanField(read_only=True)
sort_order = serializers.FloatField(read_only=True)
member_role = serializers.IntegerField(read_only=True)
is_deployed = serializers.BooleanField(read_only=True)
members = serializers.SerializerMethodField()
def get_members(self, obj):
project_members = getattr(obj, "members_list", None)
if project_members is not None:
# Filter members by the project ID
return [
{
"id": member.id,
"member_id": member.member_id,
"member__display_name": member.member.display_name,
"member__avatar": member.member.avatar,
}
for member in project_members
]
return []
class Meta:
model = Project
fields = "__all__"
class ProjectDetailSerializer(BaseSerializer):
# workspace = WorkSpaceSerializer(read_only=True)
default_assignee = UserLiteSerializer(read_only=True)
project_lead = UserLiteSerializer(read_only=True)
is_favorite = serializers.BooleanField(read_only=True)
total_members = serializers.IntegerField(read_only=True)
total_cycles = serializers.IntegerField(read_only=True)
total_modules = serializers.IntegerField(read_only=True)
is_member = serializers.BooleanField(read_only=True)
sort_order = serializers.FloatField(read_only=True)
member_role = serializers.IntegerField(read_only=True)
is_deployed = serializers.BooleanField(read_only=True)
class Meta:
model = Project
fields = "__all__"
class ProjectMemberSerializer(BaseSerializer):
workspace = WorkspaceLiteSerializer(read_only=True)
project = ProjectLiteSerializer(read_only=True)
member = UserLiteSerializer(read_only=True)
class Meta:
model = ProjectMember
fields = "__all__"
class ProjectMemberAdminSerializer(BaseSerializer):
workspace = WorkspaceLiteSerializer(read_only=True)
project = ProjectLiteSerializer(read_only=True)
member = UserAdminLiteSerializer(read_only=True)
class Meta:
model = ProjectMember
fields = "__all__"
class ProjectMemberRoleSerializer(DynamicBaseSerializer):
class Meta:
model = ProjectMember
fields = ("id", "role", "member", "project")
class ProjectMemberInviteSerializer(BaseSerializer):
project = ProjectLiteSerializer(read_only=True)
workspace = WorkspaceLiteSerializer(read_only=True)
class Meta:
model = ProjectMemberInvite
fields = "__all__"
class ProjectIdentifierSerializer(BaseSerializer):
class Meta:
model = ProjectIdentifier
fields = "__all__"
class ProjectFavoriteSerializer(BaseSerializer):
class Meta:
model = ProjectFavorite
fields = "__all__"
read_only_fields = [
"workspace",
"user",
]
class ProjectMemberLiteSerializer(BaseSerializer):
member = UserLiteSerializer(read_only=True)
is_subscribed = serializers.BooleanField(read_only=True)
class Meta:
model = ProjectMember
fields = ["member", "id", "is_subscribed"]
read_only_fields = fields
class ProjectDeployBoardSerializer(BaseSerializer):
project_details = ProjectLiteSerializer(read_only=True, source="project")
workspace_detail = WorkspaceLiteSerializer(
read_only=True, source="workspace"
)
class Meta:
model = ProjectDeployBoard
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"anchor",
]
class ProjectPublicMemberSerializer(BaseSerializer):
class Meta:
model = ProjectPublicMember
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"member",
]

View File

@@ -0,0 +1,37 @@
# Module imports
from .base import BaseSerializer
from plane.db.models import State
class StateSerializer(BaseSerializer):
class Meta:
model = State
fields = [
"id",
"project_id",
"workspace_id",
"name",
"color",
"group",
"default",
"description",
"sequence",
]
read_only_fields = [
"workspace",
"project",
]
class StateLiteSerializer(BaseSerializer):
class Meta:
model = State
fields = [
"id",
"name",
"color",
"group",
]
read_only_fields = fields

View File

@@ -0,0 +1,198 @@
# Third party imports
from rest_framework import serializers
# Module import
from .base import BaseSerializer
from plane.db.models import User, Workspace, WorkspaceMemberInvite
class UserSerializer(BaseSerializer):
class Meta:
model = User
fields = "__all__"
read_only_fields = [
"id",
"created_at",
"updated_at",
"is_superuser",
"is_staff",
"last_active",
"last_login_time",
"last_logout_time",
"last_login_ip",
"last_logout_ip",
"last_login_uagent",
"token_updated_at",
"is_onboarded",
"is_bot",
"is_password_autoset",
"is_email_verified",
]
extra_kwargs = {"password": {"write_only": True}}
# If the user has already filled first name or last name then he is onboarded
def get_is_onboarded(self, obj):
return bool(obj.first_name) or bool(obj.last_name)
class UserMeSerializer(BaseSerializer):
class Meta:
model = User
fields = [
"id",
"avatar",
"cover_image",
"date_joined",
"display_name",
"email",
"first_name",
"last_name",
"is_active",
"is_bot",
"is_email_verified",
"is_managed",
"is_onboarded",
"is_tour_completed",
"mobile_number",
"role",
"onboarding_step",
"user_timezone",
"username",
"theme",
"last_workspace_id",
"use_case",
"is_password_autoset",
"is_email_verified",
]
read_only_fields = fields
class UserMeSettingsSerializer(BaseSerializer):
workspace = serializers.SerializerMethodField()
class Meta:
model = User
fields = [
"id",
"email",
"workspace",
]
read_only_fields = fields
def get_workspace(self, obj):
workspace_invites = WorkspaceMemberInvite.objects.filter(
email=obj.email
).count()
if (
obj.last_workspace_id is not None
and Workspace.objects.filter(
pk=obj.last_workspace_id,
workspace_member__member=obj.id,
workspace_member__is_active=True,
).exists()
):
workspace = Workspace.objects.filter(
pk=obj.last_workspace_id,
workspace_member__member=obj.id,
workspace_member__is_active=True,
).first()
return {
"last_workspace_id": obj.last_workspace_id,
"last_workspace_slug": workspace.slug
if workspace is not None
else "",
"fallback_workspace_id": obj.last_workspace_id,
"fallback_workspace_slug": workspace.slug
if workspace is not None
else "",
"invites": workspace_invites,
}
else:
fallback_workspace = (
Workspace.objects.filter(
workspace_member__member_id=obj.id,
workspace_member__is_active=True,
)
.order_by("created_at")
.first()
)
return {
"last_workspace_id": None,
"last_workspace_slug": None,
"fallback_workspace_id": fallback_workspace.id
if fallback_workspace is not None
else None,
"fallback_workspace_slug": fallback_workspace.slug
if fallback_workspace is not None
else None,
"invites": workspace_invites,
}
class UserLiteSerializer(BaseSerializer):
class Meta:
model = User
fields = [
"id",
"first_name",
"last_name",
"avatar",
"is_bot",
"display_name",
]
read_only_fields = [
"id",
"is_bot",
]
class UserAdminLiteSerializer(BaseSerializer):
class Meta:
model = User
fields = [
"id",
"first_name",
"last_name",
"avatar",
"is_bot",
"display_name",
"email",
]
read_only_fields = [
"id",
"is_bot",
]
class ChangePasswordSerializer(serializers.Serializer):
model = User
"""
Serializer for password change endpoint.
"""
old_password = serializers.CharField(required=True)
new_password = serializers.CharField(required=True, min_length=8)
confirm_password = serializers.CharField(required=True, min_length=8)
def validate(self, data):
if data.get("old_password") == data.get("new_password"):
raise serializers.ValidationError(
{"error": "New password cannot be same as old password."}
)
if data.get("new_password") != data.get("confirm_password"):
raise serializers.ValidationError(
{
"error": "Confirm password should be same as the new password."
}
)
return data
class ResetPasswordSerializer(serializers.Serializer):
"""
Serializer for password change endpoint.
"""
new_password = serializers.CharField(required=True, min_length=8)

Some files were not shown because too many files have changed in this diff Show More