Compare commits

..
Author SHA1 Message Date
sriram veeraghanta 1a0f912ab4 fix: cloud fixes 2023-10-30 16:50:15 +05:30
sriram veeraghanta 912851659c Merge branch 'develop' of github.com:makeplane/plane into dev/upgrade-workspace 2023-10-30 16:15:32 +05:30
NikhilandGitHub 7edaa49c21 revert: issues endpoint (#2555) 2023-10-30 15:05:25 +05:30
Dakshesh JainandGitHub 8cc61bc427 fix: html sensitization function (#2552) 2023-10-30 13:59:00 +05:30
Anmol Singh BhatiaandGitHub fc82d6fc23 style: module ui revamp (#2548)
* chore: module constant and helper function added

* style: module card ui revamp

* chore: custom media query added

* chore: circular progress indicator added

* chore: module card item ui improvement

* chore: module list view added

* chore: module sidebar added in list and card view

* chore: module list and card ui improvement

* chore: module sidebar select, avatar and link list component improvement

* chore: sidebar improvement and refactor

* style: module sidebar revamp

* style: module sidebar ui improvement

* chore: module sidebar lead and member select improvement

* style: module sidebar progress section empty state added

* chore: module card issue count validation added

* style: module card and list item ui improvement
2023-10-27 18:45:10 +05:30
080b5a29ae refactor: issue activity (#2503)
* dev: update project and workspace save in issue activity

* chore: issue activity structuring

* chore: added workspace id

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
2023-10-27 15:38:54 +05:30
Bavisetti NarayanandGitHub 9ee3fb9c6c chore: removed duplicate issues (#2418) 2023-10-27 15:37:26 +05:30
Bavisetti NarayanandGitHub b0a24ab57b fix: issue filters validation (#2417) 2023-10-27 15:36:27 +05:30
NikhilandGitHub 3e706f9653 chore: project create/update endpoint to be simillar to list (#2476)
* chore: project create endpoint to be simillar to list

* dev: make project create and update response same
2023-10-27 15:35:15 +05:30
NikhilandGitHub 4e86110123 chore: database configuration (#2497) 2023-10-27 15:34:01 +05:30
6bebb8a93b chore: user issue display properties (#2258)
* chore: user issue display properties

* chore: added issue property

* fix: migrations and url change

* dev: add a default condition on get for issue properties

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2023-10-27 15:32:42 +05:30
NikhilandGitHub c8f98a9bc2 chore: make module create and list endpoint response structure simillar (#2524) 2023-10-27 15:31:15 +05:30
55b2927a17 chore: total issues count for issue listing endpoint (#2534)
* chore: total issues count for issue listing endpoint

* dev: add print for DEBUG mode

* fix: changed assignees_list and label_list

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2023-10-27 15:30:28 +05:30
sriram veeraghanta 85e53af66c fix: merge conflicts resolved 2023-10-27 14:06:41 +05:30
sriram veeraghantaandGitHub 597ea26d7b fix: enabling the nextjs static site generation for web and space (#2547) 2023-10-27 13:14:03 +05:30
Dakshesh JainandGitHub 4aad35e007 refactor: quick add (#2541)
* refactor: store and helper setup for quick-add

* refactor: kanban quick add with optimistic issue create

* refactor: added function definition

* refactor: list quick add with optimistic issue create

* refactor: spreadsheet quick add with optimistic issue create

* refactor: calender quick add with optimistic issue create

* refactor: gantt quick add with optimistic issue create

* refactor: input component and pre-loading data logic

* style: calender quick-add height and content shift

* feat: sub-group quick-add issue

* feat: displaying loading state when issue is being created

* fix: setting string null to null
2023-10-27 12:32:24 +05:30
Aaryan KhandelwalandGitHub d95ea463b2 chore: implement mobx in project features settings (#2533) 2023-10-26 14:58:00 +05:30
Henit ChobisaandGitHub 993b388f00 chore: added missing dependencies in packages (#2540) 2023-10-26 12:37:24 +05:30
sriram veeraghanta 6f751fd0a4 fix: integration fixes 2023-10-25 21:01:23 +05:30
Aaryan KhandelwalandGitHub a49f00bd39 chore: refactor and beautify issue properties (#2539)
* chore: update all issue property components

* style: issue properties
2023-10-25 19:47:58 +05:30
guru_sainathandGitHub ca2da41dd2 chore: issue comment reaction workflow and mutation update (#2537) 2023-10-25 19:24:14 +05:30
guru_sainathandGitHub a6d741e784 chore: implemented drag and drop between dates for project issues, cycle, module, and project views for calendar layout (#2535) 2023-10-25 16:09:50 +05:30
Anmol Singh BhatiaandGitHub cea39c758e chore: layout refactor (#2532)
* chore: layout refactor

* fix: profile auth issue

* chore: project setting layout refactor

* chore: workspace layout refactor

* chore: profile layout refactor

* chore: layout import refactor
2023-10-25 15:48:57 +05:30
sriramveeraghanta 67c0b73260 feat: Workspace upgrade functionality 2023-10-25 11:58:40 +05:30
Aaryan KhandelwalandGitHub d72d3da6de refactor: modules list page (#2521)
* refactor: modules list page

* chore: update handle favorites logic for modules

* fix: build errors
2023-10-23 19:17:42 +05:30
sriram veeraghantaandGitHub 07d548ea43 fix: cycle modal redendent component fix (#2528) 2023-10-23 18:38:01 +05:30
Anmol Singh BhatiaandGitHub 08f7ac6da7 chore: sidebar fix and ui improvement (#2527)
* fix: cycle sidebar overlapping fix

* style: issue sidebar related to and duplicate icon fix
2023-10-23 18:07:38 +05:30
Anmol Singh BhatiaandGitHub fcf9851ee4 style: command modal style (#2526) 2023-10-23 17:40:59 +05:30
Aaryan KhandelwalandGitHub f6c1dad342 fix: extend custom tailwind valiables instead of overwriting (#2525) 2023-10-23 17:40:17 +05:30
Anmol Singh BhatiaandGitHub 4c54d826ba fix: command palette fix (#2523) 2023-10-23 17:24:43 +05:30
Anmol Singh BhatiaandGitHub 1786a395dc chore: layout refactor (#2522)
* chore: pages layout refactor

* chore: view layout refactor

* chore: view layout refactor

* chore: inbox layout refactor

* chore: draft issue layout refactor

* chore: archived issue layout refactor

* chore: draft issue header layout fix

* chore: layout code refactor

* chore: code refactor

* chore: project setting layout fix
2023-10-23 16:54:26 +05:30
d7a36f5b04 dev: Self Hosting simplified with shell script (#2484)
* bug:fix recent page hiding last item on scroll #1468 (#2411)

* 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>

* selfhosting fixes

* wip

* checking selfhost

* checking selfhost

* selfhost

* selfhosting script fix

* wip

* self hosting fixes

* folder structure modified

* replica config

* self host specific version

* fix

* fix

* fixes

* docker compose modifications

* fixed install.sh

* fixed install.sh

* install.sh modified

---------

Co-authored-by: Prashant Indurkar <32466796+PrashantIndurkar@users.noreply.github.com>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
2023-10-23 15:56:19 +05:30
Aaryan KhandelwalandGitHub edc5a38973 chore: removed material icons package (#2518) 2023-10-23 15:07:09 +05:30
Aaryan KhandelwalandGitHub 38421e8106 refactor: layout roots (#2517) 2023-10-23 15:06:28 +05:30
Aaryan KhandelwalandGitHub 05a76c5ee3 fix: gantt chart blocks drag and resize logic (#2516) 2023-10-23 15:06:02 +05:30
Aaryan KhandelwalandGitHub c739b7235d refactor: publish project modal (#2514)
* chore: add publish badge to the header

* refactor: project oublish components

* chore: remove link tag
2023-10-23 12:12:42 +05:30
guru_sainathandGitHub 914657334d chore: peekoverview issue comments and comment reactions (#2507) 2023-10-20 17:55:20 +05:30
Aaryan KhandelwalandGitHub 4b03802d22 chore: removed heroicons package (#2506) 2023-10-20 17:51:01 +05:30
9f1fd2327a fix: auth redirection issue fixes when user is logged in (#2499)
* fix: auth redirection issues

* fix: redirect flickering fix

* chore: sign in page ui improvement and redirection fix (#2501)

* style: sign in page ui improvement

* chore: sign up redirection added and ui improvement

* chore: redirection validation and create workspace form fix (#2504)

* chore: sign in redirection validation

* fix: create workspace form input fix

* chore: code refactor

---------

Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
2023-10-20 17:10:17 +05:30
Aaryan KhandelwalandGitHub d78b4dccf3 chore: implemented CRUD operations in all the layouts (#2505)
* chore: basic crud operations added to the list view

* refactor: cycle details page

* refactor: module details page

* chore: added quick actions to kanban issue block

* chore: implement quick actions in calendar layout

* fix: custom menu component

* chore: separate quick action dropdowns implemented

* style: loader for calendar

* fix: build errors
2023-10-20 17:07:46 +05:30
guru_sainathandGitHub 9bddd2eb67 chore: impleted store for issue reaction, comments, and comment reactions. implemented ui and compoennts for issue reactions in issue peek overview (#2498) 2023-10-20 12:33:39 +05:30
Anmol Singh BhatiaandGitHub 2dd46be287 fix: create workspace page improvement (#2495) 2023-10-20 12:29:18 +05:30
Anmol Singh BhatiaandGitHub 1b8f6e2129 fix: project page link error fix (#2494) 2023-10-19 18:09:25 +05:30
Aaryan KhandelwalandGitHub 5f014d204c fix: workspaces list not being fetched, display properties endpoint updated (#2493)
* fix: workspaces list not being fetched

* style: truncate workspace name if length exceeds
2023-10-19 17:24:10 +05:30
Anmol Singh BhatiaandGitHub fda0a6791f style: project select dropdown & modals ui improvement (#2491)
* style: project select dropdown ui improvement

* style: create/update cycle modal ui improvement

* style: create/update module modal ui improvement

* fix: build error

* style: input and textarea border improvement

* cycle and module modal ui improvement

* style: cycle and module modal ui improvement
2023-10-19 16:38:36 +05:30
guru_sainathandGitHub 082e48c9cf chore: Issue poprties options in kanban are passing through props (#2490) 2023-10-19 16:23:30 +05:30
Anmol Singh BhatiaandGitHub c0793ec8a5 chore: swap dropdown component with plane/ui component (#2480)
* chore: swap custom menu component with plane/ui component

* chore: swap custom select component with plane/ui component

* chore: swap custom search select component with plane/ui component
2023-10-19 15:24:51 +05:30
Aaryan KhandelwalandGitHub 0b8367a262 fix: issues mutation on changing filters (#2485)
* chore: refetch issues on filters and display filters change

* fix: issues list mutation after creating an issue

* fix: module issues fetch

* fix: build error
2023-10-19 15:04:22 +05:30
Aaryan KhandelwalandGitHub 85a471305a fix: inbox issue activity not being rendered (#2487) 2023-10-19 14:53:29 +05:30
Aaryan KhandelwalandGitHub 861ff4ae94 chore: updated display properties endpoint (#2486) 2023-10-19 14:53:01 +05:30
Anmol Singh BhatiaandGitHub 3a44d4bf35 chore: bulk delete modal alignment fix (#2483) 2023-10-19 14:02:42 +05:30
15f621ad91 fix: command palette fixes and sidebar fixes (#2482)
* fix: project fav changes

* fix: project create workspace member

* style: member select dropdown ui and command k modal alignment fix (#2473)

* style: member select dropdown fix

* style: command k modal alignment fix

* fix: project create modal changes

* fix: sidebar shortcut fixes

* fix: minor console issues

---------

Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
2023-10-18 20:29:56 +05:30
guru_sainathandGitHub 9b96e297b3 chore: created new issue peek overview component and implemented in project issues list view (#2481)
* chore: created new issue peek overview component and implemented in project issues list view

* build: default project props in project, cycles, modules and view layout
2023-10-18 19:58:05 +05:30
Aaryan KhandelwalandGitHub 704fe155af chore: fetch cycles, modules and views in the project wrapper (#2479) 2023-10-18 19:26:15 +05:30
Aaryan KhandelwalandGitHub b3b79c51bb refactor: spreadsheet layout (#2478)
* refactor: spreadsheet column components

* refactor: spreadsheet layout components

* fix: unique key for each cell
2023-10-18 19:18:01 +05:30
Anmol Singh BhatiaandGitHub c270c8689f chore: settings page refactoring (#2477)
* chore: implemented project layout and integrated sidebar component

* chore: implemented workspace layout and integrated sidebar component
2023-10-18 19:17:02 +05:30
Aaryan KhandelwalandGitHub 0ec0ca133a refactor: remove unused hooks (#2474)
* chore: remove useProjects hook

* chore: remove useWorkspaces hook

* chore: remove useWorkspaceDetails hook

* chore: remove useTheme hook
2023-10-18 16:59:53 +05:30
Anmol Singh BhatiaandGitHub 98367f540c fix: svg icon properties naming convention fix (#2475) 2023-10-18 16:59:15 +05:30
Aaryan KhandelwalandGitHub b4f32ced43 chore: add emoji to projects list (#2472) 2023-10-18 14:55:26 +05:30
Aaryan KhandelwalandGitHub f54a9502f8 fix: global view filters dropdown overflow issue (#2469)
* fix: global view filters dropdown overflow issue

* chore: rename View to Display

* fix: gap between dropdowns in header
2023-10-18 13:56:52 +05:30
Aaryan KhandelwalandGitHub baa2621fe2 chore: implemented autorun for inbox issues (#2470)
* fix: inbox issues not loading

* chore: implemented autorun for inbox issues

* chore: don't revalidate inbox list
2023-10-18 13:56:19 +05:30
Aaryan KhandelwalandGitHub 3197dd484c dev: implemented the new spreadsheet layout using MobX (#2463)
* refactor: spreadsheet layout components

* refactor: spreadsheet properties

* refactor: folder structure

* chore: issue property update

* chore: spreadsheet layout in the global views

* style: quick actions menu

* fix: build errors
2023-10-18 12:32:02 +05:30
Anmol Singh BhatiaandGitHub e9cc578cca style: project card ui improvement (#2466)
* style: project card ui improvement

* style: project card ui improvement
2023-10-17 20:43:25 +05:30
sriram veeraghantaandGitHub 90776237f3 fix: command palette changes (#2465) 2023-10-17 20:34:16 +05:30
Anmol Singh BhatiaandGitHub d689c63368 style: create/update issue modal ui improvement (#2453) 2023-10-17 17:26:40 +05:30
Anmol Singh BhatiaandGitHub 4b51d9ed6c style: project list and setting page ui improvement (#2464) 2023-10-17 17:25:32 +05:30
Anmol Singh BhatiaandGitHub b0c1af2b25 chore: breadcrumbs ui component (#2458)
* chore: swap breadcrumbs component with plane/ui component

* chore: breadcrumb refactor
2023-10-17 17:25:02 +05:30
guru_sainathandGitHub 123634f5e8 chore: implemented assigned profiles issues and filters and updated workflow in list layout (#2462) 2023-10-17 16:23:54 +05:30
Aaryan KhandelwalandGitHub 4bd73630d1 fix: font sizes and sidebar icons (#2461)
* fix: font sizes

* fix: sidebar icons
2023-10-17 15:59:11 +05:30
Aaryan KhandelwalandGitHub 732e33fefc fix: wrap the project auth wrapper with observer (#2459) 2023-10-17 15:30:17 +05:30
Anmol Singh BhatiaandGitHub a49fedf69e chore: popper js integration for color picker input (#2460) 2023-10-17 15:28:09 +05:30
Anmol Singh BhatiaandGitHub 399af30b9a chore: icon improvement (#2456)
* style: app sidebar icon improvement

* style: profile section icon improvement

* style: notification popover icon improvement

* style: shortcut modal icon improvement
2023-10-17 15:27:38 +05:30
0b79f8687e refactor: exception handling (#2454)
* chore: implemented global exception handler

* dev: remove something went wrong

* chore: exception handling cleanup

* chore: changed the status code

* chore: added status 500 internal server error

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
2023-10-17 14:38:06 +05:30
Bavisetti NarayanandGitHub 46d34263f0 chore: removed notification for draft issues (#2426) 2023-10-17 13:07:40 +05:30
sriram veeraghantaandGitHub 98b1a078de fix: project layout added and theming fixes (#2455)
* fix: project layout added and theme fixes

* feat: input color picker component added to ui package

* fix: layout fixes

* fix: conflicts and build issues resolved

* fix: layout headers fixes
2023-10-17 12:46:38 +05:30
Anmol Singh BhatiaandGitHub e496cec49f chore: code refactor (#2450) 2023-10-17 12:19:34 +05:30
Aaryan KhandelwalandGitHub fc99615875 fix: sidebar collapse state not working (#2452) 2023-10-17 12:18:36 +05:30
Anmol Singh BhatiaandGitHub 8cd13e94d5 fix: profile pic dimension bug fix (#2449) 2023-10-17 12:15:46 +05:30
Anmol Singh BhatiaandGitHub 7f06d5a30d style: project list page revamp (#2448) 2023-10-17 12:14:55 +05:30
Aaryan KhandelwalandGitHub 5a4e9f42f0 chore: scale down tailwind default spacing and font sizes to 90% (#2451) 2023-10-17 11:55:00 +05:30
Lakhan BahetiandGitHub ceb878e72f fix: webview redirection logs & description rendering (#2433)
* fix: redirection console logs

* fix: sub issues overflow

* fix: cycle bottom sheet typo

* fix: description rendering

* fix: formatting
2023-10-16 20:27:55 +05:30
Anmol Singh BhatiaandGitHub 651b252c23 chore: icon revamp and refactor (#2447)
* chore: svg icons added in plane/ui package

* chore: swap priority and state icon with plane/ui icons

* chore: replace core folder icons with lucide and plane ui icons

* style: priority icon size

* chore: replace icons with lucide and plane/ui icons

* chore: replace cycle folder icons with lucide and plane/ui icons

* chore: replace existing icons with lucide and plane/ui icons

* chore: replace existing icons with lucide and plane/ui icons

* chore: replace existing icons with lucide and plane/ui icons

* chore: replace existing icons with lucide and plane/ui icons

* chore: replace existing icons with lucide and plane/ui icons

* fix: build error

* fix: build error

* fix: build error
2023-10-16 20:27:22 +05:30
1fc5d2bd45 chore: api endpoints (#2407)
* refactor: folder structure for urls

* chore: deleted the urls file

* chore: proper naming for urls

* chore: reset password url

* dev: create refresh token endpoint and endpoint to get settings for user

* dev: workspace member me serializer

* dev: remove extra fields from project list and retrieve endpoints

* dev: update the project list endpoint with member details and deploy boolean

* dev: enable user favorite project endpoint and remove is_favorite from project list

* dev: analytics refactoring

* dev: revert is_favorite settings

* dev: create new serializer for project list and add pagination from projects

* dev: fix analytics api

* dev: module and cycle

* dev:  update error message, fix module analytics and add null check for labels

* dev: member serializer

* dev: dynamic base serializer

* dev: remove view issues endpoint

* dev: url pattern updates

* dev: add comments to delete this file

* dev: last workspace id

* dev: analytics export

* dev: export analytics validation

* dev: update python runtime

* dev: update notification endpoints

* dev: cycle and validation fix

* dev: issue activity validation when creating updating and deleting issue and comments

* dev: update issue activity logging for link and reactions

* dev: update module issue activity logging

* dev: update module issue activity

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2023-10-16 19:18:45 +05:30
+8 cb80c98413 dev: implemented inbox using MobX (#2440)
* chore: kanban refactoring

* chore: Implemented new kanaban board UX and implemented draggable using react beautiful dnd

* chore: updated yarn lock

* chore: updated the store for issues and issue filters

* chore: resolved build error

* chore: created filters and updated the issue filters, display_filter and display_properties in mobx and components

* chore: implemented filters for issues

* chore: UI theming updates

* chore: handled single and multi select in filter cards

* chore: implemented filters and views in kanaban

* chore: updating filters, display_filter and display properties

* chore: filter, layout, display filters, extra filters and display properties render validation

* chore: clean up and resolved import warnings

* chore: type check

* chore: renamed gantt key to gantt_chart

* chore: filter render UI and Functionality implementation

* chore: filter empty state handling in issue filter selection

* Implementing list view

* chore: kanban drag drop logic

* filtering

* chore: store setup

* chore: handled build issues

* chore: store setup

* user filter

* chore: store setup

* chore: store fixes and static data setup

* chore: store setup for build fixes

* fix: merge conflicts (#2231)

* chore: dynamic position dropdown (#2138)

* chore: dynamic position state dropdown for issue view

* style: state select dropdown styling

* fix: state icon attribute names

* chore: state select dynamic dropdown

* chore: member select dynamic dropdown

* chore: label select dynamic dropdown

* chore: priority select dynamic dropdown

* chore: label select dropdown improvement

* refactor: state dropdown location

* chore: dropdown improvement and code refactor

* chore: dynamic dropdown hook type added

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>

* fix: fields not getting selected in the create issue form (#2212)

* fix: hydration error and draft issue workflow

* fix: build error

* fix: properties getting de-selected after create, module & cycle not getting auto-select on the form

* fix: display layout, props being updated directly

* chore: sub issues count in individual issue (#2221)

* fix: service imports

* chore: rename csv service file

---------

Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com>

* chore: store fixes

* chore: update issue detail store to handle peek overview (#2237)

* chore: dynamic position dropdown (#2138)

* chore: dynamic position state dropdown for issue view

* style: state select dropdown styling

* fix: state icon attribute names

* chore: state select dynamic dropdown

* chore: member select dynamic dropdown

* chore: label select dynamic dropdown

* chore: priority select dynamic dropdown

* chore: label select dropdown improvement

* refactor: state dropdown location

* chore: dropdown improvement and code refactor

* chore: dynamic dropdown hook type added

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>

* fix: fields not getting selected in the create issue form (#2212)

* fix: hydration error and draft issue workflow

* fix: build error

* fix: properties getting de-selected after create, module & cycle not getting auto-select on the form

* fix: display layout, props being updated directly

* chore: sub issues count in individual issue (#2221)

* Implemented nested issues in the sub issues section in issue detail page (#2233)

* feat: subissues infinte level

* feat: updated UI for sub issues

* feat: subissues new ui and nested sub issues in issue detail

* chore: removed repeated code

* refactor: product updates modal layout (#2225)

* fix: handle no issues in custom analytics (#2226)

* fix: activity label color (#2227)

* fix: profile issues layout switch (#2228)

* chore: update service imports

* chore: update issue detail store to handle peek overview

---------

Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com>
Co-authored-by: guru_sainath <gurusainath007@gmail.com>

* chore: minor fixes

* workspace project fixes

* feat: project issues topbar (#2256)

* chore: project issues topbar

* style: theming and minor UI fixes

* refactor: file structure

* chore: layout wise authorization added

* style: filter dropdowns

* chore: add fetch keys

* chore: minor fixes

* chore: filters dropdown (#2260)

* chore: project issues topbar

* style: theming and minor UI fixes

* refactor: file structure

* chore: layout wise authorization added

* style: filter dropdowns

* chore: add fetch keys

* feat: search option for filters

* fix: sticky headers

* chore: sub_group_by section added

* fix: leave project fixes

* refactor: project card component refactor

* Implemented swimlanes and kanban view  (#2262)

* chore: issue store for kanban and calendar

* chore: updated ui for kanba and swimlanes

* chore: yarn.lock updated

* fix: computed filters logic

* chore: added sub_group_by in params and handled sub-group-by render error in display filter's

* fix: ui package setup and project update form refactor

* fix: ui package setup

* fix: minor ui fixes

* dev: calendar view layout revamp (#2293)

* dev: calendar view init

* chore: new render logic

* chore: implement calendar view

* chore: calendar view

* refactor: calendar payload

* chore: remove active month logic from backend

* chore: setup new store for calendar

* refactor: issues fetching structure

* chore: months dropdown

* chore: modify request query params for calendar layout

* refactor: remove console logs and add comments

* chore: removed demo m-store routes

* cycles changes

* chore: issues grouped kanban and swimlanes UI and functionality (#2294)

* chore: updated the all the group_by and sub_group_by UI and functionality render in kanban

* chore: kanban sorting in mobx and ui updates

* chore: ui changes and drag and drop functionality changes in kanban

* chore: issues count render in kanban default and swimlanes

* chore: Added icons to the group_by and sub_group_by in kanban and swimlanes

* refactor: filter components, constants and helper functions (#2297)

* refactor: filters and display filters to accept handlers as props

* refactor: filters and display filters folder structure

* refactor: change issue layout options constant structure

* chore: display filters validations

* chore: view less filters functionality

* fix: display filters validation

* refactor: wrap functions around useCallback

* chore: start and target date filter options added

* refactor: query params generator function

* fix: query params generator function

* dev: gantt chart implementation using MobX (#2302)

* dev: fetch project gantt issues using mobx

* chore: handle group by options in the kanban layout

* dev: spreadsheet layout implementation using MobX (#2306)

* dev: implement spreadsheet view using mobx

* refactor: remove console logs and props

* chore: refactoring cycles list

* feat: adding additional ui components

* dev: applied filters list implementation using MobX (#2325)

* dev: applied filters list UI

* fix: filter item height

* chore: remove unnecessary classes

* fix: params generator

* fix: cycles views list and board

* fix: cycles list rendering fixes

* fix: layout fixes

* refactor: filter components (#2359)

* fix: calendar layout dividers

* refactor: filter selection components

* fix: dropdown closing after selection

* refactor: filters components

* chore: issue properties for list and kanban layouts and implemented estimates in project store (#2363)

* chore: issue properties for state, priorit, labels and members

* feat: implemented assignee, labels properties

* fix: implemented estimates in project store and issue properties

* chore: staer_date and due_date and validation properties in kanban

* chore: filters import conflict

* dev: setup module and module filter store (#2364)

* dev: implement module issues using mobx store

* dev: module filter store setup

* chore: module store crud operations

* chore: issue list layout (#2367)

* chore: merge develop (#2388)

* fix: build erros

* chore: cycles, modules store integration, list and kanban layouts and updated kanban logic (#2399)

* chore: cycle, cycle-issue, cycle-filters, cycle-kanban, cycle layout setup

* chore: cycles kanban and list view store

* chore: cycles, modules kanban and list, kanban view store

* refactor: change naming convention (#2383)

* fix:auth layer revamp

* chore: Implemented list and kanban views in project modules (#2402)

* chore: updated kanban logic in project cycles and modules

* chore: updated list and kanban in module

* dev: implement global views using MobX (#2404)

* fix: selfhosted fixes (#2154)

* fix: selfhosted fixes

* fix: updated env example

* chore: dynamic position dropdown (#2138)

* chore: dynamic position state dropdown for issue view

* style: state select dropdown styling

* fix: state icon attribute names

* chore: state select dynamic dropdown

* chore: member select dynamic dropdown

* chore: label select dynamic dropdown

* chore: priority select dynamic dropdown

* chore: label select dropdown improvement

* refactor: state dropdown location

* chore: dropdown improvement and code refactor

* chore: dynamic dropdown hook type added

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>

* fix: fields not getting selected in the create issue form (#2212)

* fix: hydration error and draft issue workflow

* fix: build error

* fix: properties getting de-selected after create, module & cycle not getting auto-select on the form

* fix: display layout, props being updated directly

* chore: sub issues count in individual issue (#2221)

* Implemented nested issues in the sub issues section in issue detail page (#2233)

* feat: subissues infinte level

* feat: updated UI for sub issues

* feat: subissues new ui and nested sub issues in issue detail

* chore: removed repeated code

* refactor: product updates modal layout (#2225)

* fix: handle no issues in custom analytics (#2226)

* fix: activity label color (#2227)

* fix: profile issues layout switch (#2228)

* fix: issues resolved in sub issues (#2238)

* fix: aws region name (#2234)

* chore: updated docker naming conventions (#2239)

* naming convention changes

* dev: update docker-compose-hub in consistent with docker-compose

* dev: updated docker container name

---------

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

* chore: added state and priority order in workspace user profile (#2241)

* fix: changed priority from None to none (#2229)

* fix: cycle and module stats when issues are archived (#2185)

* fix: cycle and module stats when issues are archived

* fix: added draft filter

---------

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

* feat: quick add  (#2240)

* feat: quick add

* style: made text color muted

* chore: added epoch in draft (#2244)

* chore: added epoch in draft

* chore: removed extra spaces

* fix: resolved pending issue graph in analytics, user wishes in dashboard, and typo in projects list (#2247)

* style: settings page improvement (#2211)

* style: settings page improvement

* style: toggle switch styling

---------

Co-authored-by: Anmol Singh Bhatia <asb@Anmols-MacBook-Pro.local>

* chore: changed priority props in workspace and project (#2253)

* fix: bug fix related to fetching dropdown options for the profile issue (#2246)

* fix: sub issue state and member select build error (#2254)

* rename view to layout (#2255)

Co-authored-by: Your Name <you@example.com>

* fix: bug fixes and ui improvement (#2250)

* dev: remove auto filter endpoint

* feat: quick-add placement in spreadsheet and gantt  (#2259)

* feat: sticking quick-add at the bottom of the screen

fix: opening create issue modal instead of quick-add in draft-issues, my-issue and profile page

* fix: build error due to dynamic import

* fix: draft issue delete not working (#2249)

* fix: draft issue not deleting, project can't be changed in draft issue modal

* fix: removed mutation for view where draft issues are not shown

* fix: inline create issue for draft issue

* fix: clearing data from localstorage on discard click

* feat: Add peek overview in sub issues and updated UI for empty states. (#2263)

* chore: add tooltip to show full time on activity logs (#2235)

* fix: issue automation iterable error (#2208)

* fix: n+1 queries for cycle list and project member endpoints (#2257)

* [fix] nginx continuously rewriting and reloading on index page of spaces app  (#2236)

* chore: shifted index page to /home route

* chore: added rewrite logic, to rewrite index to /home

* chore: routed home to login route as login page

* chore: updated nginx config to route to login

* chore: updated path for home

* dev: migration for 0.13 (#2266)

* dev: updated migrations

* dev: migration for 0.13

* dev: re-split migrations into two different files (#2268)

* dev: split issue activity migration separate files

* dev: resplit migrations into two different files

* dev: changed the batch size

* chore: udpate date filters to support dynamic options

* fix: bugs in quick-add and draft issues (#2269)

* fix: 'Last Drafted Issue' making sidebar look weird on collapsed

* feat: scroll to the bottom when issue is created

* fix: 'Add Issue' button overlapping issue card in spreadsheet view

* fix: wrong placement of quick-add in calender layout

* fix: spacing for issue card in spreadsheet view

* chore: add instructions to contributing guide (#2270)

* chore: add instructions to contributing guide

* dev: update contributing.md to use the new configuration

---------

Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com>

* fix: user dashboard greeting timezone (#2267)

* chore: user greeting timezone

* fix: group by labels not working on workspace level

* feat: workspace global view, style: spreadsheet view revamp (#2273)

* chore: workspace view types, services and hooks added

* style: spreadsheet view revamp and code refactor

* feat: workspace view

* fix: build fix

* chore: sidebar workspace issues redirection updated

* style: gantt layout quick-add padding (#2272)

* fix: 'Last Drafted Issue' making sidebar look weird on collapsed

* feat: scroll to the bottom when issue is created

* fix: 'Add Issue' button overlapping issue card in spreadsheet view

* fix: wrong placement of quick-add in calender layout

* fix: spacing for issue card in spreadsheet view

* style: gantt layout quick-add padding

style: removed 'State group' from draft issue

* style: decrese shadow, quick-add position on calender layout, and 'add issue' sticky

* style: button color

* fix: block click happening while moving (#2275)

* dev: refactor date filters to a single function

* chore: handle calendar date range in frontend (#2277)

* chore: gantt chart empty state (#2279)

* chore: gantt empty state

* chore: Add heading to the gantt sidebar

* style: calender quick-add same width as single date (#2280)

* style: calender quick-add same width as single date

* style: margin bottom in quick-add in spreadsheet view

* fix: quick add opening in list-layout

* style: reduced margin left

* chore: updated created at in draft issue (#2278)

* chore: make target dates inclusive when filtering (#2276)

* chore: sort order and issue props for global views (#2283)

* chore: removed project filter (#2284)

* fix: inbox issue deletes (#2290)

* chore: views (#2288)

* chore: global views order by

* chore: update permissions for global views

---------

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

* chore: fetch issues from previous and next month in the calendar view (#2282)

* fix: issue activity estimate value bug fix (#2281)

* fix: issue activity estimate value bug fix

* fix: activity typo fix

* fix: ui and bugs (#2289)

* fix: 24 character limit on first & last name in onboarding page

* fix: no option: 'Add Issue' in archive issue page

* fix: in archive issue directly sending to issue detail page

* fix: issue type showing in archive issue

* fix: custom menu overflowing

* fix: changing subscriber in filters has no effect

* style: border in quick-add

* fix: on onboarding member role overflowing

* fix: inconsistent icons in issue detail

* style: spacing, borders and shadows in quick-add

* fix: custom menu truncate

* fix: notifications for created by me and assigned to me (#2292)

* chore: workspace view display filters and properties , code refactor (#2295)

* chore: spreadsheet view context

* chore: spreadsheet context provider

* chore: spreadsheet view context

* chore: display filters and properties added in workspace view and code refactor

* fix: build error fix

* chore: set sub-issue display option to false for global views

---------

Co-authored-by: gurusainath <gurusainath007@gmail.com>

* chore: label create error (#2299)

* chore: global issues ui improvement and bug fixes (#2300)

* chore: workspace view mutation fix ,bug fixes and code refactor (#2301)

* chore: workspace view mutation fix ,bug fixes and code refactor

* chore: update workspace view toast alert added

* chore: workspace view order by removed (#2303)

* dev: updated migrations for 0.13-dev (#2305)

* chore: epoch migration batch size changed

* chore: reoredered the migration files

* dev: updated migrations for 0.13-dev

* chore: added epoch field

* dev: merged the migration files

* fix: workspace view filters count fix (#2307)

* fix: unsplash api fix (#2310)

* fix: workspace view redirection fix, style: spreadsheet view shadow scroll fix (#2314)

* fix: workspace view redirection fix

* style: spreadsheet view scroll shadow fix

* fix: update build workflow for the deploy app (#2315)

* fix: workspace view add issue mutation fix (#2317)

* dev: create action to sync PR changes to the repo (#2333)

* fix: ui package readme added (#2334)

* fix: variable name for token (#2336)

* dev: update add permissions to the action (#2337)

* dev: rename token variables (#2338)

* fix: updated readme fixes (#2339)

* dev: update sync workflow to run only when the source repo is configured (#2346)

* dev: update sync workflow to run only when the source repo is configured

* fix: naming convention changes

---------

Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>

* fix: issue relation mutation and draft issue (#2340)

* fix: issue relation mutation and draft issue

* fix: 'New Issue' in gantt view

fix: emoji select going under

* fix: profile page typo

* fix: sync workflow fixes (#2365)

* fix: sync job pr description escaped values fix (#2366)

* Update index.tsx (#2343)

Fixes #2342

* dev: update apiserver configuration files (#2348)

* dev: update apiserver configuration files

* dev: add email and minio redirection urls

* fix: themening  validation in store init. (#2350)

* chore: member can change role (#2371)

* chore: removed the issue draft log from my profile (#2368)

* adding sync info in pr title (#2373)

* chore: layout access validation and switch in plane deploy issues route (#2351)

* chore: handled route validation and layout access validation in plane deploy issues

* chore: impoved validation condition

* show current version in the help section dropdown (#2353)

* fix: table menu positioning (#2354)

* fix: handle cross project issues in the sub-issues. (#2357)

* fix: login process validation based on api config (#2361)

* dev: configuration endpoint for frontend client (#2355)

* dev: configuration endpoint for frontend clients

* dev: configuration enable magic and email/password signup

* dev: update unsplash keys

* dev: add unsplash API and add  env for magic login

* fix: 404 when redirecting user clicks on Sign In button (#2349)

* fix: 404 when redirecting user to login page

* fix: next_path redirection not working

* fix: authentication workflow update in plane deploy

---------

Co-authored-by: gurusainath <gurusainath007@gmail.com>

* fix: project setting member role validation (#2369)

* fix: project setting member role validation

* chore: opacity removed from member setting page

* chore: member setting page validation

* chore: project covers endpoint (#2370)

* chore: project covers endpoint

* dev: remove print logs

* dev: formatting

---------

Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>

* feat: default project cover images tab on the change cover popover (#2375)

* feat: default project cover images tab

* chore: remove unnecessary env vars from turbo.json

* chore: remove unnecessary OAuth envs (#2378)

* chore: remove unnecessary oauth envs

* merge conflicts resolved

* fix: adding new service

---------

Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>

* fix: added user store variables in mobx store observable (#2380)

* fix: state group icons (#2381)

* fix: removed default theme setting in the index page (#2382)

* fix: removed default theme setting in the index page

* fix: empty space

* dev: global views and workspace filters store implemented

* sync CE Master to EE Develop

* refactor: create update view modal

* chore: static issue global views

* refactor: remove old code

* refactor: filters select dropdown

* chore: fix calendar layout

* chore: mobx store for new applied filters

* chore: dded search functionality

---------

Co-authored-by: Vamsi Kurama <vamsi.kurama@gmail.com>
Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com>
Co-authored-by: guru_sainath <gurusainath007@gmail.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: Anmol Singh Bhatia <asb@Anmols-MacBook-Pro.local>
Co-authored-by: Rhea Jain <65884341+rhea0110@users.noreply.github.com>
Co-authored-by: Your Name <you@example.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Henit Chobisa <chobisa.henit@gmail.com>
Co-authored-by: Thomas <git@thomasync.dev>
Co-authored-by: Luis Cruz <55716036+luis-cruzt@users.noreply.github.com>
Co-authored-by: Manish Gupta <manish@mgupta.me>

* fix: Auth fixes and Layout fixes (#2408)

* fix: auth fixes and layout improvements

* fix: layout fixes

* fix: analytics page fixes

* dev: implemented project views using MobX (#2410)

* dev: implemented project views list using mobx

* style: views list UI

* dev: implemented view issues page using mobx

* refactor: project view issues fetching

* chore:  plane ui library component and code refactor (#2406)

* chore: swap input component with plane/ui package

* chore: swap textarea component with plane/ui package

* chore: swap button component with plane/ui package

* chore: button component revamp

* fix: button type fix

* chore: secondary button revamp

* chore: button props updated

* chore: swap loader component with plane/ui package

* fix: build error fix

* chore: button component refactor

* chore: code refactor

* chore: swap toggle switch component with plane/ui package

* chore: swap spinner component with plane/ui package

* chore: swap progress bar componenet with plan/ui package

* chore: code refactor

* fix: gitignore fixes

* fix: project card fixes

* chore: ui component revamp (#2415)

* chore: swap tooltip component with plane ui package

* chore: swap linear progress component with plane ui package

* fix: login button fix

* chore: implement new worksapace wrapper for global views (#2412)

* chore: implement new worksapace wrapper for global views pages

* fix: merge conflicts

* fix: merge conflicts

* dev: add remaining layouts to cycle (#2413)

* fix: workspace auth wrapper changes

* chore: project card revamp and refactor (#2416)

* removing dist from ui

* refactor: analytics (#2419)

* refactor: helper functions

* chore: updated all the page headers

* refactor: custom analytics

* refactor: project analytics modal

* refactor: folder structure, remove junk code (#2423)

* refactor: folder structure

* chore: ad order by target date option

* refactor: remove old layout components

* refactor: inbox folder structure

* dev: inbox stores init

* fix: services fixes

* fix: store imports changes

* fix: services export fixes

* fix: services implementation fixes

* fix: build issue fixes

* fix: react library fixes

* refactor: MobX store folder structure (#2435)

* refactor: store folder structure

* chore: update import statements

* fix: service import errors (#2436)

* fix: service imports

* chore: update service imports in store

* chore: fix remianing service imports

* build fixes

* editor ts config fixes

* dev: implemented mobx logic in inbox components

* fix: inbox crud operations

* fix: build errors

---------

Co-authored-by: gurusainath <gurusainath007@gmail.com>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com>
Co-authored-by: Vamsi Kurama <vamsi.kurama@gmail.com>
Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: Anmol Singh Bhatia <asb@Anmols-MacBook-Pro.local>
Co-authored-by: Rhea Jain <65884341+rhea0110@users.noreply.github.com>
Co-authored-by: Your Name <you@example.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Henit Chobisa <chobisa.henit@gmail.com>
Co-authored-by: Thomas <git@thomasync.dev>
Co-authored-by: Luis Cruz <55716036+luis-cruzt@users.noreply.github.com>
Co-authored-by: Manish Gupta <manish@mgupta.me>
2023-10-16 17:10:37 +05:30
+9 a361dae185 chore: cycles revamp using MobX (#2443)
* chore: kanban refactoring

* chore: Implemented new kanaban board UX and implemented draggable using react beautiful dnd

* chore: updated yarn lock

* chore: updated the store for issues and issue filters

* chore: resolved build error

* chore: created filters and updated the issue filters, display_filter and display_properties in mobx and components

* chore: implemented filters for issues

* chore: UI theming updates

* chore: handled single and multi select in filter cards

* chore: implemented filters and views in kanaban

* chore: updating filters, display_filter and display properties

* chore: filter, layout, display filters, extra filters and display properties render validation

* chore: clean up and resolved import warnings

* chore: type check

* chore: renamed gantt key to gantt_chart

* chore: filter render UI and Functionality implementation

* chore: filter empty state handling in issue filter selection

* Implementing list view

* chore: kanban drag drop logic

* filtering

* chore: store setup

* chore: handled build issues

* chore: store setup

* user filter

* chore: store setup

* chore: store fixes and static data setup

* chore: store setup for build fixes

* fix: merge conflicts (#2231)

* chore: dynamic position dropdown (#2138)

* chore: dynamic position state dropdown for issue view

* style: state select dropdown styling

* fix: state icon attribute names

* chore: state select dynamic dropdown

* chore: member select dynamic dropdown

* chore: label select dynamic dropdown

* chore: priority select dynamic dropdown

* chore: label select dropdown improvement

* refactor: state dropdown location

* chore: dropdown improvement and code refactor

* chore: dynamic dropdown hook type added

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>

* fix: fields not getting selected in the create issue form (#2212)

* fix: hydration error and draft issue workflow

* fix: build error

* fix: properties getting de-selected after create, module & cycle not getting auto-select on the form

* fix: display layout, props being updated directly

* chore: sub issues count in individual issue (#2221)

* fix: service imports

* chore: rename csv service file

---------

Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com>

* chore: store fixes

* chore: update issue detail store to handle peek overview (#2237)

* chore: dynamic position dropdown (#2138)

* chore: dynamic position state dropdown for issue view

* style: state select dropdown styling

* fix: state icon attribute names

* chore: state select dynamic dropdown

* chore: member select dynamic dropdown

* chore: label select dynamic dropdown

* chore: priority select dynamic dropdown

* chore: label select dropdown improvement

* refactor: state dropdown location

* chore: dropdown improvement and code refactor

* chore: dynamic dropdown hook type added

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>

* fix: fields not getting selected in the create issue form (#2212)

* fix: hydration error and draft issue workflow

* fix: build error

* fix: properties getting de-selected after create, module & cycle not getting auto-select on the form

* fix: display layout, props being updated directly

* chore: sub issues count in individual issue (#2221)

* Implemented nested issues in the sub issues section in issue detail page (#2233)

* feat: subissues infinte level

* feat: updated UI for sub issues

* feat: subissues new ui and nested sub issues in issue detail

* chore: removed repeated code

* refactor: product updates modal layout (#2225)

* fix: handle no issues in custom analytics (#2226)

* fix: activity label color (#2227)

* fix: profile issues layout switch (#2228)

* chore: update service imports

* chore: update issue detail store to handle peek overview

---------

Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com>
Co-authored-by: guru_sainath <gurusainath007@gmail.com>

* chore: minor fixes

* workspace project fixes

* feat: project issues topbar (#2256)

* chore: project issues topbar

* style: theming and minor UI fixes

* refactor: file structure

* chore: layout wise authorization added

* style: filter dropdowns

* chore: add fetch keys

* chore: minor fixes

* chore: filters dropdown (#2260)

* chore: project issues topbar

* style: theming and minor UI fixes

* refactor: file structure

* chore: layout wise authorization added

* style: filter dropdowns

* chore: add fetch keys

* feat: search option for filters

* fix: sticky headers

* chore: sub_group_by section added

* fix: leave project fixes

* refactor: project card component refactor

* Implemented swimlanes and kanban view  (#2262)

* chore: issue store for kanban and calendar

* chore: updated ui for kanba and swimlanes

* chore: yarn.lock updated

* fix: computed filters logic

* chore: added sub_group_by in params and handled sub-group-by render error in display filter's

* fix: ui package setup and project update form refactor

* fix: ui package setup

* fix: minor ui fixes

* dev: calendar view layout revamp (#2293)

* dev: calendar view init

* chore: new render logic

* chore: implement calendar view

* chore: calendar view

* refactor: calendar payload

* chore: remove active month logic from backend

* chore: setup new store for calendar

* refactor: issues fetching structure

* chore: months dropdown

* chore: modify request query params for calendar layout

* refactor: remove console logs and add comments

* chore: removed demo m-store routes

* cycles changes

* chore: issues grouped kanban and swimlanes UI and functionality (#2294)

* chore: updated the all the group_by and sub_group_by UI and functionality render in kanban

* chore: kanban sorting in mobx and ui updates

* chore: ui changes and drag and drop functionality changes in kanban

* chore: issues count render in kanban default and swimlanes

* chore: Added icons to the group_by and sub_group_by in kanban and swimlanes

* refactor: filter components, constants and helper functions (#2297)

* refactor: filters and display filters to accept handlers as props

* refactor: filters and display filters folder structure

* refactor: change issue layout options constant structure

* chore: display filters validations

* chore: view less filters functionality

* fix: display filters validation

* refactor: wrap functions around useCallback

* chore: start and target date filter options added

* refactor: query params generator function

* fix: query params generator function

* dev: gantt chart implementation using MobX (#2302)

* dev: fetch project gantt issues using mobx

* chore: handle group by options in the kanban layout

* dev: spreadsheet layout implementation using MobX (#2306)

* dev: implement spreadsheet view using mobx

* refactor: remove console logs and props

* chore: refactoring cycles list

* feat: adding additional ui components

* dev: applied filters list implementation using MobX (#2325)

* dev: applied filters list UI

* fix: filter item height

* chore: remove unnecessary classes

* fix: params generator

* fix: cycles views list and board

* fix: cycles list rendering fixes

* fix: layout fixes

* refactor: filter components (#2359)

* fix: calendar layout dividers

* refactor: filter selection components

* fix: dropdown closing after selection

* refactor: filters components

* chore: issue properties for list and kanban layouts and implemented estimates in project store (#2363)

* chore: issue properties for state, priorit, labels and members

* feat: implemented assignee, labels properties

* fix: implemented estimates in project store and issue properties

* chore: staer_date and due_date and validation properties in kanban

* chore: filters import conflict

* dev: setup module and module filter store (#2364)

* dev: implement module issues using mobx store

* dev: module filter store setup

* chore: module store crud operations

* chore: issue list layout (#2367)

* chore: merge develop (#2388)

* fix: build erros

* chore: cycles, modules store integration, list and kanban layouts and updated kanban logic (#2399)

* chore: cycle, cycle-issue, cycle-filters, cycle-kanban, cycle layout setup

* chore: cycles kanban and list view store

* chore: cycles, modules kanban and list, kanban view store

* refactor: change naming convention (#2383)

* fix:auth layer revamp

* chore: Implemented list and kanban views in project modules (#2402)

* chore: updated kanban logic in project cycles and modules

* chore: updated list and kanban in module

* dev: implement global views using MobX (#2404)

* fix: selfhosted fixes (#2154)

* fix: selfhosted fixes

* fix: updated env example

* chore: dynamic position dropdown (#2138)

* chore: dynamic position state dropdown for issue view

* style: state select dropdown styling

* fix: state icon attribute names

* chore: state select dynamic dropdown

* chore: member select dynamic dropdown

* chore: label select dynamic dropdown

* chore: priority select dynamic dropdown

* chore: label select dropdown improvement

* refactor: state dropdown location

* chore: dropdown improvement and code refactor

* chore: dynamic dropdown hook type added

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>

* fix: fields not getting selected in the create issue form (#2212)

* fix: hydration error and draft issue workflow

* fix: build error

* fix: properties getting de-selected after create, module & cycle not getting auto-select on the form

* fix: display layout, props being updated directly

* chore: sub issues count in individual issue (#2221)

* Implemented nested issues in the sub issues section in issue detail page (#2233)

* feat: subissues infinte level

* feat: updated UI for sub issues

* feat: subissues new ui and nested sub issues in issue detail

* chore: removed repeated code

* refactor: product updates modal layout (#2225)

* fix: handle no issues in custom analytics (#2226)

* fix: activity label color (#2227)

* fix: profile issues layout switch (#2228)

* fix: issues resolved in sub issues (#2238)

* fix: aws region name (#2234)

* chore: updated docker naming conventions (#2239)

* naming convention changes

* dev: update docker-compose-hub in consistent with docker-compose

* dev: updated docker container name

---------

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

* chore: added state and priority order in workspace user profile (#2241)

* fix: changed priority from None to none (#2229)

* fix: cycle and module stats when issues are archived (#2185)

* fix: cycle and module stats when issues are archived

* fix: added draft filter

---------

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

* feat: quick add  (#2240)

* feat: quick add

* style: made text color muted

* chore: added epoch in draft (#2244)

* chore: added epoch in draft

* chore: removed extra spaces

* fix: resolved pending issue graph in analytics, user wishes in dashboard, and typo in projects list (#2247)

* style: settings page improvement (#2211)

* style: settings page improvement

* style: toggle switch styling

---------

Co-authored-by: Anmol Singh Bhatia <asb@Anmols-MacBook-Pro.local>

* chore: changed priority props in workspace and project (#2253)

* fix: bug fix related to fetching dropdown options for the profile issue (#2246)

* fix: sub issue state and member select build error (#2254)

* rename view to layout (#2255)

Co-authored-by: Your Name <you@example.com>

* fix: bug fixes and ui improvement (#2250)

* dev: remove auto filter endpoint

* feat: quick-add placement in spreadsheet and gantt  (#2259)

* feat: sticking quick-add at the bottom of the screen

fix: opening create issue modal instead of quick-add in draft-issues, my-issue and profile page

* fix: build error due to dynamic import

* fix: draft issue delete not working (#2249)

* fix: draft issue not deleting, project can't be changed in draft issue modal

* fix: removed mutation for view where draft issues are not shown

* fix: inline create issue for draft issue

* fix: clearing data from localstorage on discard click

* feat: Add peek overview in sub issues and updated UI for empty states. (#2263)

* chore: add tooltip to show full time on activity logs (#2235)

* fix: issue automation iterable error (#2208)

* fix: n+1 queries for cycle list and project member endpoints (#2257)

* [fix] nginx continuously rewriting and reloading on index page of spaces app  (#2236)

* chore: shifted index page to /home route

* chore: added rewrite logic, to rewrite index to /home

* chore: routed home to login route as login page

* chore: updated nginx config to route to login

* chore: updated path for home

* dev: migration for 0.13 (#2266)

* dev: updated migrations

* dev: migration for 0.13

* dev: re-split migrations into two different files (#2268)

* dev: split issue activity migration separate files

* dev: resplit migrations into two different files

* dev: changed the batch size

* chore: udpate date filters to support dynamic options

* fix: bugs in quick-add and draft issues (#2269)

* fix: 'Last Drafted Issue' making sidebar look weird on collapsed

* feat: scroll to the bottom when issue is created

* fix: 'Add Issue' button overlapping issue card in spreadsheet view

* fix: wrong placement of quick-add in calender layout

* fix: spacing for issue card in spreadsheet view

* chore: add instructions to contributing guide (#2270)

* chore: add instructions to contributing guide

* dev: update contributing.md to use the new configuration

---------

Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com>

* fix: user dashboard greeting timezone (#2267)

* chore: user greeting timezone

* fix: group by labels not working on workspace level

* feat: workspace global view, style: spreadsheet view revamp (#2273)

* chore: workspace view types, services and hooks added

* style: spreadsheet view revamp and code refactor

* feat: workspace view

* fix: build fix

* chore: sidebar workspace issues redirection updated

* style: gantt layout quick-add padding (#2272)

* fix: 'Last Drafted Issue' making sidebar look weird on collapsed

* feat: scroll to the bottom when issue is created

* fix: 'Add Issue' button overlapping issue card in spreadsheet view

* fix: wrong placement of quick-add in calender layout

* fix: spacing for issue card in spreadsheet view

* style: gantt layout quick-add padding

style: removed 'State group' from draft issue

* style: decrese shadow, quick-add position on calender layout, and 'add issue' sticky

* style: button color

* fix: block click happening while moving (#2275)

* dev: refactor date filters to a single function

* chore: handle calendar date range in frontend (#2277)

* chore: gantt chart empty state (#2279)

* chore: gantt empty state

* chore: Add heading to the gantt sidebar

* style: calender quick-add same width as single date (#2280)

* style: calender quick-add same width as single date

* style: margin bottom in quick-add in spreadsheet view

* fix: quick add opening in list-layout

* style: reduced margin left

* chore: updated created at in draft issue (#2278)

* chore: make target dates inclusive when filtering (#2276)

* chore: sort order and issue props for global views (#2283)

* chore: removed project filter (#2284)

* fix: inbox issue deletes (#2290)

* chore: views (#2288)

* chore: global views order by

* chore: update permissions for global views

---------

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

* chore: fetch issues from previous and next month in the calendar view (#2282)

* fix: issue activity estimate value bug fix (#2281)

* fix: issue activity estimate value bug fix

* fix: activity typo fix

* fix: ui and bugs (#2289)

* fix: 24 character limit on first & last name in onboarding page

* fix: no option: 'Add Issue' in archive issue page

* fix: in archive issue directly sending to issue detail page

* fix: issue type showing in archive issue

* fix: custom menu overflowing

* fix: changing subscriber in filters has no effect

* style: border in quick-add

* fix: on onboarding member role overflowing

* fix: inconsistent icons in issue detail

* style: spacing, borders and shadows in quick-add

* fix: custom menu truncate

* fix: notifications for created by me and assigned to me (#2292)

* chore: workspace view display filters and properties , code refactor (#2295)

* chore: spreadsheet view context

* chore: spreadsheet context provider

* chore: spreadsheet view context

* chore: display filters and properties added in workspace view and code refactor

* fix: build error fix

* chore: set sub-issue display option to false for global views

---------

Co-authored-by: gurusainath <gurusainath007@gmail.com>

* chore: label create error (#2299)

* chore: global issues ui improvement and bug fixes (#2300)

* chore: workspace view mutation fix ,bug fixes and code refactor (#2301)

* chore: workspace view mutation fix ,bug fixes and code refactor

* chore: update workspace view toast alert added

* chore: workspace view order by removed (#2303)

* dev: updated migrations for 0.13-dev (#2305)

* chore: epoch migration batch size changed

* chore: reoredered the migration files

* dev: updated migrations for 0.13-dev

* chore: added epoch field

* dev: merged the migration files

* fix: workspace view filters count fix (#2307)

* fix: unsplash api fix (#2310)

* fix: workspace view redirection fix, style: spreadsheet view shadow scroll fix (#2314)

* fix: workspace view redirection fix

* style: spreadsheet view scroll shadow fix

* fix: update build workflow for the deploy app (#2315)

* fix: workspace view add issue mutation fix (#2317)

* dev: create action to sync PR changes to the repo (#2333)

* fix: ui package readme added (#2334)

* fix: variable name for token (#2336)

* dev: update add permissions to the action (#2337)

* dev: rename token variables (#2338)

* fix: updated readme fixes (#2339)

* dev: update sync workflow to run only when the source repo is configured (#2346)

* dev: update sync workflow to run only when the source repo is configured

* fix: naming convention changes

---------

Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>

* fix: issue relation mutation and draft issue (#2340)

* fix: issue relation mutation and draft issue

* fix: 'New Issue' in gantt view

fix: emoji select going under

* fix: profile page typo

* fix: sync workflow fixes (#2365)

* fix: sync job pr description escaped values fix (#2366)

* Update index.tsx (#2343)

Fixes #2342

* dev: update apiserver configuration files (#2348)

* dev: update apiserver configuration files

* dev: add email and minio redirection urls

* fix: themening  validation in store init. (#2350)

* chore: member can change role (#2371)

* chore: removed the issue draft log from my profile (#2368)

* adding sync info in pr title (#2373)

* chore: layout access validation and switch in plane deploy issues route (#2351)

* chore: handled route validation and layout access validation in plane deploy issues

* chore: impoved validation condition

* show current version in the help section dropdown (#2353)

* fix: table menu positioning (#2354)

* fix: handle cross project issues in the sub-issues. (#2357)

* fix: login process validation based on api config (#2361)

* dev: configuration endpoint for frontend client (#2355)

* dev: configuration endpoint for frontend clients

* dev: configuration enable magic and email/password signup

* dev: update unsplash keys

* dev: add unsplash API and add  env for magic login

* fix: 404 when redirecting user clicks on Sign In button (#2349)

* fix: 404 when redirecting user to login page

* fix: next_path redirection not working

* fix: authentication workflow update in plane deploy

---------

Co-authored-by: gurusainath <gurusainath007@gmail.com>

* fix: project setting member role validation (#2369)

* fix: project setting member role validation

* chore: opacity removed from member setting page

* chore: member setting page validation

* chore: project covers endpoint (#2370)

* chore: project covers endpoint

* dev: remove print logs

* dev: formatting

---------

Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>

* feat: default project cover images tab on the change cover popover (#2375)

* feat: default project cover images tab

* chore: remove unnecessary env vars from turbo.json

* chore: remove unnecessary OAuth envs (#2378)

* chore: remove unnecessary oauth envs

* merge conflicts resolved

* fix: adding new service

---------

Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>

* fix: added user store variables in mobx store observable (#2380)

* fix: state group icons (#2381)

* fix: removed default theme setting in the index page (#2382)

* fix: removed default theme setting in the index page

* fix: empty space

* dev: global views and workspace filters store implemented

* sync CE Master to EE Develop

* refactor: create update view modal

* chore: static issue global views

* refactor: remove old code

* refactor: filters select dropdown

* chore: fix calendar layout

* chore: mobx store for new applied filters

* chore: dded search functionality

---------

Co-authored-by: Vamsi Kurama <vamsi.kurama@gmail.com>
Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com>
Co-authored-by: guru_sainath <gurusainath007@gmail.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: Anmol Singh Bhatia <asb@Anmols-MacBook-Pro.local>
Co-authored-by: Rhea Jain <65884341+rhea0110@users.noreply.github.com>
Co-authored-by: Your Name <you@example.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Henit Chobisa <chobisa.henit@gmail.com>
Co-authored-by: Thomas <git@thomasync.dev>
Co-authored-by: Luis Cruz <55716036+luis-cruzt@users.noreply.github.com>
Co-authored-by: Manish Gupta <manish@mgupta.me>

* fix: Auth fixes and Layout fixes (#2408)

* fix: auth fixes and layout improvements

* fix: layout fixes

* fix: analytics page fixes

* dev: implemented project views using MobX (#2410)

* dev: implemented project views list using mobx

* style: views list UI

* dev: implemented view issues page using mobx

* refactor: project view issues fetching

* chore:  plane ui library component and code refactor (#2406)

* chore: swap input component with plane/ui package

* chore: swap textarea component with plane/ui package

* chore: swap button component with plane/ui package

* chore: button component revamp

* fix: button type fix

* chore: secondary button revamp

* chore: button props updated

* chore: swap loader component with plane/ui package

* fix: build error fix

* chore: button component refactor

* chore: code refactor

* chore: swap toggle switch component with plane/ui package

* chore: swap spinner component with plane/ui package

* chore: swap progress bar componenet with plan/ui package

* chore: code refactor

* chore: cycles revamp

* fix: gitignore fixes

* chore: updated cycles view adn layout mutation

* fix: project card fixes

* chore: ui component revamp (#2415)

* chore: swap tooltip component with plane ui package

* chore: swap linear progress component with plane ui package

* fix: login button fix

* chore: implement new worksapace wrapper for global views (#2412)

* chore: implement new worksapace wrapper for global views pages

* fix: merge conflicts

* fix: merge conflicts

* dev: add remaining layouts to cycle (#2413)

* fix: workspace auth wrapper changes

* chore: project card revamp and refactor (#2416)

* removing dist from ui

* chore: handled edit and delete operation in cycle board view, and gantt view

* refactor: analytics (#2419)

* refactor: helper functions

* chore: updated all the page headers

* refactor: custom analytics

* refactor: project analytics modal

* refactor: folder structure, remove junk code (#2423)

* refactor: folder structure

* chore: ad order by target date option

* refactor: remove old layout components

* refactor: inbox folder structure

* fix: services fixes

* fix: store imports changes

* fix: services export fixes

* fix: services implementation fixes

* fix: build issue fixes

* fix: react library fixes

* refactor: MobX store folder structure (#2435)

* refactor: store folder structure

* chore: update import statements

* fix: service import errors (#2436)

* fix: service imports

* chore: update service imports in store

* chore: fix remianing service imports

* build fixes

* editor ts config fixes

* fix: turbo and build fixes

* fix: Auth screen loading implementation

* fix: build issues

* fix: turbo settings for ui package

* chore: project active cycles requestes changed from swr to mobx

* chore: imports and structuring codebase in cycles

* chore: removed legacy code from cycles

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
Co-authored-by: Vamsi Kurama <vamsi.kurama@gmail.com>
Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: Anmol Singh Bhatia <asb@Anmols-MacBook-Pro.local>
Co-authored-by: Rhea Jain <65884341+rhea0110@users.noreply.github.com>
Co-authored-by: Your Name <you@example.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Henit Chobisa <chobisa.henit@gmail.com>
Co-authored-by: Thomas <git@thomasync.dev>
Co-authored-by: Luis Cruz <55716036+luis-cruzt@users.noreply.github.com>
Co-authored-by: Manish Gupta <manish@mgupta.me>
2023-10-16 13:26:18 +05:30
e684bda8b2 dev: hub compose file update (#2376)
* 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: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2023-10-16 12:41:26 +05:30
+9 d80a593520 fix: Implementing mobx, refactoring service layer and rewriting components (#2441)
* chore: kanban refactoring

* chore: Implemented new kanaban board UX and implemented draggable using react beautiful dnd

* chore: updated yarn lock

* chore: updated the store for issues and issue filters

* chore: resolved build error

* chore: created filters and updated the issue filters, display_filter and display_properties in mobx and components

* chore: implemented filters for issues

* chore: UI theming updates

* chore: handled single and multi select in filter cards

* chore: implemented filters and views in kanaban

* chore: updating filters, display_filter and display properties

* chore: filter, layout, display filters, extra filters and display properties render validation

* chore: clean up and resolved import warnings

* chore: type check

* chore: renamed gantt key to gantt_chart

* chore: filter render UI and Functionality implementation

* chore: filter empty state handling in issue filter selection

* Implementing list view

* chore: kanban drag drop logic

* filtering

* chore: store setup

* chore: handled build issues

* chore: store setup

* user filter

* chore: store setup

* chore: store fixes and static data setup

* chore: store setup for build fixes

* fix: merge conflicts (#2231)

* chore: dynamic position dropdown (#2138)

* chore: dynamic position state dropdown for issue view

* style: state select dropdown styling

* fix: state icon attribute names

* chore: state select dynamic dropdown

* chore: member select dynamic dropdown

* chore: label select dynamic dropdown

* chore: priority select dynamic dropdown

* chore: label select dropdown improvement

* refactor: state dropdown location

* chore: dropdown improvement and code refactor

* chore: dynamic dropdown hook type added

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>

* fix: fields not getting selected in the create issue form (#2212)

* fix: hydration error and draft issue workflow

* fix: build error

* fix: properties getting de-selected after create, module & cycle not getting auto-select on the form

* fix: display layout, props being updated directly

* chore: sub issues count in individual issue (#2221)

* fix: service imports

* chore: rename csv service file

---------

Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com>

* chore: store fixes

* chore: update issue detail store to handle peek overview (#2237)

* chore: dynamic position dropdown (#2138)

* chore: dynamic position state dropdown for issue view

* style: state select dropdown styling

* fix: state icon attribute names

* chore: state select dynamic dropdown

* chore: member select dynamic dropdown

* chore: label select dynamic dropdown

* chore: priority select dynamic dropdown

* chore: label select dropdown improvement

* refactor: state dropdown location

* chore: dropdown improvement and code refactor

* chore: dynamic dropdown hook type added

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>

* fix: fields not getting selected in the create issue form (#2212)

* fix: hydration error and draft issue workflow

* fix: build error

* fix: properties getting de-selected after create, module & cycle not getting auto-select on the form

* fix: display layout, props being updated directly

* chore: sub issues count in individual issue (#2221)

* Implemented nested issues in the sub issues section in issue detail page (#2233)

* feat: subissues infinte level

* feat: updated UI for sub issues

* feat: subissues new ui and nested sub issues in issue detail

* chore: removed repeated code

* refactor: product updates modal layout (#2225)

* fix: handle no issues in custom analytics (#2226)

* fix: activity label color (#2227)

* fix: profile issues layout switch (#2228)

* chore: update service imports

* chore: update issue detail store to handle peek overview

---------

Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com>
Co-authored-by: guru_sainath <gurusainath007@gmail.com>

* chore: minor fixes

* workspace project fixes

* feat: project issues topbar (#2256)

* chore: project issues topbar

* style: theming and minor UI fixes

* refactor: file structure

* chore: layout wise authorization added

* style: filter dropdowns

* chore: add fetch keys

* chore: minor fixes

* chore: filters dropdown (#2260)

* chore: project issues topbar

* style: theming and minor UI fixes

* refactor: file structure

* chore: layout wise authorization added

* style: filter dropdowns

* chore: add fetch keys

* feat: search option for filters

* fix: sticky headers

* chore: sub_group_by section added

* fix: leave project fixes

* refactor: project card component refactor

* Implemented swimlanes and kanban view  (#2262)

* chore: issue store for kanban and calendar

* chore: updated ui for kanba and swimlanes

* chore: yarn.lock updated

* fix: computed filters logic

* chore: added sub_group_by in params and handled sub-group-by render error in display filter's

* fix: ui package setup and project update form refactor

* fix: ui package setup

* fix: minor ui fixes

* dev: calendar view layout revamp (#2293)

* dev: calendar view init

* chore: new render logic

* chore: implement calendar view

* chore: calendar view

* refactor: calendar payload

* chore: remove active month logic from backend

* chore: setup new store for calendar

* refactor: issues fetching structure

* chore: months dropdown

* chore: modify request query params for calendar layout

* refactor: remove console logs and add comments

* chore: removed demo m-store routes

* cycles changes

* chore: issues grouped kanban and swimlanes UI and functionality (#2294)

* chore: updated the all the group_by and sub_group_by UI and functionality render in kanban

* chore: kanban sorting in mobx and ui updates

* chore: ui changes and drag and drop functionality changes in kanban

* chore: issues count render in kanban default and swimlanes

* chore: Added icons to the group_by and sub_group_by in kanban and swimlanes

* refactor: filter components, constants and helper functions (#2297)

* refactor: filters and display filters to accept handlers as props

* refactor: filters and display filters folder structure

* refactor: change issue layout options constant structure

* chore: display filters validations

* chore: view less filters functionality

* fix: display filters validation

* refactor: wrap functions around useCallback

* chore: start and target date filter options added

* refactor: query params generator function

* fix: query params generator function

* dev: gantt chart implementation using MobX (#2302)

* dev: fetch project gantt issues using mobx

* chore: handle group by options in the kanban layout

* dev: spreadsheet layout implementation using MobX (#2306)

* dev: implement spreadsheet view using mobx

* refactor: remove console logs and props

* chore: refactoring cycles list

* feat: adding additional ui components

* dev: applied filters list implementation using MobX (#2325)

* dev: applied filters list UI

* fix: filter item height

* chore: remove unnecessary classes

* fix: params generator

* fix: cycles views list and board

* fix: cycles list rendering fixes

* fix: layout fixes

* refactor: filter components (#2359)

* fix: calendar layout dividers

* refactor: filter selection components

* fix: dropdown closing after selection

* refactor: filters components

* chore: issue properties for list and kanban layouts and implemented estimates in project store (#2363)

* chore: issue properties for state, priorit, labels and members

* feat: implemented assignee, labels properties

* fix: implemented estimates in project store and issue properties

* chore: staer_date and due_date and validation properties in kanban

* chore: filters import conflict

* dev: setup module and module filter store (#2364)

* dev: implement module issues using mobx store

* dev: module filter store setup

* chore: module store crud operations

* chore: issue list layout (#2367)

* chore: merge develop (#2388)

* fix: build erros

* chore: cycles, modules store integration, list and kanban layouts and updated kanban logic (#2399)

* chore: cycle, cycle-issue, cycle-filters, cycle-kanban, cycle layout setup

* chore: cycles kanban and list view store

* chore: cycles, modules kanban and list, kanban view store

* refactor: change naming convention (#2383)

* fix:auth layer revamp

* chore: Implemented list and kanban views in project modules (#2402)

* chore: updated kanban logic in project cycles and modules

* chore: updated list and kanban in module

* dev: implement global views using MobX (#2404)

* fix: selfhosted fixes (#2154)

* fix: selfhosted fixes

* fix: updated env example

* chore: dynamic position dropdown (#2138)

* chore: dynamic position state dropdown for issue view

* style: state select dropdown styling

* fix: state icon attribute names

* chore: state select dynamic dropdown

* chore: member select dynamic dropdown

* chore: label select dynamic dropdown

* chore: priority select dynamic dropdown

* chore: label select dropdown improvement

* refactor: state dropdown location

* chore: dropdown improvement and code refactor

* chore: dynamic dropdown hook type added

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>

* fix: fields not getting selected in the create issue form (#2212)

* fix: hydration error and draft issue workflow

* fix: build error

* fix: properties getting de-selected after create, module & cycle not getting auto-select on the form

* fix: display layout, props being updated directly

* chore: sub issues count in individual issue (#2221)

* Implemented nested issues in the sub issues section in issue detail page (#2233)

* feat: subissues infinte level

* feat: updated UI for sub issues

* feat: subissues new ui and nested sub issues in issue detail

* chore: removed repeated code

* refactor: product updates modal layout (#2225)

* fix: handle no issues in custom analytics (#2226)

* fix: activity label color (#2227)

* fix: profile issues layout switch (#2228)

* fix: issues resolved in sub issues (#2238)

* fix: aws region name (#2234)

* chore: updated docker naming conventions (#2239)

* naming convention changes

* dev: update docker-compose-hub in consistent with docker-compose

* dev: updated docker container name

---------

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

* chore: added state and priority order in workspace user profile (#2241)

* fix: changed priority from None to none (#2229)

* fix: cycle and module stats when issues are archived (#2185)

* fix: cycle and module stats when issues are archived

* fix: added draft filter

---------

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

* feat: quick add  (#2240)

* feat: quick add

* style: made text color muted

* chore: added epoch in draft (#2244)

* chore: added epoch in draft

* chore: removed extra spaces

* fix: resolved pending issue graph in analytics, user wishes in dashboard, and typo in projects list (#2247)

* style: settings page improvement (#2211)

* style: settings page improvement

* style: toggle switch styling

---------

Co-authored-by: Anmol Singh Bhatia <asb@Anmols-MacBook-Pro.local>

* chore: changed priority props in workspace and project (#2253)

* fix: bug fix related to fetching dropdown options for the profile issue (#2246)

* fix: sub issue state and member select build error (#2254)

* rename view to layout (#2255)

Co-authored-by: Your Name <you@example.com>

* fix: bug fixes and ui improvement (#2250)

* dev: remove auto filter endpoint

* feat: quick-add placement in spreadsheet and gantt  (#2259)

* feat: sticking quick-add at the bottom of the screen

fix: opening create issue modal instead of quick-add in draft-issues, my-issue and profile page

* fix: build error due to dynamic import

* fix: draft issue delete not working (#2249)

* fix: draft issue not deleting, project can't be changed in draft issue modal

* fix: removed mutation for view where draft issues are not shown

* fix: inline create issue for draft issue

* fix: clearing data from localstorage on discard click

* feat: Add peek overview in sub issues and updated UI for empty states. (#2263)

* chore: add tooltip to show full time on activity logs (#2235)

* fix: issue automation iterable error (#2208)

* fix: n+1 queries for cycle list and project member endpoints (#2257)

* [fix] nginx continuously rewriting and reloading on index page of spaces app  (#2236)

* chore: shifted index page to /home route

* chore: added rewrite logic, to rewrite index to /home

* chore: routed home to login route as login page

* chore: updated nginx config to route to login

* chore: updated path for home

* dev: migration for 0.13 (#2266)

* dev: updated migrations

* dev: migration for 0.13

* dev: re-split migrations into two different files (#2268)

* dev: split issue activity migration separate files

* dev: resplit migrations into two different files

* dev: changed the batch size

* chore: udpate date filters to support dynamic options

* fix: bugs in quick-add and draft issues (#2269)

* fix: 'Last Drafted Issue' making sidebar look weird on collapsed

* feat: scroll to the bottom when issue is created

* fix: 'Add Issue' button overlapping issue card in spreadsheet view

* fix: wrong placement of quick-add in calender layout

* fix: spacing for issue card in spreadsheet view

* chore: add instructions to contributing guide (#2270)

* chore: add instructions to contributing guide

* dev: update contributing.md to use the new configuration

---------

Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com>

* fix: user dashboard greeting timezone (#2267)

* chore: user greeting timezone

* fix: group by labels not working on workspace level

* feat: workspace global view, style: spreadsheet view revamp (#2273)

* chore: workspace view types, services and hooks added

* style: spreadsheet view revamp and code refactor

* feat: workspace view

* fix: build fix

* chore: sidebar workspace issues redirection updated

* style: gantt layout quick-add padding (#2272)

* fix: 'Last Drafted Issue' making sidebar look weird on collapsed

* feat: scroll to the bottom when issue is created

* fix: 'Add Issue' button overlapping issue card in spreadsheet view

* fix: wrong placement of quick-add in calender layout

* fix: spacing for issue card in spreadsheet view

* style: gantt layout quick-add padding

style: removed 'State group' from draft issue

* style: decrese shadow, quick-add position on calender layout, and 'add issue' sticky

* style: button color

* fix: block click happening while moving (#2275)

* dev: refactor date filters to a single function

* chore: handle calendar date range in frontend (#2277)

* chore: gantt chart empty state (#2279)

* chore: gantt empty state

* chore: Add heading to the gantt sidebar

* style: calender quick-add same width as single date (#2280)

* style: calender quick-add same width as single date

* style: margin bottom in quick-add in spreadsheet view

* fix: quick add opening in list-layout

* style: reduced margin left

* chore: updated created at in draft issue (#2278)

* chore: make target dates inclusive when filtering (#2276)

* chore: sort order and issue props for global views (#2283)

* chore: removed project filter (#2284)

* fix: inbox issue deletes (#2290)

* chore: views (#2288)

* chore: global views order by

* chore: update permissions for global views

---------

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

* chore: fetch issues from previous and next month in the calendar view (#2282)

* fix: issue activity estimate value bug fix (#2281)

* fix: issue activity estimate value bug fix

* fix: activity typo fix

* fix: ui and bugs (#2289)

* fix: 24 character limit on first & last name in onboarding page

* fix: no option: 'Add Issue' in archive issue page

* fix: in archive issue directly sending to issue detail page

* fix: issue type showing in archive issue

* fix: custom menu overflowing

* fix: changing subscriber in filters has no effect

* style: border in quick-add

* fix: on onboarding member role overflowing

* fix: inconsistent icons in issue detail

* style: spacing, borders and shadows in quick-add

* fix: custom menu truncate

* fix: notifications for created by me and assigned to me (#2292)

* chore: workspace view display filters and properties , code refactor (#2295)

* chore: spreadsheet view context

* chore: spreadsheet context provider

* chore: spreadsheet view context

* chore: display filters and properties added in workspace view and code refactor

* fix: build error fix

* chore: set sub-issue display option to false for global views

---------

Co-authored-by: gurusainath <gurusainath007@gmail.com>

* chore: label create error (#2299)

* chore: global issues ui improvement and bug fixes (#2300)

* chore: workspace view mutation fix ,bug fixes and code refactor (#2301)

* chore: workspace view mutation fix ,bug fixes and code refactor

* chore: update workspace view toast alert added

* chore: workspace view order by removed (#2303)

* dev: updated migrations for 0.13-dev (#2305)

* chore: epoch migration batch size changed

* chore: reoredered the migration files

* dev: updated migrations for 0.13-dev

* chore: added epoch field

* dev: merged the migration files

* fix: workspace view filters count fix (#2307)

* fix: unsplash api fix (#2310)

* fix: workspace view redirection fix, style: spreadsheet view shadow scroll fix (#2314)

* fix: workspace view redirection fix

* style: spreadsheet view scroll shadow fix

* fix: update build workflow for the deploy app (#2315)

* fix: workspace view add issue mutation fix (#2317)

* dev: create action to sync PR changes to the repo (#2333)

* fix: ui package readme added (#2334)

* fix: variable name for token (#2336)

* dev: update add permissions to the action (#2337)

* dev: rename token variables (#2338)

* fix: updated readme fixes (#2339)

* dev: update sync workflow to run only when the source repo is configured (#2346)

* dev: update sync workflow to run only when the source repo is configured

* fix: naming convention changes

---------

Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>

* fix: issue relation mutation and draft issue (#2340)

* fix: issue relation mutation and draft issue

* fix: 'New Issue' in gantt view

fix: emoji select going under

* fix: profile page typo

* fix: sync workflow fixes (#2365)

* fix: sync job pr description escaped values fix (#2366)

* Update index.tsx (#2343)

Fixes #2342

* dev: update apiserver configuration files (#2348)

* dev: update apiserver configuration files

* dev: add email and minio redirection urls

* fix: themening  validation in store init. (#2350)

* chore: member can change role (#2371)

* chore: removed the issue draft log from my profile (#2368)

* adding sync info in pr title (#2373)

* chore: layout access validation and switch in plane deploy issues route (#2351)

* chore: handled route validation and layout access validation in plane deploy issues

* chore: impoved validation condition

* show current version in the help section dropdown (#2353)

* fix: table menu positioning (#2354)

* fix: handle cross project issues in the sub-issues. (#2357)

* fix: login process validation based on api config (#2361)

* dev: configuration endpoint for frontend client (#2355)

* dev: configuration endpoint for frontend clients

* dev: configuration enable magic and email/password signup

* dev: update unsplash keys

* dev: add unsplash API and add  env for magic login

* fix: 404 when redirecting user clicks on Sign In button (#2349)

* fix: 404 when redirecting user to login page

* fix: next_path redirection not working

* fix: authentication workflow update in plane deploy

---------

Co-authored-by: gurusainath <gurusainath007@gmail.com>

* fix: project setting member role validation (#2369)

* fix: project setting member role validation

* chore: opacity removed from member setting page

* chore: member setting page validation

* chore: project covers endpoint (#2370)

* chore: project covers endpoint

* dev: remove print logs

* dev: formatting

---------

Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>

* feat: default project cover images tab on the change cover popover (#2375)

* feat: default project cover images tab

* chore: remove unnecessary env vars from turbo.json

* chore: remove unnecessary OAuth envs (#2378)

* chore: remove unnecessary oauth envs

* merge conflicts resolved

* fix: adding new service

---------

Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>

* fix: added user store variables in mobx store observable (#2380)

* fix: state group icons (#2381)

* fix: removed default theme setting in the index page (#2382)

* fix: removed default theme setting in the index page

* fix: empty space

* dev: global views and workspace filters store implemented

* sync CE Master to EE Develop

* refactor: create update view modal

* chore: static issue global views

* refactor: remove old code

* refactor: filters select dropdown

* chore: fix calendar layout

* chore: mobx store for new applied filters

* chore: dded search functionality

---------

Co-authored-by: Vamsi Kurama <vamsi.kurama@gmail.com>
Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com>
Co-authored-by: guru_sainath <gurusainath007@gmail.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: Anmol Singh Bhatia <asb@Anmols-MacBook-Pro.local>
Co-authored-by: Rhea Jain <65884341+rhea0110@users.noreply.github.com>
Co-authored-by: Your Name <you@example.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Henit Chobisa <chobisa.henit@gmail.com>
Co-authored-by: Thomas <git@thomasync.dev>
Co-authored-by: Luis Cruz <55716036+luis-cruzt@users.noreply.github.com>
Co-authored-by: Manish Gupta <manish@mgupta.me>

* fix: Auth fixes and Layout fixes (#2408)

* fix: auth fixes and layout improvements

* fix: layout fixes

* fix: analytics page fixes

* dev: implemented project views using MobX (#2410)

* dev: implemented project views list using mobx

* style: views list UI

* dev: implemented view issues page using mobx

* refactor: project view issues fetching

* chore:  plane ui library component and code refactor (#2406)

* chore: swap input component with plane/ui package

* chore: swap textarea component with plane/ui package

* chore: swap button component with plane/ui package

* chore: button component revamp

* fix: button type fix

* chore: secondary button revamp

* chore: button props updated

* chore: swap loader component with plane/ui package

* fix: build error fix

* chore: button component refactor

* chore: code refactor

* chore: swap toggle switch component with plane/ui package

* chore: swap spinner component with plane/ui package

* chore: swap progress bar componenet with plan/ui package

* chore: code refactor

* fix: gitignore fixes

* fix: project card fixes

* chore: ui component revamp (#2415)

* chore: swap tooltip component with plane ui package

* chore: swap linear progress component with plane ui package

* fix: login button fix

* chore: implement new worksapace wrapper for global views (#2412)

* chore: implement new worksapace wrapper for global views pages

* fix: merge conflicts

* fix: merge conflicts

* dev: add remaining layouts to cycle (#2413)

* fix: workspace auth wrapper changes

* chore: project card revamp and refactor (#2416)

* removing dist from ui

* refactor: analytics (#2419)

* refactor: helper functions

* chore: updated all the page headers

* refactor: custom analytics

* refactor: project analytics modal

* refactor: folder structure, remove junk code (#2423)

* refactor: folder structure

* chore: ad order by target date option

* refactor: remove old layout components

* refactor: inbox folder structure

* fix: services fixes

* fix: store imports changes

* fix: services export fixes

* fix: services implementation fixes

* fix: build issue fixes

* fix: react library fixes

* refactor: MobX store folder structure (#2435)

* refactor: store folder structure

* chore: update import statements

* fix: service import errors (#2436)

* fix: service imports

* chore: update service imports in store

* chore: fix remianing service imports

* build fixes

* editor ts config fixes

* fix: turbo and build fixes

* fix: Auth screen loading implementation

* fix: build issues

* fix: turbo settings for ui package

---------

Co-authored-by: gurusainath <gurusainath007@gmail.com>
Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
Co-authored-by: Vamsi Kurama <vamsi.kurama@gmail.com>
Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: Anmol Singh Bhatia <asb@Anmols-MacBook-Pro.local>
Co-authored-by: Rhea Jain <65884341+rhea0110@users.noreply.github.com>
Co-authored-by: Your Name <you@example.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Henit Chobisa <chobisa.henit@gmail.com>
Co-authored-by: Thomas <git@thomasync.dev>
Co-authored-by: Luis Cruz <55716036+luis-cruzt@users.noreply.github.com>
Co-authored-by: Manish Gupta <manish@mgupta.me>
2023-10-15 23:50:12 +05:30
Manish GuptaandGitHub c6e021d41f dev: Docker Compose for Local Dev Environment Setup (#2409)
* local dev environment setup

* api image fixes

* yaml changed to yml

* Readme & EnvSetup MD updates

* Update README.md
2023-10-13 12:16:08 +05:30
0a8b99a074 feat: Editor Core Packaging and Restructuring (#2358)
* initialized tiptap component with common tailwind config

* added common tailwind config to web

* abstracted upload and delete functions

* removed tiptap pro extension

* fixed types

* removed old tailwind config and fixed plane package imports

* exported tiptap editor with and without ref

* updated package name to @plane/editor

* finally fixed import errors

* added turbo dependency for tiptap

* reverted back types and fixed tailwind

* migrated all components to use the common package

* removed old tiptap dependency

* improved dev experience to build the tiptap package before starting dev server

* resolved lock life and missing deps

* fixed dependency issue with react type resolution

* chore: updated pulls build CI for using turbo builds

* comment editor basic version added

* new structure of editor components

* refactored editor to not require workspace slug

* added seperation of extensions and props

* refactoring to LiteTextEditor and RichTextEditor

* fixed global css issue with highlight js

* refactoring tiptap to core/lite/rich text editor

* read only editor support added

* replaced all read-only instances

* trimming html at start and end of content added

* onSubmit on enterkey captured

* removed absolute imports from editor/core package

* removed absolute imports from lite-text-editor

* removed absolute imports from rich-text-editor

* fixed dependencies in editor package

* fixed tailwind config for editor

* Enter key behaviour added for Comments

* fixed modal form issue

* added comment editor with fixed menu

* added support for range commands

* modified turbo config for build pipeline of space and web projects

* fixed shift enter behavior for lists

* removed extra margin from access specifiers

* removed tiptap instance from web

* fixed bugs returning empty editor boxes

* fixed toggle Underline behvaiour

* updated bubble menu to use core package's utilities

* added editor/core readme and fixed imports

* fixed ts issues with link plugin

* added usage of common dependance for slash commands

* completed core package's documentation

* fixed tsconfig by removing path aliases

* Completed readme for rich-text-editor

* Added rich text editor documentation

* changed readme title of core package

---------

Co-authored-by: Henit Chobisa <chobisa.henit@gmail.com>
2023-10-13 12:05:49 +05:30
930a20ea7f bug:fix recent page hiding last item on scroll #1468 (#2411) (#2422)
Co-authored-by: Prashant Indurkar <32466796+PrashantIndurkar@users.noreply.github.com>
2023-10-12 17:56:04 +05:30
sriram veeraghantaandGitHub 0a95713911 fix: updated gitignore with dist folder (#2421) 2023-10-12 17:09:18 +05:30
Dakshesh JainandGitHub 892a30c3a8 fix: web-view action permission, logs, archive issue, and more (#2356)
* fix: web-view

* feat: select module

* dev: select cycle & module

* fix: permissions, logs and archive issue

* fix: logs for issue select

fix: hard-coded web-view validation

* fix: attachment confirm delete workflow

* fix: typo

* fix: logging link instead of redirecting to the link

* fix: made editor height 100%

* style: due-date select

* fix: update comment not working

* fix: changed button text

style: spacing

* fix: due date select not working for today's date

* fix: typography

* fix: spacing in select parent
2023-10-12 12:28:36 +05:30
Anmol Singh BhatiaandGitHub 58ea4d6ec9 chore: integrate popper js (#2398)
* chore: react-popper-js added

* chore: integrate popper js in issue properties dropdown

* chore: integrate popper js in custom menu component

* chore: integrate popper js in custom select component

* chore: integrate popper js in custom search select component

* chore: popper js placement type added

* chore: popper js placement type added
2023-10-11 12:05:53 +05:30
sriram veeraghantaandGitHub d88eb09fad Merge pull request #2271 from makeplane/chore/date_filters
chore: date filters
2023-10-09 16:01:56 +05:30
NikhilandGitHub 0f31585620 Merge branch 'develop' into chore/date_filters 2023-10-09 16:00:52 +05:30
sriram veeraghantaandGitHub 0252734e00 Merge pull request #2400 from makeplane/dev/sync-ce-master-to-ee-develop
dev: sync CE master changes
2023-10-09 15:56:42 +05:30
Manish Gupta 4a380ae242 sync CE Master to EE Develop 2023-10-09 15:09:27 +05:30
sriram veeraghantaandGitHub 5ac2c270f7 Merge pull request #2385 from makeplane/stage-release
release: stage release to master
2023-10-05 14:52:59 +05:30
sriram veeraghantaandGitHub af61054c0b Merge pull request #2384 from makeplane/develop
promote: develop to stage release
2023-10-05 14:03:37 +05:30
guru_sainathandGitHub 67922f9c5d fix: removed default theme setting in the index page (#2382)
* fix: removed default theme setting in the index page

* fix: empty space
2023-10-05 13:47:51 +05:30
Aaryan KhandelwalandGitHub 85147db85f fix: state group icons (#2381) 2023-10-05 13:08:27 +05:30
guru_sainathandGitHub 62035f3bad fix: added user store variables in mobx store observable (#2380) 2023-10-05 12:04:09 +05:30
1c43d313d1 chore: remove unnecessary OAuth envs (#2378)
* chore: remove unnecessary oauth envs

* merge conflicts resolved

* fix: adding new service

---------

Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>
2023-10-04 20:43:05 +05:30
Aaryan KhandelwalandGitHub 77c1b90e6b feat: default project cover images tab on the change cover popover (#2375)
* feat: default project cover images tab

* chore: remove unnecessary env vars from turbo.json
2023-10-04 20:04:35 +05:30
64af5e2e75 chore: project covers endpoint (#2370)
* chore: project covers endpoint

* dev: remove print logs

* dev: formatting

---------

Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>
2023-10-04 19:53:58 +05:30
Anmol Singh BhatiaandGitHub 4ec2811388 fix: project setting member role validation (#2369)
* fix: project setting member role validation

* chore: opacity removed from member setting page

* chore: member setting page validation
2023-10-04 19:24:13 +05:30
9482cc3a73 fix: 404 when redirecting user clicks on Sign In button (#2349)
* fix: 404 when redirecting user to login page

* fix: next_path redirection not working

* fix: authentication workflow update in plane deploy

---------

Co-authored-by: gurusainath <gurusainath007@gmail.com>
2023-10-04 19:21:04 +05:30
NikhilandGitHub 18bcf55f78 dev: configuration endpoint for frontend client (#2355)
* dev: configuration endpoint for frontend clients

* dev: configuration enable magic and email/password signup

* dev: update unsplash keys

* dev: add unsplash API and add  env for magic login
2023-10-04 19:06:38 +05:30
sriram veeraghantaandGitHub ea2c1e2d06 fix: login process validation based on api config (#2361) 2023-10-04 18:55:29 +05:30
guru_sainathandGitHub 48c65c9c95 fix: handle cross project issues in the sub-issues. (#2357) 2023-10-04 18:44:36 +05:30
Aaryan KhandelwalandGitHub d0a51d75f6 fix: table menu positioning (#2354) 2023-10-04 18:36:14 +05:30
Aaryan KhandelwalandGitHub 7bdca9c73a show current version in the help section dropdown (#2353) 2023-10-04 18:31:28 +05:30
guru_sainathandGitHub 05920a72a5 chore: layout access validation and switch in plane deploy issues route (#2351)
* chore: handled route validation and layout access validation in plane deploy issues

* chore: impoved validation condition
2023-10-04 18:17:34 +05:30
sriram veeraghantaandGitHub 7a3b556ae0 adding sync info in pr title (#2373) 2023-10-04 18:03:22 +05:30
Bavisetti NarayanandGitHub c2f0ae1ec5 chore: removed the issue draft log from my profile (#2368) 2023-10-04 17:47:46 +05:30
Bavisetti NarayanandGitHub fb6f6454df chore: member can change role (#2371) 2023-10-04 17:47:01 +05:30
guru_sainathandGitHub 1e9149d872 fix: themening validation in store init. (#2350) 2023-10-04 17:44:54 +05:30
NikhilandGitHub eacf543439 dev: update apiserver configuration files (#2348)
* dev: update apiserver configuration files

* dev: add email and minio redirection urls
2023-10-04 17:39:12 +05:30
Luis CruzandGitHub 981e2aafdd Update index.tsx (#2343)
Fixes #2342
2023-10-04 17:37:01 +05:30
sriram veeraghantaandGitHub 4017f6bc55 fix: sync job pr description escaped values fix (#2366) 2023-10-04 15:29:45 +05:30
sriram veeraghantaandGitHub db1bcdb54f fix: sync workflow fixes (#2365) 2023-10-04 15:07:01 +05:30
Dakshesh JainandGitHub cecdf890de fix: issue relation mutation and draft issue (#2340)
* fix: issue relation mutation and draft issue

* fix: 'New Issue' in gantt view

fix: emoji select going under

* fix: profile page typo
2023-10-04 14:55:41 +05:30
d9bd07886f dev: update sync workflow to run only when the source repo is configured (#2346)
* dev: update sync workflow to run only when the source repo is configured

* fix: naming convention changes

---------

Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>
2023-10-03 18:41:26 +05:30
sriram veeraghantaandGitHub 0d0cf3052a fix: updated readme fixes (#2339) 2023-10-02 16:13:27 +05:30
NikhilandGitHub 6d463ded1c dev: rename token variables (#2338) 2023-10-02 14:50:30 +05:30
NikhilandGitHub de33e6775f dev: update add permissions to the action (#2337) 2023-10-02 14:40:31 +05:30
NikhilandGitHub c96960955b fix: variable name for token (#2336) 2023-10-02 14:31:25 +05:30
sriram veeraghantaandGitHub 7eba4a5032 fix: ui package readme added (#2334) 2023-10-02 13:40:28 +05:30
NikhilandGitHub c56aacdd13 dev: create action to sync PR changes to the repo (#2333) 2023-10-02 13:28:43 +05:30
sriram veeraghantaandGitHub 3ff47b38f4 Merge pull request #2319 from makeplane/stage-release
release: stage release to master
2023-09-29 20:12:15 +05:30
sriram veeraghantaandGitHub 25a973aaf9 Merge pull request #2318 from makeplane/develop
promote: develop to stage release
2023-09-29 20:10:41 +05:30
Anmol Singh BhatiaandGitHub ebf79ee2aa fix: workspace view add issue mutation fix (#2317) 2023-09-29 20:02:56 +05:30
sriram veeraghantaandGitHub f001852f69 Merge pull request #2316 from makeplane/develop
promote: develop to stage release
2023-09-29 19:25:02 +05:30
NikhilandGitHub 6b5fe0fff1 fix: update build workflow for the deploy app (#2315) 2023-09-29 19:14:07 +05:30
Anmol Singh BhatiaandGitHub f73135c504 fix: workspace view redirection fix, style: spreadsheet view shadow scroll fix (#2314)
* fix: workspace view redirection fix

* style: spreadsheet view scroll shadow fix
2023-09-29 19:03:36 +05:30
sriram veeraghantaandGitHub f751619759 Merge pull request #2312 from makeplane/stage-release
release: stage release to production
2023-09-29 18:08:04 +05:30
sriram veeraghantaandGitHub adf49782ba Merge pull request #2311 from makeplane/develop
promote: develop to stage release
2023-09-29 18:07:14 +05:30
sriram veeraghantaandGitHub 843ba5bb63 fix: unsplash api fix (#2310) 2023-09-29 18:06:33 +05:30
sriram veeraghantaandGitHub 8d4ac9b430 Merge pull request #2309 from makeplane/stage-release
release: stage release to production
2023-09-29 17:40:17 +05:30
sriram veeraghantaandGitHub 7c125075b1 Merge pull request #2308 from makeplane/develop
promote: develop to stage release
2023-09-29 16:43:11 +05:30
Anmol Singh BhatiaandGitHub 4cab00ec79 fix: workspace view filters count fix (#2307) 2023-09-29 16:40:02 +05:30
Bavisetti NarayanandGitHub f1879a404d dev: updated migrations for 0.13-dev (#2305)
* chore: epoch migration batch size changed

* chore: reoredered the migration files

* dev: updated migrations for 0.13-dev

* chore: added epoch field

* dev: merged the migration files
2023-09-29 16:12:54 +05:30
sriram veeraghantaandGitHub 26bb51e686 Merge pull request #2304 from makeplane/develop
promote: develop to stage release
2023-09-29 15:31:31 +05:30
Anmol Singh BhatiaandGitHub 1b28125919 chore: workspace view order by removed (#2303) 2023-09-29 15:29:50 +05:30
Anmol Singh BhatiaandGitHub 2a770e4a95 chore: workspace view mutation fix ,bug fixes and code refactor (#2301)
* chore: workspace view mutation fix ,bug fixes and code refactor

* chore: update workspace view toast alert added
2023-09-29 15:05:27 +05:30
sriram veeraghantaandGitHub fbf88c3196 Merge pull request #2296 from makeplane/develop
promote: develop to stage release
2023-09-29 12:52:23 +05:30
Anmol Singh BhatiaandGitHub 459999e8c9 chore: global issues ui improvement and bug fixes (#2300) 2023-09-29 12:36:38 +05:30
NikhilandGitHub 6cb4b222d0 chore: label create error (#2299) 2023-09-29 12:27:19 +05:30
a048e513b7 chore: workspace view display filters and properties , code refactor (#2295)
* chore: spreadsheet view context

* chore: spreadsheet context provider

* chore: spreadsheet view context

* chore: display filters and properties added in workspace view and code refactor

* fix: build error fix

* chore: set sub-issue display option to false for global views

---------

Co-authored-by: gurusainath <gurusainath007@gmail.com>
2023-09-28 20:34:57 +05:30
NikhilandGitHub 4503810aeb fix: notifications for created by me and assigned to me (#2292) 2023-09-28 15:37:42 +05:30
Dakshesh JainandGitHub ec91a0d2e5 fix: ui and bugs (#2289)
* fix: 24 character limit on first & last name in onboarding page

* fix: no option: 'Add Issue' in archive issue page

* fix: in archive issue directly sending to issue detail page

* fix: issue type showing in archive issue

* fix: custom menu overflowing

* fix: changing subscriber in filters has no effect

* style: border in quick-add

* fix: on onboarding member role overflowing

* fix: inconsistent icons in issue detail

* style: spacing, borders and shadows in quick-add

* fix: custom menu truncate
2023-09-28 14:02:03 +05:30
Anmol Singh BhatiaandGitHub 60a69e28e3 fix: issue activity estimate value bug fix (#2281)
* fix: issue activity estimate value bug fix

* fix: activity typo fix
2023-09-28 13:18:35 +05:30
Aaryan KhandelwalandGitHub 34af666b5f chore: fetch issues from previous and next month in the calendar view (#2282) 2023-09-28 13:17:46 +05:30
6afbd3f1ba chore: views (#2288)
* chore: global views order by

* chore: update permissions for global views

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2023-09-28 13:16:31 +05:30
NikhilandGitHub 32d2f912f7 fix: inbox issue deletes (#2290) 2023-09-28 13:14:37 +05:30
Bavisetti NarayanandGitHub 9dd22f07f4 chore: removed project filter (#2284) 2023-09-27 19:16:22 +05:30
Bavisetti NarayanandGitHub 7404fe71b1 chore: sort order and issue props for global views (#2283) 2023-09-27 17:30:52 +05:30
sriram veeraghantaandGitHub dc2b4de95e Merge pull request #2265 from makeplane/stage/dev-release-0-13
promote: Develop to Stage
2023-09-27 15:16:06 +05:30
sriram veeraghanta 62ba8d5e9f Merge branch 'develop' of github.com:makeplane/plane into stage/dev-release-0-13 2023-09-27 15:08:15 +05:30
NikhilandGitHub 7b453dd6b5 chore: make target dates inclusive when filtering (#2276) 2023-09-27 15:06:23 +05:30
Bavisetti NarayanandGitHub 191aecaaac chore: updated created at in draft issue (#2278) 2023-09-27 15:05:49 +05:30
Dakshesh JainandGitHub e00ae0b48a style: calender quick-add same width as single date (#2280)
* style: calender quick-add same width as single date

* style: margin bottom in quick-add in spreadsheet view

* fix: quick add opening in list-layout

* style: reduced margin left
2023-09-27 15:02:35 +05:30
Aaryan KhandelwalandGitHub a243bb6a15 chore: gantt chart empty state (#2279)
* chore: gantt empty state

* chore: Add heading to the gantt sidebar
2023-09-27 14:53:26 +05:30
Aaryan KhandelwalandGitHub b3be363b00 chore: handle calendar date range in frontend (#2277) 2023-09-27 14:41:32 +05:30
pablohashescobar 42d3919459 dev: refactor date filters to a single function 2023-09-27 14:33:41 +05:30
Aaryan KhandelwalandGitHub 5298f1e53c fix: block click happening while moving (#2275) 2023-09-27 13:08:35 +05:30
Dakshesh JainandGitHub 2d8cbccfbc style: gantt layout quick-add padding (#2272)
* fix: 'Last Drafted Issue' making sidebar look weird on collapsed

* feat: scroll to the bottom when issue is created

* fix: 'Add Issue' button overlapping issue card in spreadsheet view

* fix: wrong placement of quick-add in calender layout

* fix: spacing for issue card in spreadsheet view

* style: gantt layout quick-add padding

style: removed 'State group' from draft issue

* style: decrese shadow, quick-add position on calender layout, and 'add issue' sticky

* style: button color
2023-09-27 08:51:29 +05:30
Anmol Singh BhatiaandGitHub 3a6d72e4b6 feat: workspace global view, style: spreadsheet view revamp (#2273)
* chore: workspace view types, services and hooks added

* style: spreadsheet view revamp and code refactor

* feat: workspace view

* fix: build fix

* chore: sidebar workspace issues redirection updated
2023-09-26 19:56:59 +05:30
sriramveeraghanta 698b42768e Merge branch 'develop' of github.com:makeplane/plane into stage/dev-release-0-13 2023-09-26 19:49:44 +05:30
Aaryan KhandelwalandGitHub a187e7765c fix: user dashboard greeting timezone (#2267)
* chore: user greeting timezone

* fix: group by labels not working on workspace level
2023-09-26 18:08:01 +05:30
4c333d5767 chore: add instructions to contributing guide (#2270)
* chore: add instructions to contributing guide

* dev: update contributing.md to use the new configuration

---------

Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com>
2023-09-26 18:06:48 +05:30
Dakshesh JainandGitHub b317a14983 fix: bugs in quick-add and draft issues (#2269)
* fix: 'Last Drafted Issue' making sidebar look weird on collapsed

* feat: scroll to the bottom when issue is created

* fix: 'Add Issue' button overlapping issue card in spreadsheet view

* fix: wrong placement of quick-add in calender layout

* fix: spacing for issue card in spreadsheet view
2023-09-26 17:35:51 +05:30
pablohashescobar 608ba9d5cb chore: udpate date filters to support dynamic options 2023-09-26 16:33:49 +05:30
Bavisetti NarayanandGitHub 6e0999c35a dev: re-split migrations into two different files (#2268)
* dev: split issue activity migration separate files

* dev: resplit migrations into two different files

* dev: changed the batch size
2023-09-26 16:25:52 +05:30
Bavisetti NarayanandGitHub 52b57b1e37 dev: migration for 0.13 (#2266)
* dev: updated migrations

* dev: migration for 0.13
2023-09-26 14:18:06 +05:30
Henit ChobisaandGitHub 88a35efa06 [fix] nginx continuously rewriting and reloading on index page of spaces app (#2236)
* chore: shifted index page to /home route

* chore: added rewrite logic, to rewrite index to /home

* chore: routed home to login route as login page

* chore: updated nginx config to route to login

* chore: updated path for home
2023-09-26 13:46:38 +05:30
sriramveeraghanta ab028a317b chore: dev merge fixes 2023-09-26 13:32:13 +05:30
NikhilandGitHub d38594376b fix: n+1 queries for cycle list and project member endpoints (#2257) 2023-09-26 13:11:23 +05:30
NikhilandGitHub dae8ca6053 fix: issue automation iterable error (#2208) 2023-09-26 13:11:00 +05:30
Aaryan KhandelwalandGitHub 6d3bd78052 chore: add tooltip to show full time on activity logs (#2235) 2023-09-26 13:10:28 +05:30
guru_sainathandGitHub 1ad99873a9 feat: Add peek overview in sub issues and updated UI for empty states. (#2263) 2023-09-26 13:09:08 +05:30
Dakshesh JainandGitHub 7db78594dc fix: draft issue delete not working (#2249)
* fix: draft issue not deleting, project can't be changed in draft issue modal

* fix: removed mutation for view where draft issues are not shown

* fix: inline create issue for draft issue

* fix: clearing data from localstorage on discard click
2023-09-25 19:11:10 +05:30
Dakshesh JainandGitHub 5e8d523ed4 feat: quick-add placement in spreadsheet and gantt (#2259)
* feat: sticking quick-add at the bottom of the screen

fix: opening create issue modal instead of quick-add in draft-issues, my-issue and profile page

* fix: build error due to dynamic import
2023-09-25 19:08:26 +05:30
pablohashescobar d47efaa0f0 dev: remove auto filter endpoint 2023-09-25 16:51:59 +05:30
Anmol Singh BhatiaandGitHub de7a672b79 fix: bug fixes and ui improvement (#2250) 2023-09-25 16:15:49 +05:30
0e96eddb57 rename view to layout (#2255)
Co-authored-by: Your Name <you@example.com>
2023-09-25 13:38:49 +05:30
Anmol Singh BhatiaandGitHub afa10d7195 fix: sub issue state and member select build error (#2254) 2023-09-25 13:18:03 +05:30
Anmol Singh BhatiaandGitHub 68c8741f93 fix: bug fix related to fetching dropdown options for the profile issue (#2246) 2023-09-25 12:18:35 +05:30
Bavisetti NarayanandGitHub e8d303dd10 chore: changed priority props in workspace and project (#2253) 2023-09-22 19:48:07 +05:30
c9a6380636 style: settings page improvement (#2211)
* style: settings page improvement

* style: toggle switch styling

---------

Co-authored-by: Anmol Singh Bhatia <asb@Anmols-MacBook-Pro.local>
2023-09-22 18:47:10 +05:30
guru_sainathandGitHub 1aadbee7e2 fix: resolved pending issue graph in analytics, user wishes in dashboard, and typo in projects list (#2247) 2023-09-22 17:43:23 +05:30
Bavisetti NarayanandGitHub 02060f654c chore: added epoch in draft (#2244)
* chore: added epoch in draft

* chore: removed extra spaces
2023-09-22 16:32:53 +05:30
Dakshesh JainandGitHub 771ca585db feat: quick add (#2240)
* feat: quick add

* style: made text color muted
2023-09-22 15:31:54 +05:30
daa0b16960 fix: cycle and module stats when issues are archived (#2185)
* fix: cycle and module stats when issues are archived

* fix: added draft filter

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2023-09-22 15:17:31 +05:30
Bavisetti NarayanandGitHub 0005ff5f99 fix: changed priority from None to none (#2229) 2023-09-22 14:44:53 +05:30
Bavisetti NarayanandGitHub 0c7b7c4e94 chore: added state and priority order in workspace user profile (#2241) 2023-09-22 14:43:55 +05:30
4d835c5b4a chore: updated docker naming conventions (#2239)
* naming convention changes

* dev: update docker-compose-hub in consistent with docker-compose

* dev: updated docker container name

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2023-09-22 13:21:55 +05:30
Bavisetti NarayanandGitHub c7092edb61 fix: aws region name (#2234) 2023-09-22 13:00:13 +05:30
guru_sainathandGitHub 73afb8f4d8 fix: issues resolved in sub issues (#2238) 2023-09-21 19:12:20 +05:30
Aaryan KhandelwalandGitHub 978909c021 fix: profile issues layout switch (#2228) 2023-09-21 16:04:57 +05:30
Aaryan KhandelwalandGitHub de9f34cac3 fix: activity label color (#2227) 2023-09-21 16:04:05 +05:30
Aaryan KhandelwalandGitHub e3793f4464 fix: handle no issues in custom analytics (#2226) 2023-09-21 16:03:33 +05:30
Aaryan KhandelwalandGitHub 1621125f6d refactor: product updates modal layout (#2225) 2023-09-21 16:03:06 +05:30
guru_sainathandGitHub bd077e6500 Implemented nested issues in the sub issues section in issue detail page (#2233)
* feat: subissues infinte level

* feat: updated UI for sub issues

* feat: subissues new ui and nested sub issues in issue detail

* chore: removed repeated code
2023-09-21 15:39:45 +05:30
Bavisetti NarayanandGitHub 60ae940d40 chore: sub issues count in individual issue (#2221) 2023-09-20 17:00:03 +05:30
Dakshesh JainandGitHub cdfff12f4f fix: fields not getting selected in the create issue form (#2212)
* fix: hydration error and draft issue workflow

* fix: build error

* fix: properties getting de-selected after create, module & cycle not getting auto-select on the form

* fix: display layout, props being updated directly
2023-09-20 13:06:51 +05:30
e01a0d20fe chore: dynamic position dropdown (#2138)
* chore: dynamic position state dropdown for issue view

* style: state select dropdown styling

* fix: state icon attribute names

* chore: state select dynamic dropdown

* chore: member select dynamic dropdown

* chore: label select dynamic dropdown

* chore: priority select dynamic dropdown

* chore: label select dropdown improvement

* refactor: state dropdown location

* chore: dropdown improvement and code refactor

* chore: dynamic dropdown hook type added

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-09-20 12:24:52 +05:30
Bavisetti NarayanandGitHub 63c4792e70 fix: changed time to timestamp (#2217) 2023-09-19 21:36:39 +05:30
Bavisetti NarayanandGitHub ce562fa3ea fix: migration files (#2215) 2023-09-19 20:15:02 +05:30
Bavisetti NarayanandGitHub a6a0eb9774 chore: added epoch in issue activity (#2187) 2023-09-19 19:46:57 +05:30
Bavisetti NarayanandGitHub d603c1e8f0 fix: tracking logs for issue activity (#2213) 2023-09-19 19:46:03 +05:30
Bavisetti NarayanandGitHub 405ef9314f feat: workspace views (#2005)
* feat: workspace views

* fix: added project member filter

* fix: added pagination in workspace views

* fix: filters and group up by for workspace issues

* fix: changed name workspace view to global view

* fix: reordered the urls
2023-09-19 19:45:37 +05:30
NikhilandGitHub 926d2ae0a0 dev: self hosted settings file (#2202)
* dev: self hosted settings file

* dev: add analytics and dockerized variable in settings

* dev: update .env.example and docker compose file also

* dev: self hosted setup minio
2023-09-19 18:30:56 +05:30
11258686ad [fix]: Removing dependency on tiptap pro extension (#2209)
* removing dependency on tiptap pro extension

* updated docs to remove tiptap pro setup instructions

* chore: removed pro extension promt from setup.sh

* chore: Removed Pro Extension Setup from CI

---------

Co-authored-by: Henit Chobisa <chobisa.henit@gmail.com>
2023-09-19 16:44:12 +05:30
Dakshesh JainandGitHub f6b92fc953 fix: activity not coming for blocking/blocked, 'related to' and duplicate (#2189)
* fix: activity not coming for duplicate, relatesd to and for blocked/blocking

refactor: mutation logic to use relation id instead of issue id

* fix: mutation logic and changed keys to be aligned with api

* fix: build error
2023-09-19 12:58:00 +05:30
Dakshesh JainandGitHub 79bf7d4c0c fix: hydration error and draft issue workflow (#2199)
* fix: hydration error and draft issue workflow

* fix: build error
2023-09-19 12:56:32 +05:30
Anmol Singh BhatiaandGitHub 5d331477ef chore: settings bug fixes and ui improvement (#2198)
* fix: settings bug fixes and ui improvement

* chore: setting sidebar scroll fix & code refactor
2023-09-15 19:30:53 +05:30
sriram veeraghantaandGitHub 3d72279edb Merge pull request #2196 from makeplane/fix/bug_fix
fix: document bug fix
2023-09-15 15:42:43 +05:30
Anmol Singh Bhatia c107b36d34 fix: document bug fix 2023-09-15 15:41:10 +05:30
Anmol Singh BhatiaandGitHub ccffbe1b4e style: workspace and profile setting revamp (#2193)
* chore: custom theme mode svg added

* style: workspace settings ui revamp

* style: project settings and image upload modal improvement

* style: profile setting ui revamp

* chore: settings ui improvement and bug fixes
2023-09-15 15:03:32 +05:30
Bavisetti NarayanandGitHub 9bfdcff44d chore: changed old values (#2194) 2023-09-15 14:18:30 +05:30
Bavisetti NarayanandGitHub b274a21ca5 chore: changed issue relation history logs (#2192)
* chore: changed issue relation history logs

* chore: change field name
2023-09-15 13:12:28 +05:30
Dakshesh JainandGitHub 32d945be0d fix: edit/delete for draft issue (#2190)
* fix: edit/delete

* fix: build issue

* fix: draft issue modal opening in kanban card
2023-09-15 12:51:10 +05:30
Dakshesh JainandGitHub eda4da8aed feat: draft issues (#2188)
* feat: draft issue

issues can be saved as draft

* style: modal position
2023-09-14 18:38:31 +05:30
sriram veeraghantaandGitHub 759a604cb8 fix: posthog integration (#2186) 2023-09-14 16:38:41 +05:30
sriram veeraghantaandGitHub 6659cfc8b0 fix: track events issue and env variables fixes (#2184)
* fix: track event fixes

* fix: adding env variables to trubo
2023-09-14 16:05:31 +05:30
Bavisetti NarayanandGitHub a53b428bbd chore: endpoints and history logs for issue draft (#2180)
* chore: history logs for issue draft

* fix: created seperated endpoints for issue drafts

* fix: fixed the typo
2023-09-14 15:38:11 +05:30
Bavisetti NarayanandGitHub 4e0e02522f fix: changed payload for issue subgroups (#2181)
* fix: sub groups in cycle module and my issues

* fix: changed payload for issue subgroups
2023-09-14 15:29:35 +05:30
sriram veeraghantaandGitHub f983d787b4 env and docker fixes (#2182) 2023-09-14 12:26:07 +05:30
Anmol Singh BhatiaandGitHub 87abf3ccb1 style: project setting ui revamp (#2177)
* style: project settings navigation sidebar added

* chore: emoji and image picker close on outside click added

* style: project setting general page revamp

* style: project setting member page revamp

* style: project setting features page revamp

* style: project setting state page revamp

* style: project setting integrations page revamp

* style: project setting estimates page revamp

* style: project setting automation page revamp

* style: project setting label page revamp

* chore: member select improvement for member setting page

* chore: toggle switch component improvement

* style: project automation setting ui improvement

* style: module icon added

* style: toggle switch improvement

* style: ui and spacing consistency

* style: project label setting revamp

* style: project state setting ui improvement

* chore: integration setting repo select validation

* chore: code refactor

* fix: build fix
2023-09-13 23:09:55 +05:30
d0f6ca3bac [chore] Update setup.sh, with removed replacement script & added project-level ENVs (#2115)
* chore: Updated Setup Script for Splitting Env File

* chore: updated dockerfile for using inproject env varaibles

* chore: removed husky replacement script

* chore: updated shell script using sed

* chore: updated dockerfiles with removed cp statement

* chore: added example env for apiserver

* chore: refactored secret generation for backend

* chore: removed replacement script

* chore: updated docker-compose with removed env variables

* chore: resolved comments in setup.sh and docker-compose

* chore: removed secret key placeholder in apiserver example env

* chore: updated root env for project less env variables

* chore: removed project level env update from root env logic

* chore: updated API_BASE_URL in .env.example

* chore: restored docker argument as env NEXT_PUBLIC_API_BASE_URL

* chore: added pg missing env variables

* [chore] Updated web and deploy backend configuration for reverse proxy & decoupled Plane Deploy URL generation for web (#2135)

* chore: removed api url build arg from compose

* chore: set public api default argument to black string for self hosted

* chore: updated web services to accept blank string as API URL

* chore: added env variables for pg compose service

* chore: modified space app services to use accept empty string as api base

* chore: conditionally trigger web url value based on argument

* fix: made web to use identical host with spaces suffix on absense of Deploy URL for deploy

* chore: added example env for PUBLIC_DEPLOY Env

* chore: updated web dockerfile with addition as PLANE_DEPLOY Argument

* API BASE URL global update

* API BASE URL replace with api server

* api base url fixes

* typo  fixes

---------

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

* dev: remove API_BASE_URL from environment variable

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
2023-09-13 20:21:02 +05:30
af73bbe718 typo: changed customize to customise in project automation settings (#2153)
Co-authored-by: Neo <neo@Neos-MacBook-Pro.local>
2023-09-13 20:15:48 +05:30
Bavisetti NarayanandGitHub 9033ceb03c fix: sub groups in cycle module and my issues (#2176) 2023-09-13 19:50:34 +05:30
Dakshesh JainandGitHub 9bac7cb036 feat: issue link to create relation between issues (#2171)
* feat: issue linking

* fix: search params to filter out selected issue

* style: changed icons

* fix: build error on web-view

* fix: build error

* fix: build error on web-view component
2023-09-13 19:41:11 +05:30
Anmol Singh BhatiaandGitHub 32d08570e7 chore: peek overview for issue view and my issue view (#2172)
* chore: peak overview for issue view and my issue view

* fix: profile issue peak overview mutation fix

* chore: code refactor

* fix: image prefix url fix
2023-09-13 19:33:58 +05:30
Bavisetti NarayanandGitHub 1b1ed37405 chore: changed default props for worskpace and project members (#2175) 2023-09-13 19:13:31 +05:30
Bavisetti NarayanandGitHub 42d38f7531 feat: changed payload for swimlanes (#2173) 2023-09-13 18:25:57 +05:30
Bavisetti NarayanandGitHub 61672f47ac fix: migration files (#2169) 2023-09-13 13:23:40 +05:30
Dakshesh JainandGitHub 23e62c83eb refactor: switched priority null -> 'none' (#2166) 2023-09-13 13:22:31 +05:30
sriram veeraghantaandGitHub e58b76c8a6 fix: tailwind common config (#2168) 2023-09-13 12:50:04 +05:30
Anmol Singh BhatiaandGitHub 4ce01ca4f8 fix: calendar issues display filters loop fix (#2167) 2023-09-13 12:37:58 +05:30
Bavisetti NarayanandGitHub a34b0b059d feat: add a relation to an issue (#1995)
* feat: add issue relation to an issue

* fix: deleted the migration file

* fix: changed link to relates to in choice fields

* fix: added the migration file

* fix: changed migration file

* fix: project id issue fixed

* fix: added issue in the payload

* fix: changed the query param for blocker
2023-09-13 12:25:10 +05:30
Bavisetti NarayanandGitHub 164e0b9301 chore: changed view props (#2146)
* chore: changed view props

* fix: changed the keywords
2023-09-13 12:12:21 +05:30
Bavisetti NarayanandGitHub 5a91031243 feat: issue drafts (#2161) 2023-09-13 12:10:22 +05:30
Bavisetti NarayanandGitHub 47bec7704b chore: priority migration (#2162) 2023-09-13 12:06:38 +05:30
sriram veeraghantaandGitHub b9c935092a chore: eslint config package fixes (#2165)
* eslint fixes

* lint rules added
2023-09-13 12:06:17 +05:30
Aaryan KhandelwalandGitHub 3a2a329000 fix: view props undefined (#2160) 2023-09-12 22:51:13 +05:30
Aaryan KhandelwalandGitHub 8e9a4dca78 refactor: view props structure (#2159)
* chore: update view_props types

* refactor: view props structure
2023-09-12 22:27:15 +05:30
sriram veeraghantaandGitHub 2bc05cc7b7 Merge pull request #2157 from makeplane/stage-release
release: self hosting fixes
2023-09-12 21:50:34 +05:30
sriram veeraghantaandGitHub 14fe545709 Merge pull request #2156 from makeplane/stage/self-hosted-fixes
fix: selfhosted fixes
2023-09-12 21:06:01 +05:30
sriram veeraghanta bc99ec0f1d fix: selfhosted fixes (#2154)
* fix: selfhosted fixes

* fix: updated env example
2023-09-12 21:03:42 +05:30
sriram veeraghantaandGitHub cdb888c23e fix: selfhosted fixes (#2154)
* fix: selfhosted fixes

* fix: updated env example
2023-09-12 20:32:26 +05:30
Dakshesh JainandGitHub 2186db8bba feat: users can select timezone during onboarding (#2148)
feat: using Intl timezone will be automatically selected but they have the option to change it
2023-09-12 13:35:15 +05:30
Bavisetti NarayanandGitHub 9bff10de6d chore: changed issue priority from NULL to none (#2142)
* chore: changed issue priority from NULL to none

* fix: deleted the migration file
2023-09-12 13:06:49 +05:30
sriram veeraghantaandGitHub cc63f67654 Merge pull request #2143 from makeplane/stage-release
promote: stage-release to master
2023-09-11 18:21:51 +05:30
sriram veeraghantaandGitHub b8dd9ca729 Merge pull request #2144 from makeplane/develop
promote: develop to stage release
2023-09-11 18:11:30 +05:30
Henit ChobisaandGitHub 6867154963 chore: added pre-release tag for workflow publications (#2133)
* chore: added pre-release tag for workflow publications

* chore: added backend services under a single image

* chore: exposed backend port for compose hub

* chore: removed backend exposed ports
2023-09-11 18:02:56 +05:30
NikhilandGitHub 73b360c2fd Merge pull request #2140 from makeplane/develop
promote: develop to stage-release
2023-09-11 14:39:11 +05:30
Aaryan KhandelwalandGitHub 7bb73b74ba refactor: priority icon component (#2132) 2023-09-11 14:35:58 +05:30
Dakshesh JainandGitHub 991258084e fix: query checking (#2137)
fix: the logic should be to check if object exist not if it's true or false
2023-09-11 13:21:50 +05:30
ThomasandGitHub 1a37668f0b fix: husky was removed in commit #2086, but prepare still uses it (#2128) 2023-09-11 13:05:09 +05:30
4447a4b519 fix: Tiptap comment card fix for space (#2129)
* fix:(space) fixed comment card's editor integration

* regression: removed content being set twice

* chore: added controller to manage tiptap editor

* chore: remove unused functions

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-09-11 12:54:19 +05:30
Dakshesh JainandGitHub 7842c4b2ea fix: authorize editor (#2122) 2023-09-11 12:24:46 +05:30
Aaryan KhandelwalandGitHub 8de93d0081 chore: remove getServerSideProps (#2130) 2023-09-11 12:13:00 +05:30
Aaryan KhandelwalandGitHub 5b228bd1eb chore: update state icons and colors (#2126)
* chore: update state icons and colors

* chore: update icons
2023-09-11 11:45:28 +05:30
Dakshesh JainandGitHub ad8a011bb9 fix: issue activity (#2127) 2023-09-11 11:44:16 +05:30
Aaryan KhandelwalandGitHub 49d0b3f4a1 fix: handleClose function of the export modal (#2124) 2023-09-08 13:29:06 +05:30
Aaryan KhandelwalandGitHub 1872dff00d fix: custom date filter not working on my issues and profile issues (#2123) 2023-09-08 13:28:32 +05:30
Dakshesh JainandGitHub faa6a2bcbc feat: select blocker, blocking, and parent (#2121)
* feat: update, delete link

refactor: using old fetch-key

* feat: issue activity with ability to view & add comment

feat: click on view more to view more options in the issue detail

* fix: upload image not working on mobile

* feat: select blocker, blocking, and parent

dev: auth layout for web-view, console.log callback for web-view

* style: made design consistant

* fix: displaying page only on web-view

* style: removed overflow hidden
2023-09-07 18:42:24 +05:30
sriram veeraghantaandGitHub 6d52707ff5 editor fixes for space (#2119) 2023-09-07 15:09:16 +05:30
NikhilandGitHub 8ba482bc9c chore: response status for project views update (#2111)
* chore: response status for project views update

* dev: remove 200 OK response from empty contents
2023-09-07 14:49:45 +05:30
Aaryan KhandelwalandGitHub 5989f2476a fix: update plane logo (#2118) 2023-09-07 13:41:29 +05:30
Aaryan KhandelwalandGitHub 8ea6dd4e84 fix: onboarding role select dropdown text color (#2117) 2023-09-07 13:40:50 +05:30
NikhilandGitHub 39bc975994 fix: remove triage issue status from public boards (#2110) 2023-09-07 13:21:58 +05:30
NikhilandGitHub 866eead35f fix: issue comment ordering for public boards (#2108) 2023-09-07 13:21:05 +05:30
NikhilandGitHub 9c3510851d dev: update python packages (#2095) 2023-09-07 13:20:32 +05:30
Aaryan KhandelwalandGitHub 81436902a3 chore: option to switch access of a comment (#2116) 2023-09-07 12:54:30 +05:30
Aaryan KhandelwalandGitHub d26aa1b2da chore: render proper icons for due dates (#2114) 2023-09-07 12:53:49 +05:30
M. PalanikannanandGitHub b47c7d363f fix: Improved Image Deletion Logic, Image ID Issue in Modals and Performance Optimization in Editor (#2092)
* added improved delete logic in modals

* added better ts support

* impoved complexity to O(1) from O(n) for large docs

* regression: removed ts nocheck
2023-09-07 12:22:02 +05:30
Aaryan KhandelwalandGitHub 85f797058d fix: edit issue comment mutation (#2109) 2023-09-06 19:02:59 +05:30
Dakshesh JainandGitHub 1655d0cb1c feat: view, create, update and delete comment (#2106)
* feat: update, delete link

refactor: using old fetch-key

* feat: issue activity with ability to view & add comment

feat: click on view more to view more options in the issue detail

* fix: upload image not working on mobile
2023-09-06 17:08:19 +05:30
Anmol Singh BhatiaandGitHub 58562dc4b7 fix: ui improvement and bug fixes (#2105)
* chore: workspace level typo fix

* fix: setting opacity fix
2023-09-06 16:15:21 +05:30
NikhilandGitHub 2ad46d7bfa fix: public issue list endpoint n+1 (#2099) 2023-09-06 16:04:12 +05:30
NikhilandGitHub 4f0cac37db fix: issue object for filtering (#2102) 2023-09-06 16:03:41 +05:30
sriram veeraghantaandGitHub b46a7481ae Merge pull request #2101 from makeplane/fix/gantt_issues
fix: don't render invalid dated blocks on the gantt chart
2023-09-06 15:00:03 +05:30
sriram veeraghantaandGitHub f11ae00201 Merge pull request #2100 from makeplane/feat/comment_reactions
feat: plane space comment reactions
2023-09-06 14:52:55 +05:30
Aaryan Khandelwal c5612ee7a3 fix: don't render invalid dated cycles and modules 2023-09-06 12:26:51 +05:30
Aaryan Khandelwal 0dd336aec8 fix: don't render invalid dated issues 2023-09-06 12:25:34 +05:30
sriram veeraghantaandGitHub 4b364f72b5 Merge pull request #2096 from makeplane/fix/login_redirection
fix: redirection after signing in on space
2023-09-06 12:21:08 +05:30
Aaryan Khandelwal 6d13332818 style: add shadow to reaction selector 2023-09-06 12:17:47 +05:30
Aaryan Khandelwal ac4127c93d chore: add tooltip for user info 2023-09-06 12:04:18 +05:30
Aaryan Khandelwal 60c3d1a6e9 feat: comment reactions 2023-09-06 11:59:57 +05:30
sriram veeraghantaandGitHub 70ed3c1fdf Merge pull request #2097 from makeplane/feat/issue_detail_for_webview
feat: issue detail for web-view
2023-09-05 17:56:33 +05:30
dakshesh14 b40059ea21 feat: add links and permission to perform actions
refactor: divided file into components
2023-09-05 17:06:17 +05:30
Bavisetti NarayanandGitHub 90276073cd fix: validation in automation task (#2094) 2023-09-05 16:53:53 +05:30
Aaryan Khandelwal 8d5ff1a628 fix: redirection after signing in on space 2023-09-05 16:12:17 +05:30
dakshesh14 065a4a3cf7 feat: issue detail for web view 2023-09-05 14:42:34 +05:30
MengYXandGitHub 928ae775f4 fix: replace Completion with ChatCompletion to use gpt-3.5-turbo model (#2066)
According to https://platform.openai.com/docs/guides/gpt/chat-completions-vs-completions , GPT-3.5 Turbo & GPT-4 is not working on **Legacy** Completions API
2023-09-05 13:11:31 +05:30
sriram veeraghantaandGitHub 80bcca71ff Merge pull request #2091 from makeplane/stage-release
promote: stage release to master
2023-09-04 19:14:29 +05:30
sriram veeraghantaandGitHub 3db0ec819a Merge pull request #2090 from makeplane/develop
Promote: Develop to Stage Release
2023-09-04 18:53:41 +05:30
Aaryan KhandelwalandGitHub 900a4fcb0e fix: profile time according to the user timezone (#2089) 2023-09-04 18:48:33 +05:30
19c65b26d6 dev: docker environment deploy fixes (#2087)
* chore: updated space and web dockerfiles

* chore: updated compose file params

* updated nextjs config for basepath

* chore: updated package.json with new packages

* chore: modified space and web dockerfiles

* dev: update deploy configuration for deploy images

* dev: update docker folder for web

* dev: add semi colon for module exports

---------

Co-authored-by: Henit Chobisa <chobisa.henit@gmail.com>
2023-09-04 18:46:10 +05:30
Anmol Singh BhatiaandGitHub 71394d3316 chore: add issue option removed from subscribed issue page (#2088)
* chore: condition for subscribed page add issue option

* chore: condition for subscribed page add issue option
2023-09-04 18:42:31 +05:30
sriram veeraghantaandGitHub 414ea7371d Merge pull request #2085 from makeplane/develop
Promote: Develop to Stage Release
2023-09-04 18:16:47 +05:30
sriram veeraghantaandGitHub 9423472838 Env Fixes (#2086)
* fixing env issues

* removing husky
2023-09-04 18:03:31 +05:30
sriram veeraghantaandGitHub 729eabdd3f next config fixes in space app (#2084) 2023-09-04 17:55:40 +05:30
Aaryan KhandelwalandGitHub 03f204a71c chore: invalid url content (#2082) 2023-09-04 17:27:29 +05:30
guru_sainathandGitHub faf5a274cb fix: mutation latency in sidebar projects when user leaves the project (#2083)
* fix: mutation latency in sidebar projects when user leaves the project

* chore: remove console
2023-09-04 17:24:52 +05:30
Aaryan KhandelwalandGitHub 2c9c8d5a89 feat: landing page after logging in (#2081) 2023-09-04 16:55:43 +05:30
Aaryan KhandelwalandGitHub 5e02ad8104 fix: project invite modal members filter function (#2080) 2023-09-04 16:35:28 +05:30
f554ad95e9 fix: favicon path on Plane space (#2077)
* fix: favicon path

* chore: add webmanifest file

* favicon fixes with nginx

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
2023-09-04 16:34:53 +05:30
Aaryan KhandelwalandGitHub 59b69d3072 chore: add the env example files (#2078) 2023-09-04 15:59:51 +05:30
ccbb54bb87 feat: Leaving from project for viewer and guest roles has implemented (#2079)
* feat: leave project services and components

* feat: Leaving from project for viewer and guest roles has implemented

---------

Co-authored-by: dakshesh14 <dakshesh.jain14@gmail.com>
2023-09-04 15:53:46 +05:30
sriram veeraghantaandGitHub 2b84b7c18d Merge pull request #2076 from makeplane/develop
Promote: Develop to Stage Release
2023-09-04 14:48:53 +05:30
Aaryan KhandelwalandGitHub 8f46492c42 fix: copy link button not working on the peek overview (#2075)
* fix: copy issue link from the peek overview

* refactor: peek overview layout
2023-09-04 14:47:28 +05:30
NikhilandGitHub 58e23304a7 fix: state ordering for projects (#2073) 2023-09-04 14:38:39 +05:30
NikhilandGitHub dc26e1ea50 chore: cycle update errors (#2070) 2023-09-04 14:37:29 +05:30
Aaryan KhandelwalandGitHub f583789584 chore: add authorization to the gantt chart (#2074) 2023-09-04 13:08:49 +05:30
Aaryan KhandelwalandGitHub 9d9c1a86bf fix: state group icon (#2072) 2023-09-04 13:02:47 +05:30
Aaryan KhandelwalandGitHub 4559a1bd5d refactor: publish project store (#2068) 2023-09-04 12:34:12 +05:30
sriram veeraghantaandGitHub 0de62b3b0c removing gitpod config (#2071) 2023-09-04 12:08:58 +05:30
sriram veeraghantaandGitHub d3a9a764dc fix: space redirections (#2069) 2023-09-04 01:50:49 +05:30
sriram veeraghantaandGitHub 4ea52302ba fixing vercel deployments by switching next config using env (#2067) 2023-09-03 20:55:37 +05:30
1e152c666c New Directory Setup (#2065)
* chore: moved app & space from apps to root

* chore: modified workspace configuration

* chore: modified dockerfiles for space and web

* chore: modified icons for space

* feat: updated files for new svg icons supported by next-images

* chore: added /spaces base path for next

* chore: added compose config for space

* chore: updated husky configuration

* chore: updated workflows for new configuration

* chore: changed app name to web

* fix: resolved build errors with web

* chore: reset file tracing root for both projects

* chore: added nginx config for deploy

* fix: eslint and tsconfig settings for space app

* husky setup fixes based on new dir

* eslint fixes

* prettier formatting

---------

Co-authored-by: Henit Chobisa <chobisa.henit@gmail.com>
2023-09-03 18:50:30 +05:30
Aaryan KhandelwalandGitHub 20e36194b4 fix: peek overview layout switch (#2064) 2023-09-03 11:25:37 +05:30
NikhilandGitHub 874d6e951b dev: updated error handling for project deploy board attributes (#2062)
* dev: updated error handling for project deploy board attributes

* dev: reaction integrity handling
2023-09-02 19:43:17 +05:30
Henit ChobisaandGitHub 63d799310b [chore] Added Husky for Automating Building and Linting Projects Before Push (#2032)
* chore: Added Husky as Root Dependency

* chore: Added Husky Prepush Script

* chore: Modified Husky Pre-Push Script to Conditionally Build Projects

* chore: added husky as dev dependency
2023-09-02 13:47:21 +05:30
M. PalanikannanandGitHub abe8df4eca added table-icons for left,right columns and top,bottom rows (#2061) 2023-09-02 00:45:34 +05:30
sriram veeraghantaandGitHub 674347c99e Merge pull request #2060 from makeplane/develop
Promote: Develop to Stage Release
2023-09-01 20:56:31 +05:30
Aaryan KhandelwalandGitHub 0196fee7e3 fix: sidebar cycle and module select (#2056) 2023-09-01 20:54:14 +05:30
sriram veeraghantaandGitHub c1102180e6 Merge pull request #2059 from makeplane/develop
Promote: Develop to Stage Release
2023-09-01 20:53:35 +05:30
sriram veeraghantaandGitHub a6cd0809fa Merge pull request #2058 from makeplane/dev-deploy-fixes
fix: speeding up reactions and votes 🚀
2023-09-01 20:52:42 +05:30
sriram veeraghanta 2155a336ed peekover mutation fixes 2023-09-01 20:52:12 +05:30
sriram veeraghanta 1732945ec6 fix: speeding up reactions and votes 🚀 2023-09-01 20:38:53 +05:30
NikhilandGitHub 71c8f79276 fix: issue vote constraints (#2057) 2023-09-01 18:46:38 +05:30
sriram veeraghantaandGitHub 650c0c3b78 promote: develop to stage-release (#2045)
promote: develop to stage-release
2023-09-01 17:23:26 +05:30
Aaryan KhandelwalandGitHub f71a62f142 style: sign in page bg color (#2055) 2023-09-01 17:21:52 +05:30
Aaryan KhandelwalandGitHub 54d781ef91 fix: auth screens (#2054) 2023-09-01 17:10:06 +05:30
Anmol Singh BhatiaandGitHub 441e83eba6 fix: notification count mutation fix (#2053) 2023-09-01 17:08:02 +05:30
Anmol Singh BhatiaandGitHub 74bf9062b4 chore: bug fixes and ui/ux enhancements (#2036) 2023-09-01 16:52:44 +05:30
8a95a41100 feat: Converting space app to pages dir (#2052)
Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: Bavisetti Narayan <narayan@Bavisettis-MacBook-Pro.local>
Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com>
Co-authored-by: M. Palanikannan <73993394+Palanikannan1437@users.noreply.github.com>
Co-authored-by: Lakhan Baheti <94619783+1akhanBaheti@users.noreply.github.com>
Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
2023-09-01 16:42:30 +05:30
NikhilandGitHub c03550656a chore: vote actor details (#2047)
* chore: vote actor details

* dev: add field in serializer

* dev: remove _id in workspace and project
2023-09-01 15:58:25 +05:30
NikhilandGitHub 82a48d4805 chore: reaction serializers (#2046)
* dev: chore reaction serializers

* fix: remove issue reaction lite serializer
2023-09-01 15:55:21 +05:30
NikhilandGitHub f4fa2e011a feat: leave project and workspace endpoint (#2042)
* feat: leave project and workspace endpoint

* fix: argument error

* dev: update endpoint status
2023-09-01 15:55:06 +05:30
Kritika UpadhyayandGitHub 42ece0d784 chore: updates project invite placeholder (#2049) 2023-09-01 15:37:27 +05:30
Bavisetti NarayanandGitHub 1e9f0823f8 fix: imported uuid (#2048) 2023-09-01 14:41:20 +05:30
Aaryan KhandelwalandGitHub 4ba3ef5c24 fix: peek overview bugs (#2043)
* fix: side peek modal shaking

* refactor: peek overview layout

* fix: date selector, activity mutation

* fix: delete issue handler

* fix: assignees mutation
2023-09-01 13:52:55 +05:30
NikhilandGitHub c6d9ace6a2 dev: migrations for v0.12 release (#2044) 2023-09-01 13:20:52 +05:30
Aaryan KhandelwalandGitHub 0d4bcd2758 fix: Gantt chart bugs (#2024)
* fix: only left mouse button should trigger all the events

* fix: extra block shadow
2023-09-01 11:23:43 +05:30
NikhilandGitHub 3a0d96a48d chore: cycle endpoint to return display name as well in the assignee distribution (#2041)
* chore: cycle endpoint to return display name as well in the assignee distribution

* fix: value error
2023-09-01 11:21:34 +05:30
Lakhan BahetiandGitHub eab1d9329b feat: editor for issue description (#2038) 2023-09-01 10:59:17 +05:30
NikhilandGitHub 099bce87b5 chore: public board endpoints (#2030) 2023-09-01 00:08:40 +05:30
NikhilandGitHub b496a62540 fix: subscribed issues are filtering (#2037) 2023-08-31 19:07:56 +05:30
Aaryan KhandelwalandGitHub af929ab741 style: tiptap table (#2033) 2023-08-31 16:30:28 +05:30
M. PalanikannanandGitHub 38b7f4382f [feat]: Tiptap table integration (#2008)
* added basic table support

* fixed table position at bottom

* fixed image node deletion logic's regression issue

* added compatible styles

* enabled slash commands

* disabled slash command and bubble menu's node selector for table cells

* added dropcursor support to type below the table/image

* blocked image uploads for handledrop and paste actions
2023-08-31 13:41:41 +05:30
NikhilandGitHub 320608ea73 chore: return issue votes in public issue list endpoint (#2026) 2023-08-31 11:32:58 +05:30
Aaryan KhandelwalandGitHub 5e00ffee05 fix: bugs on the user profile page (#2018) 2023-08-30 17:28:17 +05:30
Aaryan KhandelwalandGitHub 54527cc2bb dev: revamp publish project modal (#2022)
* dev: revamp publish project modal

* chore: sidebar dropdown text
2023-08-30 17:27:49 +05:30
Bavisetti NarayanandGitHub 6c6b81bea7 chore: tracking the history of issue reactions and votes. (#2020)
* chore: tracking the issues reaction and vote history

* fix: changed the keywords for vote and reaction

* chore: added validation
2023-08-30 16:38:04 +05:30
Aaryan KhandelwalandGitHub f5a076e9a9 dev: revamp peek overview (#2021)
* dev: mobx for issues store

* refactor: peek overview component

* chore: update open issue button

* fix: issue mutation after any crud action

* chore: remove peek overview from gantt

* chore: refactor code
2023-08-30 13:26:28 +05:30
17aff1f369 fix: asset key validation (#1938)
* fix: asset key validation

* chore: asset key validation in user assets

---------

Co-authored-by: Bavisetti Narayan <narayan@Bavisettis-MacBook-Pro.local>
2023-08-30 12:20:13 +05:30
NikhilandGitHub 761a1eb41a fix: user created by stats (#2016) 2023-08-30 12:18:56 +05:30
NikhilandGitHub 426f65898b feat: user timezones (#2009)
* dev: user timezones

* feat: user timezones
2023-08-30 12:18:18 +05:30
NikhilandGitHub 23f5d5d172 chore: track public board comments and reaction users for public deploy boards (#1972)
* chore: track project deploy board comment and reaction users for public deploy boards

* dev: remove tracking from project viewsets
2023-08-30 12:15:08 +05:30
Aaryan KhandelwalandGitHub 2e5ade05fe chore: update module status icons and colors (#2011)
* chore: update module status icons and colors

* refactor: import statements

* fix: add default alue to module status
2023-08-30 11:43:47 +05:30
Aaryan KhandelwalandGitHub 168e79d6df style: revamp of the issue details sidebar (#2014) 2023-08-29 20:15:12 +05:30
Aaryan KhandelwalandGitHub d8bbdc14ac feat: access selector for comment (#2012)
* dev: access specifier for comment

* chore: change access order
2023-08-29 20:14:13 +05:30
Aaryan KhandelwalandGitHub fd0efb0242 fix: start date filter not working on the platform (#2007) 2023-08-29 20:11:38 +05:30
Aaryan KhandelwalandGitHub 38a5623c43 dev: user timezone select option (#2002) 2023-08-29 20:11:06 +05:30
NikhilandGitHub 90cf39cf59 fix: access creation in comments (#2013) 2023-08-29 16:40:28 +05:30
Bavisetti NarayanandGitHub b2a41d3bf6 fix: issue votes (#2006)
* fix: issue votes

* fix: added default as 1 in vote

* fix: issue vote migration file
2023-08-29 15:02:29 +05:30
Bavisetti NarayanandGitHub 1cf5e8d80a fix: only external comments will show in deploy boards (#2010) 2023-08-29 15:01:18 +05:30
NikhilandGitHub 1d30a9a0a8 chore: project public board issue retrieve (#2003)
* chore: project public board issue retrieve

* dev: project issues list endpoint

* fix: issue public retrieve endpoint
2023-08-29 15:00:26 +05:30
Bavisetti NarayanandGitHub 91c10930a4 feat: mark all read notifications (#1963)
* feat: mark all read notifications

* fix: changed string to boolean

* fix: changed snoozed condition
2023-08-29 14:57:27 +05:30
NikhilandGitHub 5ad5da4fd7 dev: remove gunicorn config (#1999) 2023-08-29 13:45:25 +05:30
e1ad385688 fix: issue exports in self hosted instances (#1996)
* fix: issue exports in self hosted instances

* dev: remove print logs

* dev: update url creation function

* fix: changed the presigned url for self hosted exports

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2023-08-29 13:45:04 +05:30
NikhilandGitHub abcdebef85 fix: n+1 in issue history and issue automation tasks (#1994) 2023-08-29 13:35:36 +05:30
NikhilandGitHub 3a41ec7442 chore: update user activity endpoint to return only workspace activities (#1980) 2023-08-29 13:35:13 +05:30
8581226e60 chore: improve access field for comments for public boards (#1956)
Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com>
2023-08-29 13:34:38 +05:30
sriram veeraghantaandGitHub c65bbf865d fix: tiptap editor export fixes (#2001) 2023-08-28 15:54:49 +05:30
srinivas pendemGitHubsrinivaspendem <you@example.comsrinivaspendem2612@gmail.com>
b2e5760391 bugfix: Export download next link changed to anchor (#2000)
* bugfix: Export download next link changd to anchot

* bugfix: user workspace service name update

---------

Co-authored-by: srinivaspendem <you@example.comsrinivaspendem2612@gmail.com>
2023-08-28 15:54:20 +05:30
Henit ChobisaandGitHub 8a3b65a740 [chore] Update development workflows with every PR build and removed image update on every merge (#1985)
* chore: Combined Github Workflows for Release

* chore: Added Workflows for Building and Testing Changes
2023-08-28 13:36:08 +05:30
sriram veeraghantaandGitHub 293d90ddda fix: Cycles and Modules Cards view responsiveness (#1997)
* fix: modules cards fixes

* fix:cycles responsive cards
2023-08-28 13:34:05 +05:30
Aaryan KhandelwalandGitHub 485e56bcdf fix: my profile activity endpoint (#1983)
* fix: my profile activity endpoint

* chore: update service name
2023-08-28 13:29:48 +05:30
Aaryan KhandelwalandGitHub 6e7701d854 chore: don't show completion percentage if user has no assigned issues (#1984) 2023-08-28 13:29:07 +05:30
Aaryan KhandelwalandGitHub a1acd2772e feat: mark all as read (#1982) 2023-08-28 13:26:38 +05:30
47abe9db5e dev: gantt chart revamp (#1900)
* style: gantt chart polishing

* chore: sidebar y-axis drag and drop

* chore: remove y-axis drag and drop from the main content

* refactor: drop end function

* refactor: resizing logic

* chore: x-axis block move

* chore: x-axis block move flag

* chore: update scroll end logic

* style: modules gantt chart

* style: block background tint

* refactor: context dispatcher types

* refactor: draggable component

* chore: filters added to gantt chart

* refactor: folder structure

* style: cycle blocks

* chore: move to block arrow

* chore: move to block on the right side arrow

* chore: added proper comments for functions

* refactor: blocks render logic

* fix: x-axis drag and drop

* chore: minor ui fixes

* chore: remove link tag from blocks

---------

Co-authored-by: Aaryan Khandelwal <aaryan610@Aaryans-MacBook-Pro.local>
2023-08-28 13:25:47 +05:30
sriram veeraghantaandGitHub a61e8370b5 fix: workspace accepted invitation redirects to the workspace (#1971)
* fix: workspace accepted invitation redirects to the workspace

* chore: removing logs

* fix: updating user last workspace id with newly joined one

* adding error toast
2023-08-28 13:25:09 +05:30
sriram veeraghantaandGitHub 9f420a00d7 fix: create new project as fav (#1993) 2023-08-28 13:09:27 +05:30
NikhilandGitHub a9ff4b8c93 fix: project members n+1 (#1975) 2023-08-27 20:31:32 +05:30
2b168edd99 feat: peek overview for spreadsheet issues (#1979)
* feat: peak overview for issues

* fix: peek spelling

* chore: truncate issue property labels

* style: full screen view designed

* chore: add comment section

* chore: copy link and delete options added

* chore: update icons

---------

Co-authored-by: Aaryan Khandelwal <aaryan610@Aaryans-MacBook-Pro.local>
2023-08-25 17:41:23 +05:30
NikhilandGitHub 93fa093a79 dev: update python runtime (#1981) 2023-08-25 17:23:07 +05:30
NikhilandGitHub fd8c368c97 fix: add member role and member status in project create response (#1962) 2023-08-25 15:16:15 +05:30
sriram veeraghantaandGitHub 0525e7d6b3 fix: workspace members reordering (#1978) 2023-08-25 13:38:50 +05:30
1530993b84 fix: tiptap editor max width (#1968)
Co-authored-by: Aaryan Khandelwal <aaryan610@Aaryans-MacBook-Pro.local>
2023-08-25 12:42:12 +05:30
d8b8c903f2 fix: issue activity redirection to cycle and module (#1973)
Co-authored-by: Aaryan Khandelwal <aaryan610@Aaryans-MacBook-Pro.local>
2023-08-25 12:21:11 +05:30
bf0d0503b2 fix: redirection after deleting a project (#1970)
Co-authored-by: Aaryan Khandelwal <aaryan610@Aaryans-MacBook-Pro.local>
2023-08-25 12:17:17 +05:30
Henit ChobisaandGitHub fe1b0c1d73 [chore] Fixed Github Workflows for Building and Pushing, frontend, backend, proxy & plane-deploy (#1959)
* chore: modified frontend github workflow

* chore: modified backend github workflows

* chore: added github workflow for build and push for plane-deploy

* chore: added github workflow for plane-proxy
2023-08-25 12:12:07 +05:30
ab4a17c178 chore: custom CSS shadow variables added (#1969)
* chore: custom shadow variables added

* fix: 2xs shade

---------

Co-authored-by: Aaryan Khandelwal <aaryan610@Aaryans-MacBook-Pro.local>
2023-08-24 23:07:20 +05:30
38934e8b99 chore: group by assignees option for project issues (#1957)
* dev: group by assignees option for project issues

* fix: no assignee title

---------

Co-authored-by: Aaryan Khandelwal <aaryan610@Aaryans-MacBook-Pro.local>
2023-08-24 19:46:12 +05:30
d18ac83909 feat: start date filter added across the platform (#1955)
Co-authored-by: Aaryan Khandelwal <aaryan610@Aaryans-MacBook-Pro.local>
2023-08-24 19:45:23 +05:30
sriram veeraghantaandGitHub 802e6b3e8e fix: project member mutate issue (#1967) 2023-08-24 19:43:50 +05:30
sriram veeraghantaandGitHub 489ef6a3cc Merge pull request #1966 from makeplane/fix/workspace-members-mutate
fix: workspace members mutate issue
2023-08-24 18:26:11 +05:30
sriram veeraghanta bce8cae0da fix: mutate fixes 2023-08-24 18:21:57 +05:30
sriram veeraghanta f97597958a fix: workspace memebers mutate issue 2023-08-24 17:44:20 +05:30
NikhilandGitHub 7fca01d8c9 feat: project deploy board endpoint (#1943) 2023-08-23 22:13:37 +05:30
Bavisetti NarayanandGitHub 529ab19747 chore: removed extra exporter function (#1953) 2023-08-23 22:13:04 +05:30
sriram veeraghantaandGitHub 6f397710ce Merge pull request #1951 from makeplane/stage-release
Promote: Stage release to Production
2023-08-23 16:18:08 +05:30
sriram veeraghantaandGitHub e6bd6b6a8c Merge pull request #1950 from makeplane/develop
Promote: Develop to Stage Release
2023-08-23 15:49:14 +05:30
2d1406953e fix: mutate projects list after joining (#1944)
Co-authored-by: Aaryan Khandelwal <aaryan610@Aaryans-MacBook-Pro.local>
2023-08-23 15:44:27 +05:30
a8fdd42cb9 fix: label color select popover overflow (#1949)
Co-authored-by: Aaryan Khandelwal <aaryan610@Aaryans-MacBook-Pro.local>
2023-08-23 15:43:09 +05:30
sriram veeraghantaandGitHub 9d3952006b Merge pull request #1948 from makeplane/stage-release
Promot: Stage Release to Production
2023-08-23 14:51:27 +05:30
sriram veeraghantaandGitHub b75473a684 Merge pull request #1947 from makeplane/develop
Promote: Develop to Stage Release
2023-08-23 12:37:25 +05:30
561fb9815b chore: update export services icons (#1946)
Co-authored-by: Aaryan Khandelwal <aaryan610@Aaryans-MacBook-Pro.local>
2023-08-23 12:29:59 +05:30
Bavisetti NarayanandGitHub 2cc67f6498 fix: date validation in cycle and module (#1945) 2023-08-23 12:17:20 +05:30
NikhilandGitHub 2f5bd58c61 Merge pull request #1941 from makeplane/stage-release
promote: stage-release to master
2023-08-22 19:37:49 +05:30
NikhilandGitHub e833fccf61 Merge pull request #1940 from makeplane/develop
dev: deploy docker containers (#1939)
2023-08-22 19:34:13 +05:30
NikhilandGitHub eee6658cc2 dev: deploy docker containers (#1939) 2023-08-22 19:33:29 +05:30
NikhilandGitHub 62ba9abdb4 Merge pull request #1936 from makeplane/stage-release
promote: stage-release to master
2023-08-22 13:57:57 +05:30
NikhilandGitHub 46b138eb0b Merge pull request #1934 from makeplane/develop
fix: aws region changed for exporter (#1933)
2023-08-22 13:51:20 +05:30
68b438ab1a fix: aws region changed for exporter (#1933)
Co-authored-by: Bavisetti Narayan <narayan@Bavisettis-MacBook-Pro.local>
2023-08-22 13:18:15 +05:30
NikhilandGitHub 59bdf222f5 Merge pull request #1932 from makeplane/stage-release
promote: stage release to master
2023-08-22 01:34:37 +05:30
NikhilandGitHub eb50ade5e3 Merge pull request #1931 from makeplane/develop
fix: access environment variables is changed in services (#1930)
2023-08-22 01:14:56 +05:30
b406a70e72 fix: access environment variables is changed in services (#1930)
Co-authored-by: Sainath <sainath@Sainaths-MacBook-Pro.local>
2023-08-22 01:13:51 +05:30
Vamsi KuramaandGitHub 85a08e4abd Merge pull request #1929 from makeplane/stage-release 2023-08-22 00:17:29 +05:30
NikhilandGitHub aa2e1697b0 Merge pull request #1928 from makeplane/develop
promote: develop to stage-release
2023-08-21 20:51:46 +05:30
Aaryan KhandelwalandGitHub b02417120b chore: hide new issue button from my subscribed issues page (#1927) 2023-08-21 20:50:17 +05:30
Aaryan KhandelwalandGitHub d040394826 fix: create project button not appearing on the sidebar (#1926) 2023-08-21 20:44:51 +05:30
NikhilandGitHub f7682c57ba fix: plane space start up command (#1925) 2023-08-21 20:25:12 +05:30
NikhilandGitHub 3beab9de6f Merge pull request #1923 from makeplane/develop
promote: develop to stage-release
2023-08-21 18:18:14 +05:30
guru_sainathandGitHub 9bb6254515 chore: updated default api base_url (#1922) 2023-08-21 18:17:32 +05:30
Aaryan KhandelwalandGitHub ae052f1890 chore: update restricted workspace slugs (#1920) 2023-08-21 18:13:08 +05:30
cfc7049343 Dockerrizing space project (#1921)
* chore: Added Dockerfile for Space Project

* fix: next js config to standalone mode

* fix: workedaround build error with rename 404 page

* chore: modified dockerfile with new conventions

* chore: modified dockercompose file for new plane-deploy

* fix: handled ts errors with possibly undefined states

* chore: updated main dockerfile with plane-deploy

* feat: included space project to start.sh

* chore: modified space project port while running in production

* chore: restored changes inside space project

* chore: added ngnix config for space project running :4000

* fix: Updated docker-compose files

* chore: added space url for ngnix config

* chore: Updated ngnix template

* chore: updated space url in compose hub file

* dev: updated dockerfile.space and start and replace script

* dev: equate hub and build docker files

* dev: revert workspace space page

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2023-08-21 18:12:41 +05:30
guru_sainathandGitHub 41e55dff85 fix: build error for 404 and search params null check (#1919) 2023-08-21 12:43:41 +05:30
Aaryan KhandelwalandGitHub 0bccb63a9f fix: module start and target date validations (#1914) 2023-08-21 11:46:02 +05:30
Aaryan KhandelwalandGitHub 2eb956e97e refactor: project and workspace delete modals (#1915) 2023-08-21 11:44:59 +05:30
M. PalanikannanandGitHub d470adf262 fix: Image resize, Link selector in Modals, Delete/ sync images and so much more (#1896)
* added image-resizing support

* link form removed

* updated image upload logic and 35% default width on upload

* removed shadow, added alert if not saved and ts errors

* prevent enter key on Link selector to trigger modal submit

* added workspace slug to all tiptap instances

* added delete plugin with loading indicator

* added better syncing of "Saved" state of editor and Image uploads

* removed redundant description_html check
2023-08-19 18:58:54 +05:30
Aaryan KhandelwalandGitHub cebc8bdc8d fix: context menu dynamic positioning, multiple context menus opening issue (#1913)
* fix: context menu positioning

* fix: close already opened context menu on outside click
2023-08-19 18:50:12 +05:30
Anmol Singh BhatiaandGitHub 64b5ba196f style: responsive empty state for profile stats (#1911) 2023-08-18 20:18:03 +05:30
NikhilandGitHub 13d21e752d Merge pull request #1910 from makeplane/develop
promote: develop to stage-release
2023-08-18 19:29:11 +05:30
Anmol Singh BhatiaandGitHub 8d5018318d chore: add favorite project from sidebar (#1909) 2023-08-18 18:42:50 +05:30
Anmol Singh BhatiaandGitHub 0fbdc0b157 style: consistent ui for create update issue modal (#1907) 2023-08-18 18:42:04 +05:30
Bavisetti NarayanandGitHub 2f39181eb7 fix: priority ordering (#1908) 2023-08-18 18:27:29 +05:30
Dakshesh JainandGitHub d825dc5579 style: showing first name for bot profile (#1894) 2023-08-18 17:15:20 +05:30
Aaryan KhandelwalandGitHub 1f8117c987 fix: dashboard upcoming issues list (#1904) 2023-08-18 17:10:12 +05:30
Bavisetti NarayanandGitHub 125e9090ea chore: module link model (#1905)
* chore: module link model

* chore: added migration
2023-08-18 15:50:48 +05:30
Bavisetti NarayanandGitHub 02ac4cee22 chore: renamed target date to start date (#1902) 2023-08-18 15:25:42 +05:30
Anmol Singh BhatiaandGitHub 93164755e2 style: analytics stats empty state (#1903) 2023-08-18 15:15:33 +05:30
Aaryan KhandelwalandGitHub 6344f6f562 chore: set order by to manual on gantt chart (#1886) 2023-08-18 15:12:12 +05:30
Aaryan KhandelwalandGitHub b67e30fd9c chore: analytics start date property for x-axis and group (#1888) 2023-08-18 15:11:25 +05:30
Anmol Singh BhatiaandGitHub 93fec2c678 fix: completed cycle validation , style: assignee count alignment fix (#1901)
* style: assignee count alignment fix

* fix: completed cycle validation
2023-08-18 14:23:13 +05:30
Anmol Singh BhatiaandGitHub c3c6ba9e34 chore: link edit functionality (#1895) 2023-08-18 12:03:31 +05:30
Anmol Singh BhatiaandGitHub d74ec7bda9 fix: ui improvement and bug fixes (#1883) 2023-08-18 12:01:51 +05:30
Henit ChobisaandGitHub e593a8d4bd chore: Edited Setup Script to take TipTap Auth Token and Generate .npmrc (#1897) 2023-08-18 11:47:58 +05:30
guru_sainathandGitHub abb8782c44 fix: handled default view on plane deploy (#1893)
* fix: handled default view on plane deploy

* fix: handled default view on refresh
2023-08-17 14:24:33 +05:30
guru_sainathandGitHub 0afd72db95 fix: updated theming in workspace preferences (#1890) 2023-08-16 20:35:17 +05:30
guru_sainathandGitHub 65295f6c6f chore: updating the theme using MobX from command k (#1879)
* chore: updating the theme using mobx from command k

* feat: Showing the project published status in the app header

* dev: updated validation and redirection the project publish modal and added redirection on the app header
2023-08-16 18:26:36 +05:30
Aaryan KhandelwalandGitHub 5b6b43fb83 fix: quick action buttons: (#1884) 2023-08-16 18:25:11 +05:30
Dakshesh JainandGitHub f8497125db style: fixed display name coming twice on profile page (#1889) 2023-08-16 18:18:57 +05:30
Bavisetti NarayanandGitHub b24622e5ef fix: validation for issue activity description (#1887) 2023-08-16 17:12:09 +05:30
guru_sainathandGitHub 10dface85d chore: updated error pages 404 and project-not-found in plane deploy (#1885)
* dev: custom error messages.

* dev: updated next version in yarn.lock

* dev: updated project-not-published icon
2023-08-16 17:05:40 +05:30
Aditi PatelandGitHub 2b6debaa3e Updated setup document to include tiptap pro install (#1871) 2023-08-16 14:42:37 +05:30
1750ba344b fix: my issue duplication (#1882)
Co-authored-by: Plane Team <planeteam@srirams-Mac-mini.local>
2023-08-16 14:40:10 +05:30
NikhilandGitHub 550473bb02 Merge pull request #1876 from makeplane/stage/merge-fixes
Promote: Develop to Stage Release
2023-08-16 13:18:26 +05:30
sriramveeraghanta fde978861c Merge branch 'develop' of github.com:makeplane/plane into stage/merge-fixes 2023-08-16 13:17:21 +05:30
guru_sainathandGitHub f44d142f2c chore: tip-tap editor update in workspace user activity (#1877) 2023-08-16 13:16:20 +05:30
guru_sainathandGitHub 1ded8f486f chore: updated meta tags for project issues (#1875)
* dev: updated meta tags for project issues

* dev: updated project description in meta tags in plane deploy.
2023-08-16 13:15:57 +05:30
2c43a15515 chore: added new filed in serializer (#1874)
Co-authored-by: NarayanBavisetti <narayan311@gmail.com>
2023-08-16 13:10:03 +05:30
Dakshesh JainandGitHub 0979acc1a4 fix: notification card not redirecting to archive issue detail for archived issue (#1861) 2023-08-16 13:09:56 +05:30
sriramveeraghanta 9003c58d89 merge conflicts resolved 2023-08-16 13:00:58 +05:30
NikhilandGitHub 55e2f00ffe fix: members list filtering for workspace and projects (#1872) 2023-08-16 12:21:56 +05:30
NikhilandGitHub 08382f88b4 chore: updated migration files for 0.11 (#1851) 2023-08-16 10:37:22 +05:30
72419447ec fix: added slug in filename for export issues (#1870)
* chore: file name changed for exported issues

* fix: added slug in filename for export issues

---------

Co-authored-by: NarayanBavisetti <narayan311@gmail.com>
2023-08-16 09:39:17 +05:30
NikhilandGitHub b554087b1f chore: deploy board status for project (#1866) 2023-08-16 01:00:22 +05:30
07717e9a93 chore: file name changed for exported issues (#1865)
Co-authored-by: NarayanBavisetti <narayan311@gmail.com>
2023-08-15 19:49:17 +05:30
NikhilandGitHub df46a45afc fix: analytics export (#1862)
* fix: analytics export

* dev: export analytics assignee indexing

* dev: total counts
2023-08-15 15:48:22 +05:30
e1ae0d3b56 feat : Tiptap integration (#1832)
* remirror instances commented out to avoid prosemirror conflicts

* styles migrated for remirror to tiptap transition

* added bubblemenu support with extensions

* fixed css for task lists and code with syntax highlighting

* added support for slash command

* fixed bubble menu to match styles and added better seperation in UI

* saving with debounce logic added and it's stored in backend

* added migration support by updating to html

* Image uploads done

* improved file structure and delete image function implemented

* Integrated tiptap with Issue Modal

* added additional props and Tiptap Integration with Comments

* added tiptap integration with user activity feeds

* added ref control support and bubble menu support for readonly editor

* added tiptap support for plane pages

* added tiptap support to gpt assistant modal (yet to be tested)

* removed remirror instances and cleaned up code

* improved code structure for extracting props in Tiptap

* fixing ts errors for next build

* fixing node ts error for Horizontal Rule

* added ts fix for node types

* temp fix

* temp fix

* added min height for issue description in modal

* added resolutions to prosemirror-model version

* trying pnpm overrides

* explicitly added prosemirror deps

* bugfixes

* removed extra gap at the top and moved saved indicator to the bottom

* fix: slash command scroll position

* chore: update custom css variables

* matched theme colours

* fixed gpt-assistant modal

* updated yarn lock

* added debounced updates for the title and removed saved state after timeout

* added css animations for saved state

* build fixes and remove remirror instances

* minor commenting fixes

---------

Co-authored-by: Palanikannan1437 <73993394+Palanikannan1437@users.noreply.github.com>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-08-15 15:04:46 +05:30
guru_sainathandGitHub daa8f7d79b feat / public deploy settings workflow (#1863)
* Feat: Implemented project publish settings

* dev: updated the env dependancy in turbo and enabling the publish access to admin
2023-08-14 19:18:38 +05:30
NikhilandGitHub d2cdaaccb9 fix: slack queryset (#1860) 2023-08-14 18:53:09 +05:30
Anmol Singh BhatiaandGitHub 6774eddb66 style: sidebar select date width (#1859) 2023-08-14 18:10:21 +05:30
Dakshesh JainandGitHub 8ccc1b3fcc fix: gray background on png image (#1856) 2023-08-14 16:23:10 +05:30
Anmol Singh BhatiaandGitHub 5e76e03a55 style: profile activity loader (#1858) 2023-08-14 16:20:53 +05:30
srinivas pendemandGitHub 77fb50faa4 fix: route fix in imports and exports (#1857) 2023-08-14 16:19:13 +05:30
5ddfee12bc fix: changed the display of date format (#1855)
Co-authored-by: NarayanBavisetti <narayan311@gmail.com>
2023-08-14 16:10:57 +05:30
Aaryan KhandelwalandGitHub f7a596c113 fix: gantt chart block left drag flicker (#1854)
* fix: left drag flicker

* fix: opposite side manual scroll
2023-08-14 14:27:02 +05:30
Dakshesh JainandGitHub f73239be92 refactor: using webp image format instead of svg (#1852) 2023-08-14 13:26:16 +05:30
Anmol Singh BhatiaandGitHub dc2438b2d3 style: image upload modal aspect ratio (#1853) 2023-08-14 13:06:00 +05:30
srinivas pendemGitHubsrinivaspendem <you@example.comsrinivaspendem2612@gmail.com>
ddd3301d17 feat: csv, json and, xlsx exporter (#1840)
* feat : csv, jason and, xlxs exporter

* handeling the export fail

* adding expired state to exports

* typo update

* header change

* improvement: added validation for the expired date

---------

Co-authored-by: srinivaspendem <you@example.comsrinivaspendem2612@gmail.com>
2023-08-14 11:44:17 +05:30
Vamsi KuramaandGitHub 816f00d956 Merge pull request #1848 from ryota-murakami/fix/license 2023-08-12 17:12:14 +05:30
ryota-murakami 70e2509d52 package.json; fix license 2023-08-12 20:30:01 +09:00
1a9faa025a fix: export issues in CSV, JSON and XLSX (#1794)
* fix: file name change

* feat: added xml json and csv export

* chore: added openpyxl package

* fix: added initiated_by field

* fix: added initiated by details

* dev: refactoring

* fix: rendering assignee name and labels in sheet

* fix: handeled exception in label

* feat: implemented link expiration scheduler(8 days)

* fix: removed the expired field

---------

Co-authored-by: NarayanBavisetti <narayan311@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
2023-08-11 20:09:52 +05:30
NikhilandGitHub feba1cc4d0 feat: project public boards (#1772)
* feat: project public boards

* dev: public issue and comment reactions

* dev: public issue comments

* dev: public comments

* dev: inbox for public boards

* dev: inbox issues for public board

* dev: public inbox issue

* dev: migrations

* dev: update api endpoints

* dev: project boards and views

* dev: state and label details

* dev: public issue voting

* dev: issue voting

* dev: workspace details

* dev: project icon and emoji
2023-08-11 19:27:44 +05:30
Anmol Singh BhatiaandGitHub 079a5b28d8 style: custom theming color picker position (#1846) 2023-08-11 19:17:59 +05:30
Aaryan KhandelwalandGitHub c6eea9c7a9 fix: empty groups not appearing in the kanban view (#1843) 2023-08-11 19:17:02 +05:30
Anmol Singh BhatiaandGitHub 5f5790ebf9 chore: start date option added in spreadsheet view (#1824) 2023-08-11 19:14:54 +05:30
Anmol Singh BhatiaandGitHub a3d99100ee fix: sub issue progress indicator fix (#1847) 2023-08-11 19:05:13 +05:30
Anmol Singh BhatiaandGitHub ac6d2b0139 chore: updated user permission for project and workspace settings (#1807)
* fix: updated user permission for project and workspace settings

* chore: workspace delete modal fix
2023-08-11 18:29:24 +05:30
Dakshesh JainandGitHub 7becec4ee9 feat: randomize color on label create (#1839)
fix: create label state being persisted after edit label
2023-08-11 17:42:47 +05:30
guru_sainathandGitHub cd5e5b96da feat: Mobx integration, List and Kanban boards implementation in plane space (#1844)
* feat: init mobx and issue filter

* feat: Implemented list and kanban views in plane space and integrated mobx.

* feat: updated store type check
2023-08-11 17:18:33 +05:30
Dakshesh JainandGitHub ad4cdcc512 fix: cmdk modal not closing when choosing an option (#1833) 2023-08-11 17:16:37 +05:30
NikhilandGitHub 6617049983 fix: sub issue endpoint for state distribution (#1845) 2023-08-11 16:38:03 +05:30
Dakshesh JainandGitHub abec46a725 fix: cmdk not changing theme if active theme is custom-theme (#1842) 2023-08-11 16:16:20 +05:30
NikhilandGitHub 88e987b902 feat: cycle and module sort order (#1841)
* dev: cycle and module ordering

* dev: sort order for smallest
2023-08-11 16:03:15 +05:30
Aaryan KhandelwalandGitHub 785a6e8871 chore: gantt chart resizable blocks, y-axis drag and drop (#1810)
* chore: gantt chart resizable blocks

* chore: right scroll added

* chore: left scroll added

* fix: build errors

* chore: remove unnecessary console logs

* chore: add block type and remove info toggle

* feat: gantt chart blocks y-axis drag and drop

* chore: disable drag flag

* fix: y-axis drag mutation

* fix: scroll container undefined error

* fix: negative scroll

* fix: negative scroll

* style: blocks tooltip consistency
2023-08-11 15:59:13 +05:30
Anmol Singh BhatiaandGitHub 762ef422ca chore: project search added in project list page (#1837) 2023-08-11 15:57:11 +05:30
Anmol Singh BhatiaandGitHub e2b5657c3e chore: profile cover photo (#1836) 2023-08-11 15:56:26 +05:30
Anmol Singh BhatiaandGitHub dbb53a663e style: sidebar ui improvement (#1816)
* style: sidebar ui improvement

* style: sidebar menu spacing

* style: sidebar consistent spacing

* style: notification improvement

* style: remove from favorite filled icon added

* chore: settings option added in sidebar context menu

* chore: update delete project option visibility for sidebar

* style: sidebar project list display border top on scroll
2023-08-11 15:53:10 +05:30
Anmol Singh BhatiaandGitHub 5c964d144a style: ui improvement (#1806)
* style: kanban board theming

* style: assignee ui revamp

* style: kanban board header, label , priority , state and avatar ui revamp

* style: kanban card state dropdown

* style: sidebar profile dropdown

* style: sidebar dropdown icon

* style: sidebar workspace dropdown

* style: assignee select component

* fix: state select

* style: consistent app header button
2023-08-11 15:50:05 +05:30
Anmol Singh BhatiaandGitHub 289e81d6eb chore: issue activity user redirection added (#1805)
* feat: issue activity user redirection added

* feat: analytics page user redirection
2023-08-11 15:48:52 +05:30
NikhilandGitHub def10af1e2 fix: issue date filtering (#1834) 2023-08-11 12:48:30 +05:30
NikhilandGitHub edaeae1b69 chore: sort order for cycle and modules (#1835) 2023-08-11 12:48:02 +05:30
NikhilandGitHub e06ee25800 fix: project states create (#1830) 2023-08-11 12:46:50 +05:30
NikhilandGitHub be86a7d38e feat: project member role (#1828) 2023-08-11 12:46:29 +05:30
NikhilandGitHub 0a1483c482 chore: issue list date filters and properties (#1820)
* dev: start date and target date validation and filter for null dated issues

* dev: remove print logs

* dev: issue property dates
2023-08-11 12:45:51 +05:30
NikhilandGitHub 11abd3cadf fix: user id for default analytics (#1808) 2023-08-11 12:45:04 +05:30
NikhilandGitHub 085cd1960e fix: project members endpoint email (#1804)
* fix: project members endpoint email

* dev: cycle and module assignee display name
2023-08-11 12:44:20 +05:30
NikhilandGitHub 2769a73898 remove: auto start date configuration (#1799) 2023-08-11 12:43:43 +05:30
NikhilandGitHub 8373f20944 fix: issues n plus 1 (#1785) 2023-08-11 12:43:00 +05:30
Dakshesh JainandGitHub f562fcd466 refactor: both delete or backspace can be used to open modal (#1829)
* refactor: both delete or backspace can be used to open modal

* fix: 'Bulk Issue Delete' modal will open on single click
2023-08-10 16:02:10 +05:30
Dakshesh JainandGitHub 8c8668a3e6 refactor: not making calls to member's endpoint to get membership status (#1831)
* refactor: not making call to members endpoint to get membership status

* refactor: type for IProject
2023-08-10 15:39:32 +05:30
Dakshesh JainandGitHub 9ce85cdf21 refactor: if/else conditions (#1822)
* refactor: shortcuts if/else

* fix: merge conflict
2023-08-10 13:12:17 +05:30
Dakshesh JainandGitHub 1c6cdb8328 refactor: added validation for display_name (#1823) 2023-08-10 13:05:03 +05:30
guru_sainathandGitHub 005b42cb8d dev: Updating themening worfkflow (#1827) 2023-08-10 13:03:42 +05:30
Dakshesh JainandGitHub be062ccd34 fix: import user using endpoint with email (#1819) 2023-08-09 17:49:25 +05:30
Dakshesh JainandGitHub c9d0c5353d style: displaying email in members page depending on user role (#1817)
* style: displaying email in members page depending on user role

* refactor: added null safety
2023-08-09 17:23:57 +05:30
Dakshesh JainandGitHub 2a6eb5fe23 style: option to edit display name in profile page (#1818) 2023-08-09 16:44:35 +05:30
Aaryan KhandelwalandGitHub d9ccce41bc chore: profile and my issues page start date option (#1815)
* chore: profile and my issues page start date option

* fix: properties order in my profile view options
2023-08-09 15:46:47 +05:30
Anmol Singh BhatiaandGitHub 3db69a3a71 style: profile empty state (#1811)
* style: profile empty state

* style: priority empty state padding
2023-08-09 15:45:20 +05:30
Dakshesh JainandGitHub faa50b0bbb fix: analytics showing user id instead of display name (#1812)
* fix: replacing first, last name and email with display name

* fix: different endpoint for workspace & project member

* fix: falling back to email if display_name doesn't exist

* fix: analytics showing user id instead of display name
2023-08-09 15:20:29 +05:30
Dakshesh JainandGitHub 1991e09035 refactor: removed escape keydown listener (#1814) 2023-08-09 15:18:26 +05:30
Aaryan KhandelwalandGitHub 4fcd081d27 chore: select start date option for issue (#1813) 2023-08-09 15:17:32 +05:30
5f1209f1db chore: empty state for multi-level dropdown (#1802)
* fix :label filter should show something if there is no label #1779 (#1795)

* style: children empty state

---------

Co-authored-by: Pankaj Chotaliya <34762752+pankajvc@users.noreply.github.com>
2023-08-08 14:13:26 +05:30
Aaryan KhandelwalandGitHub 88e5a05253 chore: subscribed by me tab on my issues page (#1800)
* chore: add subscribed by me tab in my issues

* chore: update tab titles

* fix: build error
2023-08-08 13:15:25 +05:30
Dakshesh JainandGitHub 981acc81c1 fix: replaced first name, last name or email to display name (#1796)
* fix: replacing first, last name and email with display name

* fix: different endpoint for workspace & project member

* fix: falling back to email if display_name doesn't exist
2023-08-08 13:01:43 +05:30
NikhilandGitHub cf306ee605 feat: user display name (#1179)
* feat: user display name for the entire system

* feat: update issue activity to remove emails

* dev: update to display name wherever assignees__email and member__email

* dev: update display names on issue activity and the user script

* dev: update display_name function to generate display_name from email

* dev: add email for test purpose

* dev: set default display name for the user

* dev: add migration script and default value

* dev: annotate with assignees_id

* dev: return assignees id

* dev: display name for the profile

* dev: project members endpoint

* dev: url update

* dev: trailing /

* dev: update workspace member serializer

* fix: activity for assignees
2023-08-08 12:59:04 +05:30
guru_sainathandGitHub 9df0ba6e3a feat: initiated plane space (#1801) 2023-08-08 12:55:42 +05:30
guru_sainathandGitHub b6744dcd29 Chore: mobx setup and app sidebar and theme management (#1798)
* dev: Mobx integration for app sidebar and custom theme

* dev: Handled edge case and conditional rendering for mobx store
2023-08-08 12:50:27 +05:30
DevMinerandGitHub a164dfd532 chore(frontend): add sharp (#1451) 2023-08-07 15:52:08 +05:30
2b46e5f977 feat: issue export csv (#1781)
* feat: created issue export csv

* fix: optimized the queries

---------

Co-authored-by: NarayanBavisetti <narayan311@gmail.com>
2023-08-07 11:59:04 +05:30
Vamsi KuramaandGitHub 9ff8994c0e Merge pull request #1784 from makeplane/stage-release
promote: stage-release to master v0.10.1-patch
2023-08-03 18:55:24 +05:30
9b4aebc385 promote: develop to stage-release v0.10-patch (#1783)
* chore: show message if dragging unjoined project (#1763)

* fix: invalid project selection in create issue modal (#1766)

* style: sidebar project list improvement (#1767)

* fix: comment reaction mutation (#1768)

* fix: user profiles n plus 1 (#1765)

* fix: bulk issue import (#1773)

* style: profile activity (#1771)

* style: profile activity comment log styling

* chore: profile feed activity refactor

* style: sidebar project list

* chore: add non existing states for project entities (#1770)

* fix: notification read status being toggled when click on link (#1769)

* fix: custom theme persisting after signing out (#1780)

* fix: custom theme persistence

* chore: remove console logs

* fix: build error

* fix: change theme from command k

---------

Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
2023-08-03 15:28:22 +05:30
Aaryan KhandelwalandGitHub 97c3fb40e7 fix: custom theme persisting after signing out (#1780)
* fix: custom theme persistence

* chore: remove console logs

* fix: build error

* fix: change theme from command k
2023-08-03 15:01:31 +05:30
Dakshesh JainandGitHub 5aad6c71da fix: notification read status being toggled when click on link (#1769) 2023-08-02 17:23:55 +05:30
Aaryan KhandelwalandGitHub a1ae338c37 chore: add non existing states for project entities (#1770) 2023-08-02 16:47:36 +05:30
Anmol Singh BhatiaandGitHub c16b0daa22 style: profile activity (#1771)
* style: profile activity comment log styling

* chore: profile feed activity refactor

* style: sidebar project list
2023-08-02 16:45:34 +05:30
NikhilandGitHub 9a29896291 fix: bulk issue import (#1773) 2023-08-02 16:42:47 +05:30
NikhilandGitHub a66dcb9419 fix: user profiles n plus 1 (#1765) 2023-08-02 16:42:24 +05:30
Dakshesh JainandGitHub 87a920174e fix: comment reaction mutation (#1768) 2023-08-02 14:21:48 +05:30
Anmol Singh BhatiaandGitHub 584192faba style: sidebar project list improvement (#1767) 2023-08-02 14:21:26 +05:30
Dakshesh JainandGitHub b61adbed4b fix: invalid project selection in create issue modal (#1766) 2023-08-02 13:38:45 +05:30
Aaryan KhandelwalandGitHub 7434800999 chore: show message if dragging unjoined project (#1763) 2023-08-02 12:09:53 +05:30
Vamsi KuramaandGitHub 3488001197 Merge pull request #1762 from makeplane/stage-release
promote: stage-release to master
2023-08-01 22:38:20 +05:30
NikhilandGitHub 9828d2332a Merge pull request #1761 from makeplane/develop
promote: develop to stage-release
2023-08-01 22:07:03 +05:30
NikhilandGitHub 78095e3823 Merge pull request #1760 from makeplane/fix/project_member_invite
fix: project invite
2023-08-01 22:05:48 +05:30
pablohashescobar f41086fd26 fix: typeerror 2023-08-01 22:00:56 +05:30
pablohashescobar 0866dc3494 fix: project invite 2023-08-01 21:42:48 +05:30
Vamsi KuramaandGitHub 2ced7e4911 Merge pull request #1758 from makeplane/stage-release
promote: stage-release to master v0.10
2023-08-01 20:04:40 +05:30
NikhilandGitHub 9f69fe6060 Merge pull request #1750 from makeplane/develop
promote: develop to stage-release
2023-08-01 19:35:54 +05:30
NikhilandGitHub 6ea15ced02 fix: project identifier length (#1757) 2023-08-01 19:25:04 +05:30
NikhilandGitHub 11525f26d0 fix: project identifier migration (#1755) 2023-08-01 19:11:32 +05:30
Anmol Singh BhatiaandGitHub f3bd1691ce style: sidebar project list styling (#1756) 2023-08-01 19:11:09 +05:30
Anmol Singh BhatiaandGitHub d83a76a3aa style: my issue and profile page view dropdown (#1754) 2023-08-01 19:04:53 +05:30
Aaryan KhandelwalandGitHub 2cd431b4a4 fix: my issues mutation (#1753)
* fix: my issues mutation

* fix: activity message and icon return value
2023-08-01 18:40:04 +05:30
Aaryan KhandelwalandGitHub d22e4b8212 fix: profile activity workspace slug (#1752) 2023-08-01 18:38:33 +05:30
Anmol Singh BhatiaandGitHub 0e0e09c4fd style: profile dropdown updated (#1751) 2023-08-01 18:25:13 +05:30
Aaryan KhandelwalandGitHub a8816ef473 refactor: issue activity component (#1749) 2023-08-01 17:07:11 +05:30
Anmol Singh BhatiaandGitHub d315a24c1c style: primary color variable added in global (#1748) 2023-08-01 17:04:23 +05:30
NikhilandGitHub e73a4bef4e chore: issue and project details in activity (#1747)
* chore: issue and project details in activity

* dev: update capture log
2023-08-01 17:03:19 +05:30
Aaryan KhandelwalandGitHub d9339b8f8e Merge pull request #1729 from makeplane/develop
promote: develop to stage-release
2023-08-01 15:46:56 +05:30
Dakshesh JainandGitHub a66a0680df fix: showing alert on error while deleting workspace member or invited member (#1746)
style: showing 'Leave' for current user
2023-08-01 15:32:42 +05:30
Anmol Singh BhatiaandGitHub 98c7453741 style: view dropdown (#1742) 2023-08-01 14:16:43 +05:30
NikhilandGitHub 1a5faca77c chore: show created by empty for viewers and guests (#1740)
* chore: show created by empty for viewers and guests

* dev: return empty queryset
2023-08-01 14:16:21 +05:30
NikhilandGitHub 6e7fa1a39c chore: project create to return sort order (#1738)
* chore: project create retun sort order

* chore: project create return sort order
2023-08-01 14:15:40 +05:30
NikhilandGitHub 7a6e742362 dev: fix migrations (#1735)
* dev: fix migrations

* dev: migrations for issue comment reactions and preference and cover image fields
2023-08-01 14:15:09 +05:30
Anmol Singh BhatiaandGitHub 8a9ff31009 style: sidebar project disclosure list open by default (#1744) 2023-08-01 14:13:42 +05:30
Dakshesh JainandGitHub d310b8f86f style: style if user doesn't have profile pic (#1745) 2023-08-01 14:12:57 +05:30
4e297d92f3 chore: update empty states, fix: delete issue modal (#1743)
* chore: update empty states

* fix: delete issue modal

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-08-01 13:58:58 +05:30
Anmol Singh BhatiaandGitHub e48147f87e style: create project modal (#1741) 2023-08-01 13:34:54 +05:30
Aaryan KhandelwalandGitHub 85a7a7df2b chore: profile dropdown in the sidebar (#1737)
* chore: profile dropdown in the sidebar

* style: update spacing and font sizes
2023-08-01 13:31:16 +05:30
Dakshesh JainandGitHub 92b22dc99e style: showing 'Created by me' tab to all user (#1739)
* style: showing 'Created by me' tab to all user

* refactor: removed unnecessary imports
2023-08-01 13:30:51 +05:30
Aaryan KhandelwalandGitHub cb4d294608 style: sidebar projects design (#1736)
* chore: disclosure menu for sidebar projects

* fix: projects list spacing

* style: new design
2023-08-01 12:24:34 +05:30
Aaryan KhandelwalandGitHub df8504e6f7 fix: issue redirection (#1733) 2023-08-01 11:22:47 +05:30
Dakshesh JainandGitHub 7287c27b73 refactor: changed per_page to 30 (#1734) 2023-08-01 11:19:06 +05:30
NikhilandGitHub cc2e6182b6 feat: user project sorting (#1719)
* feat: user project sorting

* dev: migration typo fix and query member fix

* feat: project member sort order update

* fix: project sorting per members
2023-07-31 18:12:02 +05:30
NikhilandGitHub 40fd7790eb fix: my issues duplication (#1726) 2023-07-31 18:11:46 +05:30
Aaryan KhandelwalandGitHub d733fb92cd fix: show project drag handle only when uncollapsed (#1727)
* fix: show project drag handle only when uncollapsed

* chore: fetch profile issues auth

* chore: update theme colors

* chore: update profile page redirection
2023-07-31 18:11:25 +05:30
NikhilandGitHub 1ae78e55c9 chore: profile page permission (#1728)
* chore: profile page permission

* dev: change the default type
2023-07-31 18:04:01 +05:30
NikhilandGitHub ff3f1897bc feat: user cycle stats (#1723)
* feat: user cycle stats

* dev: revert capture exception
2023-07-31 17:49:07 +05:30
Sai PhanindraandGitHub f42f2465a9 Changed alt text for badges in Readme (#1686) 2023-07-31 17:25:37 +05:30
Aaryan KhandelwalandGitHub 7ad0466d65 refactor: new onboarding workflow (#1724)
* refactor: new onboarding workflow

* refactor: new onboarding workflow
2023-07-31 17:23:49 +05:30
Anmol Singh BhatiaandGitHub e8f748a67d style: responsive title (#1683)
* style: responsive issue title added

* style: responsive breadcrumbs and app-header layout

* style: breadcrumbs styling

* fix: app header dropdown issue and limit app header title to 32 characters
2023-07-31 17:22:48 +05:30
Dakshesh JainandGitHub 81b1405448 refactor: moved mutate to 'finally' block (#1722)
* refactor: moved mutate to 'finally' block

fix: content for success

* fix: invited -> added
2023-07-31 17:21:37 +05:30
Anmol Singh BhatiaandGitHub 98d9763f8e feat: sidebar project ordering functionality added (#1725) 2023-07-31 17:10:23 +05:30
Dakshesh JainandGitHub c9498fa54d fix: workspace member invitation mutate (#1721)
* fix: workspace member invitation mutate

* fix: mutate on finally
2023-07-31 16:58:45 +05:30
Aaryan KhandelwalandGitHub 0586d30a33 style: profile page responsiveness added (#1710)
* refactor: folder structure

* style: mobile responsiveness added

* chore: add user profile redirection

* chore: profile page authorization
2023-07-31 16:57:28 +05:30
Dakshesh JainandGitHub 406b323e8e fix: image upload accepting non-image format (#1720)
* fix: image upload accepting non-image format

* style: showing all the valid for image upload
2023-07-31 14:17:51 +05:30
Anmol Singh BhatiaandGitHub 47838a506a fix: workspace name validation added (#1702) 2023-07-31 12:03:16 +05:30
d9ce042dff handle email invitation urls in outlook clients properly (#1671)
Co-authored-by: Ankur Singh <ankur.singh@epfl.ch>
2023-07-31 11:48:29 +05:30
Dakshesh JainandGitHub 4fb11cb388 fix: project cover image upload (#1704)
* fix: popover close on image upload

* fix: removed comments
2023-07-31 11:47:45 +05:30
Anmol Singh BhatiaandGitHub 6769d1139e feat: project user preference for pages (#1673)
* feat: project user preference for pages

* feat: page block description improvement

* fix: create block input box
2023-07-31 11:47:22 +05:30
Anmol Singh BhatiaandGitHub 89e7975821 style: ui improvements (#1699)
* style: sibling issues ui

* style: view dropdown
2023-07-31 11:45:10 +05:30
NikhilandGitHub 89bf24bd64 Merge pull request #1718 from makeplane/stage-release
dev: equate stage-release and develop branch
2023-07-31 11:42:18 +05:30
NikhilandGitHub 922735e5f2 feat: issue and comments reaction (#1674)
* dev: initialize issue reactions

* dev: issue reactions

* dev: comment reactions and update in urls

* dev: reactions in issue and comment list

* dev: reaction filtering

* dev: comment reaction lite serializer

* fix: reaction delete endpoint query
2023-07-31 10:42:17 +05:30
NikhilandGitHub ed75163ec4 chore: project preferences (#1669) 2023-07-31 10:42:00 +05:30
NikhilandGitHub 8e0124be91 feat: project ordering (#1701)
* dev: project ordering

* dev: add ordering for projects list
2023-07-31 10:41:25 +05:30
Anmol Singh BhatiaandGitHub c98edd4a91 style: reactions ui improvement (#1705) 2023-07-30 02:08:52 +05:30
Dakshesh JainandGitHub 35bb71303e refactor: fetching notification only when popover is open (#1706) 2023-07-30 02:05:30 +05:30
Aaryan KhandelwalandGitHub e5a3bec28c Merge pull request #1709 from makeplane/stage-release
promote: stage-release to master
2023-07-28 19:47:04 +05:30
Aaryan KhandelwalandGitHub 30054f71a8 Merge pull request #1708 from makeplane/fix/google_authentication
fix: google not accepting width as string
2023-07-28 19:35:10 +05:30
Dakshesh Jain f40eb1add1 fix: google not accepting width as string 2023-07-28 19:21:55 +05:30
NikhilandGitHub e0affa21c4 feat: profile page endpoints (#1682)
* dev: profile page endpoints

* dev: workspace projects endpoint

* dev: user profile page endpoints

* dev: profile page endpoints

* dev: project filters

* dev: fix priority distribution

* dev: issue subscriptions

* dev: issue priority distribution and issue activity api optimization

* dev: user data in profile endpoints

* dev: profile page data

* dev: project list endpoint

* dev: project emojis

* dev: capture exception

* dev: update workspace user profile urls

* dev: user profile endpoints rename and activity filter

* dev: fix subscriber issues filtering
2023-07-28 14:35:45 +05:30
NikhilandGitHub b14c70df71 fix: workspace member only admin delete (#1700) 2023-07-28 14:29:40 +05:30
NikhilandGitHub 4c54ca5494 dev: revert issue filters query parameters (#1703) 2023-07-28 14:29:05 +05:30
Aaryan KhandelwalandGitHub 10f145f85c feat: user profile analytics, views and filters (#1698)
* feat: user profile overview

* chore: profile sidebar designed

* feat: user issues filters and view options

* refactor: filters

* refactor: mutation logic

* fix: percentage calculation logic and sidebar shadow
2023-07-28 13:39:42 +05:30
Dakshesh JainandGitHub 8930840a76 style: text color & position (#1692)
* feat: developed reaction selector component

* feat add reaction on issue & issue comment

refactor: reaction selector component, made hooks to abstracted reaction logic & state by making custom hook

* fix: emoji.helper.tsx function

* refactor: reaction not working on inbox issue

* fix: user not been passed to create/delete function

* style: text position & color
2023-07-28 10:46:35 +05:30
Vamsi KuramaandGitHub e930f8cc7b Merge pull request #1697 from makeplane/stage-release
promote: stage-release to master
2023-07-28 00:55:32 +05:30
Vihar KuramaandGitHub 865698bcb4 Merge pull request #1696 from makeplane/remove-events
chore: remove events
2023-07-28 00:33:12 +05:30
Aaryan Khandelwal a3678b490a chore: update .env.example 2023-07-28 00:32:34 +05:30
Aaryan Khandelwal 5117859142 chore: update .env.example 2023-07-28 00:29:47 +05:30
Aaryan Khandelwal 05c923a97f chore: remove events 2023-07-28 00:26:35 +05:30
Aaryan KhandelwalandGitHub bedc3ab5a1 fix: create view form (#1691) 2023-07-27 21:38:16 +05:30
Dakshesh JainandGitHub 0cc4468091 feat: issue & comment reaction (#1690)
* feat: developed reaction selector component

* feat add reaction on issue & issue comment

refactor: reaction selector component, made hooks to abstracted reaction logic & state by making custom hook

* fix: emoji.helper.tsx function

* refactor: reaction not working on inbox issue
2023-07-27 18:55:03 +05:30
NikhilandGitHub 5cfea3948f fix: issue filters (#1688) 2023-07-27 17:30:39 +05:30
NikhilandGitHub c947a6dd64 fix: cycles n+1 (#1689) 2023-07-27 17:30:00 +05:30
Aaryan KhandelwalandGitHub c54b8b9a15 refactor: filters list component (#1687)
* refactor: filters list component

* fix: build error
2023-07-27 00:57:12 +05:30
Dakshesh JainandGitHub 0b86080166 fix: displaying API error for AI response (#1677) 2023-07-26 23:59:34 +05:30
Dakshesh JainandGitHub 0e352b7bcb style: padding bottom no load more (#1678) 2023-07-26 23:59:05 +05:30
Dakshesh JainandGitHub bc8be73d6c fix: invalid isOpen flag (#1676) 2023-07-26 23:58:40 +05:30
Aaryan KhandelwalandGitHub d6f3c2515a fix: fetch states only when the dropdown is opened (#1684) 2023-07-26 23:20:44 +05:30
NikhilandGitHub fd9dcfa2ec feat: my issues filtering (#1666)
* feat: my issues filtering

* dev: migrations

* dev: remove state list endpoint

* dev: state group filtering
2023-07-26 17:52:35 +05:30
Dakshesh JainandGitHub 39274fd5fa fix: show-sub issue filter is true now by default (#1679) 2023-07-26 17:52:03 +05:30
Aaryan KhandelwalandGitHub 3d7fe40035 feat: my issues view layouts and filters, refactor: issue views (#1681)
* refactor: issue views and my issues

* chore: update view dropdown options

* refactor: render emoji function

* refactor: api calss

* fix: build errors

* fix: fetch states only when dropdown is opened

* chore: my issues dnd

* fix: build errors

* refactor: folder structure
2023-07-26 17:51:26 +05:30
Aaryan KhandelwalandGitHub ec62308195 refactor: cmdk (#1680)
* chore: add project identifier to each result item

* refactor: cmdk code structure

* refactor: folder structure
2023-07-26 15:12:00 +05:30
Dakshesh JainandGitHub 9c28011ea5 refactor: notification pagination & made context for handling functions related to notification (#1654)
* refactor: add pagination and notification context

* refactor: moved header & snooze option to their own files

added check before toUpperCase function
2023-07-26 12:02:14 +05:30
Dakshesh JainandGitHub 10059b2150 feat: upload cover image for project (#1668) 2023-07-26 12:01:33 +05:30
Anmol Singh BhatiaandGitHub 6fe99c7f3e fix: custom search select query fix (#1610) 2023-07-25 16:08:11 +05:30
Anmol Singh BhatiaandGitHub 2229d8d828 fix: link validation fix (#1645)
* fix: link validation fix

* fix: build fix and code refactor

* fix: build fix

* chore: code refactor

* fix: link url input fix

* chore: link type updated

* fix: build fix
2023-07-25 15:43:47 +05:30
ad410d134f fix: project identifier validation (#1643)
* fix: project identifier validation

* feat: alphanumric identifier validation

* chore: code refactor

* refactor: project identifier change function

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-07-25 15:43:28 +05:30
Anmol Singh BhatiaandGitHub 9b531aca47 feat: workspace member bulk invite (#1530)
* feat: workspace member bulk invite

* style: invite dropdown styling
2023-07-25 15:42:52 +05:30
NikhilandGitHub 2bb842367f fix: pages n plus 1 (#1651) 2023-07-25 14:23:58 +05:30
NikhilandGitHub 916fca53ac chore: project identifier for all the search items and take project id as query params (#1663) 2023-07-25 14:23:41 +05:30
NikhilandGitHub 7763cca9a2 fix: email magic sign in integrity error (#1662) 2023-07-25 14:23:26 +05:30
NikhilandGitHub 3ad3cc77f9 fix: handle model save error in issue activity (#1661) 2023-07-25 14:23:10 +05:30
NikhilandGitHub 998fab80b5 feat: project preferences (#1652) 2023-07-25 14:22:54 +05:30
NikhilandGitHub 679c97bbe3 fix: make title as optional for issue links (#1641) 2023-07-25 14:22:38 +05:30
Aaryan KhandelwalandGitHub c87d70195d refactor: command k and enable workspace level search (#1664)
* refactor: command k and enable workspace level search

* fix: global level search
2023-07-25 13:52:21 +05:30
Dakshesh JainandGitHub c2327fa538 feat: show sub-issue on list-view (#1665) 2023-07-25 13:09:38 +05:30
Anmol Singh BhatiaandGitHub bc076e69f7 style: issue label ui updated (#1653)
* style: issue label ui updated

* chore: code refactor
2023-07-25 12:07:16 +05:30
Rhea JainandGitHub 1db9030f20 Update README.md (#1659) 2023-07-24 21:19:31 +05:30
NikhilandGitHub 737ac33d5d chore: project issue link activity (#1639)
* chore: project issue link activity

* dev: fix delete activity
2023-07-24 17:41:04 +05:30
NikhilandGitHub fd17c249fd chore: ability to comment for guests and viewers (#1650) 2023-07-24 17:40:26 +05:30
NikhilandGitHub afce027bf3 chore: global search endpoint for workspace (#1649)
* chore: global search endpoint

* chore: global search endpoitnt for workspace search
2023-07-24 17:39:29 +05:30
Aaryan KhandelwalandGitHub 6db1db55e9 fix: create issue modal restting (#1647) 2023-07-24 14:39:47 +05:30
NikhilandGitHub 08a025f67c fix: notification select related fields (#1646) 2023-07-24 14:39:25 +05:30
Aaryan KhandelwalandGitHub 8a2cc6f919 fix: remove issue link activity (#1644) 2023-07-24 13:57:23 +05:30
Aaryan KhandelwalandGitHub 29b04bb3ef chore: increase project identifier max length (#1638) 2023-07-24 12:57:05 +05:30
Aaryan KhandelwalandGitHub f3b09a13b8 chore: set all issue properties as true by default (#1640) 2023-07-24 12:56:27 +05:30
NikhilandGitHub e83ef7332d fix: issue create update n+1 and issue activity get n+1 (#1606)
* fix: issue create update n+1 and issue activity get n+1

* dev: notifications n+1
2023-07-24 12:23:34 +05:30
NikhilandGitHub 8ff834c328 chore: return total members, cycle and modules (#1637) 2023-07-24 12:23:04 +05:30
NikhilandGitHub 4ee161bae2 chore: project identifiers (#1636) 2023-07-24 12:19:11 +05:30
NikhilandGitHub 27402b52b6 fix: issue activity for attachments (#1626)
* fix: issue activity for attachments

* fix: issue activity assignees
2023-07-24 12:17:41 +05:30
NikhilandGitHub 479dfc17f5 feat: notification pagination (#1580) 2023-07-24 12:10:21 +05:30
NikhilandGitHub 8e70a036b7 chore: add project lead as project member (#1627)
* chore: add project lead as project member

* fix: project member lead create
2023-07-24 12:09:32 +05:30
NikhilandGitHub 73b38f4db9 feat: cross project issue linking (#1609)
* dev: sub issue listing

* feat: cross project issue linking

* dev: project search

* dev: workspace search logic

* dev: return state and project details for parent issues

* dev: issue state flat serializer

* dev: id for lite serializer

* dev: project name in for the response issues

* dev: issue cross project

* dev: issue project identifiers

* dev: blocked and blocked by activity
2023-07-24 12:08:47 +05:30
Ikko Eltociear AshimineandGitHub e357283789 chore: fix typo in replace-env-vars.sh (#1632)
peform -> perform
2023-07-24 12:08:29 +05:30
Aaryan KhandelwalandGitHub 2ce7914b7a style: new cycles list page design with empty states (#1633) 2023-07-24 11:32:59 +05:30
Aaryan KhandelwalandGitHub fe60771943 fix: create cycle modal (#1631)
* fix: cycle date select in the modal

* chore: update remove assignee issue activity
2023-07-23 22:54:17 +05:30
Aaryan KhandelwalandGitHub 464c13fcd0 fix: issue parent activity (#1629) 2023-07-23 22:14:41 +05:30
Aaryan KhandelwalandGitHub a7b5ad55ab style: create project modal (#1628)
* style: create project modal

* fix: build error

* fix: modal width
2023-07-23 22:14:26 +05:30
Aaryan KhandelwalandGitHub ccbcfecc6d fix: form not submitting on enter (#1613) 2023-07-23 22:13:09 +05:30
Aaryan KhandelwalandGitHub 7669ee8755 fix: project select not working on the create issue modal (#1608) 2023-07-23 22:12:13 +05:30
Aaryan KhandelwalandGitHub fdb7da4d45 chore: show proper error messages on profile form submit (#1611) 2023-07-23 22:10:40 +05:30
Aaryan KhandelwalandGitHub ff6690afd2 chore: workspace level toggle (#1625) 2023-07-23 15:11:28 +05:30
Aaryan KhandelwalandGitHub f9c3f02d15 refactor: issue activity message logic (#1614) 2023-07-23 14:38:07 +05:30
Aaryan KhandelwalandGitHub 6c2600efa7 feat: cross-project issue linking (#1612)
* feat: cross project issue linking

* fix: remove parent issue mutation

* fix: build error
2023-07-22 14:53:48 +05:30
Aaryan KhandelwalandGitHub 0e5c0fe31e refactor: issue search (#1607) 2023-07-21 14:29:06 +05:30
Anmol Singh BhatiaandGitHub 4424d67073 style: ui improvements (#1605)
* style: automation setting border

* style: sidebar ui improvement
2023-07-21 12:37:48 +05:30
guru_sainathandGitHub 26eb3b59ce Merge pull request #1604 from makeplane/stage-release
promote: stage-release to master
2023-07-20 22:45:17 +05:30
guru_sainathandGitHub 4d909fbef3 Merge pull request #1603 from makeplane/develop
promote: develop to stage-release
2023-07-20 22:42:28 +05:30
guru_sainathandGitHub 89210accae Merge pull request #1602 from makeplane/dev/monitoring_settings
dev: add server monitoring configuration
2023-07-20 22:40:08 +05:30
pablohashescobar 7c5c02bba6 dev: add server monitoring configuration 2023-07-20 22:34:47 +05:30
Vamsi KuramaandGitHub 11faf3f810 Merge pull request #1600 from makeplane/stage-release
promote: stage-release to master
2023-07-20 19:36:43 +05:30
Aaryan KhandelwalandGitHub 30ea1adf61 Merge pull request #1597 from makeplane/develop
promote: develop to stage-release
2023-07-20 19:32:04 +05:30
Dakshesh JainandGitHub 546aa40aa3 fix: un-read count not mutating (#1598)
* refactor: height of popover & api fetch call

* fix: notification subscribe endpoint

* fix: un-read count not mutating
2023-07-20 19:15:13 +05:30
NikhilandGitHub 78669363b1 fix: restrict notifications for created by when the actor is same (#1596) 2023-07-20 18:53:02 +05:30
guru_sainathandGitHub bca749986a Merge pull request #1595 from makeplane/develop
promote: develop to stage-release
2023-07-20 18:11:18 +05:30
gurusainath 229938114d Merge branch 'develop' of gurusainath:makeplane/plane into develop 2023-07-20 17:25:31 +05:30
gurusainath c5e418ab47 Merge branch 'stage-release' of gurusainath:makeplane/plane into develop 2023-07-20 17:25:00 +05:30
ecdd1f1d03 promote: develop to stage-release (#1594)
* fix: onboarding invitations overflow (#1575)

* fix: onboarding invitations overflow

* fix: user avatar in the notification card

* style: update graph grid color

* fix: no 'Create by me' label coming up (#1573)

* feat: added new issue subscriber table

* dev: notification model

* feat: added CRUD operation for issue subscriber

* Revert "feat: added CRUD operation for issue subscriber"

This reverts commit b22e062576.

* feat: added CRUD operation for issue subscriber

* dev: notification models and operations

* dev: remove delete endpoint response data

* dev: notification endpoints and fix bg worker for saving notifications

* feat: added list and unsubscribe function in issue subscriber

* dev: filter by snoozed and response update for list and permissions

* dev: update issue notifications

* dev: notification  segregation

* dev: update notifications

* dev: notification filtering

* dev: add issue name in notifications

* dev: notification new endpoints

* fix: pushing local settings

* feat: notification workflow setup and made basic UI

* style: improved UX with toast alerts and other interactions

refactor: changed classnames according to new theme structure, changed all icons to material icons

* feat: showing un-read notification count

* feat: not showing 'subscribe' button on issue created by user & assigned to user

not showing 'Create by you' for view & guest of the workspace

* fix: 'read' -> 'unread' heading, my issue wrong filter

* feat: made snooze dropdown & modal

feat: switched to calendar

* fix: minor ui fixes

* feat: snooze modal date/time select

* fix: params for read/un-read notification

* style: snooze notification modal

* fix: no label for 'Create by me'

* fix: no label for 'Create by me'

* fix: removed console log

* fix: tooltip going behind popover

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>

* style: tooltip on notification header actions (#1577)

* style: tooltip on notification header

* chore: update tooltip content

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>

* fix: user migrations for back population (#1578)

* fix: total notifications count (#1579)

* fix: notification card (#1583)

* feat: add new icons package (#1586)

* feat: add material icons package

* chore: replace issue view icons

* chore: notification ordering (#1584)

* fix: uuid error when cycle and module updates (#1585)

* refactor: height of popover & api fetch call (#1587)

* fix: snooze dropdown overflow (#1588)

* fix: notification subscribe endpoint (#1593)

* refactor: height of popover & api fetch call

* fix: notification subscribe endpoint

* chore: notification empty state overflow (#1592)

* chore: notification empty state overflow

* fix: white logo

* fix: custom theme default values

* fix: custom theme default values

* fix: issues count to remove archived issues (#1591)

* dev: background migration for user custom themes (#1590)

---------

Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com>
Co-authored-by: gurusainath <gurusainath007@gmail.com>
2023-07-20 17:24:17 +05:30
NikhilandGitHub e687cd6f6a dev: background migration for user custom themes (#1590) 2023-07-20 16:59:09 +05:30
NikhilandGitHub 9b6721790f fix: issues count to remove archived issues (#1591) 2023-07-20 16:58:34 +05:30
gurusainath 05df65577a Merge branch 'stage-release' of gurusainath:makeplane/plane into develop 2023-07-20 16:54:40 +05:30
Aaryan KhandelwalandGitHub 52d21b9dda chore: notification empty state overflow (#1592)
* chore: notification empty state overflow

* fix: white logo

* fix: custom theme default values

* fix: custom theme default values
2023-07-20 16:51:00 +05:30
Dakshesh JainandGitHub 51f10d5f36 fix: notification subscribe endpoint (#1593)
* refactor: height of popover & api fetch call

* fix: notification subscribe endpoint
2023-07-20 16:35:18 +05:30
9275e6f373 promote: develop to stage-release (#1589)
* fix: onboarding invitations overflow (#1575)

* fix: onboarding invitations overflow

* fix: user avatar in the notification card

* style: update graph grid color

* fix: no 'Create by me' label coming up (#1573)

* feat: added new issue subscriber table

* dev: notification model

* feat: added CRUD operation for issue subscriber

* Revert "feat: added CRUD operation for issue subscriber"

This reverts commit b22e062576.

* feat: added CRUD operation for issue subscriber

* dev: notification models and operations

* dev: remove delete endpoint response data

* dev: notification endpoints and fix bg worker for saving notifications

* feat: added list and unsubscribe function in issue subscriber

* dev: filter by snoozed and response update for list and permissions

* dev: update issue notifications

* dev: notification  segregation

* dev: update notifications

* dev: notification filtering

* dev: add issue name in notifications

* dev: notification new endpoints

* fix: pushing local settings

* feat: notification workflow setup and made basic UI

* style: improved UX with toast alerts and other interactions

refactor: changed classnames according to new theme structure, changed all icons to material icons

* feat: showing un-read notification count

* feat: not showing 'subscribe' button on issue created by user & assigned to user

not showing 'Create by you' for view & guest of the workspace

* fix: 'read' -> 'unread' heading, my issue wrong filter

* feat: made snooze dropdown & modal

feat: switched to calendar

* fix: minor ui fixes

* feat: snooze modal date/time select

* fix: params for read/un-read notification

* style: snooze notification modal

* fix: no label for 'Create by me'

* fix: no label for 'Create by me'

* fix: removed console log

* fix: tooltip going behind popover

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>

* style: tooltip on notification header actions (#1577)

* style: tooltip on notification header

* chore: update tooltip content

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>

* fix: user migrations for back population (#1578)

* fix: total notifications count (#1579)

* fix: notification card (#1583)

* feat: add new icons package (#1586)

* feat: add material icons package

* chore: replace issue view icons

* chore: notification ordering (#1584)

* fix: uuid error when cycle and module updates (#1585)

* refactor: height of popover & api fetch call (#1587)

* fix: snooze dropdown overflow (#1588)

---------

Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com>
2023-07-20 15:14:57 +05:30
Aaryan KhandelwalandGitHub 4aef8c2242 fix: snooze dropdown overflow (#1588) 2023-07-20 14:49:30 +05:30
Dakshesh JainandGitHub 780573dadd refactor: height of popover & api fetch call (#1587) 2023-07-20 14:33:24 +05:30
NikhilandGitHub 5e625ab132 fix: uuid error when cycle and module updates (#1585) 2023-07-20 14:18:28 +05:30
NikhilandGitHub 34123681cf chore: notification ordering (#1584) 2023-07-20 14:18:06 +05:30
Aaryan KhandelwalandGitHub c72ff782ac feat: add new icons package (#1586)
* feat: add material icons package

* chore: replace issue view icons
2023-07-20 14:17:21 +05:30
Aaryan KhandelwalandGitHub 6eb72507a5 fix: notification card (#1583) 2023-07-20 10:52:52 +05:30
Aaryan KhandelwalandGitHub 26b18b431b fix: total notifications count (#1579) 2023-07-19 20:37:40 +05:30
NikhilandGitHub 1bae9289f5 fix: user migrations for back population (#1578) 2023-07-19 20:06:12 +05:30
5c5bcb33e3 style: tooltip on notification header actions (#1577)
* style: tooltip on notification header

* chore: update tooltip content

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-07-19 18:42:32 +05:30
bed5f76082 fix: no 'Create by me' label coming up (#1573)
* feat: added new issue subscriber table

* dev: notification model

* feat: added CRUD operation for issue subscriber

* Revert "feat: added CRUD operation for issue subscriber"

This reverts commit b22e062576.

* feat: added CRUD operation for issue subscriber

* dev: notification models and operations

* dev: remove delete endpoint response data

* dev: notification endpoints and fix bg worker for saving notifications

* feat: added list and unsubscribe function in issue subscriber

* dev: filter by snoozed and response update for list and permissions

* dev: update issue notifications

* dev: notification  segregation

* dev: update notifications

* dev: notification filtering

* dev: add issue name in notifications

* dev: notification new endpoints

* fix: pushing local settings

* feat: notification workflow setup and made basic UI

* style: improved UX with toast alerts and other interactions

refactor: changed classnames according to new theme structure, changed all icons to material icons

* feat: showing un-read notification count

* feat: not showing 'subscribe' button on issue created by user & assigned to user

not showing 'Create by you' for view & guest of the workspace

* fix: 'read' -> 'unread' heading, my issue wrong filter

* feat: made snooze dropdown & modal

feat: switched to calendar

* fix: minor ui fixes

* feat: snooze modal date/time select

* fix: params for read/un-read notification

* style: snooze notification modal

* fix: no label for 'Create by me'

* fix: no label for 'Create by me'

* fix: removed console log

* fix: tooltip going behind popover

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-07-19 17:40:40 +05:30
Aaryan KhandelwalandGitHub 124c2f772e fix: onboarding invitations overflow (#1575)
* fix: onboarding invitations overflow

* fix: user avatar in the notification card

* style: update graph grid color
2023-07-19 17:09:53 +05:30
Vihar KuramaandGitHub b38898753f Merge pull request #1570 from makeplane/develop
promote: develop to stage-release
2023-07-19 16:07:43 +05:30
guru_sainathandGitHub 86a120f11e Merge branch 'stage-release' into develop 2023-07-19 16:05:13 +05:30
Rhea JainandGitHub da603dc3f8 Update README.md (#1543) 2023-07-19 15:28:01 +05:30
NikhilandGitHub 2f3970f641 feat: beat worker for configuration (#1571) 2023-07-19 15:25:33 +05:30
NikhilandGitHub d759438ebd fix: issue automation (#1569) 2023-07-19 15:08:41 +05:30
NikhilandGitHub 0102f1d693 fix: psycopg errors (#1568) 2023-07-19 15:07:54 +05:30
53e443d816 feat: notifications (#1566)
* feat: added new issue subscriber table

* dev: notification model

* feat: added CRUD operation for issue subscriber

* Revert "feat: added CRUD operation for issue subscriber"

This reverts commit b22e062576.

* feat: added CRUD operation for issue subscriber

* dev: notification models and operations

* dev: remove delete endpoint response data

* dev: notification endpoints and fix bg worker for saving notifications

* feat: added list and unsubscribe function in issue subscriber

* dev: filter by snoozed and response update for list and permissions

* dev: update issue notifications

* dev: notification  segregation

* dev: update notifications

* dev: notification filtering

* dev: add issue name in notifications

* dev: notification new endpoints

* fix: pushing local settings

* feat: notification workflow setup and made basic UI

* style: improved UX with toast alerts and other interactions

refactor: changed classnames according to new theme structure, changed all icons to material icons

* feat: showing un-read notification count

* feat: not showing 'subscribe' button on issue created by user & assigned to user

not showing 'Create by you' for view & guest of the workspace

* fix: 'read' -> 'unread' heading, my issue wrong filter

* feat: made snooze dropdown & modal

feat: switched to calendar

* fix: minor ui fixes

* feat: snooze modal date/time select

* fix: params for read/un-read notification

* style: snooze notification modal

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-07-19 14:44:04 +05:30
NikhilandGitHub 98b9957753 fix: issue created notification (#1553)
* fix: issue created notification

* chore: my issues assigned notifications

* dev: fix read at notification
2023-07-19 14:31:45 +05:30
NikhilandGitHub 509af4662d dev: migrations (#1561) 2023-07-19 14:31:07 +05:30
Aaryan KhandelwalandGitHub d1f2a819f5 fix: update border colors on theme switch (#1565) 2023-07-19 13:53:22 +05:30
Aaryan KhandelwalandGitHub a42bff675b fix: archived issue details page overflow, restore button loading (#1564) 2023-07-19 13:29:08 +05:30
Aaryan KhandelwalandGitHub c71a2137e6 fix: scope and demand line graph y axis values (#1563) 2023-07-19 13:13:23 +05:30
Rhea JainandGitHub 5e1f0a2604 fixes as per consistency (#1562) 2023-07-19 13:00:11 +05:30
Aaryan KhandelwalandGitHub ccab382d7d chore: add dashboard navbar (#1560) 2023-07-19 12:16:39 +05:30
Aaryan KhandelwalandGitHub a7aa78a349 chore: high contrast theming (#1559)
* fix: archived issues empty state

* chore: update high contrast theme colors
2023-07-19 11:47:41 +05:30
Aaryan KhandelwalandGitHub 5ae963c451 fix: tour sidebar icons, auth pages scroll (#1555)
* fix: tour sidebar icons, menu button word wrap

* chore: change theme on sign out

* fix: auth pages scroll y-padding
2023-07-18 19:02:33 +05:30
Dakshesh JainandGitHub 07c097c9ad style: different style for archive & project issues (#1552)
* fix: same empty state coming for all issue list related UI

* style: different style for archive & project  issues
2023-07-18 18:58:36 +05:30
Aaryan KhandelwalandGitHub fc92d7d1a0 chore: hide issue properties if there are none (#1554) 2023-07-18 18:47:28 +05:30
NikhilandGitHub 9ba8f5c21f fix: cycle module notifications (#1536)
* chore: add triggered by details for notifications

* dev: update issue activity json to include extra fields

* dev: improve issue fetch
2023-07-18 15:59:58 +05:30
Aaryan KhandelwalandGitHub 059b8c793a refactor: parent issue select (#1546)
* refactor: parent issue select

* fix: sibling issues list
2023-07-18 15:36:03 +05:30
Dakshesh JainandGitHub 93da220c4a fix: same empty state coming for all issue list related UI (#1547) 2023-07-18 15:28:12 +05:30
Aaryan KhandelwalandGitHub 0feab162ff style: onboarding screens (#1539)
* fix: onboarding screen styling

* chore: minor styling fixes

* chore: disable buttons if form is invalid
2023-07-18 15:20:05 +05:30
NikhilandGitHub 55a1291b1d fix: inbox issue archival (#1538)
* chore: add triggered by details for notifications

* dev: update issue activity json to include extra fields

* dev: fix inboc issue archival

* dev: update the filter for closing issues
2023-07-18 15:10:20 +05:30
NikhilandGitHub b12a00cf4a chore: default project state colors (#1540)
* chore: add triggered by details for notifications

* dev: update issue activity json to include extra fields

* chore: project create default state colors
2023-07-18 15:10:01 +05:30
NikhilandGitHub 52ee8c5615 chore: remove logger from gpt assistant (#1542)
* chore: add triggered by details for notifications

* dev: update issue activity json to include extra fields

* chore: remove logger from gpt assistant
2023-07-18 15:09:44 +05:30
NikhilandGitHub 68108c9fe9 fix: favorite permissions (#1549)
* chore: add triggered by details for notifications

* dev: update issue activity json to include extra fields

* dev: fix permissions for cycle and views
2023-07-18 15:09:18 +05:30
Aaryan KhandelwalandGitHub 88fbe85087 fix: button and input heights (#1548) 2023-07-18 15:00:06 +05:30
Dakshesh JainandGitHub 9b423cea4b fix: create workspace valid (#1535)
show alert on-change rather then on-submit
2023-07-18 12:08:22 +05:30
Dakshesh JainandGitHub 9d891ecce1 fix: selecting label once create using modal (#1537) 2023-07-18 12:08:09 +05:30
16a7bd3bda feat: user issue notifications (#1523)
* feat: added new issue subscriber table

* dev: notification model

* feat: added CRUD operation for issue subscriber

* Revert "feat: added CRUD operation for issue subscriber"

This reverts commit b22e062576.

* feat: added CRUD operation for issue subscriber

* dev: notification models and operations

* dev: remove delete endpoint response data

* dev: notification endpoints and fix bg worker for saving notifications

* feat: added list and unsubscribe function in issue subscriber

* dev: filter by snoozed and response update for list and permissions

* dev: update issue notifications

* dev: notification  segregation

* dev: update notifications

* dev: notification filtering

* dev: add issue name in notifications

* dev: notification new endpoints

* fix: pushing local settings

* feat: notification workflow setup and made basic UI

* style: improved UX with toast alerts and other interactions

refactor: changed classnames according to new theme structure, changed all icons to material icons

* feat: showing un-read notification count

* feat: not showing 'subscribe' button on issue created by user & assigned to user

not showing 'Create by you' for view & guest of the workspace

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
2023-07-18 12:07:55 +05:30
Dakshesh JainandGitHub 6e9f3971a5 style: empty state for archive issue (#1541) 2023-07-18 12:07:35 +05:30
Aaryan KhandelwalandGitHub dddfeb17b7 refactor: dropdowns (#1532) 2023-07-17 17:35:47 +05:30
Aaryan KhandelwalandGitHub 538d67dbd9 fix: theming colors (#1533)
* chore: update border colors

* chore: loading screens bg color, custom theming default values

* chore: remove unnecessary images

* chore: update static colors

* chore: update old variable names

* chore: update issue activity icon colors

* chore: update user activity icon colors
2023-07-17 16:28:23 +05:30
NikhilandGitHub 090870b03e fix: created_by notifications (#1534)
* chore: add triggered by details for notifications

* dev: update issue activity json to include extra fields

* dev: remove unused imports and improve the filtering

* dev: fix unread filter

* dev: fix created_by none when updating the field
2023-07-17 16:09:30 +05:30
Aaryan KhandelwalandGitHub 0a56a30ab2 fix: issue sidebar create label form (#1527)
* fix: issue sidebar create label form

* fix: issue details page overflow
2023-07-17 15:37:50 +05:30
Aaryan KhandelwalandGitHub 8df1648329 fix: dashboard pie chart legends overflow (#1528) 2023-07-17 14:48:43 +05:30
NikhilandGitHub b69c4b6b30 chore: notifications (#1515)
* chore: add triggered by details for notifications

* dev: update issue activity json to include extra fields

* dev: triggered_by details

* dev: add bot filtering

* dev: unread notification count endpoint

* dev: update endpoint to send count for all notification types
2023-07-17 13:17:48 +05:30
NikhilandGitHub e0181342c0 fix: n+1s (#1514)
* dev: fix workspace n+1

* dev: trim down label details for issue state serializer
2023-07-17 13:17:34 +05:30
NikhilandGitHub f9f8b5c3d9 chore: update bridge id to cycle and module id (#1513)
* chore: update bridge id to cycle and module id

* fix: typo
2023-07-17 13:17:11 +05:30
Aaryan KhandelwalandGitHub 5fadf53580 style: auth screens (#1531)
* chore: invitations empty state

* style: new auth screens

* chore: update typography

* chore: update border colors

* chore: resend code text
2023-07-17 13:00:44 +05:30
Aaryan KhandelwalandGitHub da6ecd439c refactor: favorite projects fetch function, chore: update tooltip design (#1526)
* chore: update tooltip design

* fix: due date popup overflow

* fix: build error

* fix: build error

* chore: add key to map return value

* refactor: favorite projects sidebar list
2023-07-14 15:08:07 +05:30
pablohashescobarandGitHub 7914bcf486 refactor: serializers to avoid getting child items (#1504)
* refactor: serializers to avoid getting child items

* dev: update issue state serializer to remove n+1 items
2023-07-14 12:01:38 +05:30
Aaryan KhandelwalandGitHub c9a5893c3f chore: update favorite projects endpoint (#1522)
* chore: upate favorite projects list endpoint

* chore: add project button on the sidebar

* refactor: sidebar favorite projects workflow
2023-07-13 19:44:53 +05:30
Anmol Singh BhatiaandGitHub 7361657660 fix: issue delete redirection, chore: code refactor (#1521) 2023-07-13 19:00:43 +05:30
60e96bcb72 style: app sidebar revamp (#1120)
* style: app sidebar, sidebar workspace dropdown and help section styling

* style: consistent  padding and spacing

* feat: material icon global component

* style: icons updated and tooltip added

* style: project list spacing and project name truncate

* style: sidebar padding and theming

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-07-13 18:35:43 +05:30
Anmol Singh BhatiaandGitHub 3f3fb373cc fix: issue archive improvement, chore: code refactor (#1520)
* style: select month modal

* chore: automation setting dropdown updated ,style: month dropdown styling

* chore: restore issue alert message updated

* chore: archive issue fetching updated and code refactor

* fix: build fix
2023-07-13 17:04:24 +05:30
a829e6fc40 chore: added is_favorite filter (#1518)
* fix: notification filtering

* dev: reverse logic for archive filtering

* dev: fix watching notification

* dev: read filter

* dev: update automatic issue archival and close to send notifications

* dev: update archival structure for auto close issues

* chore: added is_favorite filter

* fix: removed the unwanted filter

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
2023-07-13 16:51:42 +05:30
Aaryan KhandelwalandGitHub 864e592bc5 fix: onboarding screen design issues (#1517)
* fix: projects empty state flicker

* fix: tour images padding

* chore: add greeting icon on the dashboard

* chore: update email id font weight

* fix: placeholder color
2023-07-13 16:51:08 +05:30
pablohashescobarandGitHub 411a661abd fix: magic code casing (#1519) 2023-07-13 16:49:37 +05:30
pablohashescobarandGitHub 61ad6b9e0e chore: upgrade psycopg to 3 (#1507) 2023-07-13 13:51:02 +05:30
2ff49c93bd fix: notifications (#1498)
* fix: notification filtering

* dev: reverse logic for archive filtering

* dev: fix watching notification

* dev: read filter

* dev: update automatic issue archival and close to send notifications

* dev: update archival structure for auto close issues

* Closing of dialog when we click around the dialog (#1364)

* style: new empty states (#1497)

* fix: custom colors opacity

* chore: update text colors for dark mode

* fix: dropdown text colors, datepicker bg color

* chore: update text colors

* chore: updated primary bg color

* style: new empty states added

* refactor: empty state for issues

* style: empty state for estimates

* chore: update labels, estimates and integrations empty states

* fix: custom analytics sidebar

* fix: archival spelling mistake

* fix: updated the default state logic

* dev: fix key

---------

Co-authored-by: Khrystyna Derenivska <56432889+kblueberry@users.noreply.github.com>
Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2023-07-13 13:47:24 +05:30
Aaryan KhandelwalandGitHub 120d06983d style: inbox (#1508) 2023-07-13 11:38:45 +05:30
Anmol Singh BhatiaandGitHub c9cbca5ec8 feat: auto-archive and auto-close (#1502)
* chore: issue archive services and types added

* chore: project type and constant updated

* feat: auto-close and auto-archive feature added

* feat: implement rendering of archived issues

* feat: implemented rendering of only list view for archived issues , chore: update types and services

* feat: implemented archive issue detail page and unarchive issue functionality , chore: refactor code

* feat: activity for issue archive and issue restore added

* fix: redirection and delete fix

* fix: merge conflict

* fix: restore issue redirection fix

* fix: disable modification of issue properties for archived issues, style: disable properties styling

* fix: hide empty group, switch to list view on redirct to archived issues

* fix: remove unnecessary header buttons for archived issue

* fix: auto-close dropdown fix
2023-07-13 11:34:37 +05:30
pablohashescobarandGitHub 275942a246 feat: flag for onboarding tour completion (#1499)
* feat: flag for onboarding tour completion

* dev: boolean field

* dev: user tour completed endpoint

* dev: onboarding step json
2023-07-12 22:10:07 +05:30
Aaryan KhandelwalandGitHub a1b09fcbc6 style: onboarding screens (#1412)
* style: new onboarding screens

* chore: onboarding tour screens

* fix: build error

* fix: build errors

* style: default layout background

* chor: update user auth hook logic, style: new onboarding screens

* fix: component structure

* chore: tab responsiveness added

* fix: redirection logic

* style: welcome screens responsiveness

* chore: update workspace url input field

* style: mobile responsiveness added

* chore: complete onboarding workflow

* style: create workspace page design update

* style: workspace invitations page design update

* chore: update steps logic

* fix: step change logic

* style: tour steps
2023-07-12 19:55:08 +05:30
Aaryan KhandelwalandGitHub 26f0e9da00 fix: add missing argument (#1506) 2023-07-12 17:55:58 +05:30
Aaryan KhandelwalandGitHub 4a2057c0b3 style: new empty states (#1497)
* fix: custom colors opacity

* chore: update text colors for dark mode

* fix: dropdown text colors, datepicker bg color

* chore: update text colors

* chore: updated primary bg color

* style: new empty states added

* refactor: empty state for issues

* style: empty state for estimates

* chore: update labels, estimates and integrations empty states

* fix: custom analytics sidebar
2023-07-12 11:45:45 +05:30
Khrystyna DerenivskaandGitHub 82ff786666 Closing of dialog when we click around the dialog (#1364) 2023-07-12 11:39:40 +05:30
Bavisetti NarayanandGitHub 5773ff2afd chore: updated old and new value in issue archival (#1500) 2023-07-11 19:15:39 +05:30
+6 1403a536c1 feat: openai host (#1447)
* support custom openai api endpoints (for azure and local deployments)

* update openai python package and use new api format

* fix: project member list endpoint n+1 (#1458)

* chore: workspace char name and slug maximum length (#1453)

* fix: user invitation workflow for self hosted version (#1441)

* chore: due date filter (#1460)

* refactor: standardized date format throughout the platform (#1461)

* chore: due date filter (#965)

* chore: due date filter

* fix: deployment error

* chore: optimized code

* chore: created constants for due date

* chore: create seperated css file for react datepicker styling

* fix: due date filter

* chore: highlight selected option

* fix: merge conflicts

* fix: build error

* chore: date range selector validation

* fix: issue views overflow

* refactor: due date filter modal code

* refactor: multi level dropdown

* chore: due date filter select default value

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>

* fix: layout of tabs on Pages is not adaptable to mobile screens #1380 (#1400)

* fix: layout of tabs on Pages is not adaptable to mobile screens #1380

* fix: scrolling experience on page

* chore: update project members type (#1459)

* fix: state icon color on group titles (#1435)

* fix: workspace invitation delete for self hosted (#1475)

* chore: upgrade backend dependencies (#1479)

* chore: upgrade backend dependencies

* dev: update storage settings for self hosted version

* chore: project members endpoint to support bulk operations (#1464)

* chore: rename workspace company size to organization size (#1463)

* chore: rename workspace company size to organization size

* chore: make workspace organization size as required

* fix: static and media files storages (#1482)

* fix: emoji render function (#1484)

* fix: emoji render function

* fix: emoji render function

* feat: bulk invite for project (#1466)

* feat: bulk invite for project

* feat: members dropdown updated

* fix: error message added ,style: ui improvement

* feat: added add members button for scenarios with multiple members

* chore: updated watch to fields

* feat: created on and updated on column added in spreadsheet view (#1454)

* feat: created on and updated on column added in spreadsheet view

* fix: build fix

* refactor: simplify logic

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>

* fix: resolved overflow issue with longer state names (#1444)

* chore: update theming structure (#1422)

* chore: store various shades of accent color

* refactor: custom theme selector

* refactor: custom theme selector

* chore: update custom theme input labels

* fix: color generator function logic

* fix: accent color preloaded data

* chore: new theming structure

* chore: update shades calculation logic

* refactor: variable names

* chore: update color scheming

* chore: new color scheming

* refactor: themes folder structure

* chore: update classnames according to new variables

* Revert "chore: update classnames according to new variables"

This reverts commit 60a87453b2.

* chore: remove temp file

* chore: update classnames according to the new theming structure (#1494)

* chore: store various shades of accent color

* refactor: custom theme selector

* refactor: custom theme selector

* chore: update custom theme input labels

* fix: color generator function logic

* fix: accent color preloaded data

* chore: new theming structure

* chore: update shades calculation logic

* refactor: variable names

* chore: update color scheming

* chore: new color scheming

* refactor: themes folder structure

* chore: update classnames to the new ones

* chore: update static colors

* chore: sidebar link colors

* chore: placeholder color

* chore: update border classnames

* chore:  environment variables for worker and api (#1492)

* feat: notifications (#1363)

* feat: added new issue subscriber table

* dev: notification model

* feat: added CRUD operation for issue subscriber

* Revert "feat: added CRUD operation for issue subscriber"

This reverts commit b22e062576.

* feat: added CRUD operation for issue subscriber

* dev: notification models and operations

* dev: remove delete endpoint response data

* dev: notification endpoints and fix bg worker for saving notifications

* feat: added list and unsubscribe function in issue subscriber

* dev: filter by snoozed and response update for list and permissions

* dev: update issue notifications

* dev: notification  segregation

* dev: update notifications

* dev: notification filtering

* dev: add issue name in notifications

* dev: notification new endpoints

---------

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

* fix: docker inconsistencies (#1493)

Co-authored-by: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com>

* feat: issue archival and close (#1474)

* chore: added issue archive using celery beat

* chore: changed the file name

* fix: created API and updated logic for achived-issues

* chore: added issue activity message

* chore: added the beat scheduler command

* feat: added unarchive issue functionality

* feat: auto issue close

* dev: refactor endpoints and issue archive activity

* dev: update manager for global filtering

* fix: added id in issue unarchive url

* dev: update auto close to include default close state

* fix: updated the list and retrive function

* fix: added the prefetch fields

* dev: update unarchive

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>

* fix: updated text and background colors (#1496)

* fix: custom colors opacity

* chore: update text colors for dark mode

* fix: dropdown text colors, datepicker bg color

* chore: update text colors

* chore: updated primary bg color

* feat: web waitlist modal integration (#1487)

* dev : Updating the limit of the issues in the sidebar and a weight list modal

* dev: integrated supabase and implemented web waitlist api endpoint

* dev : updated web pro weightlist request

* dev: rename typo

* dev: web waitlist endpoint update

* update: ui fixes

* fix: removed supabase from env.example

* chore: replaced supabase npm package to cdn

* chore: updated supabase req

* fix: Handled error status and error message.

---------

Co-authored-by: srinivaspendem <you@example.comsrinivaspendem2612@gmail.com>
Co-authored-by: gurusainath <gurusainath007@gmail.com>

* add openai host env in all places

---------

Co-authored-by: Ankur Singh <ankur.singh@epfl.ch>
Co-authored-by: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com>
Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Co-authored-by: Kunal Vishwakarma <116634168+kunalv17@users.noreply.github.com>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
Co-authored-by: Chandan Jal <97095857+ChandanJal@users.noreply.github.com>
Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: Quadrubo <71718414+Quadrubo@users.noreply.github.com>
Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: srinivas pendem <65014795+srinivaspendem@users.noreply.github.com>
Co-authored-by: srinivaspendem <you@example.comsrinivaspendem2612@gmail.com>
Co-authored-by: gurusainath <gurusainath007@gmail.com>
2023-07-11 18:51:25 +05:30
srinivas pendemGitHubsrinivaspendem <you@example.comsrinivaspendem2612@gmail.com>gurusainath
eba2f3820a feat: web waitlist modal integration (#1487)
* dev : Updating the limit of the issues in the sidebar and a weight list modal

* dev: integrated supabase and implemented web waitlist api endpoint

* dev : updated web pro weightlist request

* dev: rename typo

* dev: web waitlist endpoint update

* update: ui fixes

* fix: removed supabase from env.example

* chore: replaced supabase npm package to cdn

* chore: updated supabase req

* fix: Handled error status and error message.

---------

Co-authored-by: srinivaspendem <you@example.comsrinivaspendem2612@gmail.com>
Co-authored-by: gurusainath <gurusainath007@gmail.com>
2023-07-11 16:27:29 +05:30
Aaryan KhandelwalandGitHub 253edebb93 fix: updated text and background colors (#1496)
* fix: custom colors opacity

* chore: update text colors for dark mode

* fix: dropdown text colors, datepicker bg color

* chore: update text colors

* chore: updated primary bg color
2023-07-11 15:18:47 +05:30
7554988164 feat: issue archival and close (#1474)
* chore: added issue archive using celery beat

* chore: changed the file name

* fix: created API and updated logic for achived-issues

* chore: added issue activity message

* chore: added the beat scheduler command

* feat: added unarchive issue functionality

* feat: auto issue close

* dev: refactor endpoints and issue archive activity

* dev: update manager for global filtering

* fix: added id in issue unarchive url

* dev: update auto close to include default close state

* fix: updated the list and retrive function

* fix: added the prefetch fields

* dev: update unarchive

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
2023-07-11 14:35:20 +05:30
7087b1b5f2 fix: docker inconsistencies (#1493)
Co-authored-by: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com>
2023-07-11 14:10:57 +05:30
abdb4a4778 feat: notifications (#1363)
* feat: added new issue subscriber table

* dev: notification model

* feat: added CRUD operation for issue subscriber

* Revert "feat: added CRUD operation for issue subscriber"

This reverts commit b22e062576.

* feat: added CRUD operation for issue subscriber

* dev: notification models and operations

* dev: remove delete endpoint response data

* dev: notification endpoints and fix bg worker for saving notifications

* feat: added list and unsubscribe function in issue subscriber

* dev: filter by snoozed and response update for list and permissions

* dev: update issue notifications

* dev: notification  segregation

* dev: update notifications

* dev: notification filtering

* dev: add issue name in notifications

* dev: notification new endpoints

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
2023-07-11 13:36:31 +05:30
pablohashescobarandGitHub ad1a074292 chore: environment variables for worker and api (#1492) 2023-07-11 13:30:55 +05:30
Aaryan KhandelwalandGitHub 4c2cb2368a chore: update classnames according to the new theming structure (#1494)
* chore: store various shades of accent color

* refactor: custom theme selector

* refactor: custom theme selector

* chore: update custom theme input labels

* fix: color generator function logic

* fix: accent color preloaded data

* chore: new theming structure

* chore: update shades calculation logic

* refactor: variable names

* chore: update color scheming

* chore: new color scheming

* refactor: themes folder structure

* chore: update classnames to the new ones

* chore: update static colors

* chore: sidebar link colors

* chore: placeholder color

* chore: update border classnames
2023-07-10 12:47:00 +05:30
Aaryan KhandelwalandGitHub a14f8c281b chore: update theming structure (#1422)
* chore: store various shades of accent color

* refactor: custom theme selector

* refactor: custom theme selector

* chore: update custom theme input labels

* fix: color generator function logic

* fix: accent color preloaded data

* chore: new theming structure

* chore: update shades calculation logic

* refactor: variable names

* chore: update color scheming

* chore: new color scheming

* refactor: themes folder structure

* chore: update classnames according to new variables

* Revert "chore: update classnames according to new variables"

This reverts commit 60a87453b2.

* chore: remove temp file
2023-07-10 12:25:32 +05:30
Anmol Singh BhatiaandGitHub d564ea8898 fix: resolved overflow issue with longer state names (#1444) 2023-07-07 14:11:33 +05:30
7868bfa2b1 feat: created on and updated on column added in spreadsheet view (#1454)
* feat: created on and updated on column added in spreadsheet view

* fix: build fix

* refactor: simplify logic

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-07-07 14:11:07 +05:30
Anmol Singh BhatiaandGitHub 353c85120f feat: bulk invite for project (#1466)
* feat: bulk invite for project

* feat: members dropdown updated

* fix: error message added ,style: ui improvement

* feat: added add members button for scenarios with multiple members

* chore: updated watch to fields
2023-07-07 14:10:36 +05:30
Aaryan KhandelwalandGitHub 49f37e0346 fix: emoji render function (#1484)
* fix: emoji render function

* fix: emoji render function
2023-07-06 16:08:49 +05:30
pablohashescobarandGitHub 3a2f4d55d7 fix: static and media files storages (#1482) 2023-07-06 15:30:34 +05:30
pablohashescobarandGitHub 8443dfc9dd chore: rename workspace company size to organization size (#1463)
* chore: rename workspace company size to organization size

* chore: make workspace organization size as required
2023-07-06 11:58:47 +05:30
pablohashescobarandGitHub 2f8a1dbe20 chore: project members endpoint to support bulk operations (#1464) 2023-07-06 11:58:35 +05:30
pablohashescobarandGitHub 6bfeb6af34 chore: upgrade backend dependencies (#1479)
* chore: upgrade backend dependencies

* dev: update storage settings for self hosted version
2023-07-06 11:58:24 +05:30
pablohashescobarandGitHub fd3f6ab7a4 Merge pull request #1478 from makeplane/stage-release
promote: stage-release to master
2023-07-05 21:24:39 +05:30
pablohashescobarandGitHub d05d814efe chore: workspace invitation for self-hosted versions (#1477) 2023-07-05 21:21:31 +05:30
pablohashescobarandGitHub cc0701a823 fix: workspace invitation delete for self hosted (#1475) 2023-07-05 21:00:30 +05:30
Aaryan KhandelwalandGitHub 3906503c1b fix: state icon color on group titles (#1435) 2023-07-05 00:56:39 +05:30
Aaryan KhandelwalandGitHub c3fe221e7a chore: update project members type (#1459) 2023-07-05 00:56:15 +05:30
Chandan JalandGitHub e530533f9e fix: layout of tabs on Pages is not adaptable to mobile screens #1380 (#1400)
* fix: layout of tabs on Pages is not adaptable to mobile screens #1380

* fix: scrolling experience on page
2023-07-04 23:13:31 +05:30
4c3e2c252a chore: due date filter (#965)
* chore: due date filter

* fix: deployment error

* chore: optimized code

* chore: created constants for due date

* chore: create seperated css file for react datepicker styling

* fix: due date filter

* chore: highlight selected option

* fix: merge conflicts

* fix: build error

* chore: date range selector validation

* fix: issue views overflow

* refactor: due date filter modal code

* refactor: multi level dropdown

* chore: due date filter select default value

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-07-04 23:13:07 +05:30
Anmol Singh BhatiaandGitHub 4ede04d72f refactor: standardized date format throughout the platform (#1461) 2023-07-04 18:19:19 +05:30
pablohashescobarandGitHub 5a6fd0efdb chore: due date filter (#1460) 2023-07-04 16:48:59 +05:30
pablohashescobarandGitHub 1a72a0dff4 fix: user invitation workflow for self hosted version (#1441) 2023-07-04 13:56:51 +05:30
pablohashescobarandGitHub e4ee6a5bfb chore: workspace char name and slug maximum length (#1453) 2023-07-04 13:54:48 +05:30
pablohashescobarandGitHub e23e9ccdbb fix: project member list endpoint n+1 (#1458) 2023-07-04 13:54:37 +05:30
guru_sainathandGitHub 54a536e268 Merge pull request #1436 from makeplane/fix/bug_fix_and_ui_improvement 2023-07-03 13:57:15 +05:30
pablohashescobarandGitHub 110eb39a51 dev: update packages to latest version (#1431) 2023-06-30 19:37:26 +05:30
anmolsinghbhatia fc15da826b style: label dropdown empty state 2023-06-30 18:28:12 +05:30
Mark PercivalandGitHub 7e2caf65bb chore: Dry up docker compose environment variables (#1438)
* chore: Use docker compose extensions to dry up duplicate environment
variables between worker and api services.
See: https://docs.docker.com/compose/compose-file/11-extension/
2023-06-30 10:00:45 +05:30
tajkirkpatrickandGitHub d62dc25aa0 Add network timeout option to yarn install (#1382)
Inside of the Dockerfile.web, the yarn install command is susceptible to encountering a ECONNECT timeout error on aarch64 and ARM devices (such as a raspberry pi). This explicit overwrite of the default network timeout extends the window for low power CPU devices.
2023-06-30 09:53:44 +05:30
anmolsinghbhatia 32bf2ed56a fix: display properties fix 2023-06-29 18:37:19 +05:30
anmolsinghbhatia ebf877b7fa fix: display properties fix 2023-06-29 18:33:44 +05:30
anmolsinghbhatia d8eeb9e17a fix: cycle list item fix 2023-06-29 18:20:22 +05:30
anmolsinghbhatia f2a4e026a5 fix: cycle and module sidebar overflow fix 2023-06-29 18:19:38 +05:30
anmolsinghbhatia 8229b12c9c style: recent page overflow fix 2023-06-29 18:17:52 +05:30
anmolsinghbhatia 3c07da433a fix: overflow fix 2023-06-29 18:16:01 +05:30
pablohashescobarandGitHub 1ed7935bf0 docs: update readme.md for self hosting setup and remove pnpm from contribution guidelines (#1423) 2023-06-29 11:52:44 +05:30
Aaryan KhandelwalandGitHub c743d43ce2 fix: charts design and mutation (#1426)
* fix: pie chart overlap issue

* fix: burndown chart mutation

* fix: burndown chart mutation
2023-06-28 19:08:33 +05:30
1876 changed files with 120903 additions and 50876 deletions
+4 -51
View File
@@ -1,32 +1,3 @@
# Frontend
# Extra image domains that need to be added for Next Image
NEXT_PUBLIC_EXTRA_IMAGE_DOMAINS=
# Google Client ID for Google OAuth
NEXT_PUBLIC_GOOGLE_CLIENTID=""
# Github ID for Github OAuth
NEXT_PUBLIC_GITHUB_ID=""
# Github App Name for GitHub Integration
NEXT_PUBLIC_GITHUB_APP_NAME=""
# Sentry DSN for error monitoring
NEXT_PUBLIC_SENTRY_DSN=""
# Enable/Disable OAUTH - default 0 for selfhosted instance
NEXT_PUBLIC_ENABLE_OAUTH=0
# Enable/Disable sentry
NEXT_PUBLIC_ENABLE_SENTRY=0
# Enable/Disable session recording
NEXT_PUBLIC_ENABLE_SESSION_RECORDER=0
# Enable/Disable event tracking
NEXT_PUBLIC_TRACK_EVENTS=0
# Slack for Slack Integration
NEXT_PUBLIC_SLACK_CLIENT_ID=""
# Backend
# Debug value for api server use it as 0 for production use
DEBUG=0
# Error logs
SENTRY_DSN=""
# Database Settings
PGUSER="plane"
PGPASSWORD="plane"
@@ -39,15 +10,6 @@ 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"
@@ -59,24 +21,15 @@ AWS_S3_BUCKET_NAME="uploads"
FILE_SIZE_LIMIT=5242880
# GPT settings
OPENAI_API_KEY=""
GPT_ENGINE=""
# Github
GITHUB_CLIENT_SECRET="" # For fetching release notes
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
# Settings related to Docker
DOCKERIZED=1
# set to 1 If using the pre-configured minio setup
# 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"
# Auto generated and Required that will be generated from setup.sh
+1 -1
View File
@@ -4,7 +4,7 @@ module.exports = {
extends: ["custom"],
settings: {
next: {
rootDir: ["apps/*"],
rootDir: ["web/", "space/"],
},
},
};
@@ -0,0 +1,48 @@
name: Build Pull Request Contents
on:
pull_request:
types: ["opened", "synchronize"]
jobs:
build-pull-request-contents:
name: Build Pull Request Contents
runs-on: ubuntu-20.04
permissions:
pull-requests: read
steps:
- name: Checkout Repository to Actions
uses: actions/checkout@v3.3.0
- name: Setup Node.js 18.x
uses: actions/setup-node@v2
with:
node-version: 18.x
cache: 'yarn'
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v38
with:
files_yaml: |
apiserver:
- apiserver/**
web:
- web/**
deploy:
- space/**
- name: Build Plane's Main App
if: steps.changed-files.outputs.web_any_changed == 'true'
run: |
yarn
yarn build --filter=web
- name: Build Plane's Deploy App
if: steps.changed-files.outputs.deploy_any_changed == 'true'
run: |
yarn
yarn build --filter=space
+79
View File
@@ -0,0 +1,79 @@
name: Create PR in Plane EE Repository to sync the changes
on:
pull_request:
branches:
- master
types:
- closed
jobs:
create_pr:
# Only run the job when a PR is merged
if: github.event.pull_request.merged == true
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
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
sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt update
sudo apt install gh -y
- name: Create Pull Request
if: steps.check_repo.outputs.is_correct_repo == 'true'
env:
GH_TOKEN: ${{ secrets.ACCESS_TOKEN }}
run: |
TARGET_REPO="${{ secrets.TARGET_REPO_NAME }}"
TARGET_BRANCH="${{ secrets.TARGET_REPO_BRANCH }}"
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
-77
View File
@@ -1,77 +0,0 @@
name: Build and Push Backend Docker Image
on:
push:
branches:
- 'develop'
- 'master'
tags:
- '*'
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 QEMU
uses: docker/setup-qemu-action@v2.1.0
with:
platforms: linux/arm64,linux/amd64
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.5.0
- name: Login to GitHub Container Registry
uses: docker/login-action@v2.1.0
with:
registry: "ghcr.io"
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Docker Hub
uses: docker/login-action@v2.1.0
with:
registry: "registry.hub.docker.com"
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Extract metadata (tags, labels) for Docker (Docker Hub)
id: ghmeta
uses: docker/metadata-action@v4.3.0
with:
images: makeplane/plane-backend
- name: Extract metadata (tags, labels) for Docker (Github)
id: dkrmeta
uses: docker/metadata-action@v4.3.0
with:
images: ghcr.io/${{ github.repository }}-backend
- name: Build and Push to GitHub Container Registry
uses: docker/build-push-action@v4.0.0
with:
context: ./apiserver
file: ./apiserver/Dockerfile.api
platforms: linux/arm64,linux/amd64
push: true
cache-from: type=gha
cache-to: type=gha
tags: ${{ steps.ghmeta.outputs.tags }}
labels: ${{ steps.ghmeta.outputs.labels }}
- name: Build and Push to Docker Hub
uses: docker/build-push-action@v4.0.0
with:
context: ./apiserver
file: ./apiserver/Dockerfile.api
platforms: linux/arm64,linux/amd64
push: true
cache-from: type=gha
cache-to: type=gha
tags: ${{ steps.dkrmeta.outputs.tags }}
labels: ${{ steps.dkrmeta.outputs.labels }}
-77
View File
@@ -1,77 +0,0 @@
name: Build and Push Frontend Docker Image
on:
push:
branches:
- 'develop'
- 'master'
tags:
- '*'
jobs:
build_push_frontend:
name: Build Frontend Docker Image
runs-on: ubuntu-20.04
steps:
- name: Check out the repo
uses: actions/checkout@v3.3.0
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.1.0
with:
platforms: linux/arm64,linux/amd64
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.5.0
- name: Login to Github Container Registry
uses: docker/login-action@v2.1.0
with:
registry: "ghcr.io"
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Docker Hub
uses: docker/login-action@v2.1.0
with:
registry: "registry.hub.docker.com"
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Extract metadata (tags, labels) for Docker (Docker Hub)
id: ghmeta
uses: docker/metadata-action@v4.3.0
with:
images: makeplane/plane-frontend
- name: Extract metadata (tags, labels) for Docker (Github)
id: meta
uses: docker/metadata-action@v4.3.0
with:
images: ghcr.io/${{ github.repository }}-frontend
- name: Build and Push to GitHub Container Registry
uses: docker/build-push-action@v4.0.0
with:
context: .
file: ./apps/app/Dockerfile.web
platforms: linux/arm64,linux/amd64
push: true
cache-from: type=gha
cache-to: type=gha
tags: ${{ steps.ghmeta.outputs.tags }}
labels: ${{ steps.ghmeta.outputs.labels }}
- name: Build and Push to Docker Container Registry
uses: docker/build-push-action@v4.0.0
with:
context: .
file: ./apps/app/Dockerfile.web
platforms: linux/arm64,linux/amd64
push: true
cache-from: type=gha
cache-to: type=gha
tags: ${{ steps.dkrmeta.outputs.tags }}
labels: ${{ steps.dkrmeta.outputs.labels }}
+107
View File
@@ -0,0 +1,107 @@
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 }}
+9 -1
View File
@@ -16,6 +16,8 @@ node_modules
# Production
/build
dist/
out/
# Misc
.DS_Store
@@ -70,4 +72,10 @@ package-lock.json
# lock files
package-lock.json
pnpm-lock.yaml
pnpm-workspace.yaml
pnpm-workspace.yaml
.npmrc
tmp/
## packages
dist
+12 -12
View File
@@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
@@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
@@ -125,4 +125,4 @@ enforcement ladder](https://github.com/mozilla/diversity).
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
https://www.contributor-covenant.org/translations.
+58 -17
View File
@@ -8,8 +8,8 @@ Before submitting a new issue, please search the [issues](https://github.com/mak
While we want to fix all the [issues](https://github.com/makeplane/plane/issues), before fixing a bug we need to be able to reproduce and confirm it. Please provide us with a minimal reproduction scenario using a repository or [Gist](https://gist.github.com/). Having a live, reproducible scenario gives us the information without asking questions back & forth with additional questions like:
- 3rd-party libraries being used and their versions
- a use-case that fails
- 3rd-party libraries being used and their versions
- a use-case that fails
Without said minimal reproduction, we won't be able to investigate all [issues](https://github.com/makeplane/plane/issues), and the issue might not be resolved.
@@ -19,11 +19,10 @@ You can open a new issue with this [issue form](https://github.com/makeplane/pla
### Requirements
- Node.js version v16.18.0
- Python version 3.8+
- Postgres version v14
- Redis version v6.2.7
- pnpm version 7.22.0
- Node.js version v16.18.0
- Python version 3.8+
- Postgres version v14
- Redis version v6.2.7
### Setup the project
@@ -31,6 +30,48 @@ The project is a monorepo, with backend api and frontend in a single repo.
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
chmod +x setup.sh
```
2. Run setup.sh
```bash
./setup.sh
```
3. Define `NEXT_PUBLIC_API_BASE_URL=http://localhost` in **web/.env** and **space/.env** file
```bash
echo "\nNEXT_PUBLIC_API_BASE_URL=http://localhost\n" >> ./web/.env
```
```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?
If a feature is missing, you can directly _request_ a new one [here](https://github.com/makeplane/plane/issues/new?assignees=&labels=feature&template=feature_request.yml&title=%F0%9F%9A%80+Feature%3A+). You also can do the same by choosing "🚀 Feature" when raising a [New Issue](https://github.com/makeplane/plane/issues/new/choose) on our GitHub Repository.
@@ -40,8 +81,8 @@ If you would like to _implement_ it, an issue with your proposal must be submitt
To ensure consistency throughout the source code, please keep these rules in mind as you are working:
- All features or bug fixes must be tested by one or more specs (unit-tests).
- We use [Eslint default rule guide](https://eslint.org/docs/rules/), with minor changes. An automated formatter is available using prettier.
- All features or bug fixes must be tested by one or more specs (unit-tests).
- We use [Eslint default rule guide](https://eslint.org/docs/rules/), with minor changes. An automated formatter is available using prettier.
## Need help? Questions and suggestions
@@ -49,11 +90,11 @@ Questions, suggestions, and thoughts are most welcome. We can also be reached in
## Ways to contribute
- Try Plane Cloud and the self hosting platform and give feedback
- Add new integrations
- Help with open [issues](https://github.com/makeplane/plane/issues) or [create your own](https://github.com/makeplane/plane/issues/new/choose)
- Share your thoughts and suggestions with us
- Help create tutorials and blog posts
- Request a feature by submitting a proposal
- Report a bug
- **Improve documentation** - fix incomplete or missing [docs](https://docs.plane.so/), bad wording, examples or explanations.
- Try Plane Cloud and the self hosting platform and give feedback
- Add new integrations
- Help with open [issues](https://github.com/makeplane/plane/issues) or [create your own](https://github.com/makeplane/plane/issues/new/choose)
- Share your thoughts and suggestions with us
- Help create tutorials and blog posts
- Request a feature by submitting a proposal
- Report a bug
- **Improve documentation** - fix incomplete or missing [docs](https://docs.plane.so/), bad wording, examples or explanations.
+10 -3
View File
@@ -5,9 +5,11 @@ WORKDIR /app
ENV NEXT_PUBLIC_API_BASE_URL=http://NEXT_PUBLIC_API_BASE_URL_PLACEHOLDER
RUN yarn global add turbo
RUN apk add tree
COPY . .
RUN turbo prune --scope=app --docker
RUN turbo prune --scope=app --scope=plane-deploy --docker
CMD tree -I node_modules/
# Add lockfile and package.json's of isolated subworkspace
FROM node:18-alpine AS installer
@@ -21,14 +23,14 @@ COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/yarn.lock ./yarn.lock
RUN yarn install
# Build the project
# # Build the project
COPY --from=builder /app/out/full/ .
COPY turbo.json turbo.json
COPY replace-env-vars.sh /usr/local/bin/
USER root
RUN chmod +x /usr/local/bin/replace-env-vars.sh
RUN yarn turbo run build --filter=app
RUN yarn turbo run build
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL \
BUILT_NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
@@ -96,11 +98,16 @@ RUN adduser --system --uid 1001 captain
COPY --from=installer /app/apps/app/next.config.js .
COPY --from=installer /app/apps/app/package.json .
COPY --from=installer /app/apps/space/next.config.js .
COPY --from=installer /app/apps/space/package.json .
COPY --from=installer --chown=captain:plane /app/apps/app/.next/standalone ./
COPY --from=installer --chown=captain:plane /app/apps/app/.next/static ./apps/app/.next/static
COPY --from=installer --chown=captain:plane /app/apps/space/.next/standalone ./
COPY --from=installer --chown=captain:plane /app/apps/space/.next ./apps/space/.next
ENV NEXT_TELEMETRY_DISABLED 1
# RUN rm /etc/nginx/conf.d/default.conf
+134
View File
@@ -0,0 +1,134 @@
# Environment Variables
Environment variables are distributed in various files. Please refer them carefully.
## {PROJECT_FOLDER}/.env
File is available in the project root folder
```
# Database Settings
PGUSER="plane"
PGPASSWORD="plane"
PGHOST="plane-db"
PGDATABASE="plane"
DATABASE_URL=postgresql://${PGUSER}:${PGPASSWORD}@${PGHOST}/${PGDATABASE}
# Redis Settings
REDIS_HOST="plane-redis"
REDIS_PORT="6379"
REDIS_URL="redis://${REDIS_HOST}:6379/"
# AWS Settings
AWS_REGION=""
AWS_ACCESS_KEY_ID="access-key"
AWS_SECRET_ACCESS_KEY="secret-key"
AWS_S3_ENDPOINT_URL="http://plane-minio:9000"
# Changing this requires change in the nginx.conf for uploads if using minio setup
AWS_S3_BUCKET_NAME="uploads"
# Maximum file upload limit
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
# Settings related to Docker
DOCKERIZED=1
# set to 1 If using the pre-configured minio setup
USE_MINIO=1
# Nginx Configuration
NGINX_PORT=80
```
## {PROJECT_FOLDER}/web/.env.example
```
# 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
```
# 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=""
# Database Settings
PGUSER="plane"
PGPASSWORD="plane"
PGHOST="plane-db"
PGDATABASE="plane"
DATABASE_URL=postgresql://${PGUSER}:${PGPASSWORD}@${PGHOST}/${PGDATABASE}
# 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"
AWS_SECRET_ACCESS_KEY="secret-key"
AWS_S3_ENDPOINT_URL="http://plane-minio:9000"
# Changing this requires change in the nginx.conf for uploads if using minio setup
AWS_S3_BUCKET_NAME="uploads"
# Maximum file upload limit
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
# 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"
# Email Redirection URL
WEB_URL="http://localhost"
```
## Updates
- The environment variable NEXT_PUBLIC_API_BASE_URL has been removed from both the web and space projects.
- The naming convention for containers and images has been updated.
- The plane-worker image will no longer be maintained, as it has been merged with plane-backend.
- The Tiptap pro-extension dependency has been removed, eliminating the need for Tiptap API keys.
- The image name for Plane deployment has been changed to plane-space.
+39 -48
View File
@@ -2,7 +2,7 @@
<p align="center">
<a href="https://plane.so">
<img src="https://res.cloudinary.com/toolspacedev/image/upload/v1680596414/Plane/Plane_Icon_Blue_on_White_150x150_muysa3.jpg" alt="Plane Logo" width="70">
<img src="https://plane-marketing.s3.ap-south-1.amazonaws.com/plane-readme/plane_logo_.webp" alt="Plane Logo" width="70">
</a>
</p>
@@ -11,22 +11,22 @@
<p align="center">
<a href="https://discord.com/invite/A92xrEGCge">
<img alt="Discord" src="https://img.shields.io/discord/1031547764020084846?color=5865F2&label=Discord&style=for-the-badge" />
<img alt="Discord online members" src="https://img.shields.io/discord/1031547764020084846?color=5865F2&label=Discord&style=for-the-badge" />
</a>
<img alt="Discord" src="https://img.shields.io/github/commit-activity/m/makeplane/plane?style=for-the-badge" />
<img alt="Commit activity per month" src="https://img.shields.io/github/commit-activity/m/makeplane/plane?style=for-the-badge" />
</p>
<p>
<a href="https://app.plane.so/#gh-light-mode-only" target="_blank">
<img
src="https://ik.imagekit.io/killbluedog/Plane_Screen.png?updatedAt=1684942001069"
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/plane-readme/plane_screen.webp"
alt="Plane Screens"
width="100%"
/>
</a>
<a href="https://app.plane.so/#gh-dark-mode-only" target="_blank">
<img
src="https://ik.imagekit.io/killbluedog/Plane_Screens_Dark_Mode.png?updatedAt=1684942388044"
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/plane-readme/plane_screens_dark_mode.webp"
alt="Plane Screens"
width="100%"
/>
@@ -35,66 +35,58 @@
Meet [Plane](https://plane.so). An open-source software development tool to manage issues, sprints, and product roadmaps with peace of mind 🧘‍♀️.
> 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).
## ⚡️ Contributors Quick Start
## ⚡️ Quick start with Docker Compose
### Prerequisite
### Docker Compose Setup
Development system must have docker engine installed and running.
- Clone the repository
### Steps
Setting up local environment is extremely easy and straight forward. Follow the below step and you will be ready to contribute
1. Clone the code locally using `git clone https://github.com/makeplane/plane.git`
1. Switch to the code folder `cd plane`
1. Create your feature or fix branch you plan to work on using `git checkout -b <feature-branch-name>`
1. Open terminal and run `./setup.sh`
1. Open the code on VSCode or similar equivalent IDE
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
git clone https://github.com/makeplane/plane
cd plane
chmod +x setup.sh
./setup.sh
```
- Run 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)
```bash
./setup.sh http://localhost
```
Thats it!
> If running in a cloud env replace localhost with public facing IP address of the VM
## 🍙 Self Hosting
- Export Environment Variables
```bash
set -a
source .env
set +a
```
- Run Docker compose up
```bash
docker compose up -d
```
<strong>You can use the default email and password for your first login `captain@plane.so` and `password123`.</strong>
For self hosting environment setup, visit the [Self Hosting](https://docs.plane.so/self-hosting) documentation page
## 🚀 Features
* **Issue Planning and Tracking**: Quickly create issues and add details using a powerful rich text editor that supports file uploads. Add sub-properties and references to issues for better organization and tracking.
* **Issue Attachments**: Collaborate effectively by attaching files to issues, making it easy for your team to find and share important project-related documents.
* **Layouts**: Customize your project view with your preferred layout - choose from List, Kanban, or Calendar to visualize your project in a way that makes sense to you.
* **Cycles**: Plan sprints with Cycles to keep your team on track and productive. Gain insights into your project's progress with burn-down charts and other useful features.
* **Modules**: Break down your large projects into smaller, more manageable modules. Assign modules between teams to easily track and plan your project's progress.
* **Views**: Create custom filters to display only the issues that matter to you. Save and share your filters in just a few clicks.
* **Pages**: Plane pages function as an AI-powered notepad, allowing you to easily document issues, cycle plans, and module details, and then synchronize them with your issues.
* **Command K**: Enjoy a better user experience with the new Command + K menu. Easily manage and navigate through your projects from one convenient location.
* **GitHub Sync**: Streamline your planning process by syncing your GitHub issues with Plane. Keep all your issues in one place for better tracking and collaboration.
- **Issue Planning and Tracking**: Quickly create issues and add details using a powerful rich text editor that supports file uploads. Add sub-properties and references to issues for better organization and tracking.
- **Issue Attachments**: Collaborate effectively by attaching files to issues, making it easy for your team to find and share important project-related documents.
- **Layouts**: Customize your project view with your preferred layout - choose from List, Kanban, or Calendar to visualize your project in a way that makes sense to you.
- **Cycles**: Plan sprints with Cycles to keep your team on track and productive. Gain insights into your project's progress with burn-down charts and other useful features.
- **Modules**: Break down your large projects into smaller, more manageable modules. Assign modules between teams to easily track and plan your project's progress.
- **Views**: Create custom filters to display only the issues that matter to you. Save and share your filters in just a few clicks.
- **Pages**: Plane pages function as an AI-powered notepad, allowing you to easily document issues, cycle plans, and module details, and then synchronize them with your issues.
- **Command K**: Enjoy a better user experience with the new Command + K menu. Easily manage and navigate through your projects from one convenient location.
- **GitHub Sync**: Streamline your planning process by syncing your GitHub issues with Plane. Keep all your issues in one place for better tracking and collaboration.
## 📸 Screenshots
<p>
<a href="https://plane.so" target="_blank">
<img
src="https://ik.imagekit.io/killbluedog/Plane_Views_Dark_Mode.png?updatedAt=1684943050275"
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/plane-readme/plane_views_dark_mode.webp"
alt="Plane Views"
width="100%"
/>
@@ -103,7 +95,7 @@ docker compose up -d
<p>
<a href="https://plane.so" target="_blank">
<img
src="https://ik.imagekit.io/killbluedog/Plane_Issue_Detail_Dark_Mode.png?updatedAt=1684943050202"
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/plane-readme/plane_issue_detail_dark_mode.webp"
alt="Plane Issue Details"
width="100%"
/>
@@ -112,7 +104,7 @@ docker compose up -d
<p>
<a href="https://plane.so" target="_blank">
<img
src="https://ik.imagekit.io/killbluedog/Plane_Cycles___Modules_Dark_Mode.png?updatedAt=1684943050281"
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/plane-readme/plane_cycles_modules_dark_mode.webp"
alt="Plane Cycles and Modules"
width="100%"
/>
@@ -121,7 +113,7 @@ docker compose up -d
<p>
<a href="https://plane.so" target="_blank">
<img
src="https://ik.imagekit.io/killbluedog/Plane_Analytics_Dark_Mode.png?updatedAt=1684944596824"
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/plane-readme/plane_analytics_dark_mode.webp"
alt="Plane Analytics"
width="100%"
/>
@@ -130,7 +122,7 @@ docker compose up -d
<p>
<a href="https://plane.so" target="_blank">
<img
src="https://ik.imagekit.io/killbluedog/Plane_Pages_Dark_Mode.png?updatedAt=1684943050202"
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/plane-readme/plane_pages_dark_mode.webp"
alt="Plane Pages"
width="100%"
/>
@@ -140,7 +132,7 @@ docker compose up -d
<p>
<a href="https://plane.so" target="_blank">
<img
src="https://ik.imagekit.io/killbluedog/Plane_Commad_K_Dark_Mode.png?updatedAt=1684943050312"
src="https://plane-marketing.s3.ap-south-1.amazonaws.com/plane-readme/plane_commad_k_dark_mode.webp"
alt="Plane Command Menu"
width="100%"
/>
@@ -148,7 +140,6 @@ docker compose up -d
</p>
</p>
## 📚Documentation
For full documentation, visit [docs.plane.so](https://docs.plane.so/)
+72
View File
@@ -0,0 +1,72 @@
# Backend
# Debug value for api server use it as 0 for production use
DEBUG=0
DJANGO_SETTINGS_MODULE="plane.settings.production"
# Error logs
SENTRY_DSN=""
# Database Settings
PGUSER="plane"
PGPASSWORD="plane"
PGHOST="plane-db"
PGDATABASE="plane"
DATABASE_URL=postgresql://${PGUSER}:${PGPASSWORD}@${PGHOST}/${PGDATABASE}
# 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"
AWS_SECRET_ACCESS_KEY="secret-key"
AWS_S3_ENDPOINT_URL="http://plane-minio:9000"
# Changing this requires change in the nginx.conf for uploads if using minio setup
AWS_S3_BUCKET_NAME="uploads"
# Maximum file upload limit
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
# 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"
# Enable Email/Password Signup
ENABLE_EMAIL_PASSWORD="1"
# Enable Magic link Login
ENABLE_MAGIC_LINK_LOGIN="0"
# Email redirections and minio domain settings
WEB_URL="http://localhost"
+1 -1
View File
@@ -49,7 +49,7 @@ USER root
RUN apk --no-cache add "bash~=5.2"
COPY ./bin ./bin/
RUN chmod +x ./bin/takeoff ./bin/worker
RUN chmod +x ./bin/takeoff ./bin/worker ./bin/beat
RUN chmod -R 777 /code
USER captain
+52
View File
@@ -0,0 +1,52 @@
FROM python:3.11.1-alpine3.17 AS backend
# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
RUN apk --no-cache add \
"bash~=5.2" \
"libpq~=15" \
"libxslt~=1.1" \
"nodejs-current~=19" \
"xmlsec~=1.2" \
"libffi-dev" \
"bash~=5.2" \
"g++~=12.2" \
"gcc~=12.2" \
"cargo~=1.64" \
"git~=2" \
"make~=4.3" \
"postgresql13-dev~=13" \
"libc-dev" \
"linux-headers"
WORKDIR /code
COPY requirements.txt ./requirements.txt
ADD requirements ./requirements
RUN pip install -r requirements.txt --compile --no-cache-dir
RUN addgroup -S plane && \
adduser -S captain -G plane
RUN chown captain.plane /code
USER captain
# Add in Django deps and generate Django's static files
USER root
# RUN chmod +x ./bin/takeoff ./bin/worker ./bin/beat
RUN chmod -R 777 /code
USER captain
# Expose container port and run entry point script
EXPOSE 8000
# CMD [ "./bin/takeoff" ]
+3 -2
View File
@@ -1,2 +1,3 @@
web: gunicorn -w 4 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:$PORT --config gunicorn.config.py --max-requests 10000 --max-requests-jitter 1000 --access-logfile -
worker: celery -A plane worker -l info
web: gunicorn -w 4 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:$PORT --max-requests 10000 --max-requests-jitter 1000 --access-logfile -
worker: celery -A plane worker -l info
beat: celery -A plane beat -l INFO
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
set -e
python manage.py wait_for_db
celery -A plane beat -l info
+1 -1
View File
@@ -6,4 +6,4 @@ python manage.py migrate
# Create a Default User
python bin/user_script.py
exec gunicorn -w 8 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --config gunicorn.config.py --max-requests 1200 --max-requests-jitter 1000 --access-logfile -
exec gunicorn -w 8 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile -
+4 -4
View File
@@ -1,4 +1,4 @@
import os, sys
import os, sys, random, string
import uuid
sys.path.append("/code")
@@ -19,9 +19,9 @@ def populate():
user = User.objects.create(email=default_email, username=uuid.uuid4().hex)
user.set_password(default_password)
user.save()
print("User created")
print("Success")
print(f"User created with an email: {default_email}")
else:
print(f"User already exists with the default email: {default_email}")
if __name__ == "__main__":
+1 -1
View File
@@ -1,2 +1,2 @@
from .workspace import WorkSpaceBasePermission, WorkSpaceAdminPermission, WorkspaceEntityPermission
from .workspace import WorkSpaceBasePermission, WorkSpaceAdminPermission, WorkspaceEntityPermission, WorkspaceViewerPermission
from .project import ProjectBasePermission, ProjectEntityPermission, ProjectMemberPermission, ProjectLitePermission
+20 -1
View File
@@ -58,6 +58,25 @@ class WorkspaceEntityPermission(BasePermission):
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,
).exists()
return WorkspaceMember.objects.filter(
member=request.user, workspace__slug=view.workspace_slug
member=request.user,
workspace__slug=view.workspace_slug,
role__in=[Owner, Admin],
).exists()
class WorkspaceViewerPermission(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, role__gte=10
).exists()
+32 -7
View File
@@ -1,10 +1,13 @@
from .base import BaseSerializer
from .people import (
from .user import (
UserSerializer,
UserLiteSerializer,
ChangePasswordSerializer,
ResetPasswordSerializer,
TokenSerializer,
UserAdminLiteSerializer,
UserMeSerializer,
UserMeSettingsSerializer,
)
from .user import UserSerializer, UserLiteSerializer
from .workspace import (
WorkSpaceSerializer,
WorkSpaceMemberSerializer,
@@ -12,27 +15,37 @@ from .workspace import (
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 IssueViewSerializer, IssueViewFavoriteSerializer
from .cycle import CycleSerializer, CycleIssueSerializer, CycleFavoriteSerializer
from .view import GlobalViewSerializer, IssueViewSerializer, IssueViewFavoriteSerializer
from .cycle import (
CycleSerializer,
CycleIssueSerializer,
CycleFavoriteSerializer,
CycleWriteSerializer,
)
from .asset import FileAssetSerializer
from .issue import (
IssueCreateSerializer,
IssueActivitySerializer,
IssueCommentSerializer,
IssuePropertySerializer,
BlockerIssueSerializer,
BlockedIssueSerializer,
IssueAssigneeSerializer,
LabelSerializer,
IssueSerializer,
@@ -41,6 +54,13 @@ from .issue import (
IssueLinkSerializer,
IssueLiteSerializer,
IssueAttachmentSerializer,
IssueSubscriberSerializer,
IssueReactionSerializer,
CommentReactionSerializer,
IssueVoteSerializer,
IssueRelationSerializer,
RelatedIssueSerializer,
IssuePublicSerializer,
)
from .module import (
@@ -74,4 +94,9 @@ from .estimate import (
)
from .inbox import InboxSerializer, InboxIssueSerializer, IssueStateInboxSerializer
from .analytic import AnalyticViewSerializer
from .notification import NotificationSerializer
from .exporter import ExporterHistorySerializer
+53
View File
@@ -3,3 +3,56 @@ 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)
# 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])
# Convert the current serializer's fields and the allowed fields to sets.
existing = set(self.fields)
allowed = set(allowed)
# Remove fields from the serializer that aren't in the 'allowed' list.
for field_name in (existing - allowed):
self.fields.pop(field_name)
return self.fields
+29 -22
View File
@@ -13,6 +13,21 @@ 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__"
class CycleSerializer(BaseSerializer):
owned_by = UserLiteSerializer(read_only=True)
is_favorite = serializers.BooleanField(read_only=True)
@@ -23,21 +38,31 @@ class CycleSerializer(BaseSerializer):
unstarted_issues = serializers.IntegerField(read_only=True)
backlog_issues = serializers.IntegerField(read_only=True)
assignees = serializers.SerializerMethodField(read_only=True)
labels = 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 (
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,
"first_name": assignee.first_name,
"display_name": assignee.display_name,
"id": assignee.id,
}
for issue_cycle in obj.issue_cycle.all()
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
@@ -47,24 +72,6 @@ class CycleSerializer(BaseSerializer):
unique_list = [dict(item) for item in unique_objects]
return unique_list
def get_labels(self, obj):
labels = [
{
"name": label.name,
"color": label.color,
"id": label.id,
}
for issue_cycle in obj.issue_cycle.all()
for label in issue_cycle.issue.labels.all()
]
# Use a set comprehension to return only the unique objects
unique_objects = {frozenset(item.items()) for item in labels}
# 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
@@ -0,0 +1,26 @@
# Module imports
from .base import BaseSerializer
from plane.db.models import ExporterHistory
from .user import UserLiteSerializer
class ExporterHistorySerializer(BaseSerializer):
initiated_by_detail = UserLiteSerializer(source="initiated_by", read_only=True)
class Meta:
model = ExporterHistory
fields = [
"id",
"created_at",
"updated_at",
"project",
"provider",
"status",
"url",
"initiated_by",
"initiated_by_detail",
"token",
"created_by",
"updated_by",
]
read_only_fields = fields
+236 -132
View File
@@ -17,17 +17,20 @@ from plane.db.models import (
IssueActivity,
IssueComment,
IssueProperty,
IssueBlocker,
IssueAssignee,
IssueSubscriber,
IssueLabel,
Label,
IssueBlocker,
CycleIssue,
Cycle,
Module,
ModuleIssue,
IssueLink,
IssueAttachment,
IssueReaction,
CommentReaction,
IssueVote,
IssueRelation,
)
@@ -46,9 +49,24 @@ class IssueFlatSerializer(BaseSerializer):
"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):
@@ -57,31 +75,18 @@ class IssueCreateSerializer(BaseSerializer):
project_detail = ProjectLiteSerializer(read_only=True, source="project")
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
assignees_list = serializers.ListField(
assignees = serializers.ListField(
child=serializers.PrimaryKeyRelatedField(queryset=User.objects.all()),
write_only=True,
required=False,
)
# List of issues that are blocking this issue
blockers_list = serializers.ListField(
child=serializers.PrimaryKeyRelatedField(queryset=Issue.objects.all()),
write_only=True,
required=False,
)
labels_list = serializers.ListField(
labels = serializers.ListField(
child=serializers.PrimaryKeyRelatedField(queryset=Label.objects.all()),
write_only=True,
required=False,
)
# List of issues that are blocked by this issue
blocks_list = serializers.ListField(
child=serializers.PrimaryKeyRelatedField(queryset=Issue.objects.all()),
write_only=True,
required=False,
)
class Meta:
model = Issue
fields = "__all__"
@@ -94,30 +99,34 @@ class IssueCreateSerializer(BaseSerializer):
"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
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):
blockers = validated_data.pop("blockers_list", None)
assignees = validated_data.pop("assignees_list", None)
labels = validated_data.pop("labels_list", None)
blocks = validated_data.pop("blocks_list", None)
assignees = validated_data.pop("assignees", None)
labels = validated_data.pop("labels", None)
project = self.context["project"]
issue = Issue.objects.create(**validated_data, project=project)
project_id = self.context["project_id"]
workspace_id = self.context["workspace_id"]
default_assignee_id = self.context["default_assignee_id"]
if blockers is not None and len(blockers):
IssueBlocker.objects.bulk_create(
[
IssueBlocker(
block=issue,
blocked_by=blocker,
project=project,
workspace=project.workspace,
created_by=issue.created_by,
updated_by=issue.updated_by,
)
for blocker in blockers
],
batch_size=10,
)
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(
@@ -125,10 +134,10 @@ class IssueCreateSerializer(BaseSerializer):
IssueAssignee(
assignee=user,
issue=issue,
project=project,
workspace=project.workspace,
created_by=issue.created_by,
updated_by=issue.updated_by,
project_id=project_id,
workspace_id=workspace_id,
created_by_id=created_by_id,
updated_by_id=updated_by_id,
)
for user in assignees
],
@@ -136,14 +145,14 @@ class IssueCreateSerializer(BaseSerializer):
)
else:
# Then assign it to default assignee
if project.default_assignee is not None:
if default_assignee_id is not None:
IssueAssignee.objects.create(
assignee=project.default_assignee,
assignee_id=default_assignee_id,
issue=issue,
project=project,
workspace=project.workspace,
created_by=issue.created_by,
updated_by=issue.updated_by,
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):
@@ -152,56 +161,27 @@ class IssueCreateSerializer(BaseSerializer):
IssueLabel(
label=label,
issue=issue,
project=project,
workspace=project.workspace,
created_by=issue.created_by,
updated_by=issue.updated_by,
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,
)
if blocks is not None and len(blocks):
IssueBlocker.objects.bulk_create(
[
IssueBlocker(
block=block,
blocked_by=issue,
project=project,
workspace=project.workspace,
created_by=issue.created_by,
updated_by=issue.updated_by,
)
for block in blocks
],
batch_size=10,
)
return issue
def update(self, instance, validated_data):
blockers = validated_data.pop("blockers_list", None)
assignees = validated_data.pop("assignees_list", None)
labels = validated_data.pop("labels_list", None)
blocks = validated_data.pop("blocks_list", None)
assignees = validated_data.pop("assignees", None)
labels = validated_data.pop("labels", None)
if blockers is not None:
IssueBlocker.objects.filter(block=instance).delete()
IssueBlocker.objects.bulk_create(
[
IssueBlocker(
block=instance,
blocked_by=blocker,
project=instance.project,
workspace=instance.project.workspace,
created_by=instance.created_by,
updated_by=instance.updated_by,
)
for blocker in blockers
],
batch_size=10,
)
# 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()
@@ -210,10 +190,10 @@ class IssueCreateSerializer(BaseSerializer):
IssueAssignee(
assignee=user,
issue=instance,
project=instance.project,
workspace=instance.project.workspace,
created_by=instance.created_by,
updated_by=instance.updated_by,
project_id=project_id,
workspace_id=workspace_id,
created_by_id=created_by_id,
updated_by_id=updated_by_id,
)
for user in assignees
],
@@ -227,40 +207,25 @@ class IssueCreateSerializer(BaseSerializer):
IssueLabel(
label=label,
issue=instance,
project=instance.project,
workspace=instance.project.workspace,
created_by=instance.created_by,
updated_by=instance.updated_by,
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,
)
if blocks is not None:
IssueBlocker.objects.filter(blocked_by=instance).delete()
IssueBlocker.objects.bulk_create(
[
IssueBlocker(
block=block,
blocked_by=instance,
project=instance.project,
workspace=instance.project.workspace,
created_by=instance.created_by,
updated_by=instance.updated_by,
)
for block in blocks
],
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")
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
issue_detail = IssueFlatSerializer(read_only=True, source="issue")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
class Meta:
model = IssueActivity
@@ -333,20 +298,39 @@ class IssueLabelSerializer(BaseSerializer):
]
class BlockedIssueSerializer(BaseSerializer):
blocked_issue_detail = IssueFlatSerializer(source="block", read_only=True)
class IssueRelationSerializer(BaseSerializer):
issue_detail = IssueProjectLiteSerializer(read_only=True, source="related_issue")
class Meta:
model = IssueBlocker
fields = "__all__"
model = IssueRelation
fields = [
"issue_detail",
"relation_type",
"related_issue",
"issue",
"id"
]
read_only_fields = [
"workspace",
"project",
]
class BlockerIssueSerializer(BaseSerializer):
blocker_issue_detail = IssueFlatSerializer(source="blocked_by", read_only=True)
class RelatedIssueSerializer(BaseSerializer):
issue_detail = IssueProjectLiteSerializer(read_only=True, source="issue")
class Meta:
model = IssueBlocker
fields = "__all__"
model = IssueRelation
fields = [
"issue_detail",
"relation_type",
"related_issue",
"issue",
"id"
]
read_only_fields = [
"workspace",
"project",
]
class IssueAssigneeSerializer(BaseSerializer):
@@ -459,11 +443,93 @@ class IssueAttachmentSerializer(BaseSerializer):
]
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):
state_detail = StateSerializer(read_only=True, source="state")
project_detail = ProjectSerializer(read_only=True, source="project")
label_details = LabelSerializer(read_only=True, source="labels", many=True)
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)
@@ -476,20 +542,19 @@ class IssueStateSerializer(BaseSerializer):
class IssueSerializer(BaseSerializer):
project_detail = ProjectSerializer(read_only=True, source="project")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
state_detail = StateSerializer(read_only=True, source="state")
parent_detail = IssueFlatSerializer(read_only=True, source="parent")
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)
# List of issues blocked by this issue
blocked_issues = BlockedIssueSerializer(read_only=True, many=True)
# List of issues that block this issue
blocker_issues = BlockerIssueSerializer(read_only=True, 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
@@ -515,6 +580,7 @@ class IssueLiteSerializer(BaseSerializer):
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
@@ -530,3 +596,41 @@ class IssueLiteSerializer(BaseSerializer):
"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",
]
+7 -2
View File
@@ -40,6 +40,11 @@ class ModuleWriteSerializer(BaseSerializer):
"updated_at",
]
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_list", None)
@@ -106,7 +111,7 @@ class ModuleFlatSerializer(BaseSerializer):
class ModuleIssueSerializer(BaseSerializer):
module_detail = ModuleFlatSerializer(read_only=True, source="module")
issue_detail = IssueStateSerializer(read_only=True, source="issue")
issue_detail = ProjectLiteSerializer(read_only=True, source="issue")
sub_issues_count = serializers.IntegerField(read_only=True)
class Meta:
@@ -151,7 +156,7 @@ class ModuleLinkSerializer(BaseSerializer):
class ModuleSerializer(BaseSerializer):
project_detail = ProjectSerializer(read_only=True, source="project")
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)
@@ -0,0 +1,12 @@
# 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__"
+9 -3
View File
@@ -3,7 +3,7 @@ from rest_framework import serializers
# Module imports
from .base import BaseSerializer
from .issue import IssueFlatSerializer, LabelSerializer
from .issue import IssueFlatSerializer, LabelLiteSerializer
from .workspace import WorkspaceLiteSerializer
from .project import ProjectLiteSerializer
from plane.db.models import Page, PageBlock, PageFavorite, PageLabel, Label
@@ -23,16 +23,22 @@ class PageBlockSerializer(BaseSerializer):
"page",
]
class PageBlockLiteSerializer(BaseSerializer):
class Meta:
model = PageBlock
fields = "__all__"
class PageSerializer(BaseSerializer):
is_favorite = serializers.BooleanField(read_only=True)
label_details = LabelSerializer(read_only=True, source="labels", many=True)
label_details = LabelLiteSerializer(read_only=True, source="labels", many=True)
labels_list = serializers.ListField(
child=serializers.PrimaryKeyRelatedField(queryset=Label.objects.all()),
write_only=True,
required=False,
)
blocks = PageBlockSerializer(read_only=True, many=True)
blocks = PageBlockLiteSerializer(read_only=True, many=True)
project_detail = ProjectLiteSerializer(source="project", read_only=True)
workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
-57
View File
@@ -1,57 +0,0 @@
from rest_framework.serializers import (
ModelSerializer,
Serializer,
CharField,
SerializerMethodField,
)
from rest_framework.authtoken.models import Token
from rest_framework_simplejwt.tokens import RefreshToken
from plane.db.models import User
class UserSerializer(ModelSerializer):
class Meta:
model = User
fields = "__all__"
extra_kwargs = {"password": {"write_only": True}}
class ChangePasswordSerializer(Serializer):
model = User
"""
Serializer for password change endpoint.
"""
old_password = CharField(required=True)
new_password = CharField(required=True)
class ResetPasswordSerializer(Serializer):
model = User
"""
Serializer for password change endpoint.
"""
new_password = CharField(required=True)
confirm_password = CharField(required=True)
class TokenSerializer(ModelSerializer):
user = UserSerializer()
access_token = SerializerMethodField()
refresh_token = SerializerMethodField()
def get_access_token(self, obj):
refresh_token = RefreshToken.for_user(obj.user)
return str(refresh_token.access_token)
def get_refresh_token(self, obj):
refresh_token = RefreshToken.for_user(obj.user)
return str(refresh_token)
class Meta:
model = Token
fields = "__all__"
+93 -15
View File
@@ -1,19 +1,18 @@
# Django imports
from django.db import IntegrityError
# Third party imports
from rest_framework import serializers
# Module imports
from .base import BaseSerializer
from .base import BaseSerializer, DynamicBaseSerializer
from plane.api.serializers.workspace import WorkSpaceSerializer, WorkspaceLiteSerializer
from plane.api.serializers.user import UserLiteSerializer
from plane.api.serializers.user import UserLiteSerializer, UserAdminLiteSerializer
from plane.db.models import (
Project,
ProjectMember,
ProjectMemberInvite,
ProjectIdentifier,
ProjectFavorite,
ProjectDeployBoard,
ProjectPublicMember,
)
@@ -77,8 +76,48 @@ class ProjectSerializer(BaseSerializer):
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 = 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)
# workspace = WorkSpaceSerializer(read_only=True)
default_assignee = UserLiteSerializer(read_only=True)
project_lead = UserLiteSerializer(read_only=True)
is_favorite = serializers.BooleanField(read_only=True)
@@ -86,6 +125,9 @@ class ProjectDetailSerializer(BaseSerializer):
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
@@ -93,8 +135,8 @@ class ProjectDetailSerializer(BaseSerializer):
class ProjectMemberSerializer(BaseSerializer):
workspace = WorkSpaceSerializer(read_only=True)
project = ProjectSerializer(read_only=True)
workspace = WorkspaceLiteSerializer(read_only=True)
project = ProjectLiteSerializer(read_only=True)
member = UserLiteSerializer(read_only=True)
class Meta:
@@ -102,9 +144,19 @@ class ProjectMemberSerializer(BaseSerializer):
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 = ProjectSerializer(read_only=True)
workspace = WorkSpaceSerializer(read_only=True)
project = ProjectLiteSerializer(read_only=True)
workspace = WorkspaceLiteSerializer(read_only=True)
class Meta:
model = ProjectMemberInvite
@@ -118,8 +170,6 @@ class ProjectIdentifierSerializer(BaseSerializer):
class ProjectFavoriteSerializer(BaseSerializer):
project_detail = ProjectSerializer(source="project", read_only=True)
class Meta:
model = ProjectFavorite
fields = "__all__"
@@ -129,8 +179,36 @@ class ProjectFavoriteSerializer(BaseSerializer):
]
class ProjectLiteSerializer(BaseSerializer):
class ProjectMemberLiteSerializer(BaseSerializer):
member = UserLiteSerializer(read_only=True)
is_subscribed = serializers.BooleanField(read_only=True)
class Meta:
model = Project
fields = ["id", "identifier", "name"]
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",
]
+118 -2
View File
@@ -1,6 +1,9 @@
# Third party imports
from rest_framework import serializers
# Module import
from .base import BaseSerializer
from plane.db.models import User
from plane.db.models import User, Workspace, WorkspaceMemberInvite
class UserSerializer(BaseSerializer):
@@ -30,6 +33,81 @@ class UserSerializer(BaseSerializer):
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.get(
pk=obj.last_workspace_id, workspace_member__member=obj.id
)
return {
"last_workspace_id": obj.last_workspace_id,
"last_workspace_slug": workspace.slug,
"fallback_workspace_id": obj.last_workspace_id,
"fallback_workspace_slug": workspace.slug,
"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):
class Meta:
model = User
@@ -37,11 +115,49 @@ class UserLiteSerializer(BaseSerializer):
"id",
"first_name",
"last_name",
"email",
"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)
+30 -1
View File
@@ -5,10 +5,39 @@ from rest_framework import serializers
from .base import BaseSerializer
from .workspace import WorkspaceLiteSerializer
from .project import ProjectLiteSerializer
from plane.db.models import IssueView, IssueViewFavorite
from plane.db.models import GlobalView, IssueView, IssueViewFavorite
from plane.utils.issue_filters import issue_filters
class GlobalViewSerializer(BaseSerializer):
workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
class Meta:
model = GlobalView
fields = "__all__"
read_only_fields = [
"workspace",
"query",
]
def create(self, validated_data):
query_params = validated_data.get("query_data", {})
if bool(query_params):
validated_data["query"] = issue_filters(query_params, "POST")
else:
validated_data["query"] = dict()
return GlobalView.objects.create(**validated_data)
def update(self, instance, validated_data):
query_params = validated_data.get("query_data", {})
if bool(query_params):
validated_data["query"] = issue_filters(query_params, "POST")
else:
validated_data["query"] = dict()
validated_data["query"] = issue_filters(query_params, "PATCH")
return super().update(instance, validated_data)
class IssueViewSerializer(BaseSerializer):
is_favorite = serializers.BooleanField(read_only=True)
project_detail = ProjectLiteSerializer(source="project", read_only=True)
+29 -13
View File
@@ -3,7 +3,7 @@ from rest_framework import serializers
# Module imports
from .base import BaseSerializer
from .user import UserLiteSerializer
from .user import UserLiteSerializer, UserAdminLiteSerializer
from plane.db.models import (
User,
@@ -33,10 +33,37 @@ class WorkSpaceSerializer(BaseSerializer):
"owner",
]
class WorkspaceLiteSerializer(BaseSerializer):
class Meta:
model = Workspace
fields = [
"name",
"slug",
"id",
]
read_only_fields = fields
class WorkSpaceMemberSerializer(BaseSerializer):
member = UserLiteSerializer(read_only=True)
workspace = WorkSpaceSerializer(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
@@ -101,17 +128,6 @@ class TeamSerializer(BaseSerializer):
return super().update(instance, validated_data)
class WorkspaceLiteSerializer(BaseSerializer):
class Meta:
model = Workspace
fields = [
"name",
"slug",
"id",
]
read_only_fields = fields
class WorkspaceThemeSerializer(BaseSerializer):
class Meta:
model = WorkspaceTheme
+50
View File
@@ -0,0 +1,50 @@
from .analytic import urlpatterns as analytic_urls
from .asset import urlpatterns as asset_urls
from .authentication import urlpatterns as authentication_urls
from .configuration import urlpatterns as configuration_urls
from .cycle import urlpatterns as cycle_urls
from .estimate import urlpatterns as estimate_urls
from .gpt import urlpatterns as gpt_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 .release_note import urlpatterns as release_note_urls
from .search import urlpatterns as search_urls
from .state import urlpatterns as state_urls
from .unsplash import urlpatterns as unsplash_urls
from .user import urlpatterns as user_urls
from .views import urlpatterns as view_urls
from .workspace import urlpatterns as workspace_urls
urlpatterns = [
*analytic_urls,
*asset_urls,
*authentication_urls,
*configuration_urls,
*cycle_urls,
*estimate_urls,
*gpt_urls,
*importer_urls,
*inbox_urls,
*integration_urls,
*issue_urls,
*module_urls,
*notification_urls,
*page_urls,
*project_urls,
*public_board_urls,
*release_note_urls,
*search_urls,
*state_urls,
*unsplash_urls,
*user_urls,
*view_urls,
*workspace_urls,
]
+46
View File
@@ -0,0 +1,46 @@
from django.urls import path
from plane.api.views import (
AnalyticsEndpoint,
AnalyticViewViewset,
SavedAnalyticEndpoint,
ExportAnalyticsEndpoint,
DefaultAnalyticsEndpoint,
)
urlpatterns = [
path(
"workspaces/<str:slug>/analytics/",
AnalyticsEndpoint.as_view(),
name="plane-analytics",
),
path(
"workspaces/<str:slug>/analytic-view/",
AnalyticViewViewset.as_view({"get": "list", "post": "create"}),
name="analytic-view",
),
path(
"workspaces/<str:slug>/analytic-view/<uuid:pk>/",
AnalyticViewViewset.as_view(
{"get": "retrieve", "patch": "partial_update", "delete": "destroy"}
),
name="analytic-view",
),
path(
"workspaces/<str:slug>/saved-analytic-view/<uuid:analytic_id>/",
SavedAnalyticEndpoint.as_view(),
name="saved-analytic-view",
),
path(
"workspaces/<str:slug>/export-analytics/",
ExportAnalyticsEndpoint.as_view(),
name="export-analytics",
),
path(
"workspaces/<str:slug>/default-analytics/",
DefaultAnalyticsEndpoint.as_view(),
name="default-analytics",
),
]
+31
View File
@@ -0,0 +1,31 @@
from django.urls import path
from plane.api.views import (
FileAssetEndpoint,
UserAssetsEndpoint,
)
urlpatterns = [
path(
"workspaces/<str:slug>/file-assets/",
FileAssetEndpoint.as_view(),
name="file-assets",
),
path(
"workspaces/file-assets/<uuid:workspace_id>/<str:asset_key>/",
FileAssetEndpoint.as_view(),
name="file-assets",
),
path(
"users/file-assets/",
UserAssetsEndpoint.as_view(),
name="user-file-assets",
),
path(
"users/file-assets/<str:asset_key>/",
UserAssetsEndpoint.as_view(),
name="user-file-assets",
),
]
@@ -0,0 +1,68 @@
from django.urls import path
from rest_framework_simplejwt.views import TokenRefreshView
from plane.api.views import (
# Authentication
SignUpEndpoint,
SignInEndpoint,
SignOutEndpoint,
MagicSignInEndpoint,
MagicSignInGenerateEndpoint,
OauthEndpoint,
## End Authentication
# Auth Extended
ForgotPasswordEndpoint,
VerifyEmailEndpoint,
ResetPasswordEndpoint,
RequestEmailVerificationEndpoint,
ChangePasswordEndpoint,
## End Auth Extender
# API Tokens
ApiTokenEndpoint,
## End API Tokens
)
urlpatterns = [
# Social Auth
path("social-auth/", OauthEndpoint.as_view(), name="oauth"),
# Auth
path("sign-up/", SignUpEndpoint.as_view(), name="sign-up"),
path("sign-in/", SignInEndpoint.as_view(), name="sign-in"),
path("sign-out/", SignOutEndpoint.as_view(), name="sign-out"),
# Magic Sign In/Up
path(
"magic-generate/", MagicSignInGenerateEndpoint.as_view(), name="magic-generate"
),
path("magic-sign-in/", MagicSignInEndpoint.as_view(), name="magic-sign-in"),
path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
# Email verification
path("email-verify/", VerifyEmailEndpoint.as_view(), name="email-verify"),
path(
"request-email-verify/",
RequestEmailVerificationEndpoint.as_view(),
name="request-reset-email",
),
# Password Manipulation
path(
"users/me/change-password/",
ChangePasswordEndpoint.as_view(),
name="change-password",
),
path(
"reset-password/<uidb64>/<token>/",
ResetPasswordEndpoint.as_view(),
name="password-reset",
),
path(
"forgot-password/",
ForgotPasswordEndpoint.as_view(),
name="forgot-password",
),
# API Tokens
path("api-tokens/", ApiTokenEndpoint.as_view(), name="api-tokens"),
path("api-tokens/<uuid:pk>/", ApiTokenEndpoint.as_view(), name="api-tokens"),
## End API Tokens
]
+12
View File
@@ -0,0 +1,12 @@
from django.urls import path
from plane.api.views import ConfigurationEndpoint
urlpatterns = [
path(
"configs/",
ConfigurationEndpoint.as_view(),
name="configuration",
),
]
+87
View File
@@ -0,0 +1,87 @@
from django.urls import path
from plane.api.views import (
CycleViewSet,
CycleIssueViewSet,
CycleDateCheckEndpoint,
CycleFavoriteViewSet,
TransferCycleIssueEndpoint,
)
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/",
CycleViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-cycle",
),
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",
),
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",
),
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",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/<uuid:cycle_id>/transfer-issues/",
TransferCycleIssueEndpoint.as_view(),
name="transfer-issues",
),
]
+37
View File
@@ -0,0 +1,37 @@
from django.urls import path
from plane.api.views import (
ProjectEstimatePointEndpoint,
BulkEstimatePointEndpoint,
)
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/project-estimates/",
ProjectEstimatePointEndpoint.as_view(),
name="project-estimate-points",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/estimates/",
BulkEstimatePointEndpoint.as_view(
{
"get": "list",
"post": "create",
}
),
name="bulk-create-estimate-points",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/estimates/<uuid:estimate_id>/",
BulkEstimatePointEndpoint.as_view(
{
"get": "retrieve",
"patch": "partial_update",
"delete": "destroy",
}
),
name="bulk-create-estimate-points",
),
]
+13
View File
@@ -0,0 +1,13 @@
from django.urls import path
from plane.api.views import GPTIntegrationEndpoint
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/ai-assistant/",
GPTIntegrationEndpoint.as_view(),
name="importer",
),
]
+37
View File
@@ -0,0 +1,37 @@
from django.urls import path
from plane.api.views import (
ServiceIssueImportSummaryEndpoint,
ImportServiceEndpoint,
UpdateServiceImportStatusEndpoint,
)
urlpatterns = [
path(
"workspaces/<str:slug>/importers/<str:service>/",
ServiceIssueImportSummaryEndpoint.as_view(),
name="importer-summary",
),
path(
"workspaces/<str:slug>/projects/importers/<str:service>/",
ImportServiceEndpoint.as_view(),
name="importer",
),
path(
"workspaces/<str:slug>/importers/",
ImportServiceEndpoint.as_view(),
name="importer",
),
path(
"workspaces/<str:slug>/importers/<str:service>/<uuid:pk>/",
ImportServiceEndpoint.as_view(),
name="importer",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/service/<str:service>/importers/<uuid:importer_id>/",
UpdateServiceImportStatusEndpoint.as_view(),
name="importer-status",
),
]
+53
View File
@@ -0,0 +1,53 @@
from django.urls import path
from plane.api.views import (
InboxViewSet,
InboxIssueViewSet,
)
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",
}
),
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",
}
),
name="inbox-issue",
),
]
+150
View File
@@ -0,0 +1,150 @@
from django.urls import path
from plane.api.views import (
IntegrationViewSet,
WorkspaceIntegrationViewSet,
GithubRepositoriesEndpoint,
GithubRepositorySyncViewSet,
GithubIssueSyncViewSet,
GithubCommentSyncViewSet,
BulkCreateGithubIssueSyncEndpoint,
SlackProjectSyncViewSet,
)
urlpatterns = [
path(
"integrations/",
IntegrationViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="integrations",
),
path(
"integrations/<uuid:pk>/",
IntegrationViewSet.as_view(
{
"get": "retrieve",
"patch": "partial_update",
"delete": "destroy",
}
),
name="integrations",
),
path(
"workspaces/<str:slug>/workspace-integrations/",
WorkspaceIntegrationViewSet.as_view(
{
"get": "list",
}
),
name="workspace-integrations",
),
path(
"workspaces/<str:slug>/workspace-integrations/<str:provider>/",
WorkspaceIntegrationViewSet.as_view(
{
"post": "create",
}
),
name="workspace-integrations",
),
path(
"workspaces/<str:slug>/workspace-integrations/<uuid:pk>/provider/",
WorkspaceIntegrationViewSet.as_view(
{
"get": "retrieve",
"delete": "destroy",
}
),
name="workspace-integrations",
),
# Github Integrations
path(
"workspaces/<str:slug>/workspace-integrations/<uuid:workspace_integration_id>/github-repositories/",
GithubRepositoriesEndpoint.as_view(),
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/workspace-integrations/<uuid:workspace_integration_id>/github-repository-sync/",
GithubRepositorySyncViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/workspace-integrations/<uuid:workspace_integration_id>/github-repository-sync/<uuid:pk>/",
GithubRepositorySyncViewSet.as_view(
{
"get": "retrieve",
"delete": "destroy",
}
),
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/github-repository-sync/<uuid:repo_sync_id>/github-issue-sync/",
GithubIssueSyncViewSet.as_view(
{
"post": "create",
"get": "list",
}
),
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/github-repository-sync/<uuid:repo_sync_id>/bulk-create-github-issue-sync/",
BulkCreateGithubIssueSyncEndpoint.as_view(),
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/github-repository-sync/<uuid:repo_sync_id>/github-issue-sync/<uuid:pk>/",
GithubIssueSyncViewSet.as_view(
{
"get": "retrieve",
"delete": "destroy",
}
),
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/github-repository-sync/<uuid:repo_sync_id>/github-issue-sync/<uuid:issue_sync_id>/github-comment-sync/",
GithubCommentSyncViewSet.as_view(
{
"post": "create",
"get": "list",
}
),
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/github-repository-sync/<uuid:repo_sync_id>/github-issue-sync/<uuid:issue_sync_id>/github-comment-sync/<uuid:pk>/",
GithubCommentSyncViewSet.as_view(
{
"get": "retrieve",
"delete": "destroy",
}
),
),
## End Github Integrations
# Slack Integration
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/workspace-integrations/<uuid:workspace_integration_id>/project-slack-sync/",
SlackProjectSyncViewSet.as_view(
{
"post": "create",
"get": "list",
}
),
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/workspace-integrations/<uuid:workspace_integration_id>/project-slack-sync/<uuid:pk>/",
SlackProjectSyncViewSet.as_view(
{
"delete": "destroy",
"get": "retrieve",
}
),
),
## End Slack Integration
]
+315
View File
@@ -0,0 +1,315 @@
from django.urls import path
from plane.api.views import (
IssueViewSet,
LabelViewSet,
BulkCreateIssueLabelsEndpoint,
BulkDeleteIssuesEndpoint,
BulkImportIssuesEndpoint,
UserWorkSpaceIssues,
SubIssuesEndpoint,
IssueLinkViewSet,
IssueAttachmentEndpoint,
ExportIssuesEndpoint,
IssueActivityEndpoint,
IssueCommentViewSet,
IssueSubscriberViewSet,
IssueReactionViewSet,
CommentReactionViewSet,
IssueUserDisplayPropertyEndpoint,
IssueArchiveViewSet,
IssueRelationViewSet,
IssueDraftViewSet,
)
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/",
IssueViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-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",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issue-labels/",
LabelViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-issue-labels",
),
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",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/bulk-create-labels/",
BulkCreateIssueLabelsEndpoint.as_view(),
name="project-bulk-labels",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/bulk-delete-issues/",
BulkDeleteIssuesEndpoint.as_view(),
name="project-issues-bulk",
),
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",
),
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",
),
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",
),
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",
),
]
+104
View File
@@ -0,0 +1,104 @@
from django.urls import path
from plane.api.views import (
ModuleViewSet,
ModuleIssueViewSet,
ModuleLinkViewSet,
ModuleFavoriteViewSet,
BulkImportModulesEndpoint,
)
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/",
ModuleViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-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",
),
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",
),
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",
),
]
+66
View File
@@ -0,0 +1,66 @@
from django.urls import path
from plane.api.views import (
NotificationViewSet,
UnreadNotificationEndpoint,
MarkAllReadNotificationViewSet,
)
urlpatterns = [
path(
"workspaces/<str:slug>/users/notifications/",
NotificationViewSet.as_view(
{
"get": "list",
}
),
name="notifications",
),
path(
"workspaces/<str:slug>/users/notifications/<uuid:pk>/",
NotificationViewSet.as_view(
{
"get": "retrieve",
"patch": "partial_update",
"delete": "destroy",
}
),
name="notifications",
),
path(
"workspaces/<str:slug>/users/notifications/<uuid:pk>/read/",
NotificationViewSet.as_view(
{
"post": "mark_read",
"delete": "mark_unread",
}
),
name="notifications",
),
path(
"workspaces/<str:slug>/users/notifications/<uuid:pk>/archive/",
NotificationViewSet.as_view(
{
"post": "archive",
"delete": "unarchive",
}
),
name="notifications",
),
path(
"workspaces/<str:slug>/users/notifications/unread/",
UnreadNotificationEndpoint.as_view(),
name="unread-notifications",
),
path(
"workspaces/<str:slug>/users/notifications/mark-all-read/",
MarkAllReadNotificationViewSet.as_view(
{
"post": "create",
}
),
name="mark-all-read-notifications",
),
]
+79
View File
@@ -0,0 +1,79 @@
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",
),
]
+144
View File
@@ -0,0 +1,144 @@
from django.urls import path
from plane.api.views import (
ProjectViewSet,
InviteProjectEndpoint,
ProjectMemberViewSet,
ProjectMemberEndpoint,
ProjectMemberInvitationsViewset,
ProjectMemberUserEndpoint,
AddMemberToProjectEndpoint,
ProjectJoinEndpoint,
AddTeamToProjectEndpoint,
ProjectUserViewsEndpoint,
ProjectIdentifierEndpoint,
ProjectFavoritesViewSet,
LeaveProjectEndpoint,
ProjectPublicCoverImagesEndpoint
)
urlpatterns = [
path(
"workspaces/<str:slug>/projects/",
ProjectViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project",
),
path(
"workspaces/<str:slug>/projects/<uuid:pk>/",
ProjectViewSet.as_view(
{
"get": "retrieve",
"put": "update",
"patch": "partial_update",
"delete": "destroy",
}
),
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"}),
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/<uuid:project_id>/project-members/",
ProjectMemberEndpoint.as_view(),
name="project-member",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/members/add/",
AddMemberToProjectEndpoint.as_view(),
name="project",
),
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",
),
]
+151
View File
@@ -0,0 +1,151 @@
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",
),
]
+13
View File
@@ -0,0 +1,13 @@
from django.urls import path
from plane.api.views import ReleaseNotesEndpoint
urlpatterns = [
path(
"release-notes/",
ReleaseNotesEndpoint.as_view(),
name="release-notes",
),
]
+21
View File
@@ -0,0 +1,21 @@
from django.urls import path
from plane.api.views import (
GlobalSearchEndpoint,
IssueSearchEndpoint,
)
urlpatterns = [
path(
"workspaces/<str:slug>/search/",
GlobalSearchEndpoint.as_view(),
name="global-search",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/search-issues/",
IssueSearchEndpoint.as_view(),
name="project-issue-search",
),
]
+30
View File
@@ -0,0 +1,30 @@
from django.urls import path
from plane.api.views import StateViewSet
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/states/",
StateViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-states",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/states/<uuid:pk>/",
StateViewSet.as_view(
{
"get": "retrieve",
"put": "update",
"patch": "partial_update",
"delete": "destroy",
}
),
name="project-state",
),
]
+13
View File
@@ -0,0 +1,13 @@
from django.urls import path
from plane.api.views import UnsplashEndpoint
urlpatterns = [
path(
"unsplash/",
UnsplashEndpoint.as_view(),
name="unsplash",
),
]
+113
View File
@@ -0,0 +1,113 @@
from django.urls import path
from plane.api.views import (
## User
UserEndpoint,
UpdateUserOnBoardedEndpoint,
UpdateUserTourCompletedEndpoint,
UserActivityEndpoint,
ChangePasswordEndpoint,
## End User
## Workspaces
UserWorkspaceInvitationsEndpoint,
UserWorkSpacesEndpoint,
JoinWorkspaceEndpoint,
UserWorkspaceInvitationsEndpoint,
UserWorkspaceInvitationEndpoint,
UserActivityGraphEndpoint,
UserIssueCompletedGraphEndpoint,
UserWorkspaceDashboardEndpoint,
UserProjectInvitationsViewset,
## End Workspaces
)
urlpatterns = [
# User Profile
path(
"users/me/",
UserEndpoint.as_view(
{"get": "retrieve", "patch": "partial_update", "delete": "destroy"}
),
name="users",
),
path(
"users/me/settings/",
UserEndpoint.as_view(
{
"get": "retrieve_user_settings",
}
),
name="users",
),
path(
"users/me/change-password/",
ChangePasswordEndpoint.as_view(),
name="change-password",
),
path(
"users/me/onboard/",
UpdateUserOnBoardedEndpoint.as_view(),
name="user-onboard",
),
path(
"users/me/tour-completed/",
UpdateUserTourCompletedEndpoint.as_view(),
name="user-tour",
),
path(
"users/workspaces/<str:slug>/activities/",
UserActivityEndpoint.as_view(),
name="user-activities",
),
# user workspaces
path(
"users/me/workspaces/",
UserWorkSpacesEndpoint.as_view(),
name="user-workspace",
),
# user workspace invitations
path(
"users/me/invitations/workspaces/",
UserWorkspaceInvitationsEndpoint.as_view({"get": "list", "post": "create"}),
name="user-workspace-invitations",
),
# user workspace invitation
path(
"users/me/invitations/<uuid:pk>/",
UserWorkspaceInvitationEndpoint.as_view(
{
"get": "retrieve",
}
),
name="user-workspace-invitation",
),
# user join workspace
# User Graphs
path(
"users/me/workspaces/<str:slug>/activity-graph/",
UserActivityGraphEndpoint.as_view(),
name="user-activity-graph",
),
path(
"users/me/workspaces/<str:slug>/issues-completed-graph/",
UserIssueCompletedGraphEndpoint.as_view(),
name="completed-graph",
),
path(
"users/me/workspaces/<str:slug>/dashboard/",
UserWorkspaceDashboardEndpoint.as_view(),
name="user-workspace-dashboard",
),
## End User Graph
path(
"users/me/invitations/workspaces/<str:slug>/<uuid:pk>/join/",
JoinWorkspaceEndpoint.as_view(),
name="user-join-workspace",
),
# user project invitations
path(
"users/me/invitations/projects/",
UserProjectInvitationsViewset.as_view({"get": "list", "post": "create"}),
name="user-project-invitations",
),
]
+85
View File
@@ -0,0 +1,85 @@
from django.urls import path
from plane.api.views import (
IssueViewViewSet,
GlobalViewViewSet,
GlobalViewIssuesViewSet,
IssueViewFavoriteViewSet,
)
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/views/",
IssueViewViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-view",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/views/<uuid:pk>/",
IssueViewViewSet.as_view(
{
"get": "retrieve",
"put": "update",
"patch": "partial_update",
"delete": "destroy",
}
),
name="project-view",
),
path(
"workspaces/<str:slug>/views/",
GlobalViewViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="global-view",
),
path(
"workspaces/<str:slug>/views/<uuid:pk>/",
GlobalViewViewSet.as_view(
{
"get": "retrieve",
"put": "update",
"patch": "partial_update",
"delete": "destroy",
}
),
name="global-view",
),
path(
"workspaces/<str:slug>/issues/",
GlobalViewIssuesViewSet.as_view(
{
"get": "list",
}
),
name="global-view-issues",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-views/",
IssueViewFavoriteViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="user-favorite-view",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-views/<uuid:view_id>/",
IssueViewFavoriteViewSet.as_view(
{
"delete": "destroy",
}
),
name="user-favorite-view",
),
]
+182
View File
@@ -0,0 +1,182 @@
from django.urls import path
from plane.api.views import (
WorkSpaceViewSet,
InviteWorkspaceEndpoint,
WorkSpaceMemberViewSet,
WorkspaceMembersEndpoint,
WorkspaceInvitationsViewset,
WorkspaceMemberUserEndpoint,
WorkspaceMemberUserViewsEndpoint,
WorkSpaceAvailabilityCheckEndpoint,
TeamMemberViewSet,
UserLastProjectWithWorkspaceEndpoint,
WorkspaceThemeViewSet,
WorkspaceUserProfileStatsEndpoint,
WorkspaceUserActivityEndpoint,
WorkspaceUserProfileEndpoint,
WorkspaceUserProfileIssuesEndpoint,
WorkspaceLabelsEndpoint,
LeaveWorkspaceEndpoint,
)
urlpatterns = [
path(
"workspace-slug-check/",
WorkSpaceAvailabilityCheckEndpoint.as_view(),
name="workspace-availability",
),
path(
"workspaces/",
WorkSpaceViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="workspace",
),
path(
"workspaces/<str:slug>/",
WorkSpaceViewSet.as_view(
{
"get": "retrieve",
"put": "update",
"patch": "partial_update",
"delete": "destroy",
}
),
name="workspace",
),
path(
"workspaces/<str:slug>/invite/",
InviteWorkspaceEndpoint.as_view(),
name="invite-workspace",
),
path(
"workspaces/<str:slug>/invitations/",
WorkspaceInvitationsViewset.as_view({"get": "list"}),
name="workspace-invitations",
),
path(
"workspaces/<str:slug>/invitations/<uuid:pk>/",
WorkspaceInvitationsViewset.as_view(
{
"delete": "destroy",
"get": "retrieve",
}
),
name="workspace-invitations",
),
path(
"workspaces/<str:slug>/members/",
WorkSpaceMemberViewSet.as_view({"get": "list"}),
name="workspace-member",
),
path(
"workspaces/<str:slug>/members/<uuid:pk>/",
WorkSpaceMemberViewSet.as_view(
{
"patch": "partial_update",
"delete": "destroy",
"get": "retrieve",
}
),
name="workspace-member",
),
path(
"workspaces/<str:slug>/workspace-members/",
WorkspaceMembersEndpoint.as_view(),
name="workspace-members",
),
path(
"workspaces/<str:slug>/teams/",
TeamMemberViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="workspace-team-members",
),
path(
"workspaces/<str:slug>/teams/<uuid:pk>/",
TeamMemberViewSet.as_view(
{
"put": "update",
"patch": "partial_update",
"delete": "destroy",
"get": "retrieve",
}
),
name="workspace-team-members",
),
path(
"users/last-visited-workspace/",
UserLastProjectWithWorkspaceEndpoint.as_view(),
name="workspace-project-details",
),
path(
"workspaces/<str:slug>/workspace-members/me/",
WorkspaceMemberUserEndpoint.as_view(),
name="workspace-member-details",
),
path(
"workspaces/<str:slug>/workspace-views/",
WorkspaceMemberUserViewsEndpoint.as_view(),
name="workspace-member-views-details",
),
path(
"workspaces/<str:slug>/workspace-themes/",
WorkspaceThemeViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="workspace-themes",
),
path(
"workspaces/<str:slug>/workspace-themes/<uuid:pk>/",
WorkspaceThemeViewSet.as_view(
{
"get": "retrieve",
"patch": "partial_update",
"delete": "destroy",
}
),
name="workspace-themes",
),
path(
"workspaces/<str:slug>/user-stats/<uuid:user_id>/",
WorkspaceUserProfileStatsEndpoint.as_view(),
name="workspace-user-stats",
),
path(
"workspaces/<str:slug>/user-activity/<uuid:user_id>/",
WorkspaceUserActivityEndpoint.as_view(),
name="workspace-user-activity",
),
path(
"workspaces/<str:slug>/user-profile/<uuid:user_id>/",
WorkspaceUserProfileEndpoint.as_view(),
name="workspace-user-profile-page",
),
path(
"workspaces/<str:slug>/user-issues/<uuid:user_id>/",
WorkspaceUserProfileIssuesEndpoint.as_view(),
name="workspace-user-profile-issues",
),
path(
"workspaces/<str:slug>/labels/",
WorkspaceLabelsEndpoint.as_view(),
name="workspace-labels",
),
path(
"workspaces/<str:slug>/members/leave/",
LeaveWorkspaceEndpoint.as_view(),
name="leave-workspace-members",
),
]
@@ -1,5 +1,6 @@
from django.urls import path
from rest_framework_simplejwt.views import TokenRefreshView
# Create your urls here.
@@ -22,6 +23,7 @@ from plane.api.views import (
# User
UserEndpoint,
UpdateUserOnBoardedEndpoint,
UpdateUserTourCompletedEndpoint,
UserActivityEndpoint,
## End User
# Workspaces
@@ -31,6 +33,7 @@ from plane.api.views import (
InviteWorkspaceEndpoint,
JoinWorkspaceEndpoint,
WorkSpaceMemberViewSet,
WorkspaceMembersEndpoint,
WorkspaceInvitationsViewset,
UserWorkspaceInvitationsEndpoint,
WorkspaceMemberUserEndpoint,
@@ -44,6 +47,12 @@ from plane.api.views import (
UserIssueCompletedGraphEndpoint,
UserWorkspaceDashboardEndpoint,
WorkspaceThemeViewSet,
WorkspaceUserProfileStatsEndpoint,
WorkspaceUserActivityEndpoint,
WorkspaceUserProfileEndpoint,
WorkspaceUserProfileIssuesEndpoint,
WorkspaceLabelsEndpoint,
LeaveWorkspaceEndpoint,
## End Workspaces
# File Assets
FileAssetEndpoint,
@@ -53,6 +62,7 @@ from plane.api.views import (
ProjectViewSet,
InviteProjectEndpoint,
ProjectMemberViewSet,
ProjectMemberEndpoint,
ProjectMemberInvitationsViewset,
ProjectMemberUserEndpoint,
AddMemberToProjectEndpoint,
@@ -60,6 +70,8 @@ from plane.api.views import (
UserProjectInvitationsViewset,
ProjectIdentifierEndpoint,
ProjectFavoritesViewSet,
LeaveProjectEndpoint,
ProjectPublicCoverImagesEndpoint,
## End Projects
# Issues
IssueViewSet,
@@ -70,12 +82,19 @@ from plane.api.views import (
BulkDeleteIssuesEndpoint,
BulkImportIssuesEndpoint,
ProjectUserViewsEndpoint,
IssuePropertyViewSet,
IssueUserDisplayPropertyEndpoint,
LabelViewSet,
SubIssuesEndpoint,
IssueLinkViewSet,
BulkCreateIssueLabelsEndpoint,
IssueAttachmentEndpoint,
IssueArchiveViewSet,
IssueSubscriberViewSet,
IssueCommentPublicViewSet,
IssueReactionViewSet,
IssueRelationViewSet,
CommentReactionViewSet,
IssueDraftViewSet,
## End Issues
# States
StateViewSet,
@@ -85,8 +104,9 @@ from plane.api.views import (
BulkEstimatePointEndpoint,
## End Estimates
# Views
GlobalViewViewSet,
GlobalViewIssuesViewSet,
IssueViewViewSet,
ViewIssuesEndpoint,
IssueViewFavoriteViewSet,
## End Views
# Cycles
@@ -131,12 +151,11 @@ from plane.api.views import (
GlobalSearchEndpoint,
IssueSearchEndpoint,
## End Search
# Gpt
# External
GPTIntegrationEndpoint,
## End Gpt
# Release Notes
ReleaseNotesEndpoint,
## End Release Notes
UnsplashEndpoint,
## End External
# Inbox
InboxViewSet,
InboxIssueViewSet,
@@ -148,9 +167,34 @@ from plane.api.views import (
ExportAnalyticsEndpoint,
DefaultAnalyticsEndpoint,
## End Analytics
# Notification
NotificationViewSet,
UnreadNotificationEndpoint,
MarkAllReadNotificationViewSet,
## End Notification
# Public Boards
ProjectDeployBoardViewSet,
ProjectIssuesPublicEndpoint,
ProjectDeployBoardPublicSettingsEndpoint,
IssueReactionPublicViewSet,
CommentReactionPublicViewSet,
InboxIssuePublicViewSet,
IssueVotePublicViewSet,
WorkspaceProjectDeployBoardEndpoint,
IssueRetrievePublicEndpoint,
## End Public Boards
## Exporter
ExportIssuesEndpoint,
## End Exporter
# Configuration
ConfigurationEndpoint,
## End Configuration
)
#TODO: Delete this file
# This url file has been deprecated use apiserver/plane/urls folder to create new urls
urlpatterns = [
# Social Auth
path("social-auth/", OauthEndpoint.as_view(), name="oauth"),
@@ -163,6 +207,7 @@ urlpatterns = [
"magic-generate/", MagicSignInGenerateEndpoint.as_view(), name="magic-generate"
),
path("magic-sign-in/", MagicSignInEndpoint.as_view(), name="magic-sign-in"),
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
# Email verification
path("email-verify/", VerifyEmailEndpoint.as_view(), name="email-verify"),
path(
@@ -189,6 +234,15 @@ urlpatterns = [
),
name="users",
),
path(
"users/me/settings/",
UserEndpoint.as_view(
{
"get": "retrieve_user_settings",
}
),
name="users",
),
path(
"users/me/change-password/",
ChangePasswordEndpoint.as_view(),
@@ -197,9 +251,18 @@ urlpatterns = [
path(
"users/me/onboard/",
UpdateUserOnBoardedEndpoint.as_view(),
name="change-password",
name="user-onboard",
),
path(
"users/me/tour-completed/",
UpdateUserTourCompletedEndpoint.as_view(),
name="user-tour",
),
path(
"users/workspaces/<str:slug>/activities/",
UserActivityEndpoint.as_view(),
name="user-activities",
),
path("users/activities/", UserActivityEndpoint.as_view(), name="user-activities"),
# user workspaces
path(
"users/me/workspaces/",
@@ -295,7 +358,6 @@ urlpatterns = [
{
"delete": "destroy",
"get": "retrieve",
"get": "retrieve",
}
),
name="workspace",
@@ -316,6 +378,11 @@ urlpatterns = [
),
name="workspace",
),
path(
"workspaces/<str:slug>/workspace-members/",
WorkspaceMembersEndpoint.as_view(),
name="workspace-members",
),
path(
"workspaces/<str:slug>/teams/",
TeamMemberViewSet.as_view(
@@ -374,6 +441,36 @@ urlpatterns = [
),
name="workspace-themes",
),
path(
"workspaces/<str:slug>/user-stats/<uuid:user_id>/",
WorkspaceUserProfileStatsEndpoint.as_view(),
name="workspace-user-stats",
),
path(
"workspaces/<str:slug>/user-activity/<uuid:user_id>/",
WorkspaceUserActivityEndpoint.as_view(),
name="workspace-user-activity",
),
path(
"workspaces/<str:slug>/user-profile/<uuid:user_id>/",
WorkspaceUserProfileEndpoint.as_view(),
name="workspace-user-profile-page",
),
path(
"workspaces/<str:slug>/user-issues/<uuid:user_id>/",
WorkspaceUserProfileIssuesEndpoint.as_view(),
name="workspace-user-profile-issues",
),
path(
"workspaces/<str:slug>/labels/",
WorkspaceLabelsEndpoint.as_view(),
name="workspace-labels",
),
path(
"workspaces/<str:slug>/members/leave/",
LeaveWorkspaceEndpoint.as_view(),
name="workspace-labels",
),
## End Workspaces ##
# Projects
path(
@@ -424,6 +521,11 @@ urlpatterns = [
),
name="project",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/project-members/",
ProjectMemberEndpoint.as_view(),
name="project",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/members/add/",
AddMemberToProjectEndpoint.as_view(),
@@ -483,6 +585,16 @@ urlpatterns = [
),
name="project",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/members/leave/",
LeaveProjectEndpoint.as_view(),
name="project",
),
path(
"project-covers/",
ProjectPublicCoverImagesEndpoint.as_view(),
name="project-covers",
),
# End Projects
# States
path(
@@ -560,9 +672,35 @@ urlpatterns = [
name="project-view",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/views/<uuid:view_id>/issues/",
ViewIssuesEndpoint.as_view(),
name="project-view-issues",
"workspaces/<str:slug>/views/",
GlobalViewViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="global-view",
),
path(
"workspaces/<str:slug>/views/<uuid:pk>/",
GlobalViewViewSet.as_view(
{
"get": "retrieve",
"put": "update",
"patch": "partial_update",
"delete": "destroy",
}
),
name="global-view",
),
path(
"workspaces/<str:slug>/issues/",
GlobalViewIssuesViewSet.as_view(
{
"get": "list",
}
),
name="global-view-issues",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-views/",
@@ -682,11 +820,6 @@ urlpatterns = [
),
name="project-issue",
),
path(
"workspaces/<str:slug>/issues/",
WorkSpaceIssuesEndpoint.as_view(),
name="workspace-issue",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issue-labels/",
LabelViewSet.as_view(
@@ -766,6 +899,11 @@ urlpatterns = [
IssueAttachmentEndpoint.as_view(),
name="project-issue-attachments",
),
path(
"workspaces/<str:slug>/export-issues/",
ExportIssuesEndpoint.as_view(),
name="export-issues",
),
## End Issues
## Issue Activity
path(
@@ -798,30 +936,156 @@ urlpatterns = [
name="project-issue-comment",
),
## End IssueComments
## IssueProperty
# Issue Subscribers
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issue-properties/",
IssuePropertyViewSet.as_view(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-subscribers/",
IssueSubscriberViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-issue-roadmap",
name="project-issue-subscribers",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issue-properties/<uuid:pk>/",
IssuePropertyViewSet.as_view(
"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",
),
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 Ebd
## 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",
"put": "update",
"patch": "partial_update",
"delete": "destroy",
}
),
name="project-issue-roadmap",
name="project-issue-draft",
),
## IssueProperty Ebd
## End Issue Drafts
## File Assets
path(
"workspaces/<str:slug>/file-assets/",
@@ -1172,7 +1436,7 @@ urlpatterns = [
## End Importer
# Search
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/search/",
"workspaces/<str:slug>/search/",
GlobalSearchEndpoint.as_view(),
name="global-search",
),
@@ -1182,20 +1446,23 @@ urlpatterns = [
name="project-issue-search",
),
## End Search
# Gpt
# External
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/ai-assistant/",
GPTIntegrationEndpoint.as_view(),
name="importer",
),
## End Gpt
# Release Notes
path(
"release-notes/",
ReleaseNotesEndpoint.as_view(),
name="release-notes",
),
## End Release Notes
path(
"unsplash/",
UnsplashEndpoint.as_view(),
name="release-notes",
),
## End External
# Inbox
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/inboxes/",
@@ -1274,4 +1541,201 @@ urlpatterns = [
name="default-analytics",
),
## End Analytics
# Notification
path(
"workspaces/<str:slug>/users/notifications/",
NotificationViewSet.as_view(
{
"get": "list",
}
),
name="notifications",
),
path(
"workspaces/<str:slug>/users/notifications/<uuid:pk>/",
NotificationViewSet.as_view(
{
"get": "retrieve",
"patch": "partial_update",
"delete": "destroy",
}
),
name="notifications",
),
path(
"workspaces/<str:slug>/users/notifications/<uuid:pk>/read/",
NotificationViewSet.as_view(
{
"post": "mark_read",
"delete": "mark_unread",
}
),
name="notifications",
),
path(
"workspaces/<str:slug>/users/notifications/<uuid:pk>/archive/",
NotificationViewSet.as_view(
{
"post": "archive",
"delete": "unarchive",
}
),
name="notifications",
),
path(
"workspaces/<str:slug>/users/notifications/unread/",
UnreadNotificationEndpoint.as_view(),
name="unread-notifications",
),
path(
"workspaces/<str:slug>/users/notifications/mark-all-read/",
MarkAllReadNotificationViewSet.as_view(
{
"post": "create",
}
),
name="mark-all-read-notifications",
),
## End Notification
# Public Boards
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",
),
## End Public Boards
# Configuration
path(
"configs/",
ConfigurationEndpoint.as_view(),
name="configuration",
),
## End Configuration
]
+37 -7
View File
@@ -12,10 +12,17 @@ from .project import (
ProjectUserViewsEndpoint,
ProjectMemberUserEndpoint,
ProjectFavoritesViewSet,
ProjectDeployBoardViewSet,
ProjectDeployBoardPublicSettingsEndpoint,
ProjectMemberEndpoint,
WorkspaceProjectDeployBoardEndpoint,
LeaveProjectEndpoint,
ProjectPublicCoverImagesEndpoint,
)
from .people import (
from .user import (
UserEndpoint,
UpdateUserOnBoardedEndpoint,
UpdateUserTourCompletedEndpoint,
UserActivityEndpoint,
)
@@ -41,9 +48,16 @@ from .workspace import (
UserIssueCompletedGraphEndpoint,
UserWorkspaceDashboardEndpoint,
WorkspaceThemeViewSet,
WorkspaceUserProfileStatsEndpoint,
WorkspaceUserActivityEndpoint,
WorkspaceUserProfileEndpoint,
WorkspaceUserProfileIssuesEndpoint,
WorkspaceLabelsEndpoint,
WorkspaceMembersEndpoint,
LeaveWorkspaceEndpoint,
)
from .state import StateViewSet
from .view import IssueViewViewSet, ViewIssuesEndpoint, IssueViewFavoriteViewSet
from .view import GlobalViewViewSet, GlobalViewIssuesViewSet, IssueViewViewSet, IssueViewFavoriteViewSet
from .cycle import (
CycleViewSet,
CycleIssueViewSet,
@@ -57,7 +71,7 @@ from .issue import (
WorkSpaceIssuesEndpoint,
IssueActivityEndpoint,
IssueCommentViewSet,
IssuePropertyViewSet,
IssueUserDisplayPropertyEndpoint,
LabelViewSet,
BulkDeleteIssuesEndpoint,
UserWorkSpaceIssues,
@@ -65,6 +79,18 @@ from .issue import (
IssueLinkViewSet,
BulkCreateIssueLabelsEndpoint,
IssueAttachmentEndpoint,
IssueArchiveViewSet,
IssueSubscriberViewSet,
IssueCommentPublicViewSet,
CommentReactionViewSet,
IssueReactionViewSet,
IssueReactionPublicViewSet,
CommentReactionPublicViewSet,
IssueVotePublicViewSet,
IssueRelationViewSet,
IssueRetrievePublicEndpoint,
ProjectIssuesPublicEndpoint,
IssueDraftViewSet,
)
from .auth_extended import (
@@ -122,17 +148,15 @@ from .page import (
from .search import GlobalSearchEndpoint, IssueSearchEndpoint
from .gpt import GPTIntegrationEndpoint
from .external import GPTIntegrationEndpoint, ReleaseNotesEndpoint, UnsplashEndpoint
from .estimate import (
ProjectEstimatePointEndpoint,
BulkEstimatePointEndpoint,
)
from .inbox import InboxViewSet, InboxIssueViewSet, InboxIssuePublicViewSet
from .release import ReleaseNotesEndpoint
from .inbox import InboxViewSet, InboxIssueViewSet
from .analytic import (
AnalyticsEndpoint,
AnalyticViewViewset,
@@ -140,3 +164,9 @@ from .analytic import (
ExportAnalyticsEndpoint,
DefaultAnalyticsEndpoint,
)
from .notification import NotificationViewSet, UnreadNotificationEndpoint, MarkAllReadNotificationViewSet
from .exporter import ExportIssuesEndpoint
from .config import ConfigurationEndpoint
+286 -199
View File
@@ -1,10 +1,5 @@
# Django imports
from django.db.models import (
Count,
Sum,
F,
Q
)
from django.db.models import Count, Sum, F, Q
from django.db.models.functions import ExtractMonth
# Third party imports
@@ -28,82 +23,156 @@ class AnalyticsEndpoint(BaseAPIView):
]
def get(self, request, slug):
try:
x_axis = request.GET.get("x_axis", False)
y_axis = request.GET.get("y_axis", False)
x_axis = request.GET.get("x_axis", False)
y_axis = request.GET.get("y_axis", False)
segment = request.GET.get("segment", False)
if not x_axis or not y_axis:
return Response(
{"error": "x-axis and y-axis dimensions are required"},
status=status.HTTP_400_BAD_REQUEST,
)
segment = request.GET.get("segment", False)
filters = issue_filters(request.GET, "GET")
queryset = Issue.issue_objects.filter(workspace__slug=slug, **filters)
total_issues = queryset.count()
distribution = build_graph_plot(
queryset=queryset, x_axis=x_axis, y_axis=y_axis, segment=segment
)
colors = dict()
if x_axis in ["state__name", "state__group"] or segment in [
"state__name",
"state__group",
]:
if x_axis in ["state__name", "state__group"]:
key = "name" if x_axis == "state__name" else "group"
else:
key = "name" if segment == "state__name" else "group"
colors = (
State.objects.filter(
~Q(name="Triage"),
workspace__slug=slug, project_id__in=filters.get("project__in")
).values(key, "color")
if filters.get("project__in", False)
else State.objects.filter(~Q(name="Triage"), workspace__slug=slug).values(key, "color")
)
if x_axis in ["labels__name"] or segment in ["labels__name"]:
colors = (
Label.objects.filter(
workspace__slug=slug, project_id__in=filters.get("project__in")
).values("name", "color")
if filters.get("project__in", False)
else Label.objects.filter(workspace__slug=slug).values(
"name", "color"
)
)
assignee_details = {}
if x_axis in ["assignees__email"] or segment in ["assignees__email"]:
assignee_details = (
Issue.issue_objects.filter(workspace__slug=slug, **filters, assignees__avatar__isnull=False)
.order_by("assignees__id")
.distinct("assignees__id")
.values("assignees__avatar", "assignees__email", "assignees__first_name", "assignees__last_name")
)
valid_xaxis_segment = [
"state_id",
"state__group",
"labels__id",
"assignees__id",
"estimate_point",
"issue_cycle__cycle_id",
"issue_module__module_id",
"priority",
"start_date",
"target_date",
"created_at",
"completed_at",
]
valid_yaxis = [
"issue_count",
"estimate",
]
# Check for x-axis and y-axis as thery are required parameters
if (
not x_axis
or not y_axis
or not x_axis in valid_xaxis_segment
or not y_axis in valid_yaxis
):
return Response(
{
"total": total_issues,
"distribution": distribution,
"extras": {"colors": colors, "assignee_details": assignee_details},
"error": "x-axis and y-axis dimensions are required and the values should be valid"
},
status=status.HTTP_200_OK,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
# If segment is present it cannot be same as x-axis
if segment and (segment not in valid_xaxis_segment or x_axis == segment):
return Response(
{
"error": "Both segment and x axis cannot be same and segment should be valid"
},
status=status.HTTP_400_BAD_REQUEST,
)
# Additional filters that need to be applied
filters = issue_filters(request.GET, "GET")
# Get the issues for the workspace with the additional filters applied
queryset = Issue.issue_objects.filter(workspace__slug=slug, **filters)
# Get the total issue count
total_issues = queryset.count()
# Build the graph payload
distribution = build_graph_plot(
queryset=queryset, x_axis=x_axis, y_axis=y_axis, segment=segment
)
state_details = {}
if x_axis in ["state_id"] or segment in ["state_id"]:
state_details = (
Issue.issue_objects.filter(
workspace__slug=slug,
**filters,
)
.distinct("state_id")
.order_by("state_id")
.values("state_id", "state__name", "state__color")
)
label_details = {}
if x_axis in ["labels__id"] or segment in ["labels__id"]:
label_details = (
Issue.objects.filter(
workspace__slug=slug, **filters, labels__id__isnull=False
)
.distinct("labels__id")
.order_by("labels__id")
.values("labels__id", "labels__color", "labels__name")
)
assignee_details = {}
if x_axis in ["assignees__id"] or segment in ["assignees__id"]:
assignee_details = (
Issue.issue_objects.filter(
workspace__slug=slug, **filters, assignees__avatar__isnull=False
)
.order_by("assignees__id")
.distinct("assignees__id")
.values(
"assignees__avatar",
"assignees__display_name",
"assignees__first_name",
"assignees__last_name",
"assignees__id",
)
)
cycle_details = {}
if x_axis in ["issue_cycle__cycle_id"] or segment in ["issue_cycle__cycle_id"]:
cycle_details = (
Issue.issue_objects.filter(
workspace__slug=slug,
**filters,
issue_cycle__cycle_id__isnull=False,
)
.distinct("issue_cycle__cycle_id")
.order_by("issue_cycle__cycle_id")
.values(
"issue_cycle__cycle_id",
"issue_cycle__cycle__name",
)
)
module_details = {}
if x_axis in ["issue_module__module_id"] or segment in [
"issue_module__module_id"
]:
module_details = (
Issue.issue_objects.filter(
workspace__slug=slug,
**filters,
issue_module__module_id__isnull=False,
)
.distinct("issue_module__module_id")
.order_by("issue_module__module_id")
.values(
"issue_module__module_id",
"issue_module__module__name",
)
)
return Response(
{
"total": total_issues,
"distribution": distribution,
"extras": {
"state_details": state_details,
"assignee_details": assignee_details,
"label_details": label_details,
"cycle_details": cycle_details,
"module_details": module_details,
},
},
status=status.HTTP_200_OK,
)
class AnalyticViewViewset(BaseViewSet):
permission_classes = [
@@ -128,45 +197,30 @@ class SavedAnalyticEndpoint(BaseAPIView):
]
def get(self, request, slug, analytic_id):
try:
analytic_view = AnalyticView.objects.get(
pk=analytic_id, workspace__slug=slug
)
analytic_view = AnalyticView.objects.get(pk=analytic_id, workspace__slug=slug)
filter = analytic_view.query
queryset = Issue.issue_objects.filter(**filter)
filter = analytic_view.query
queryset = Issue.issue_objects.filter(**filter)
x_axis = analytic_view.query_dict.get("x_axis", False)
y_axis = analytic_view.query_dict.get("y_axis", False)
x_axis = analytic_view.query_dict.get("x_axis", False)
y_axis = analytic_view.query_dict.get("y_axis", False)
if not x_axis or not y_axis:
return Response(
{"error": "x-axis and y-axis dimensions are required"},
status=status.HTTP_400_BAD_REQUEST,
)
segment = request.GET.get("segment", False)
distribution = build_graph_plot(
queryset=queryset, x_axis=x_axis, y_axis=y_axis, segment=segment
)
total_issues = queryset.count()
if not x_axis or not y_axis:
return Response(
{"total": total_issues, "distribution": distribution},
status=status.HTTP_200_OK,
)
except AnalyticView.DoesNotExist:
return Response(
{"error": "Analytic View Does not exist"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
{"error": "x-axis and y-axis dimensions are required"},
status=status.HTTP_400_BAD_REQUEST,
)
segment = request.GET.get("segment", False)
distribution = build_graph_plot(
queryset=queryset, x_axis=x_axis, y_axis=y_axis, segment=segment
)
total_issues = queryset.count()
return Response(
{"total": total_issues, "distribution": distribution},
status=status.HTTP_200_OK,
)
class ExportAnalyticsEndpoint(BaseAPIView):
permission_classes = [
@@ -174,33 +228,64 @@ class ExportAnalyticsEndpoint(BaseAPIView):
]
def post(self, request, slug):
try:
x_axis = request.data.get("x_axis", False)
y_axis = request.data.get("y_axis", False)
x_axis = request.data.get("x_axis", False)
y_axis = request.data.get("y_axis", False)
segment = request.data.get("segment", False)
if not x_axis or not y_axis:
return Response(
{"error": "x-axis and y-axis dimensions are required"},
status=status.HTTP_400_BAD_REQUEST,
)
valid_xaxis_segment = [
"state_id",
"state__group",
"labels__id",
"assignees__id",
"estimate_point",
"issue_cycle__cycle_id",
"issue_module__module_id",
"priority",
"start_date",
"target_date",
"created_at",
"completed_at",
]
analytic_export_task.delay(
email=request.user.email, data=request.data, slug=slug
)
valid_yaxis = [
"issue_count",
"estimate",
]
# Check for x-axis and y-axis as thery are required parameters
if (
not x_axis
or not y_axis
or not x_axis in valid_xaxis_segment
or not y_axis in valid_yaxis
):
return Response(
{
"message": f"Once the export is ready it will be emailed to you at {str(request.user.email)}"
"error": "x-axis and y-axis dimensions are required and the values should be valid"
},
status=status.HTTP_200_OK,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
# If segment is present it cannot be same as x-axis
if segment and (segment not in valid_xaxis_segment or x_axis == segment):
return Response(
{
"error": "Both segment and x axis cannot be same and segment should be valid"
},
status=status.HTTP_400_BAD_REQUEST,
)
analytic_export_task.delay(
email=request.user.email, data=request.data, slug=slug
)
return Response(
{
"message": f"Once the export is ready it will be emailed to you at {str(request.user.email)}"
},
status=status.HTTP_200_OK,
)
class DefaultAnalyticsEndpoint(BaseAPIView):
permission_classes = [
@@ -208,90 +293,92 @@ class DefaultAnalyticsEndpoint(BaseAPIView):
]
def get(self, request, slug):
try:
filters = issue_filters(request.GET, "GET")
filters = issue_filters(request.GET, "GET")
base_issues = Issue.issue_objects.filter(workspace__slug=slug, **filters)
queryset = Issue.issue_objects.filter(workspace__slug=slug, **filters)
total_issues = base_issues.count()
total_issues = queryset.count()
state_groups = base_issues.annotate(state_group=F("state__group"))
total_issues_classified = (
queryset.annotate(state_group=F("state__group"))
.values("state_group")
.annotate(state_count=Count("state_group"))
.order_by("state_group")
)
total_issues_classified = (
state_groups.values("state_group")
.annotate(state_count=Count("state_group"))
.order_by("state_group")
)
open_issues = queryset.filter(
state__group__in=["backlog", "unstarted", "started"]
).count()
open_issues_groups = ["backlog", "unstarted", "started"]
open_issues_queryset = state_groups.filter(state__group__in=open_issues_groups)
open_issues_classified = (
queryset.filter(state__group__in=["backlog", "unstarted", "started"])
.annotate(state_group=F("state__group"))
.values("state_group")
.annotate(state_count=Count("state_group"))
.order_by("state_group")
)
open_issues = open_issues_queryset.count()
open_issues_classified = (
open_issues_queryset.values("state_group")
.annotate(state_count=Count("state_group"))
.order_by("state_group")
)
issue_completed_month_wise = (
queryset.filter(completed_at__isnull=False)
.annotate(month=ExtractMonth("completed_at"))
.values("month")
.annotate(count=Count("*"))
.order_by("month")
)
most_issue_created_user = (
queryset.exclude(created_by=None)
.values("created_by__first_name", "created_by__last_name", "created_by__avatar", "created_by__email")
.annotate(count=Count("id"))
.order_by("-count")
)[:5]
issue_completed_month_wise = (
base_issues.filter(completed_at__isnull=False)
.annotate(month=ExtractMonth("completed_at"))
.values("month")
.annotate(count=Count("*"))
.order_by("month")
)
most_issue_closed_user = (
queryset.filter(completed_at__isnull=False, assignees__isnull=False)
.values("assignees__first_name", "assignees__last_name", "assignees__avatar", "assignees__email")
.annotate(count=Count("id"))
.order_by("-count")
)[:5]
user_details = [
"created_by__first_name",
"created_by__last_name",
"created_by__avatar",
"created_by__display_name",
"created_by__id",
]
pending_issue_user = (
queryset.filter(completed_at__isnull=True)
.values("assignees__first_name", "assignees__last_name", "assignees__avatar", "assignees__email")
.annotate(count=Count("id"))
.order_by("-count")
)
most_issue_created_user = (
base_issues.exclude(created_by=None)
.values(*user_details)
.annotate(count=Count("id"))
.order_by("-count")[:5]
)
open_estimate_sum = (
queryset.filter(
state__group__in=["backlog", "unstarted", "started"]
).aggregate(open_estimate_sum=Sum("estimate_point"))
)["open_estimate_sum"]
print(open_estimate_sum)
total_estimate_sum = queryset.aggregate(
total_estimate_sum=Sum("estimate_point")
)["total_estimate_sum"]
user_assignee_details = [
"assignees__first_name",
"assignees__last_name",
"assignees__avatar",
"assignees__display_name",
"assignees__id",
]
return Response(
{
"total_issues": total_issues,
"total_issues_classified": total_issues_classified,
"open_issues": open_issues,
"open_issues_classified": open_issues_classified,
"issue_completed_month_wise": issue_completed_month_wise,
"most_issue_created_user": most_issue_created_user,
"most_issue_closed_user": most_issue_closed_user,
"pending_issue_user": pending_issue_user,
"open_estimate_sum": open_estimate_sum,
"total_estimate_sum": total_estimate_sum,
},
status=status.HTTP_200_OK,
)
most_issue_closed_user = (
base_issues.filter(completed_at__isnull=False)
.exclude(assignees=None)
.values(*user_assignee_details)
.annotate(count=Count("id"))
.order_by("-count")[:5]
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
pending_issue_user = (
base_issues.filter(completed_at__isnull=True)
.values(*user_assignee_details)
.annotate(count=Count("id"))
.order_by("-count")
)
open_estimate_sum = open_issues_queryset.aggregate(sum=Sum("estimate_point"))[
"sum"
]
total_estimate_sum = base_issues.aggregate(sum=Sum("estimate_point"))["sum"]
return Response(
{
"total_issues": total_issues,
"total_issues_classified": total_issues_classified,
"open_issues": open_issues,
"open_issues_classified": open_issues_classified,
"issue_completed_month_wise": issue_completed_month_wise,
"most_issue_created_user": most_issue_created_user,
"most_issue_closed_user": most_issue_closed_user,
"pending_issue_user": pending_issue_user,
"open_estimate_sum": open_estimate_sum,
"total_estimate_sum": total_estimate_sum,
},
status=status.HTTP_200_OK,
)
+23 -46
View File
@@ -14,57 +14,34 @@ from plane.api.serializers import APITokenSerializer
class ApiTokenEndpoint(BaseAPIView):
def post(self, request):
try:
label = request.data.get("label", str(uuid4().hex))
workspace = request.data.get("workspace", False)
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
if not workspace:
return Response(
{"api_token": serializer.data, "token": api_token.token},
status=status.HTTP_201_CREATED,
{"error": "Workspace is required"}, status=status.HTTP_200_OK
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
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):
try:
api_tokens = APIToken.objects.filter(user=request.user)
serializer = APITokenSerializer(api_tokens, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_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):
try:
api_token = APIToken.objects.get(pk=pk)
api_token.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except APIToken.DoesNotExist:
return Response(
{"error": "Token does not exists"}, status=status.HTTP_400_BAD_REQUEST
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
api_token = APIToken.objects.get(pk=pk)
api_token.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
+27 -61
View File
@@ -20,90 +20,56 @@ class FileAssetEndpoint(BaseAPIView):
def get(self, request, workspace_id, asset_key):
asset_key = str(workspace_id) + "/" + asset_key
files = FileAsset.objects.filter(asset=asset_key)
serializer = FileAssetSerializer(files, context={"request": request}, many=True)
return Response(serializer.data)
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):
try:
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)
except Workspace.DoesNotExist:
return Response({"error": "Workspace does not exist"}, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
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):
try:
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)
except FileAsset.DoesNotExist:
return Response(
{"error": "File Asset doesn't exist"}, status=status.HTTP_404_NOT_FOUND
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
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):
try:
files = FileAsset.objects.filter(asset=asset_key, created_by=request.user)
serializer = FileAssetSerializer(files, context={"request": request})
return Response(serializer.data)
except FileAsset.DoesNotExist:
return Response(
{"error": "File Asset does not exist"}, status=status.HTTP_404_NOT_FOUND
)
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):
try:
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)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
def delete(self, request, asset_key):
try:
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)
except FileAsset.DoesNotExist:
return Response(
{"error": "File Asset doesn't exist"}, status=status.HTTP_404_NOT_FOUND
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
+20 -28
View File
@@ -9,7 +9,6 @@ from django.utils.encoding import (
DjangoUnicodeDecodeError,
)
from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
from django.contrib.sites.shortcuts import get_current_site
from django.conf import settings
## Third Party Imports
@@ -22,7 +21,7 @@ from sentry_sdk import capture_exception
## Module imports
from . import BaseAPIView
from plane.api.serializers.people import (
from plane.api.serializers import (
ChangePasswordSerializer,
ResetPasswordSerializer,
)
@@ -128,32 +127,25 @@ class ResetPasswordEndpoint(BaseAPIView):
class ChangePasswordEndpoint(BaseAPIView):
def post(self, request):
try:
serializer = ChangePasswordSerializer(data=request.data)
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",
}
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(response)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+289 -348
View File
@@ -40,229 +40,194 @@ class SignUpEndpoint(BaseAPIView):
permission_classes = (AllowAny,)
def post(self, request):
try:
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()
serialized_user = UserSerializer(user).data
access_token, refresh_token = get_tokens_for_user(user)
data = {
"access_token": access_token,
"refresh_token": refresh_token,
"user": serialized_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_UP",
},
)
return Response(data, status=status.HTTP_200_OK)
except Exception as e:
capture_exception(e)
if not settings.ENABLE_SIGNUP:
return Response(
{"error": "Something went wrong please try again later"},
{
"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):
try:
email = request.data.get("email", False)
password = request.data.get("password", False)
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,
)
serialized_user = UserSerializer(user).data
# 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,
"user": serialized_user,
}
return Response(data, status=status.HTTP_200_OK)
except Exception as e:
capture_exception(e)
## Raise exception if any of the above are missing
if not email or not password:
return Response(
{
"error": "Something went wrong. Please try again later or contact the support team."
},
{"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):
try:
refresh_token = request.data.get("refresh_token", False)
refresh_token = request.data.get("refresh_token", False)
if not refresh_token:
capture_message("No refresh token provided")
return Response(
{
"error": "Something went wrong. Please try again later or contact the support team."
},
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)
except Exception as e:
capture_exception(e)
if not refresh_token:
capture_message("No refresh token provided")
return Response(
{
"error": "Something went wrong. Please try again later or contact the support team."
},
{"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 = [
@@ -270,72 +235,62 @@ class MagicSignInGenerateEndpoint(BaseAPIView):
]
def post(self, request):
try:
email = request.data.get("email", False)
email = request.data.get("email", False)
if not email:
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 + string.digits, k=4))
+ "-"
+ "".join(random.choices(string.ascii_lowercase + string.digits, k=4))
+ "-"
+ "".join(random.choices(string.ascii_lowercase + string.digits, 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": "Please provide a valid email address"},
{"error": "Max attempts exhausted. Please try again later."},
status=status.HTTP_400_BAD_REQUEST,
)
validate_email(email)
value = {
"current_attempt": current_attempt,
"email": email,
"token": token,
}
expiry = 600
## Generate a random token
token = (
"".join(random.choices(string.ascii_lowercase + string.digits, k=4))
+ "-"
+ "".join(random.choices(string.ascii_lowercase + string.digits, k=4))
+ "-"
+ "".join(random.choices(string.ascii_lowercase + string.digits, k=4))
)
ri.set(key, json.dumps(value), ex=expiry)
ri = redis_instance()
else:
value = {"current_attempt": 0, "email": email, "token": token}
expiry = 600
key = "magic_" + str(email)
ri.set(key, json.dumps(value), ex=expiry)
# Check if the key already exists in python
if ri.exists(key):
data = json.loads(ri.get(key))
current_site = settings.WEB_URL
magic_link.delay(email, key, token, current_site)
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)
except ValidationError:
return Response(
{"error": "Please provide a valid email address."},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
return Response({"key": key}, status=status.HTTP_200_OK)
class MagicSignInEndpoint(BaseAPIView):
@@ -344,113 +299,99 @@ class MagicSignInEndpoint(BaseAPIView):
]
def post(self, request):
try:
user_token = request.data.get("token", "").strip().lower()
key = request.data.get("key", False)
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,
)
if not key or user_token == "":
return Response(
{"error": "User token and key are required"},
status=status.HTTP_400_BAD_REQUEST,
)
ri = redis_instance()
ri = redis_instance()
if ri.exists(key):
data = json.loads(ri.get(key))
if ri.exists(key):
data = json.loads(ri.get(key))
token = data["token"]
email = data["email"]
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,
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",
},
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",
"user": {"email": email, "id": str(user.id)},
"device_ctx": {
"ip": request.META.get("REMOTE_ADDR"),
"user_agent": request.META.get("HTTP_USER_AGENT"),
},
)
else:
user = User.objects.create(
email=email,
username=uuid.uuid4().hex,
password=make_password(uuid.uuid4().hex),
is_password_autoset=True,
"event_type": "SIGN_IN",
},
)
# 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()
serialized_user = UserSerializer(user).data
access_token, refresh_token = get_tokens_for_user(user)
data = {
"access_token": access_token,
"refresh_token": refresh_token,
"user": serialized_user,
}
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,
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": "The magic code/link has expired please try again"},
{"error": "Your login code was incorrect. Please try again."},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
else:
return Response(
{"error": "Something went wrong please try again later"},
{"error": "The magic code/link has expired please try again"},
status=status.HTTP_400_BAD_REQUEST,
)
+103 -18
View File
@@ -1,24 +1,43 @@
# Python imports
import zoneinfo
# 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
# 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
from rest_framework.views import APIView
from rest_framework.filters import SearchFilter
from rest_framework.permissions import IsAuthenticated
from rest_framework.exceptions import NotFound
from sentry_sdk import capture_exception
from django_filters.rest_framework import DjangoFilterBackend
# Module imports
from plane.db.models import Workspace, Project
from plane.utils.paginator import BasePaginator
class BaseViewSet(ModelViewSet, BasePaginator):
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:
timezone.activate(zoneinfo.ZoneInfo(request.user.user_timezone))
else:
timezone.deactivate()
class BaseViewSet(TimezoneMixin, ModelViewSet, BasePaginator):
model = None
@@ -41,17 +60,50 @@ class BaseViewSet(ModelViewSet, BasePaginator):
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):
response = super().dispatch(request, *args, **kwargs)
try:
response = super().dispatch(request, *args, **kwargs)
if settings.DEBUG:
from django.db import connection
if settings.DEBUG:
from django.db import connection
print(
f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}"
)
return response
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):
@@ -67,7 +119,7 @@ class BaseViewSet(ModelViewSet, BasePaginator):
return self.kwargs.get("pk", None)
class BaseAPIView(APIView, BasePaginator):
class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
permission_classes = [
IsAuthenticated,
@@ -87,16 +139,49 @@ class BaseAPIView(APIView, BasePaginator):
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,
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):
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):
response = super().dispatch(request, *args, **kwargs)
try:
response = super().dispatch(request, *args, **kwargs)
if settings.DEBUG:
from django.db import connection
if settings.DEBUG:
from django.db import connection
print(
f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}"
)
return response
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):
+33
View File
@@ -0,0 +1,33 @@
# 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"] = os.environ.get("GOOGLE_CLIENT_ID", None)
data["github"] = 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"
)
return Response(data, status=status.HTTP_200_OK)
File diff suppressed because it is too large Load Diff
+119 -194
View File
@@ -1,6 +1,3 @@
# Django imports
from django.db import IntegrityError
# Third party imports
from rest_framework.response import Response
from rest_framework import status
@@ -23,7 +20,6 @@ class ProjectEstimatePointEndpoint(BaseAPIView):
]
def get(self, request, slug, project_id):
try:
project = Project.objects.get(workspace__slug=slug, pk=project_id)
if project.estimate_id is not None:
estimate_points = EstimatePoint.objects.filter(
@@ -34,12 +30,6 @@ class ProjectEstimatePointEndpoint(BaseAPIView):
serializer = EstimatePointSerializer(estimate_points, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response([], status=status.HTTP_200_OK)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
class BulkEstimatePointEndpoint(BaseViewSet):
@@ -50,204 +40,139 @@ class BulkEstimatePointEndpoint(BaseViewSet):
serializer_class = EstimateSerializer
def list(self, request, slug, project_id):
try:
estimates = Estimate.objects.filter(
workspace__slug=slug, project_id=project_id
).prefetch_related("points").select_related("workspace", "project")
serializer = EstimateReadSerializer(estimates, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
estimates = Estimate.objects.filter(
workspace__slug=slug, project_id=project_id
).prefetch_related("points").select_related("workspace", "project")
serializer = EstimateReadSerializer(estimates, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def create(self, request, slug, project_id):
try:
if not request.data.get("estimate", False):
return Response(
{"error": "Estimate is required"},
status=status.HTTP_400_BAD_REQUEST,
)
estimate_points = request.data.get("estimate_points", [])
if not len(estimate_points) or len(estimate_points) > 8:
return Response(
{"error": "Estimate points are required"},
status=status.HTTP_400_BAD_REQUEST,
)
estimate_serializer = EstimateSerializer(data=request.data.get("estimate"))
if not estimate_serializer.is_valid():
return Response(
estimate_serializer.errors, status=status.HTTP_400_BAD_REQUEST
)
try:
estimate = estimate_serializer.save(project_id=project_id)
except IntegrityError:
return Response(
{"errror": "Estimate with the name already exists"},
status=status.HTTP_400_BAD_REQUEST,
)
estimate_points = EstimatePoint.objects.bulk_create(
[
EstimatePoint(
estimate=estimate,
key=estimate_point.get("key", 0),
value=estimate_point.get("value", ""),
description=estimate_point.get("description", ""),
project_id=project_id,
workspace_id=estimate.workspace_id,
created_by=request.user,
updated_by=request.user,
)
for estimate_point in estimate_points
],
batch_size=10,
ignore_conflicts=True,
)
estimate_point_serializer = EstimatePointSerializer(
estimate_points, many=True
)
if not request.data.get("estimate", False):
return Response(
{
"estimate": estimate_serializer.data,
"estimate_points": estimate_point_serializer.data,
},
status=status.HTTP_200_OK,
)
except Estimate.DoesNotExist:
return Response(
{"error": "Estimate does not exist"},
{"error": "Estimate is required"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
estimate_points = request.data.get("estimate_points", [])
if not len(estimate_points) or len(estimate_points) > 8:
return Response(
{"error": "Something went wrong please try again later"},
{"error": "Estimate points are required"},
status=status.HTTP_400_BAD_REQUEST,
)
estimate_serializer = EstimateSerializer(data=request.data.get("estimate"))
if not estimate_serializer.is_valid():
return Response(
estimate_serializer.errors, status=status.HTTP_400_BAD_REQUEST
)
estimate = estimate_serializer.save(project_id=project_id)
estimate_points = EstimatePoint.objects.bulk_create(
[
EstimatePoint(
estimate=estimate,
key=estimate_point.get("key", 0),
value=estimate_point.get("value", ""),
description=estimate_point.get("description", ""),
project_id=project_id,
workspace_id=estimate.workspace_id,
created_by=request.user,
updated_by=request.user,
)
for estimate_point in estimate_points
],
batch_size=10,
ignore_conflicts=True,
)
estimate_point_serializer = EstimatePointSerializer(
estimate_points, many=True
)
return Response(
{
"estimate": estimate_serializer.data,
"estimate_points": estimate_point_serializer.data,
},
status=status.HTTP_200_OK,
)
def retrieve(self, request, slug, project_id, estimate_id):
try:
estimate = Estimate.objects.get(
pk=estimate_id, workspace__slug=slug, project_id=project_id
)
serializer = EstimateReadSerializer(estimate)
return Response(
serializer.data,
status=status.HTTP_200_OK,
)
except Estimate.DoesNotExist:
return Response(
{"error": "Estimate does not exist"}, status=status.HTTP_400_BAD_REQUEST
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
estimate = Estimate.objects.get(
pk=estimate_id, workspace__slug=slug, project_id=project_id
)
serializer = EstimateReadSerializer(estimate)
return Response(
serializer.data,
status=status.HTTP_200_OK,
)
def partial_update(self, request, slug, project_id, estimate_id):
try:
if not request.data.get("estimate", False):
return Response(
{"error": "Estimate is required"},
status=status.HTTP_400_BAD_REQUEST,
)
if not len(request.data.get("estimate_points", [])):
return Response(
{"error": "Estimate points are required"},
status=status.HTTP_400_BAD_REQUEST,
)
estimate = Estimate.objects.get(pk=estimate_id)
estimate_serializer = EstimateSerializer(
estimate, data=request.data.get("estimate"), partial=True
)
if not estimate_serializer.is_valid():
return Response(
estimate_serializer.errors, status=status.HTTP_400_BAD_REQUEST
)
try:
estimate = estimate_serializer.save()
except IntegrityError:
return Response(
{"errror": "Estimate with the name already exists"},
status=status.HTTP_400_BAD_REQUEST,
)
estimate_points_data = request.data.get("estimate_points", [])
estimate_points = EstimatePoint.objects.filter(
pk__in=[
estimate_point.get("id") for estimate_point in estimate_points_data
],
workspace__slug=slug,
project_id=project_id,
estimate_id=estimate_id,
)
updated_estimate_points = []
for estimate_point in estimate_points:
# Find the data for that estimate point
estimate_point_data = [
point
for point in estimate_points_data
if point.get("id") == str(estimate_point.id)
]
if len(estimate_point_data):
estimate_point.value = estimate_point_data[0].get(
"value", estimate_point.value
)
updated_estimate_points.append(estimate_point)
try:
EstimatePoint.objects.bulk_update(
updated_estimate_points, ["value"], batch_size=10,
)
except IntegrityError as e:
return Response(
{"error": "Values need to be unique for each key"},
status=status.HTTP_400_BAD_REQUEST,
)
estimate_point_serializer = EstimatePointSerializer(estimate_points, many=True)
if not request.data.get("estimate", False):
return Response(
{
"estimate": estimate_serializer.data,
"estimate_points": estimate_point_serializer.data,
},
status=status.HTTP_200_OK,
)
except Estimate.DoesNotExist:
return Response(
{"error": "Estimate does not exist"}, status=status.HTTP_400_BAD_REQUEST
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
{"error": "Estimate is required"},
status=status.HTTP_400_BAD_REQUEST,
)
if not len(request.data.get("estimate_points", [])):
return Response(
{"error": "Estimate points are required"},
status=status.HTTP_400_BAD_REQUEST,
)
estimate = Estimate.objects.get(pk=estimate_id)
estimate_serializer = EstimateSerializer(
estimate, data=request.data.get("estimate"), partial=True
)
if not estimate_serializer.is_valid():
return Response(
estimate_serializer.errors, status=status.HTTP_400_BAD_REQUEST
)
estimate = estimate_serializer.save()
estimate_points_data = request.data.get("estimate_points", [])
estimate_points = EstimatePoint.objects.filter(
pk__in=[
estimate_point.get("id") for estimate_point in estimate_points_data
],
workspace__slug=slug,
project_id=project_id,
estimate_id=estimate_id,
)
updated_estimate_points = []
for estimate_point in estimate_points:
# Find the data for that estimate point
estimate_point_data = [
point
for point in estimate_points_data
if point.get("id") == str(estimate_point.id)
]
if len(estimate_point_data):
estimate_point.value = estimate_point_data[0].get(
"value", estimate_point.value
)
updated_estimate_points.append(estimate_point)
EstimatePoint.objects.bulk_update(
updated_estimate_points, ["value"], batch_size=10,
)
estimate_point_serializer = EstimatePointSerializer(estimate_points, many=True)
return Response(
{
"estimate": estimate_serializer.data,
"estimate_points": estimate_point_serializer.data,
},
status=status.HTTP_200_OK,
)
def destroy(self, request, slug, project_id, estimate_id):
try:
estimate = Estimate.objects.get(
pk=estimate_id, workspace__slug=slug, project_id=project_id
)
estimate.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
estimate = Estimate.objects.get(
pk=estimate_id, workspace__slug=slug, project_id=project_id
)
estimate.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
+81
View File
@@ -0,0 +1,81 @@
# Third Party imports
from rest_framework.response import Response
from rest_framework import status
from sentry_sdk import capture_exception
# Module imports
from . import BaseAPIView
from plane.api.permissions import WorkSpaceAdminPermission
from plane.bgtasks.export_task import issue_export_task
from plane.db.models import Project, ExporterHistory, Workspace
from plane.api.serializers import ExporterHistorySerializer
class ExportIssuesEndpoint(BaseAPIView):
permission_classes = [
WorkSpaceAdminPermission,
]
model = ExporterHistory
serializer_class = ExporterHistorySerializer
def post(self, request, slug):
# Get the workspace
workspace = Workspace.objects.get(slug=slug)
provider = request.data.get("provider", False)
multiple = request.data.get("multiple", False)
project_ids = request.data.get("project", [])
if provider in ["csv", "xlsx", "json"]:
if not project_ids:
project_ids = Project.objects.filter(
workspace__slug=slug
).values_list("id", flat=True)
project_ids = [str(project_id) for project_id in project_ids]
exporter = ExporterHistory.objects.create(
workspace=workspace,
project=project_ids,
initiated_by=request.user,
provider=provider,
)
issue_export_task.delay(
provider=exporter.provider,
workspace_id=workspace.id,
project_ids=project_ids,
token_id=exporter.token,
multiple=multiple,
slug=slug,
)
return Response(
{
"message": f"Once the export is ready you will be able to download it"
},
status=status.HTTP_200_OK,
)
else:
return Response(
{"error": f"Provider '{provider}' not found."},
status=status.HTTP_400_BAD_REQUEST,
)
def get(self, request, slug):
exporter_history = ExporterHistory.objects.filter(
workspace__slug=slug
).select_related("workspace","initiated_by")
if request.GET.get("per_page", False) and request.GET.get("cursor", False):
return self.paginate(
request=request,
queryset=exporter_history,
on_results=lambda exporter_history: ExporterHistorySerializer(
exporter_history, many=True
).data,
)
else:
return Response(
{"error": "per_page and cursor are required"},
status=status.HTTP_400_BAD_REQUEST,
)
+92
View File
@@ -0,0 +1,92 @@
# Python imports
import requests
# Third party imports
import openai
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import AllowAny
from sentry_sdk import capture_exception
# Django imports
from django.conf import settings
# Module imports
from .base import BaseAPIView
from plane.api.permissions import ProjectEntityPermission
from plane.db.models import Workspace, Project
from plane.api.serializers import ProjectLiteSerializer, WorkspaceLiteSerializer
from plane.utils.integrations.github import get_release_notes
class GPTIntegrationEndpoint(BaseAPIView):
permission_classes = [
ProjectEntityPermission,
]
def post(self, request, slug, project_id):
if not settings.OPENAI_API_KEY or not settings.GPT_ENGINE:
return Response(
{"error": "OpenAI API key and engine is required"},
status=status.HTTP_400_BAD_REQUEST,
)
prompt = request.data.get("prompt", False)
task = request.data.get("task", False)
if not task:
return Response(
{"error": "Task is required"}, status=status.HTTP_400_BAD_REQUEST
)
final_text = task + "\n" + prompt
openai.api_key = settings.OPENAI_API_KEY
response = openai.ChatCompletion.create(
model=settings.GPT_ENGINE,
messages=[{"role": "user", "content": final_text}],
temperature=0.7,
max_tokens=1024,
)
workspace = Workspace.objects.get(slug=slug)
project = Project.objects.get(pk=project_id)
text = response.choices[0].message.content.strip()
text_html = text.replace("\n", "<br/>")
return Response(
{
"response": text,
"response_html": text_html,
"project_detail": ProjectLiteSerializer(project).data,
"workspace_detail": WorkspaceLiteSerializer(workspace).data,
},
status=status.HTTP_200_OK,
)
class ReleaseNotesEndpoint(BaseAPIView):
def get(self, request):
release_notes = get_release_notes()
return Response(release_notes, status=status.HTTP_200_OK)
class UnsplashEndpoint(BaseAPIView):
def get(self, request):
query = request.GET.get("query", False)
page = request.GET.get("page", 1)
per_page = request.GET.get("per_page", 20)
url = (
f"https://api.unsplash.com/search/photos/?client_id={settings.UNSPLASH_ACCESS_KEY}&query={query}&page=${page}&per_page={per_page}"
if query
else f"https://api.unsplash.com/photos/?client_id={settings.UNSPLASH_ACCESS_KEY}&page={page}&per_page={per_page}"
)
headers = {
"Content-Type": "application/json",
}
resp = requests.get(url=url, headers=headers)
return Response(resp.json(), status=status.HTTP_200_OK)
-101
View File
@@ -1,101 +0,0 @@
# Python imports
import requests
# Third party imports
from rest_framework.response import Response
from rest_framework import status
import openai
from sentry_sdk import capture_exception
# Django imports
from django.conf import settings
# Module imports
from .base import BaseAPIView
from plane.api.permissions import ProjectEntityPermission
from plane.db.models import Workspace, Project
from plane.api.serializers import ProjectLiteSerializer, WorkspaceLiteSerializer
class GPTIntegrationEndpoint(BaseAPIView):
permission_classes = [
ProjectEntityPermission,
]
def post(self, request, slug, project_id):
try:
if not settings.OPENAI_API_KEY or not settings.GPT_ENGINE:
return Response(
{"error": "OpenAI API key and engine is required"},
status=status.HTTP_400_BAD_REQUEST,
)
count = 0
# If logger is enabled check for request limit
if settings.LOGGER_BASE_URL:
try:
headers = {
"Content-Type": "application/json",
}
response = requests.post(
settings.LOGGER_BASE_URL,
json={"user_id": str(request.user.id)},
headers=headers,
)
count = response.json().get("count", 0)
if not response.json().get("success", False):
return Response(
{
"error": "You have surpassed the monthly limit for AI assistance"
},
status=status.HTTP_429_TOO_MANY_REQUESTS,
)
except Exception as e:
capture_exception(e)
prompt = request.data.get("prompt", False)
task = request.data.get("task", False)
if not task:
return Response(
{"error": "Task is required"}, status=status.HTTP_400_BAD_REQUEST
)
final_text = task + "\n" + prompt
openai.api_key = settings.OPENAI_API_KEY
response = openai.Completion.create(
engine=settings.GPT_ENGINE,
prompt=final_text,
temperature=0.7,
max_tokens=1024,
)
workspace = Workspace.objects.get(slug=slug)
project = Project.objects.get(pk=project_id)
text = response.choices[0].text.strip()
text_html = text.replace("\n", "<br/>")
return Response(
{
"response": text,
"response_html": text_html,
"count": count,
"project_detail": ProjectLiteSerializer(project).data,
"workspace_detail": WorkspaceLiteSerializer(workspace).data,
},
status=status.HTTP_200_OK,
)
except (Workspace.DoesNotExist, Project.DoesNotExist) as e:
return Response(
{"error": "Workspace or Project Does not exist"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
+402 -482
View File
@@ -44,559 +44,479 @@ from plane.utils.html_processor import strip_tags
class ServiceIssueImportSummaryEndpoint(BaseAPIView):
def get(self, request, slug, service):
try:
if service == "github":
owner = request.GET.get("owner", False)
repo = request.GET.get("repo", False)
if service == "github":
owner = request.GET.get("owner", False)
repo = request.GET.get("repo", False)
if not owner or not repo:
return Response(
{"error": "Owner and repo are required"},
status=status.HTTP_400_BAD_REQUEST,
)
workspace_integration = WorkspaceIntegration.objects.get(
integration__provider="github", workspace__slug=slug
if not owner or not repo:
return Response(
{"error": "Owner and repo are required"},
status=status.HTTP_400_BAD_REQUEST,
)
access_tokens_url = workspace_integration.metadata.get(
"access_tokens_url", False
)
workspace_integration = WorkspaceIntegration.objects.get(
integration__provider="github", workspace__slug=slug
)
if not access_tokens_url:
return Response(
{
"error": "There was an error during the installation of the GitHub app. To resolve this issue, we recommend reinstalling the GitHub app."
},
status=status.HTTP_400_BAD_REQUEST,
)
access_tokens_url = workspace_integration.metadata.get(
"access_tokens_url", False
)
issue_count, labels, collaborators = get_github_repo_details(
access_tokens_url, owner, repo
)
if not access_tokens_url:
return Response(
{
"issue_count": issue_count,
"labels": labels,
"collaborators": collaborators,
"error": "There was an error during the installation of the GitHub app. To resolve this issue, we recommend reinstalling the GitHub app."
},
status=status.HTTP_400_BAD_REQUEST,
)
issue_count, labels, collaborators = get_github_repo_details(
access_tokens_url, owner, repo
)
return Response(
{
"issue_count": issue_count,
"labels": labels,
"collaborators": collaborators,
},
status=status.HTTP_200_OK,
)
if service == "jira":
# Check for all the keys
params = {
"project_key": "Project key is required",
"api_token": "API token is required",
"email": "Email is required",
"cloud_hostname": "Cloud hostname is required",
}
for key, error_message in params.items():
if not request.GET.get(key, False):
return Response(
{"error": error_message}, status=status.HTTP_400_BAD_REQUEST
)
project_key = request.GET.get("project_key", "")
api_token = request.GET.get("api_token", "")
email = request.GET.get("email", "")
cloud_hostname = request.GET.get("cloud_hostname", "")
response = jira_project_issue_summary(
email, api_token, project_key, cloud_hostname
)
if "error" in response:
return Response(response, status=status.HTTP_400_BAD_REQUEST)
else:
return Response(
response,
status=status.HTTP_200_OK,
)
if service == "jira":
# Check for all the keys
params = {
"project_key": "Project key is required",
"api_token": "API token is required",
"email": "Email is required",
"cloud_hostname": "Cloud hostname is required",
}
for key, error_message in params.items():
if not request.GET.get(key, False):
return Response(
{"error": error_message}, status=status.HTTP_400_BAD_REQUEST
)
project_key = request.GET.get("project_key", "")
api_token = request.GET.get("api_token", "")
email = request.GET.get("email", "")
cloud_hostname = request.GET.get("cloud_hostname", "")
response = jira_project_issue_summary(
email, api_token, project_key, cloud_hostname
)
if "error" in response:
return Response(response, status=status.HTTP_400_BAD_REQUEST)
else:
return Response(
response,
status=status.HTTP_200_OK,
)
return Response(
{"error": "Service not supported yet"},
status=status.HTTP_400_BAD_REQUEST,
)
except WorkspaceIntegration.DoesNotExist:
return Response(
{"error": "Requested integration was not installed in the workspace"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
return Response(
{"error": "Service not supported yet"},
status=status.HTTP_400_BAD_REQUEST,
)
class ImportServiceEndpoint(BaseAPIView):
def post(self, request, slug, service):
try:
project_id = request.data.get("project_id", False)
project_id = request.data.get("project_id", False)
if not project_id:
if not project_id:
return Response(
{"error": "Project ID is required"},
status=status.HTTP_400_BAD_REQUEST,
)
workspace = Workspace.objects.get(slug=slug)
if service == "github":
data = request.data.get("data", False)
metadata = request.data.get("metadata", False)
config = request.data.get("config", False)
if not data or not metadata or not config:
return Response(
{"error": "Project ID is required"},
{"error": "Data, config and metadata are required"},
status=status.HTTP_400_BAD_REQUEST,
)
workspace = Workspace.objects.get(slug=slug)
if service == "github":
data = request.data.get("data", False)
metadata = request.data.get("metadata", False)
config = request.data.get("config", False)
if not data or not metadata or not config:
return Response(
{"error": "Data, config and metadata are required"},
status=status.HTTP_400_BAD_REQUEST,
)
api_token = APIToken.objects.filter(
user=request.user, workspace=workspace
).first()
if api_token is None:
api_token = APIToken.objects.create(
user=request.user,
label="Importer",
workspace=workspace,
)
importer = Importer.objects.create(
service=service,
project_id=project_id,
status="queued",
initiated_by=request.user,
data=data,
metadata=metadata,
token=api_token,
config=config,
created_by=request.user,
updated_by=request.user,
api_token = APIToken.objects.filter(
user=request.user, workspace=workspace
).first()
if api_token is None:
api_token = APIToken.objects.create(
user=request.user,
label="Importer",
workspace=workspace,
)
service_importer.delay(service, importer.id)
serializer = ImporterSerializer(importer)
return Response(serializer.data, status=status.HTTP_201_CREATED)
importer = Importer.objects.create(
service=service,
project_id=project_id,
status="queued",
initiated_by=request.user,
data=data,
metadata=metadata,
token=api_token,
config=config,
created_by=request.user,
updated_by=request.user,
)
if service == "jira":
data = request.data.get("data", False)
metadata = request.data.get("metadata", False)
config = request.data.get("config", False)
if not data or not metadata:
return Response(
{"error": "Data, config and metadata are required"},
status=status.HTTP_400_BAD_REQUEST,
)
api_token = APIToken.objects.filter(
user=request.user, workspace=workspace
).first()
if api_token is None:
api_token = APIToken.objects.create(
user=request.user,
label="Importer",
workspace=workspace,
)
service_importer.delay(service, importer.id)
serializer = ImporterSerializer(importer)
return Response(serializer.data, status=status.HTTP_201_CREATED)
importer = Importer.objects.create(
service=service,
project_id=project_id,
status="queued",
initiated_by=request.user,
data=data,
metadata=metadata,
token=api_token,
config=config,
created_by=request.user,
updated_by=request.user,
if service == "jira":
data = request.data.get("data", False)
metadata = request.data.get("metadata", False)
config = request.data.get("config", False)
if not data or not metadata:
return Response(
{"error": "Data, config and metadata are required"},
status=status.HTTP_400_BAD_REQUEST,
)
api_token = APIToken.objects.filter(
user=request.user, workspace=workspace
).first()
if api_token is None:
api_token = APIToken.objects.create(
user=request.user,
label="Importer",
workspace=workspace,
)
service_importer.delay(service, importer.id)
serializer = ImporterSerializer(importer)
return Response(serializer.data, status=status.HTTP_201_CREATED)
importer = Importer.objects.create(
service=service,
project_id=project_id,
status="queued",
initiated_by=request.user,
data=data,
metadata=metadata,
token=api_token,
config=config,
created_by=request.user,
updated_by=request.user,
)
return Response(
{"error": "Servivce not supported yet"},
status=status.HTTP_400_BAD_REQUEST,
)
except (
Workspace.DoesNotExist,
WorkspaceIntegration.DoesNotExist,
Project.DoesNotExist,
) as e:
return Response(
{"error": "Workspace Integration or Project does not exist"},
status=status.HTTP_404_NOT_FOUND,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
service_importer.delay(service, importer.id)
serializer = ImporterSerializer(importer)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(
{"error": "Servivce not supported yet"},
status=status.HTTP_400_BAD_REQUEST,
)
def get(self, request, slug):
try:
imports = (
Importer.objects.filter(workspace__slug=slug)
.order_by("-created_at")
.select_related("initiated_by", "project", "workspace")
)
serializer = ImporterSerializer(imports, many=True)
return Response(serializer.data)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
imports = (
Importer.objects.filter(workspace__slug=slug)
.order_by("-created_at")
.select_related("initiated_by", "project", "workspace")
)
serializer = ImporterSerializer(imports, many=True)
return Response(serializer.data)
def delete(self, request, slug, service, pk):
try:
importer = Importer.objects.get(
pk=pk, service=service, workspace__slug=slug
)
importer = Importer.objects.get(
pk=pk, service=service, workspace__slug=slug
)
if importer.imported_data is not None:
# Delete all imported Issues
imported_issues = importer.imported_data.get("issues", [])
Issue.issue_objects.filter(id__in=imported_issues).delete()
if importer.imported_data is not None:
# Delete all imported Issues
imported_issues = importer.imported_data.get("issues", [])
Issue.issue_objects.filter(id__in=imported_issues).delete()
# Delete all imported Labels
imported_labels = importer.imported_data.get("labels", [])
Label.objects.filter(id__in=imported_labels).delete()
# Delete all imported Labels
imported_labels = importer.imported_data.get("labels", [])
Label.objects.filter(id__in=imported_labels).delete()
if importer.service == "jira":
imported_modules = importer.imported_data.get("modules", [])
Module.objects.filter(id__in=imported_modules).delete()
importer.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
if importer.service == "jira":
imported_modules = importer.imported_data.get("modules", [])
Module.objects.filter(id__in=imported_modules).delete()
importer.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
def patch(self, request, slug, service, pk):
try:
importer = Importer.objects.get(
pk=pk, service=service, workspace__slug=slug
)
serializer = ImporterSerializer(importer, 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)
except Importer.DoesNotExist:
return Response(
{"error": "Importer Does not exists"}, status=status.HTTP_404_NOT_FOUND
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
importer = Importer.objects.get(
pk=pk, service=service, workspace__slug=slug
)
serializer = ImporterSerializer(importer, 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)
class UpdateServiceImportStatusEndpoint(BaseAPIView):
def post(self, request, slug, project_id, service, importer_id):
try:
importer = Importer.objects.get(
pk=importer_id,
workspace__slug=slug,
project_id=project_id,
service=service,
)
importer.status = request.data.get("status", "processing")
importer.save()
return Response(status.HTTP_200_OK)
except Importer.DoesNotExist:
return Response(
{"error": "Importer does not exist"}, status=status.HTTP_404_NOT_FOUND
)
importer = Importer.objects.get(
pk=importer_id,
workspace__slug=slug,
project_id=project_id,
service=service,
)
importer.status = request.data.get("status", "processing")
importer.save()
return Response(status.HTTP_200_OK)
class BulkImportIssuesEndpoint(BaseAPIView):
def post(self, request, slug, project_id, service):
try:
# Get the project
project = Project.objects.get(pk=project_id, workspace__slug=slug)
# Get the project
project = Project.objects.get(pk=project_id, workspace__slug=slug)
# Get the default state
# Get the default state
default_state = State.objects.filter(
~Q(name="Triage"), project_id=project_id, default=True
).first()
# if there is no default state assign any random state
if default_state is None:
default_state = State.objects.filter(
~Q(name="Triage"), project_id=project_id, default=True
~Q(name="Triage"), project_id=project_id
).first()
# if there is no default state assign any random state
if default_state is None:
default_state = State.objects.filter(
~Q(name="Triage"), sproject_id=project_id
).first()
# Get the maximum sequence_id
last_id = IssueSequence.objects.filter(project_id=project_id).aggregate(
largest=Max("sequence")
)["largest"]
# Get the maximum sequence_id
last_id = IssueSequence.objects.filter(project_id=project_id).aggregate(
largest=Max("sequence")
)["largest"]
last_id = 1 if last_id is None else last_id + 1
last_id = 1 if last_id is None else last_id + 1
# Get the maximum sort order
largest_sort_order = Issue.objects.filter(
project_id=project_id, state=default_state
).aggregate(largest=Max("sort_order"))["largest"]
# Get the maximum sort order
largest_sort_order = Issue.objects.filter(
project_id=project_id, state=default_state
).aggregate(largest=Max("sort_order"))["largest"]
largest_sort_order = (
65535 if largest_sort_order is None else largest_sort_order + 10000
)
largest_sort_order = (
65535 if largest_sort_order is None else largest_sort_order + 10000
)
# Get the issues_data
issues_data = request.data.get("issues_data", [])
if not len(issues_data):
return Response(
{"error": "Issue data is required"},
status=status.HTTP_400_BAD_REQUEST,
)
# Issues
bulk_issues = []
for issue_data in issues_data:
bulk_issues.append(
Issue(
project_id=project_id,
workspace_id=project.workspace_id,
state_id=issue_data.get("state")
if issue_data.get("state", False)
else default_state.id,
name=issue_data.get("name", "Issue Created through Bulk"),
description_html=issue_data.get("description_html", "<p></p>"),
description_stripped=(
None
if (
issue_data.get("description_html") == ""
or issue_data.get("description_html") is None
)
else strip_tags(issue_data.get("description_html"))
),
sequence_id=last_id,
sort_order=largest_sort_order,
start_date=issue_data.get("start_date", None),
target_date=issue_data.get("target_date", None),
priority=issue_data.get("priority", None),
created_by=request.user,
)
)
largest_sort_order = largest_sort_order + 10000
last_id = last_id + 1
issues = Issue.objects.bulk_create(
bulk_issues,
batch_size=100,
ignore_conflicts=True,
)
# Sequences
_ = IssueSequence.objects.bulk_create(
[
IssueSequence(
issue=issue,
sequence=issue.sequence_id,
project_id=project_id,
workspace_id=project.workspace_id,
)
for issue in issues
],
batch_size=100,
)
# Attach Labels
bulk_issue_labels = []
for issue, issue_data in zip(issues, issues_data):
labels_list = issue_data.get("labels_list", [])
bulk_issue_labels = bulk_issue_labels + [
IssueLabel(
issue=issue,
label_id=label_id,
project_id=project_id,
workspace_id=project.workspace_id,
created_by=request.user,
)
for label_id in labels_list
]
_ = IssueLabel.objects.bulk_create(
bulk_issue_labels, batch_size=100, ignore_conflicts=True
)
# Attach Assignees
bulk_issue_assignees = []
for issue, issue_data in zip(issues, issues_data):
assignees_list = issue_data.get("assignees_list", [])
bulk_issue_assignees = bulk_issue_assignees + [
IssueAssignee(
issue=issue,
assignee_id=assignee_id,
project_id=project_id,
workspace_id=project.workspace_id,
created_by=request.user,
)
for assignee_id in assignees_list
]
_ = IssueAssignee.objects.bulk_create(
bulk_issue_assignees, batch_size=100, ignore_conflicts=True
)
# Track the issue activities
IssueActivity.objects.bulk_create(
[
IssueActivity(
issue=issue,
actor=request.user,
project_id=project_id,
workspace_id=project.workspace_id,
comment=f"{request.user.email} importer the issue from {service}",
verb="created",
created_by=request.user,
)
for issue in issues
],
batch_size=100,
)
# Create Comments
bulk_issue_comments = []
for issue, issue_data in zip(issues, issues_data):
comments_list = issue_data.get("comments_list", [])
bulk_issue_comments = bulk_issue_comments + [
IssueComment(
issue=issue,
comment_html=comment.get("comment_html", "<p></p>"),
actor=request.user,
project_id=project_id,
workspace_id=project.workspace_id,
created_by=request.user,
)
for comment in comments_list
]
_ = IssueComment.objects.bulk_create(bulk_issue_comments, batch_size=100)
# Attach Links
_ = IssueLink.objects.bulk_create(
[
IssueLink(
issue=issue,
url=issue_data.get("link", {}).get("url", "https://github.com"),
title=issue_data.get("link", {}).get("title", "Original Issue"),
project_id=project_id,
workspace_id=project.workspace_id,
created_by=request.user,
)
for issue, issue_data in zip(issues, issues_data)
]
)
# Get the issues_data
issues_data = request.data.get("issues_data", [])
if not len(issues_data):
return Response(
{"issues": IssueFlatSerializer(issues, many=True).data},
status=status.HTTP_201_CREATED,
)
except Project.DoesNotExist:
return Response(
{"error": "Project Does not exist"}, status=status.HTTP_404_NOT_FOUND
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
{"error": "Issue data is required"},
status=status.HTTP_400_BAD_REQUEST,
)
# Issues
bulk_issues = []
for issue_data in issues_data:
bulk_issues.append(
Issue(
project_id=project_id,
workspace_id=project.workspace_id,
state_id=issue_data.get("state")
if issue_data.get("state", False)
else default_state.id,
name=issue_data.get("name", "Issue Created through Bulk"),
description_html=issue_data.get("description_html", "<p></p>"),
description_stripped=(
None
if (
issue_data.get("description_html") == ""
or issue_data.get("description_html") is None
)
else strip_tags(issue_data.get("description_html"))
),
sequence_id=last_id,
sort_order=largest_sort_order,
start_date=issue_data.get("start_date", None),
target_date=issue_data.get("target_date", None),
priority=issue_data.get("priority", "none"),
created_by=request.user,
)
)
largest_sort_order = largest_sort_order + 10000
last_id = last_id + 1
issues = Issue.objects.bulk_create(
bulk_issues,
batch_size=100,
ignore_conflicts=True,
)
# Sequences
_ = IssueSequence.objects.bulk_create(
[
IssueSequence(
issue=issue,
sequence=issue.sequence_id,
project_id=project_id,
workspace_id=project.workspace_id,
)
for issue in issues
],
batch_size=100,
)
# Attach Labels
bulk_issue_labels = []
for issue, issue_data in zip(issues, issues_data):
labels_list = issue_data.get("labels_list", [])
bulk_issue_labels = bulk_issue_labels + [
IssueLabel(
issue=issue,
label_id=label_id,
project_id=project_id,
workspace_id=project.workspace_id,
created_by=request.user,
)
for label_id in labels_list
]
_ = IssueLabel.objects.bulk_create(
bulk_issue_labels, batch_size=100, ignore_conflicts=True
)
# Attach Assignees
bulk_issue_assignees = []
for issue, issue_data in zip(issues, issues_data):
assignees_list = issue_data.get("assignees_list", [])
bulk_issue_assignees = bulk_issue_assignees + [
IssueAssignee(
issue=issue,
assignee_id=assignee_id,
project_id=project_id,
workspace_id=project.workspace_id,
created_by=request.user,
)
for assignee_id in assignees_list
]
_ = IssueAssignee.objects.bulk_create(
bulk_issue_assignees, batch_size=100, ignore_conflicts=True
)
# Track the issue activities
IssueActivity.objects.bulk_create(
[
IssueActivity(
issue=issue,
actor=request.user,
project_id=project_id,
workspace_id=project.workspace_id,
comment=f"imported the issue from {service}",
verb="created",
created_by=request.user,
)
for issue in issues
],
batch_size=100,
)
# Create Comments
bulk_issue_comments = []
for issue, issue_data in zip(issues, issues_data):
comments_list = issue_data.get("comments_list", [])
bulk_issue_comments = bulk_issue_comments + [
IssueComment(
issue=issue,
comment_html=comment.get("comment_html", "<p></p>"),
actor=request.user,
project_id=project_id,
workspace_id=project.workspace_id,
created_by=request.user,
)
for comment in comments_list
]
_ = IssueComment.objects.bulk_create(bulk_issue_comments, batch_size=100)
# Attach Links
_ = IssueLink.objects.bulk_create(
[
IssueLink(
issue=issue,
url=issue_data.get("link", {}).get("url", "https://github.com"),
title=issue_data.get("link", {}).get("title", "Original Issue"),
project_id=project_id,
workspace_id=project.workspace_id,
created_by=request.user,
)
for issue, issue_data in zip(issues, issues_data)
]
)
return Response(
{"issues": IssueFlatSerializer(issues, many=True).data},
status=status.HTTP_201_CREATED,
)
class BulkImportModulesEndpoint(BaseAPIView):
def post(self, request, slug, project_id, service):
try:
modules_data = request.data.get("modules_data", [])
project = Project.objects.get(pk=project_id, workspace__slug=slug)
modules_data = request.data.get("modules_data", [])
project = Project.objects.get(pk=project_id, workspace__slug=slug)
modules = Module.objects.bulk_create(
modules = Module.objects.bulk_create(
[
Module(
name=module.get("name", uuid.uuid4().hex),
description=module.get("description", ""),
start_date=module.get("start_date", None),
target_date=module.get("target_date", None),
project_id=project_id,
workspace_id=project.workspace_id,
created_by=request.user,
)
for module in modules_data
],
batch_size=100,
ignore_conflicts=True,
)
modules = Module.objects.filter(id__in=[module.id for module in modules])
if len(modules) == len(modules_data):
_ = ModuleLink.objects.bulk_create(
[
Module(
name=module.get("name", uuid.uuid4().hex),
description=module.get("description", ""),
start_date=module.get("start_date", None),
target_date=module.get("target_date", None),
ModuleLink(
module=module,
url=module_data.get("link", {}).get(
"url", "https://plane.so"
),
title=module_data.get("link", {}).get(
"title", "Original Issue"
),
project_id=project_id,
workspace_id=project.workspace_id,
created_by=request.user,
)
for module in modules_data
for module, module_data in zip(modules, modules_data)
],
batch_size=100,
ignore_conflicts=True,
)
modules = Module.objects.filter(id__in=[module.id for module in modules])
bulk_module_issues = []
for module, module_data in zip(modules, modules_data):
module_issues_list = module_data.get("module_issues_list", [])
bulk_module_issues = bulk_module_issues + [
ModuleIssue(
issue_id=issue,
module=module,
project_id=project_id,
workspace_id=project.workspace_id,
created_by=request.user,
)
for issue in module_issues_list
]
if len(modules) == len(modules_data):
_ = ModuleLink.objects.bulk_create(
[
ModuleLink(
module=module,
url=module_data.get("link", {}).get(
"url", "https://plane.so"
),
title=module_data.get("link", {}).get(
"title", "Original Issue"
),
project_id=project_id,
workspace_id=project.workspace_id,
created_by=request.user,
)
for module, module_data in zip(modules, modules_data)
],
batch_size=100,
ignore_conflicts=True,
)
bulk_module_issues = []
for module, module_data in zip(modules, modules_data):
module_issues_list = module_data.get("module_issues_list", [])
bulk_module_issues = bulk_module_issues + [
ModuleIssue(
issue_id=issue,
module=module,
project_id=project_id,
workspace_id=project.workspace_id,
created_by=request.user,
)
for issue in module_issues_list
]
_ = ModuleIssue.objects.bulk_create(
bulk_module_issues, batch_size=100, ignore_conflicts=True
)
serializer = ModuleSerializer(modules, many=True)
return Response(
{"modules": serializer.data}, status=status.HTTP_201_CREATED
)
else:
return Response(
{"message": "Modules created but issues could not be imported"},
status=status.HTTP_200_OK,
)
except Project.DoesNotExist:
return Response(
{"error": "Project does not exist"}, status=status.HTTP_404_NOT_FOUND
_ = ModuleIssue.objects.bulk_create(
bulk_module_issues, batch_size=100, ignore_conflicts=True
)
except Exception as e:
capture_exception(e)
serializer = ModuleSerializer(modules, many=True)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
{"modules": serializer.data}, status=status.HTTP_201_CREATED
)
else:
return Response(
{"message": "Modules created but issues could not be imported"},
status=status.HTTP_200_OK,
)
+433 -253
View File
@@ -15,7 +15,6 @@ from sentry_sdk import capture_exception
from .base import BaseViewSet
from plane.api.permissions import ProjectBasePermission, ProjectLitePermission
from plane.db.models import (
Project,
Inbox,
InboxIssue,
Issue,
@@ -23,6 +22,7 @@ from plane.db.models import (
IssueLink,
IssueAttachment,
ProjectMember,
ProjectDeployBoard,
)
from plane.api.serializers import (
IssueSerializer,
@@ -64,24 +64,17 @@ class InboxViewSet(BaseViewSet):
serializer.save(project_id=self.kwargs.get("project_id"))
def destroy(self, request, slug, project_id, pk):
try:
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)
except Exception as e:
capture_exception(e)
inbox = Inbox.objects.get(
workspace__slug=slug, project_id=project_id, pk=pk
)
# Handle default inbox delete
if inbox.is_default:
return Response(
{"error": "Something went wronf please try again later"},
{"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):
@@ -110,271 +103,458 @@ class InboxIssueViewSet(BaseViewSet):
)
def list(self, request, slug, project_id, inbox_id):
try:
filters = issue_filters(request.query_params, "GET")
issues = (
Issue.objects.filter(
issue_inbox__inbox_id=inbox_id,
workspace__slug=slug,
project_id=project_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,
)
.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")
)
.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"
),
)
.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,
)
)
issues_data = IssueStateInboxSerializer(issues, many=True).data
return Response(
issues_data,
status=status.HTTP_200_OK,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
def create(self, request, slug, project_id, inbox_id):
try:
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,
)
# 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)
except Exception as e:
capture_exception(e)
if not request.data.get("issue", {}).get("name", False):
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
{"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):
try:
inbox_issue = InboxIssue.objects.get(
pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id
inbox_issue = InboxIssue.objects.get(
pk=pk, 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)
# 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)
# 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
)
# Get the project member
project_member = ProjectMember.objects.get(workspace__slug=slug, project_id=project_id, member=request.user)
# 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)
# Only allow guests and viewers to edit name and description
if project_member.role <= 10:
# 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)
}
# Get issue data
issue_data = request.data.pop("issue", False)
issue_serializer = IssueCreateSerializer(
issue, data=issue_data, partial=True
)
if bool(issue_data):
issue = Issue.objects.get(
pk=inbox_issue.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
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()
else:
return Response(
issue_serializer.errors, status=status.HTTP_400_BAD_REQUEST
)
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,
),
)
issue_serializer.save()
else:
return Response(
issue_serializer.errors, status=status.HTTP_400_BAD_REQUEST
# Only project admins and members can edit inbox issue attributes
if project_member.role > 10:
serializer = InboxIssueSerializer(
inbox_issue, data=request.data, partial=True
)
if serializer.is_valid():
serializer.save()
# 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,
workspace__slug=slug,
project_id=project_id,
)
state = State.objects.filter(
group="cancelled", workspace__slug=slug, project_id=project_id
).first()
if state is not None:
issue.state = state
issue.save()
# Update the issue state if it is accepted
if serializer.data["status"] in [1]:
issue = Issue.objects.get(
pk=inbox_issue.issue_id,
workspace__slug=slug,
project_id=project_id,
)
# Only project admins and members can edit inbox issue attributes
if project_member.role > 10:
serializer = InboxIssueSerializer(
inbox_issue, data=request.data, partial=True
)
if serializer.is_valid():
serializer.save()
# 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,
workspace__slug=slug,
project_id=project_id,
)
# Update the issue state only if it is in triage state
if issue.state.name == "Triage":
# Move to default state
state = State.objects.filter(
group="cancelled", workspace__slug=slug, project_id=project_id
workspace__slug=slug, project_id=project_id, default=True
).first()
if state is not None:
issue.state = state
issue.save()
# Update the issue state if it is accepted
if serializer.data["status"] in [1]:
issue = Issue.objects.get(
pk=inbox_issue.issue_id,
workspace__slug=slug,
project_id=project_id,
)
# Update the issue state only if it is in triage state
if issue.state.name == "Triage":
# Move to default state
state = State.objects.filter(
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)
else:
return Response(InboxIssueSerializer(inbox_issue).data, status=status.HTTP_200_OK)
except InboxIssue.DoesNotExist:
return Response(
{"error": "Inbox Issue does not exist"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
return Response(InboxIssueSerializer(inbox_issue).data, status=status.HTTP_200_OK)
def retrieve(self, request, slug, project_id, inbox_id, pk):
try:
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)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
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):
try:
inbox_issue = InboxIssue.objects.get(
pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id
inbox_issue = InboxIssue.objects.get(
pk=pk, 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)
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")
)
# Get the project member
project_member = ProjectMember.objects.get(workspace__slug=slug, project_id=project_id, member=request.user)
else:
return InboxIssue.objects.none()
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)
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)
inbox_issue.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except InboxIssue.DoesNotExist:
return Response({"error": "Inbox Issue does not exists"}, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
capture_exception(e)
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": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
{"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)
inbox_issue.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
+95 -160
View File
@@ -2,7 +2,6 @@
import uuid
# Django imports
from django.db import IntegrityError
from django.contrib.auth.hashers import make_password
# Third party imports
@@ -33,66 +32,40 @@ class IntegrationViewSet(BaseViewSet):
model = Integration
def create(self, request):
try:
serializer = IntegrationSerializer(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)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
serializer = IntegrationSerializer(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 partial_update(self, request, pk):
try:
integration = Integration.objects.get(pk=pk)
if integration.verified:
return Response(
{"error": "Verified integrations cannot be updated"},
status=status.HTTP_400_BAD_REQUEST,
)
serializer = IntegrationSerializer(
integration, 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)
except Integration.DoesNotExist:
integration = Integration.objects.get(pk=pk)
if integration.verified:
return Response(
{"error": "Integration Does not exist"},
status=status.HTTP_404_NOT_FOUND,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
{"error": "Verified integrations cannot be updated"},
status=status.HTTP_400_BAD_REQUEST,
)
def destroy(self, request, pk):
try:
integration = Integration.objects.get(pk=pk)
if integration.verified:
return Response(
{"error": "Verified integrations cannot be updated"},
status=status.HTTP_400_BAD_REQUEST,
)
serializer = IntegrationSerializer(
integration, data=request.data, partial=True
)
integration.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except Integration.DoesNotExist:
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 destroy(self, request, pk):
integration = Integration.objects.get(pk=pk)
if integration.verified:
return Response(
{"error": "Integration Does not exist"},
status=status.HTTP_404_NOT_FOUND,
{"error": "Verified integrations cannot be updated"},
status=status.HTTP_400_BAD_REQUEST,
)
integration.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
class WorkspaceIntegrationViewSet(BaseViewSet):
serializer_class = WorkspaceIntegrationSerializer
@@ -111,119 +84,81 @@ class WorkspaceIntegrationViewSet(BaseViewSet):
)
def create(self, request, slug, provider):
try:
workspace = Workspace.objects.get(slug=slug)
integration = Integration.objects.get(provider=provider)
config = {}
if provider == "github":
installation_id = request.data.get("installation_id", None)
if not installation_id:
return Response(
{"error": "Installation ID is required"},
status=status.HTTP_400_BAD_REQUEST,
)
metadata = get_github_metadata(installation_id)
config = {"installation_id": installation_id}
if provider == "slack":
metadata = request.data.get("metadata", {})
access_token = metadata.get("access_token", False)
team_id = metadata.get("team", {}).get("id", False)
if not metadata or not access_token or not team_id:
return Response(
{"error": "Access token and team id is required"},
status=status.HTTP_400_BAD_REQUEST,
)
config = {"team_id": team_id, "access_token": access_token}
# Create a bot user
bot_user = User.objects.create(
email=f"{uuid.uuid4().hex}@plane.so",
username=uuid.uuid4().hex,
password=make_password(uuid.uuid4().hex),
is_password_autoset=True,
is_bot=True,
first_name=integration.title,
avatar=integration.avatar_url
if integration.avatar_url is not None
else "",
)
# Create an API Token for the bot user
api_token = APIToken.objects.create(
user=bot_user,
user_type=1, # bot user
workspace=workspace,
)
workspace_integration = WorkspaceIntegration.objects.create(
workspace=workspace,
integration=integration,
actor=bot_user,
api_token=api_token,
metadata=metadata,
config=config,
)
# Add bot user as a member of workspace
_ = WorkspaceMember.objects.create(
workspace=workspace_integration.workspace,
member=bot_user,
role=20,
)
return Response(
WorkspaceIntegrationSerializer(workspace_integration).data,
status=status.HTTP_201_CREATED,
)
except IntegrityError as e:
if "already exists" in str(e):
workspace = Workspace.objects.get(slug=slug)
integration = Integration.objects.get(provider=provider)
config = {}
if provider == "github":
installation_id = request.data.get("installation_id", None)
if not installation_id:
return Response(
{"error": "Integration is already active in the workspace"},
status=status.HTTP_410_GONE,
)
else:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
{"error": "Installation ID is required"},
status=status.HTTP_400_BAD_REQUEST,
)
except (Workspace.DoesNotExist, Integration.DoesNotExist) as e:
capture_exception(e)
return Response(
{"error": "Workspace or Integration not found"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
metadata = get_github_metadata(installation_id)
config = {"installation_id": installation_id}
if provider == "slack":
metadata = request.data.get("metadata", {})
access_token = metadata.get("access_token", False)
team_id = metadata.get("team", {}).get("id", False)
if not metadata or not access_token or not team_id:
return Response(
{"error": "Access token and team id is required"},
status=status.HTTP_400_BAD_REQUEST,
)
config = {"team_id": team_id, "access_token": access_token}
# Create a bot user
bot_user = User.objects.create(
email=f"{uuid.uuid4().hex}@plane.so",
username=uuid.uuid4().hex,
password=make_password(uuid.uuid4().hex),
is_password_autoset=True,
is_bot=True,
first_name=integration.title,
avatar=integration.avatar_url
if integration.avatar_url is not None
else "",
)
# Create an API Token for the bot user
api_token = APIToken.objects.create(
user=bot_user,
user_type=1, # bot user
workspace=workspace,
)
workspace_integration = WorkspaceIntegration.objects.create(
workspace=workspace,
integration=integration,
actor=bot_user,
api_token=api_token,
metadata=metadata,
config=config,
)
# Add bot user as a member of workspace
_ = WorkspaceMember.objects.create(
workspace=workspace_integration.workspace,
member=bot_user,
role=20,
)
return Response(
WorkspaceIntegrationSerializer(workspace_integration).data,
status=status.HTTP_201_CREATED,
)
def destroy(self, request, slug, pk):
try:
workspace_integration = WorkspaceIntegration.objects.get(
pk=pk, workspace__slug=slug
)
workspace_integration = WorkspaceIntegration.objects.get(
pk=pk, workspace__slug=slug
)
if workspace_integration.integration.provider == "github":
installation_id = workspace_integration.config.get(
"installation_id", False
)
if installation_id:
delete_github_installation(installation_id=installation_id)
workspace_integration.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except WorkspaceIntegration.DoesNotExist:
return Response(
{"error": "Workspace Integration Does not exists"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
if workspace_integration.integration.provider == "github":
installation_id = workspace_integration.config.get(
"installation_id", False
)
if installation_id:
delete_github_installation(installation_id=installation_id)
workspace_integration.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
+101 -132
View File
@@ -30,31 +30,25 @@ class GithubRepositoriesEndpoint(BaseAPIView):
]
def get(self, request, slug, workspace_integration_id):
try:
page = request.GET.get("page", 1)
workspace_integration = WorkspaceIntegration.objects.get(
workspace__slug=slug, pk=workspace_integration_id
)
page = request.GET.get("page", 1)
workspace_integration = WorkspaceIntegration.objects.get(
workspace__slug=slug, pk=workspace_integration_id
)
if workspace_integration.integration.provider != "github":
return Response(
{"error": "Not a github integration"},
status=status.HTTP_400_BAD_REQUEST,
)
access_tokens_url = workspace_integration.metadata["access_tokens_url"]
repositories_url = (
workspace_integration.metadata["repositories_url"]
+ f"?per_page=100&page={page}"
)
repositories = get_github_repos(access_tokens_url, repositories_url)
return Response(repositories, status=status.HTTP_200_OK)
except WorkspaceIntegration.DoesNotExist:
if workspace_integration.integration.provider != "github":
return Response(
{"error": "Workspace Integration Does not exists"},
{"error": "Not a github integration"},
status=status.HTTP_400_BAD_REQUEST,
)
access_tokens_url = workspace_integration.metadata["access_tokens_url"]
repositories_url = (
workspace_integration.metadata["repositories_url"]
+ f"?per_page=100&page={page}"
)
repositories = get_github_repos(access_tokens_url, repositories_url)
return Response(repositories, status=status.HTTP_200_OK)
class GithubRepositorySyncViewSet(BaseViewSet):
permission_classes = [
@@ -76,89 +70,76 @@ class GithubRepositorySyncViewSet(BaseViewSet):
)
def create(self, request, slug, project_id, workspace_integration_id):
try:
name = request.data.get("name", False)
url = request.data.get("url", False)
config = request.data.get("config", {})
repository_id = request.data.get("repository_id", False)
owner = request.data.get("owner", False)
name = request.data.get("name", False)
url = request.data.get("url", False)
config = request.data.get("config", {})
repository_id = request.data.get("repository_id", False)
owner = request.data.get("owner", False)
if not name or not url or not repository_id or not owner:
return Response(
{"error": "Name, url, repository_id and owner are required"},
status=status.HTTP_400_BAD_REQUEST,
)
# Get the workspace integration
workspace_integration = WorkspaceIntegration.objects.get(
pk=workspace_integration_id
)
# Delete the old repository object
GithubRepositorySync.objects.filter(
project_id=project_id, workspace__slug=slug
).delete()
GithubRepository.objects.filter(
project_id=project_id, workspace__slug=slug
).delete()
# Create repository
repo = GithubRepository.objects.create(
name=name,
url=url,
config=config,
repository_id=repository_id,
owner=owner,
project_id=project_id,
)
# Create a Label for github
label = Label.objects.filter(
name="GitHub",
project_id=project_id,
).first()
if label is None:
label = Label.objects.create(
name="GitHub",
project_id=project_id,
description="Label to sync Plane issues with GitHub issues",
color="#003773",
)
# Create repo sync
repo_sync = GithubRepositorySync.objects.create(
repository=repo,
workspace_integration=workspace_integration,
actor=workspace_integration.actor,
credentials=request.data.get("credentials", {}),
project_id=project_id,
label=label,
)
# Add bot as a member in the project
_ = ProjectMember.objects.get_or_create(
member=workspace_integration.actor, role=20, project_id=project_id
)
# Return Response
if not name or not url or not repository_id or not owner:
return Response(
GithubRepositorySyncSerializer(repo_sync).data,
status=status.HTTP_201_CREATED,
)
except WorkspaceIntegration.DoesNotExist:
return Response(
{"error": "Workspace Integration does not exist"},
status=status.HTTP_404_NOT_FOUND,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
{"error": "Name, url, repository_id and owner are required"},
status=status.HTTP_400_BAD_REQUEST,
)
# Get the workspace integration
workspace_integration = WorkspaceIntegration.objects.get(
pk=workspace_integration_id
)
# Delete the old repository object
GithubRepositorySync.objects.filter(
project_id=project_id, workspace__slug=slug
).delete()
GithubRepository.objects.filter(
project_id=project_id, workspace__slug=slug
).delete()
# Create repository
repo = GithubRepository.objects.create(
name=name,
url=url,
config=config,
repository_id=repository_id,
owner=owner,
project_id=project_id,
)
# Create a Label for github
label = Label.objects.filter(
name="GitHub",
project_id=project_id,
).first()
if label is None:
label = Label.objects.create(
name="GitHub",
project_id=project_id,
description="Label to sync Plane issues with GitHub issues",
color="#003773",
)
# Create repo sync
repo_sync = GithubRepositorySync.objects.create(
repository=repo,
workspace_integration=workspace_integration,
actor=workspace_integration.actor,
credentials=request.data.get("credentials", {}),
project_id=project_id,
label=label,
)
# Add bot as a member in the project
_ = ProjectMember.objects.get_or_create(
member=workspace_integration.actor, role=20, project_id=project_id
)
# Return Response
return Response(
GithubRepositorySyncSerializer(repo_sync).data,
status=status.HTTP_201_CREATED,
)
class GithubIssueSyncViewSet(BaseViewSet):
permission_classes = [
@@ -177,42 +158,30 @@ class GithubIssueSyncViewSet(BaseViewSet):
class BulkCreateGithubIssueSyncEndpoint(BaseAPIView):
def post(self, request, slug, project_id, repo_sync_id):
try:
project = Project.objects.get(pk=project_id, workspace__slug=slug)
project = Project.objects.get(pk=project_id, workspace__slug=slug)
github_issue_syncs = request.data.get("github_issue_syncs", [])
github_issue_syncs = GithubIssueSync.objects.bulk_create(
[
GithubIssueSync(
issue_id=github_issue_sync.get("issue"),
repo_issue_id=github_issue_sync.get("repo_issue_id"),
issue_url=github_issue_sync.get("issue_url"),
github_issue_id=github_issue_sync.get("github_issue_id"),
repository_sync_id=repo_sync_id,
project_id=project_id,
workspace_id=project.workspace_id,
created_by=request.user,
updated_by=request.user,
)
for github_issue_sync in github_issue_syncs
],
batch_size=100,
ignore_conflicts=True,
)
github_issue_syncs = request.data.get("github_issue_syncs", [])
github_issue_syncs = GithubIssueSync.objects.bulk_create(
[
GithubIssueSync(
issue_id=github_issue_sync.get("issue"),
repo_issue_id=github_issue_sync.get("repo_issue_id"),
issue_url=github_issue_sync.get("issue_url"),
github_issue_id=github_issue_sync.get("github_issue_id"),
repository_sync_id=repo_sync_id,
project_id=project_id,
workspace_id=project.workspace_id,
created_by=request.user,
updated_by=request.user,
)
for github_issue_sync in github_issue_syncs
],
batch_size=100,
ignore_conflicts=True,
)
serializer = GithubIssueSyncSerializer(github_issue_syncs, many=True)
return Response(serializer.data, status=status.HTTP_201_CREATED)
except Project.DoesNotExist:
return Response(
{"error": "Project does not exist"},
status=status.HTTP_404_NOT_FOUND,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
serializer = GithubIssueSyncSerializer(github_issue_syncs, many=True)
return Response(serializer.data, status=status.HTTP_201_CREATED)
class GithubCommentSyncViewSet(BaseViewSet):
+28 -31
View File
@@ -20,40 +20,37 @@ class SlackProjectSyncViewSet(BaseViewSet):
serializer_class = SlackProjectSyncSerializer
model = SlackProjectSync
def get_queryset(self):
return (
super()
.get_queryset()
.filter(
workspace__slug=self.kwargs.get("slug"),
project_id=self.kwargs.get("project_id"),
)
.filter(project__project_projectmember__member=self.request.user)
)
def create(self, request, slug, project_id, workspace_integration_id):
try:
serializer = SlackProjectSyncSerializer(data=request.data)
serializer = SlackProjectSyncSerializer(data=request.data)
workspace_integration = WorkspaceIntegration.objects.get(
workspace__slug=slug, pk=workspace_integration_id
)
if serializer.is_valid():
serializer.save(
project_id=project_id,
workspace_integration_id=workspace_integration_id,
)
workspace_integration = WorkspaceIntegration.objects.get(
workspace__slug=slug, pk=workspace_integration_id
pk=workspace_integration_id, workspace__slug=slug
)
if serializer.is_valid():
serializer.save(
project_id=project_id,
workspace_integration_id=workspace_integration_id,
)
workspace_integration = WorkspaceIntegration.objects.get(
pk=workspace_integration_id, workspace__slug=slug
)
_ = ProjectMember.objects.get_or_create(
member=workspace_integration.actor, role=20, project_id=project_id
)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except IntegrityError:
return Response({"error": "Slack is already enabled for the project"}, status=status.HTTP_400_BAD_REQUEST)
except WorkspaceIntegration.DoesNotExist:
return Response(
{"error": "Workspace Integration does not exist"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
print(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
_ = ProjectMember.objects.get_or_create(
member=workspace_integration.actor, role=20, project_id=project_id
)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
File diff suppressed because it is too large Load Diff
+322 -323
View File
@@ -2,6 +2,7 @@
import json
# Django Imports
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
@@ -39,6 +40,7 @@ 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):
model = Module
permission_classes = [
@@ -53,6 +55,8 @@ class ModuleViewSet(BaseViewSet):
)
def get_queryset(self):
order_by = self.request.GET.get("order_by", "sort_order")
subquery = ModuleFavorite.objects.filter(
user=self.request.user,
module_id=OuterRef("pk"),
@@ -75,171 +79,209 @@ class ModuleViewSet(BaseViewSet):
queryset=ModuleLink.objects.select_related("module", "created_by"),
)
)
.annotate(total_issues=Count("issue_module"))
.annotate(
total_issues=Count(
"issue_module",
filter=Q(
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
),
),
)
.annotate(
completed_issues=Count(
"issue_module__issue__state__group",
filter=Q(issue_module__issue__state__group="completed"),
filter=Q(
issue_module__issue__state__group="completed",
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
),
)
)
.annotate(
cancelled_issues=Count(
"issue_module__issue__state__group",
filter=Q(issue_module__issue__state__group="cancelled"),
filter=Q(
issue_module__issue__state__group="cancelled",
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
),
)
)
.annotate(
started_issues=Count(
"issue_module__issue__state__group",
filter=Q(issue_module__issue__state__group="started"),
filter=Q(
issue_module__issue__state__group="started",
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
),
)
)
.annotate(
unstarted_issues=Count(
"issue_module__issue__state__group",
filter=Q(issue_module__issue__state__group="unstarted"),
filter=Q(
issue_module__issue__state__group="unstarted",
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
),
)
)
.annotate(
backlog_issues=Count(
"issue_module__issue__state__group",
filter=Q(issue_module__issue__state__group="backlog"),
filter=Q(
issue_module__issue__state__group="backlog",
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
),
)
)
.order_by("-is_favorite", "name")
.order_by(order_by, "name")
)
def perform_destroy(self, instance):
module_issues = list(
ModuleIssue.objects.filter(module_id=self.kwargs.get("pk")).values_list(
"issue", flat=True
)
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}
)
if serializer.is_valid():
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)
assignee_distribution = (
Issue.objects.filter(
issue_module__module_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(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")
)
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")
)
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)
)
module.delete()
issue_activity.delay(
type="module.activity.deleted",
requested_data=json.dumps(
{
"module_id": str(self.kwargs.get("pk")),
"module_id": str(pk),
"issues": [str(issue_id) for issue_id in module_issues],
}
),
actor_id=str(self.request.user.id),
issue_id=str(self.kwargs.get("pk", None)),
project_id=str(self.kwargs.get("project_id", None)),
actor_id=str(request.user.id),
issue_id=str(pk),
project_id=str(project_id),
current_instance=None,
epoch=int(timezone.now().timestamp()),
)
return super().perform_destroy(instance)
def create(self, request, slug, project_id):
try:
project = Project.objects.get(workspace__slug=slug, pk=project_id)
serializer = ModuleWriteSerializer(
data=request.data, context={"project": project}
)
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)
except Project.DoesNotExist:
return Response(
{"error": "Project was not found"}, status=status.HTTP_404_NOT_FOUND
)
except IntegrityError as e:
if "already exists" in str(e):
return Response(
{"name": "The module name is already taken"},
status=status.HTTP_410_GONE,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
def retrieve(self, request, slug, project_id, pk):
try:
queryset = self.get_queryset().get(pk=pk)
assignee_distribution = (
Issue.objects.filter(
issue_module__module_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"))
.values("first_name", "last_name", "assignee_id", "avatar")
.annotate(total_issues=Count("assignee_id"))
.annotate(
completed_issues=Count(
"assignee_id",
filter=Q(completed_at__isnull=False),
)
)
.annotate(
pending_issues=Count(
"assignee_id",
filter=Q(completed_at__isnull=True),
)
)
.order_by("first_name", "last_name")
)
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"))
.annotate(
completed_issues=Count(
"label_id",
filter=Q(completed_at__isnull=False),
)
)
.annotate(
pending_issues=Count(
"label_id",
filter=Q(completed_at__isnull=True),
)
)
.order_by("label_name")
)
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,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
return Response(status=status.HTTP_204_NO_CONTENT)
class ModuleIssueViewSet(BaseViewSet):
@@ -261,22 +303,6 @@ class ModuleIssueViewSet(BaseViewSet):
module_id=self.kwargs.get("module_id"),
)
def perform_destroy(self, instance):
issue_activity.delay(
type="module.activity.deleted",
requested_data=json.dumps(
{
"module_id": str(self.kwargs.get("module_id")),
"issues": [str(instance.issue_id)],
}
),
actor_id=str(self.request.user.id),
issue_id=str(self.kwargs.get("pk", None)),
project_id=str(self.kwargs.get("project_id", None)),
current_instance=None,
)
return super().perform_destroy(instance)
def get_queryset(self):
return self.filter_queryset(
super()
@@ -302,154 +328,162 @@ class ModuleIssueViewSet(BaseViewSet):
@method_decorator(gzip_page)
def list(self, request, slug, project_id, module_id):
try:
order_by = request.GET.get("order_by", "created_at")
group_by = request.GET.get("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"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(bridge_id=F("issue_module__id"))
.filter(project_id=project_id)
.filter(workspace__slug=slug)
.select_related("project")
.select_related("workspace")
.select_related("state")
.select_related("parent")
.prefetch_related("assignees")
.prefetch_related("labels")
.order_by(order_by)
.filter(**filters)
.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")
)
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"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
issues_data = IssueStateSerializer(issues, many=True).data
if group_by:
return Response(
group_results(issues_data, group_by),
status=status.HTTP_200_OK,
)
return Response(
issues_data,
status=status.HTTP_200_OK,
.annotate(bridge_id=F("issue_module__id"))
.filter(project_id=project_id)
.filter(workspace__slug=slug)
.select_related("project")
.select_related("workspace")
.select_related("state")
.select_related("parent")
.prefetch_related("assignees")
.prefetch_related("labels")
.order_by(order_by)
.filter(**filters)
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
except Exception as e:
capture_exception(e)
.annotate(
attachment_count=IssueAttachment.objects.filter(issue=OuterRef("id"))
.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": "Something went wrong please try again later"},
{"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
)
def create(self, request, slug, project_id, module_id):
try:
issues = request.data.get("issues", [])
if not len(issues):
return Response(
{"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 = request.data.get("issues", [])
if not len(issues):
return Response(
{"error": "Issues are required"}, status=status.HTTP_400_BAD_REQUEST
)
module = Module.objects.get(
workspace__slug=slug, project_id=project_id, pk=module_id
)
module_issues = list(ModuleIssue.objects.filter(issue_id__in=issues))
module_issues = list(ModuleIssue.objects.filter(issue_id__in=issues))
update_module_issue_activity = []
records_to_update = []
record_to_create = []
update_module_issue_activity = []
records_to_update = []
record_to_create = []
for issue in issues:
module_issue = [
module_issue
for module_issue in module_issues
if str(module_issue.issue_id) in issues
]
for issue in issues:
module_issue = [
module_issue
for module_issue in module_issues
if str(module_issue.issue_id) in issues
]
if len(module_issue):
if module_issue[0].module_id != module_id:
update_module_issue_activity.append(
{
"old_module_id": str(module_issue[0].module_id),
"new_module_id": str(module_id),
"issue_id": str(module_issue[0].issue_id),
}
)
module_issue[0].module_id = module_id
records_to_update.append(module_issue[0])
else:
record_to_create.append(
ModuleIssue(
module=module,
issue_id=issue,
project_id=project_id,
workspace=module.workspace,
created_by=request.user,
updated_by=request.user,
)
if len(module_issue):
if module_issue[0].module_id != module_id:
update_module_issue_activity.append(
{
"old_module_id": str(module_issue[0].module_id),
"new_module_id": str(module_id),
"issue_id": str(module_issue[0].issue_id),
}
)
module_issue[0].module_id = module_id
records_to_update.append(module_issue[0])
else:
record_to_create.append(
ModuleIssue(
module=module,
issue_id=issue,
project_id=project_id,
workspace=module.workspace,
created_by=request.user,
updated_by=request.user,
)
)
ModuleIssue.objects.bulk_create(
record_to_create,
batch_size=10,
ignore_conflicts=True,
)
ModuleIssue.objects.bulk_create(
record_to_create,
batch_size=10,
ignore_conflicts=True,
)
ModuleIssue.objects.bulk_update(
records_to_update,
["module"],
batch_size=10,
)
ModuleIssue.objects.bulk_update(
records_to_update,
["module"],
batch_size=10,
)
# Capture Issue Activity
issue_activity.delay(
type="module.activity.created",
requested_data=json.dumps({"modules_list": issues}),
actor_id=str(self.request.user.id),
issue_id=str(self.kwargs.get("pk", None)),
project_id=str(self.kwargs.get("project_id", None)),
current_instance=json.dumps(
{
"updated_module_issues": update_module_issue_activity,
"created_module_issues": serializers.serialize(
"json", record_to_create
),
}
),
)
# Capture Issue Activity
issue_activity.delay(
type="module.activity.created",
requested_data=json.dumps({"modules_list": issues}),
actor_id=str(self.request.user.id),
issue_id=str(self.kwargs.get("pk", None)),
project_id=str(self.kwargs.get("project_id", None)),
current_instance=json.dumps(
{
"updated_module_issues": update_module_issue_activity,
"created_module_issues": serializers.serialize(
"json", record_to_create
),
}
),
epoch=int(timezone.now().timestamp()),
)
return Response(
ModuleIssueSerializer(self.get_queryset(), many=True).data,
status=status.HTTP_200_OK,
)
except Module.DoesNotExist:
return Response(
{"error": "Module Does not exists"}, status=status.HTTP_400_BAD_REQUEST
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
return Response(
ModuleIssueSerializer(self.get_queryset(), many=True).data,
status=status.HTTP_200_OK,
)
def destroy(self, request, slug, project_id, module_id, pk):
module_issue = ModuleIssue.objects.get(
workspace__slug=slug, project_id=project_id, module_id=module_id, pk=pk
)
module_issue.delete()
issue_activity.delay(
type="module.activity.deleted",
requested_data=json.dumps(
{
"module_id": str(module_id),
"issues": [str(module_issue.issue_id)],
}
),
actor_id=str(request.user.id),
issue_id=str(pk),
project_id=str(project_id),
current_instance=None,
epoch=int(timezone.now().timestamp()),
)
return Response(status=status.HTTP_204_NO_CONTENT)
class ModuleLinkViewSet(BaseViewSet):
@@ -480,10 +514,6 @@ class ModuleLinkViewSet(BaseViewSet):
class ModuleFavoriteViewSet(BaseViewSet):
permission_classes = [
ProjectEntityPermission,
]
serializer_class = ModuleFavoriteSerializer
model = ModuleFavorite
@@ -497,49 +527,18 @@ class ModuleFavoriteViewSet(BaseViewSet):
)
def create(self, request, slug, project_id):
try:
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)
except IntegrityError as e:
if "already exists" in str(e):
return Response(
{"error": "The module is already added to favorites"},
status=status.HTTP_410_GONE,
)
else:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
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):
try:
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)
except ModuleFavorite.DoesNotExist:
return Response(
{"error": "Module is not in favorites"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
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)
+274
View File
@@ -0,0 +1,274 @@
# Django imports
from django.db.models import Q
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
from plane.utils.paginator import BasePaginator
# Module imports
from .base import BaseViewSet, BaseAPIView
from plane.db.models import (
Notification,
IssueAssignee,
IssueSubscriber,
Issue,
WorkspaceMember,
)
from plane.api.serializers import NotificationSerializer
class NotificationViewSet(BaseViewSet, BasePaginator):
model = Notification
serializer_class = NotificationSerializer
def get_queryset(self):
return (
super()
.get_queryset()
.filter(
workspace__slug=self.kwargs.get("slug"),
receiver_id=self.request.user.id,
)
.select_related("workspace", "project," "triggered_by", "receiver")
)
def list(self, request, slug):
# Get query parameters
snoozed = request.GET.get("snoozed", "false")
archived = request.GET.get("archived", "false")
read = request.GET.get("read", "true")
type = request.GET.get("type", "all")
notifications = (
Notification.objects.filter(
workspace__slug=slug, receiver_id=request.user.id
)
.select_related("workspace", "project", "triggered_by", "receiver")
.order_by("snoozed_till", "-created_at")
)
# Filters based on query parameters
snoozed_filters = {
"true": Q(snoozed_till__lt=timezone.now()) | Q(snoozed_till__isnull=False),
"false": Q(snoozed_till__gte=timezone.now()) | Q(snoozed_till__isnull=True),
}
notifications = notifications.filter(snoozed_filters[snoozed])
archived_filters = {
"true": Q(archived_at__isnull=False),
"false": Q(archived_at__isnull=True),
}
notifications = notifications.filter(archived_filters[archived])
if read == "false":
notifications = notifications.filter(read_at__isnull=True)
# Subscribed issues
if type == "watching":
issue_ids = IssueSubscriber.objects.filter(
workspace__slug=slug, subscriber_id=request.user.id
).values_list("issue_id", flat=True)
notifications = notifications.filter(entity_identifier__in=issue_ids)
# Assigned Issues
if type == "assigned":
issue_ids = IssueAssignee.objects.filter(
workspace__slug=slug, assignee_id=request.user.id
).values_list("issue_id", flat=True)
notifications = notifications.filter(entity_identifier__in=issue_ids)
# Created issues
if type == "created":
if WorkspaceMember.objects.filter(
workspace__slug=slug, member=request.user, role__lt=15
).exists():
notifications = Notification.objects.none()
else:
issue_ids = Issue.objects.filter(
workspace__slug=slug, created_by=request.user
).values_list("pk", flat=True)
notifications = notifications.filter(entity_identifier__in=issue_ids)
# Pagination
if request.GET.get("per_page", False) and request.GET.get("cursor", False):
return self.paginate(
request=request,
queryset=(notifications),
on_results=lambda notifications: NotificationSerializer(
notifications, many=True
).data,
)
serializer = NotificationSerializer(notifications, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def partial_update(self, request, slug, pk):
notification = Notification.objects.get(
workspace__slug=slug, pk=pk, receiver=request.user
)
# Only read_at and snoozed_till can be updated
notification_data = {
"snoozed_till": request.data.get("snoozed_till", None),
}
serializer = NotificationSerializer(
notification, data=notification_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 mark_read(self, request, slug, pk):
notification = Notification.objects.get(
receiver=request.user, workspace__slug=slug, pk=pk
)
notification.read_at = timezone.now()
notification.save()
serializer = NotificationSerializer(notification)
return Response(serializer.data, status=status.HTTP_200_OK)
def mark_unread(self, request, slug, pk):
notification = Notification.objects.get(
receiver=request.user, workspace__slug=slug, pk=pk
)
notification.read_at = None
notification.save()
serializer = NotificationSerializer(notification)
return Response(serializer.data, status=status.HTTP_200_OK)
def archive(self, request, slug, pk):
notification = Notification.objects.get(
receiver=request.user, workspace__slug=slug, pk=pk
)
notification.archived_at = timezone.now()
notification.save()
serializer = NotificationSerializer(notification)
return Response(serializer.data, status=status.HTTP_200_OK)
def unarchive(self, request, slug, pk):
notification = Notification.objects.get(
receiver=request.user, workspace__slug=slug, pk=pk
)
notification.archived_at = None
notification.save()
serializer = NotificationSerializer(notification)
return Response(serializer.data, status=status.HTTP_200_OK)
class UnreadNotificationEndpoint(BaseAPIView):
def get(self, request, slug):
# Watching Issues Count
watching_issues_count = Notification.objects.filter(
workspace__slug=slug,
receiver_id=request.user.id,
read_at__isnull=True,
archived_at__isnull=True,
entity_identifier__in=IssueSubscriber.objects.filter(
workspace__slug=slug, subscriber_id=request.user.id
).values_list("issue_id", flat=True),
).count()
# My Issues Count
my_issues_count = Notification.objects.filter(
workspace__slug=slug,
receiver_id=request.user.id,
read_at__isnull=True,
archived_at__isnull=True,
entity_identifier__in=IssueAssignee.objects.filter(
workspace__slug=slug, assignee_id=request.user.id
).values_list("issue_id", flat=True),
).count()
# Created Issues Count
created_issues_count = Notification.objects.filter(
workspace__slug=slug,
receiver_id=request.user.id,
read_at__isnull=True,
archived_at__isnull=True,
entity_identifier__in=Issue.objects.filter(
workspace__slug=slug, created_by=request.user
).values_list("pk", flat=True),
).count()
return Response(
{
"watching_issues": watching_issues_count,
"my_issues": my_issues_count,
"created_issues": created_issues_count,
},
status=status.HTTP_200_OK,
)
class MarkAllReadNotificationViewSet(BaseViewSet):
def create(self, request, slug):
snoozed = request.data.get("snoozed", False)
archived = request.data.get("archived", False)
type = request.data.get("type", "all")
notifications = (
Notification.objects.filter(
workspace__slug=slug,
receiver_id=request.user.id,
read_at__isnull=True,
)
.select_related("workspace", "project", "triggered_by", "receiver")
.order_by("snoozed_till", "-created_at")
)
# Filter for snoozed notifications
if snoozed:
notifications = notifications.filter(
Q(snoozed_till__lt=timezone.now()) | Q(snoozed_till__isnull=False)
)
else:
notifications = notifications.filter(
Q(snoozed_till__gte=timezone.now()) | Q(snoozed_till__isnull=True),
)
# Filter for archived or unarchive
if archived:
notifications = notifications.filter(archived_at__isnull=False)
else:
notifications = notifications.filter(archived_at__isnull=True)
# Subscribed issues
if type == "watching":
issue_ids = IssueSubscriber.objects.filter(
workspace__slug=slug, subscriber_id=request.user.id
).values_list("issue_id", flat=True)
notifications = notifications.filter(entity_identifier__in=issue_ids)
# Assigned Issues
if type == "assigned":
issue_ids = IssueAssignee.objects.filter(
workspace__slug=slug, assignee_id=request.user.id
).values_list("issue_id", flat=True)
notifications = notifications.filter(entity_identifier__in=issue_ids)
# Created issues
if type == "created":
if WorkspaceMember.objects.filter(
workspace__slug=slug, member=request.user, role__lt=15
).exists():
notifications = Notification.objects.none()
else:
issue_ids = Issue.objects.filter(
workspace__slug=slug, created_by=request.user
).values_list("pk", flat=True)
notifications = notifications.filter(entity_identifier__in=issue_ids)
updated_notifications = []
for notification in notifications:
notification.read_at = timezone.now()
updated_notifications.append(notification)
Notification.objects.bulk_update(
updated_notifications, ["read_at"], batch_size=100
)
return Response({"message": "Successful"}, status=status.HTTP_200_OK)
+1 -14
View File
@@ -15,6 +15,7 @@ from rest_framework.views import APIView
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework import status
from sentry_sdk import capture_exception
# sso authentication
from google.oauth2 import id_token
from google.auth.transport import requests as google_auth_request
@@ -186,14 +187,11 @@ class OauthEndpoint(BaseAPIView):
user.is_email_verified = email_verified
user.save()
serialized_user = UserSerializer(user).data
access_token, refresh_token = get_tokens_for_user(user)
data = {
"access_token": access_token,
"refresh_token": refresh_token,
"user": serialized_user,
}
SocialLoginConnection.objects.update_or_create(
@@ -264,14 +262,11 @@ class OauthEndpoint(BaseAPIView):
user.last_login_uagent = request.META.get("HTTP_USER_AGENT")
user.token_updated_at = timezone.now()
user.save()
serialized_user = UserSerializer(user).data
access_token, refresh_token = get_tokens_for_user(user)
data = {
"access_token": access_token,
"refresh_token": refresh_token,
"user": serialized_user,
"permissions": [],
}
if settings.ANALYTICS_BASE_API:
_ = requests.post(
@@ -304,11 +299,3 @@ class OauthEndpoint(BaseAPIView):
},
)
return Response(data, status=status.HTTP_201_CREATED)
except Exception as e:
capture_exception(e)
return Response(
{
"error": "Something went wrong. Please try again later or contact the support team."
},
status=status.HTTP_400_BAD_REQUEST,
)
+101 -167
View File
@@ -2,7 +2,6 @@
from datetime import timedelta, datetime, date
# Django imports
from django.db import IntegrityError
from django.db.models import Exists, OuterRef, Q, Prefetch
from django.utils import timezone
@@ -78,104 +77,82 @@ class PageViewSet(BaseViewSet):
)
def create(self, request, slug, project_id):
try:
serializer = PageSerializer(
data=request.data,
context={"project_id": project_id, "owned_by_id": request.user.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)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
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):
try:
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)
except Page.DoesNotExist:
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": "Page Does not exist"}, status=status.HTTP_400_BAD_REQUEST
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
{
"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):
try:
queryset = self.get_queryset()
page_view = request.GET.get("page_view", False)
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)
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)
# 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,
)
# 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)
# 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)
# 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)
return Response({"error": "No matching view found"}, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
capture_exception(e)
return Response({"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST)
class PageBlockViewSet(BaseViewSet):
serializer_class = PageBlockSerializer
@@ -225,53 +202,21 @@ class PageFavoriteViewSet(BaseViewSet):
)
def create(self, request, slug, project_id):
try:
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)
except IntegrityError as e:
if "already exists" in str(e):
return Response(
{"error": "The page is already added to favorites"},
status=status.HTTP_410_GONE,
)
else:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
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):
try:
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)
except PageFavorite.DoesNotExist:
return Response(
{"error": "Page is not in favorites"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
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 = [
@@ -279,43 +224,32 @@ class CreateIssueFromPageBlockEndpoint(BaseAPIView):
]
def post(self, request, slug, project_id, page_id, page_block_id):
try:
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
)
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"{request.user.email} created the issue from {page_block.name} block",
verb="created",
)
_ = 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()
page_block.issue = issue
page_block.save()
return Response(IssueLiteSerializer(issue).data, status=status.HTTP_200_OK)
except PageBlock.DoesNotExist:
return Response(
{"error": "Page Block does not exist"}, status=status.HTTP_404_NOT_FOUND
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
return Response(IssueLiteSerializer(issue).data, status=status.HTTP_200_OK)
-131
View File
@@ -1,131 +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,
)
from plane.api.views.base import BaseViewSet, BaseAPIView
from plane.db.models import (
User,
Workspace,
WorkspaceMemberInvite,
Issue,
IssueActivity,
WorkspaceMember,
)
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):
try:
workspace = Workspace.objects.get(
pk=request.user.last_workspace_id, workspace_member__member=request.user
)
workspace_invites = WorkspaceMemberInvite.objects.filter(
email=request.user.email
).count()
assigned_issues = Issue.issue_objects.filter(assignees__in=[request.user]).count()
serialized_data = UserSerializer(request.user).data
serialized_data["workspace"] = {
"last_workspace_id": request.user.last_workspace_id,
"last_workspace_slug": workspace.slug,
"fallback_workspace_id": request.user.last_workspace_id,
"fallback_workspace_slug": workspace.slug,
"invites": workspace_invites,
}
serialized_data.setdefault("issues", {})["assigned_issues"] = assigned_issues
return Response(
serialized_data,
status=status.HTTP_200_OK,
)
except Workspace.DoesNotExist:
# This exception will be hit even when the `last_workspace_id` is None
workspace_invites = WorkspaceMemberInvite.objects.filter(
email=request.user.email
).count()
assigned_issues = Issue.issue_objects.filter(assignees__in=[request.user]).count()
fallback_workspace = Workspace.objects.filter(
workspace_member__member=request.user
).order_by("created_at").first()
serialized_data = UserSerializer(request.user).data
serialized_data["workspace"] = {
"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,
}
serialized_data.setdefault("issues", {})["assigned_issues"] = assigned_issues
return Response(
serialized_data,
status=status.HTTP_200_OK,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
class UpdateUserOnBoardedEndpoint(BaseAPIView):
def patch(self, request):
try:
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
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
class UserActivityEndpoint(BaseAPIView, BasePaginator):
def get(self, request):
try:
queryset = IssueActivity.objects.filter(actor=request.user).select_related(
"actor", "workspace"
)
return self.paginate(
request=request,
queryset=queryset,
on_results=lambda issue_activities: IssueActivitySerializer(
issue_activities, many=True
).data,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
File diff suppressed because it is too large Load Diff
-21
View File
@@ -1,21 +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 .base import BaseAPIView
from plane.utils.integrations.github import get_release_notes
class ReleaseNotesEndpoint(BaseAPIView):
def get(self, request):
try:
release_notes = get_release_notes()
return Response(release_notes, status=status.HTTP_200_OK)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
+179 -183
View File
@@ -20,7 +20,7 @@ class GlobalSearchEndpoint(BaseAPIView):
also show related workspace if found
"""
def filter_workspaces(self, query, slug, project_id):
def filter_workspaces(self, query, slug, project_id, workspace_search):
fields = ["name"]
q = Q()
for field in fields:
@@ -31,8 +31,8 @@ class GlobalSearchEndpoint(BaseAPIView):
.values("name", "id", "slug")
)
def filter_projects(self, query, slug, project_id):
fields = ["name"]
def filter_projects(self, query, slug, project_id, workspace_search):
fields = ["name", "identifier"]
q = Q()
for field in fields:
q |= Q(**{f"{field}__icontains": query})
@@ -46,8 +46,8 @@ class GlobalSearchEndpoint(BaseAPIView):
.values("name", "id", "identifier", "workspace__slug")
)
def filter_issues(self, query, slug, project_id):
fields = ["name", "sequence_id"]
def filter_issues(self, query, slug, project_id, workspace_search):
fields = ["name", "sequence_id", "project__identifier"]
q = Q()
for field in fields:
if field == "sequence_id":
@@ -56,223 +56,219 @@ class GlobalSearchEndpoint(BaseAPIView):
q |= Q(**{"sequence_id": sequence_id})
else:
q |= Q(**{f"{field}__icontains": query})
return (
Issue.issue_objects.filter(
q,
project__project_projectmember__member=self.request.user,
workspace__slug=slug,
project_id=project_id,
)
.distinct()
.values(
"name",
"id",
"sequence_id",
"project__identifier",
"project_id",
"workspace__slug",
)
issues = Issue.issue_objects.filter(
q,
project__project_projectmember__member=self.request.user,
workspace__slug=slug,
)
def filter_cycles(self, query, slug, project_id):
if workspace_search == "false" and project_id:
issues = issues.filter(project_id=project_id)
return issues.distinct().values(
"name",
"id",
"sequence_id",
"project__identifier",
"project_id",
"workspace__slug",
)
def filter_cycles(self, query, slug, project_id, workspace_search):
fields = ["name"]
q = Q()
for field in fields:
q |= Q(**{f"{field}__icontains": query})
return (
Cycle.objects.filter(
q,
project__project_projectmember__member=self.request.user,
workspace__slug=slug,
project_id=project_id,
)
.distinct()
.values(
"name",
"id",
"project_id",
"workspace__slug",
)
cycles = Cycle.objects.filter(
q,
project__project_projectmember__member=self.request.user,
workspace__slug=slug,
)
def filter_modules(self, query, slug, project_id):
if workspace_search == "false" and project_id:
cycles = cycles.filter(project_id=project_id)
return cycles.distinct().values(
"name",
"id",
"project_id",
"project__identifier",
"workspace__slug",
)
def filter_modules(self, query, slug, project_id, workspace_search):
fields = ["name"]
q = Q()
for field in fields:
q |= Q(**{f"{field}__icontains": query})
return (
Module.objects.filter(
q,
project__project_projectmember__member=self.request.user,
workspace__slug=slug,
project_id=project_id,
)
.distinct()
.values(
"name",
"id",
"project_id",
"workspace__slug",
)
modules = Module.objects.filter(
q,
project__project_projectmember__member=self.request.user,
workspace__slug=slug,
)
def filter_pages(self, query, slug, project_id):
if workspace_search == "false" and project_id:
modules = modules.filter(project_id=project_id)
return modules.distinct().values(
"name",
"id",
"project_id",
"project__identifier",
"workspace__slug",
)
def filter_pages(self, query, slug, project_id, workspace_search):
fields = ["name"]
q = Q()
for field in fields:
q |= Q(**{f"{field}__icontains": query})
return (
Page.objects.filter(
q,
project__project_projectmember__member=self.request.user,
workspace__slug=slug,
project_id=project_id,
)
.distinct()
.values(
"name",
"id",
"project_id",
"workspace__slug",
)
pages = Page.objects.filter(
q,
project__project_projectmember__member=self.request.user,
workspace__slug=slug,
)
def filter_views(self, query, slug, project_id):
if workspace_search == "false" and project_id:
pages = pages.filter(project_id=project_id)
return pages.distinct().values(
"name",
"id",
"project_id",
"project__identifier",
"workspace__slug",
)
def filter_views(self, query, slug, project_id, workspace_search):
fields = ["name"]
q = Q()
for field in fields:
q |= Q(**{f"{field}__icontains": query})
return (
IssueView.objects.filter(
q,
project__project_projectmember__member=self.request.user,
workspace__slug=slug,
project_id=project_id,
)
.distinct()
.values(
"name",
"id",
"project_id",
"workspace__slug",
)
issue_views = IssueView.objects.filter(
q,
project__project_projectmember__member=self.request.user,
workspace__slug=slug,
)
def get(self, request, slug, project_id):
try:
query = request.query_params.get("search", False)
if not query:
return Response(
{
"results": {
"workspace": [],
"project": [],
"issue": [],
"cycle": [],
"module": [],
"issue_view": [],
"page": [],
}
},
status=status.HTTP_200_OK,
)
if workspace_search == "false" and project_id:
issue_views = issue_views.filter(project_id=project_id)
MODELS_MAPPER = {
"workspace": self.filter_workspaces,
"project": self.filter_projects,
"issue": self.filter_issues,
"cycle": self.filter_cycles,
"module": self.filter_modules,
"issue_view": self.filter_views,
"page": self.filter_pages,
}
return issue_views.distinct().values(
"name",
"id",
"project_id",
"project__identifier",
"workspace__slug",
)
results = {}
def get(self, request, slug):
query = request.query_params.get("search", False)
workspace_search = request.query_params.get("workspace_search", "false")
project_id = request.query_params.get("project_id", False)
for model in MODELS_MAPPER.keys():
func = MODELS_MAPPER.get(model, None)
results[model] = func(query, slug, project_id)
return Response({"results": results}, status=status.HTTP_200_OK)
except Exception as e:
capture_exception(e)
if not query:
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
{
"results": {
"workspace": [],
"project": [],
"issue": [],
"cycle": [],
"module": [],
"issue_view": [],
"page": [],
}
},
status=status.HTTP_200_OK,
)
MODELS_MAPPER = {
"workspace": self.filter_workspaces,
"project": self.filter_projects,
"issue": self.filter_issues,
"cycle": self.filter_cycles,
"module": self.filter_modules,
"issue_view": self.filter_views,
"page": self.filter_pages,
}
results = {}
for model in MODELS_MAPPER.keys():
func = MODELS_MAPPER.get(model, None)
results[model] = func(query, slug, project_id, workspace_search)
return Response({"results": results}, status=status.HTTP_200_OK)
class IssueSearchEndpoint(BaseAPIView):
def get(self, request, slug, project_id):
try:
query = request.query_params.get("search", False)
parent = request.query_params.get("parent", "false")
blocker_blocked_by = request.query_params.get("blocker_blocked_by", "false")
cycle = request.query_params.get("cycle", "false")
module = request.query_params.get("module", "false")
sub_issue = request.query_params.get("sub_issue", "false")
query = request.query_params.get("search", False)
workspace_search = request.query_params.get("workspace_search", "false")
parent = request.query_params.get("parent", "false")
issue_relation = request.query_params.get("issue_relation", "false")
cycle = request.query_params.get("cycle", "false")
module = request.query_params.get("module", "false")
sub_issue = request.query_params.get("sub_issue", "false")
issue_id = request.query_params.get("issue_id", False)
issue_id = request.query_params.get("issue_id", False)
issues = Issue.issue_objects.filter(
workspace__slug=slug,
project_id=project_id,
project__project_projectmember__member=self.request.user,
)
issues = Issue.issue_objects.filter(
workspace__slug=slug,
project__project_projectmember__member=self.request.user,
)
if query:
issues = search_issues(query, issues)
if workspace_search == "false":
issues = issues.filter(project_id=project_id)
if parent == "true" and issue_id:
issue = Issue.issue_objects.get(pk=issue_id)
issues = issues.filter(
~Q(pk=issue_id), ~Q(pk=issue.parent_id), parent__isnull=True
).exclude(
pk__in=Issue.issue_objects.filter(parent__isnull=False).values_list(
"parent_id", flat=True
)
if query:
issues = search_issues(query, issues)
if parent == "true" and issue_id:
issue = Issue.issue_objects.get(pk=issue_id)
issues = issues.filter(
~Q(pk=issue_id), ~Q(pk=issue.parent_id), parent__isnull=True
).exclude(
pk__in=Issue.issue_objects.filter(parent__isnull=False).values_list(
"parent_id", flat=True
)
if blocker_blocked_by == "true" and issue_id:
issue = Issue.issue_objects.get(pk=issue_id)
issues = issues.filter(
~Q(pk=issue_id),
~Q(blocked_issues__block=issue),
~Q(blocker_issues__blocked_by=issue),
)
if sub_issue == "true" and issue_id:
issue = Issue.issue_objects.get(pk=issue_id)
issues = issues.filter(~Q(pk=issue_id), parent__isnull=True)
if issue.parent:
issues = issues.filter(~Q(pk=issue.parent_id))
if cycle == "true":
issues = issues.exclude(issue_cycle__isnull=False)
if module == "true":
issues = issues.exclude(issue_module__isnull=False)
return Response(
issues.values(
"name",
"id",
"sequence_id",
"project__identifier",
"project_id",
"workspace__slug",
"state__name",
"state__group",
"state__color",
),
status=status.HTTP_200_OK,
)
except Issue.DoesNotExist:
return Response(
{"error": "Issue Does not exist"}, status=status.HTTP_400_BAD_REQUEST
)
except Exception as e:
print(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
if issue_relation == "true" and issue_id:
issue = Issue.issue_objects.get(pk=issue_id)
issues = issues.filter(
~Q(pk=issue_id),
~Q(issue_related__issue=issue),
~Q(issue_relation__related_issue=issue),
)
if sub_issue == "true" and issue_id:
issue = Issue.issue_objects.get(pk=issue_id)
issues = issues.filter(~Q(pk=issue_id), parent__isnull=True)
if issue.parent:
issues = issues.filter(~Q(pk=issue.parent_id))
if cycle == "true":
issues = issues.exclude(issue_cycle__isnull=False)
if module == "true":
issues = issues.exclude(issue_module__isnull=False)
return Response(
issues.values(
"name",
"id",
"sequence_id",
"project__name",
"project__identifier",
"project_id",
"workspace__slug",
"state__name",
"state__group",
"state__color",
),
status=status.HTTP_200_OK,
)
+34 -57
View File
@@ -2,7 +2,6 @@
from itertools import groupby
# Django imports
from django.db import IntegrityError
from django.db.models import Q
# Third party imports
@@ -41,67 +40,45 @@ class StateViewSet(BaseViewSet):
)
def create(self, request, slug, project_id):
try:
serializer = StateSerializer(data=request.data)
if serializer.is_valid():
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)
except IntegrityError:
return Response(
{"error": "State with the name already exists"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
serializer = StateSerializer(data=request.data)
if serializer.is_valid():
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):
try:
state_dict = dict()
states = StateSerializer(self.get_queryset(), many=True).data
state_dict = dict()
states = StateSerializer(self.get_queryset(), many=True).data
for key, value in groupby(
sorted(states, key=lambda state: state["group"]),
lambda state: state.get("group"),
):
state_dict[str(key)] = list(value)
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)
except Exception as e:
capture_exception(e)
return Response(state_dict, status=status.HTTP_200_OK)
def destroy(self, request, slug, project_id, pk):
state = State.objects.get(
~Q(name="Triage"),
pk=pk, project_id=project_id, workspace__slug=slug,
)
if state.default:
return Response(
{"error": "Something went wrong please try again later"},
{"error": "Default state cannot be deleted"}, status=False
)
# Check for any issues in the state
issue_exist = Issue.issue_objects.filter(state=pk).exists()
if issue_exist:
return Response(
{
"error": "The state is not empty, only empty states can be deleted"
},
status=status.HTTP_400_BAD_REQUEST,
)
def destroy(self, request, slug, project_id, pk):
try:
state = State.objects.get(
~Q(name="Triage"),
pk=pk, project_id=project_id, workspace__slug=slug,
)
if state.default:
return Response(
{"error": "Default state cannot be deleted"}, status=False
)
# Check for any issues in the state
issue_exist = Issue.issue_objects.filter(state=pk).exists()
if issue_exist:
return Response(
{
"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)
except State.DoesNotExist:
return Response({"error": "State does not exists"}, status=status.HTTP_404)
state.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
+74
View File
@@ -0,0 +1,74 @@
# 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,
WorkspaceMember,
)
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,
)
+191 -85
View File
@@ -1,5 +1,18 @@
# Django imports
from django.db import IntegrityError
from django.db.models import (
Prefetch,
OuterRef,
Func,
F,
Case,
Value,
CharField,
When,
Exists,
Max,
)
from django.utils.decorators import method_decorator
from django.views.decorators.gzip import gzip_page
from django.db.models import Prefetch, OuterRef, Exists
# Third party imports
@@ -10,17 +23,180 @@ from sentry_sdk import capture_exception
# Module imports
from . import BaseViewSet, BaseAPIView
from plane.api.serializers import (
GlobalViewSerializer,
IssueViewSerializer,
IssueLiteSerializer,
IssueViewFavoriteSerializer,
)
from plane.api.permissions import ProjectEntityPermission
from plane.api.permissions import WorkspaceEntityPermission, ProjectEntityPermission
from plane.db.models import (
Workspace,
GlobalView,
IssueView,
Issue,
IssueViewFavorite,
IssueReaction,
IssueLink,
IssueAttachment,
)
from plane.utils.issue_filters import issue_filters
from plane.utils.grouper import group_results
class GlobalViewViewSet(BaseViewSet):
serializer_class = GlobalViewSerializer
model = GlobalView
permission_classes = [
WorkspaceEntityPermission,
]
def perform_create(self, serializer):
workspace = Workspace.objects.get(slug=self.kwargs.get("slug"))
serializer.save(workspace_id=workspace.id)
def get_queryset(self):
return self.filter_queryset(
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.select_related("workspace")
.order_by(self.request.GET.get("order_by", "-created_at"))
.distinct()
)
class GlobalViewIssuesViewSet(BaseViewSet):
permission_classes = [
WorkspaceEntityPermission,
]
def get_queryset(self):
return (
Issue.issue_objects.annotate(
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.filter(workspace__slug=self.kwargs.get("slug"))
.select_related("project")
.select_related("workspace")
.select_related("state")
.select_related("parent")
.prefetch_related("assignees")
.prefetch_related("labels")
.prefetch_related(
Prefetch(
"issue_reactions",
queryset=IssueReaction.objects.select_related("actor"),
)
)
)
@method_decorator(gzip_page)
def list(self, request, slug):
filters = issue_filters(request.query_params, "GET")
# Custom ordering for priority and state
priority_order = ["urgent", "high", "medium", "low", "none"]
state_order = ["backlog", "unstarted", "started", "completed", "cancelled"]
order_by_param = request.GET.get("order_by", "-created_at")
issue_queryset = (
self.get_queryset()
.filter(**filters)
.filter(project__project_projectmember__member=self.request.user)
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(module_id=F("issue_module__module_id"))
.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")
)
)
# Priority Ordering
if order_by_param == "priority" or order_by_param == "-priority":
priority_order = (
priority_order if order_by_param == "priority" else priority_order[::-1]
)
issue_queryset = issue_queryset.annotate(
priority_order=Case(
*[
When(priority=p, then=Value(i))
for i, p in enumerate(priority_order)
],
output_field=CharField(),
)
).order_by("priority_order")
# State Ordering
elif order_by_param in [
"state__name",
"state__group",
"-state__name",
"-state__group",
]:
state_order = (
state_order
if order_by_param in ["state__name", "state__group"]
else state_order[::-1]
)
issue_queryset = issue_queryset.annotate(
state_order=Case(
*[
When(state__group=state_group, then=Value(i))
for i, state_group in enumerate(state_order)
],
default=Value(len(state_order)),
output_field=CharField(),
)
).order_by("state_order")
# assignee and label ordering
elif order_by_param in [
"labels__name",
"-labels__name",
"assignees__first_name",
"-assignees__first_name",
]:
issue_queryset = issue_queryset.annotate(
max_values=Max(
order_by_param[1::]
if order_by_param.startswith("-")
else order_by_param
)
).order_by(
"-max_values" if order_by_param.startswith("-") else "max_values"
)
else:
issue_queryset = issue_queryset.order_by(order_by_param)
issues = IssueLiteSerializer(issue_queryset, many=True).data
## Grouping the results
group_by = request.GET.get("group_by", False)
sub_group_by = request.GET.get("sub_group_by", False)
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, group_by, sub_group_by)
return Response(
grouped_results,
status=status.HTTP_200_OK,
)
return Response(issues, status=status.HTTP_200_OK)
class IssueViewViewSet(BaseViewSet):
@@ -54,45 +230,6 @@ class IssueViewViewSet(BaseViewSet):
)
class ViewIssuesEndpoint(BaseAPIView):
permission_classes = [
ProjectEntityPermission,
]
def get(self, request, slug, project_id, view_id):
try:
view = IssueView.objects.get(pk=view_id)
queries = view.query
filters = issue_filters(request.query_params, "GET")
issues = (
Issue.issue_objects.filter(
**queries, project_id=project_id, workspace__slug=slug
)
.filter(**filters)
.select_related("project")
.select_related("workspace")
.select_related("state")
.select_related("parent")
.prefetch_related("assignees")
.prefetch_related("labels")
)
serializer = IssueLiteSerializer(issues, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
except IssueView.DoesNotExist:
return Response(
{"error": "Issue View does not exist"}, status=status.HTTP_404_NOT_FOUND
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
class IssueViewFavoriteViewSet(BaseViewSet):
serializer_class = IssueViewFavoriteSerializer
model = IssueViewFavorite
@@ -107,49 +244,18 @@ class IssueViewFavoriteViewSet(BaseViewSet):
)
def create(self, request, slug, project_id):
try:
serializer = IssueViewFavoriteSerializer(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)
except IntegrityError as e:
if "already exists" in str(e):
return Response(
{"error": "The view is already added to favorites"},
status=status.HTTP_410_GONE,
)
else:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
serializer = IssueViewFavoriteSerializer(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, view_id):
try:
view_favourite = IssueViewFavorite.objects.get(
project=project_id,
user=request.user,
workspace__slug=slug,
view_id=view_id,
)
view_favourite.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except IssueViewFavorite.DoesNotExist:
return Response(
{"error": "View is not in favorites"},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
view_favourite = IssueViewFavorite.objects.get(
project=project_id,
user=request.user,
workspace__slug=slug,
view_id=view_id,
)
view_favourite.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
File diff suppressed because it is too large Load Diff
+368 -119
View File
@@ -20,8 +20,8 @@ from plane.utils.issue_filters import issue_filters
row_mapping = {
"state__name": "State",
"state__group": "State Group",
"labels__name": "Label",
"assignees__email": "Assignee Name",
"labels__id": "Label",
"assignees__id": "Assignee Name",
"start_date": "Start Date",
"target_date": "Due Date",
"completed_at": "Completed At",
@@ -29,8 +29,321 @@ row_mapping = {
"issue_count": "Issue Count",
"priority": "Priority",
"estimate": "Estimate",
"issue_cycle__cycle_id": "Cycle",
"issue_module__module_id": "Module"
}
ASSIGNEE_ID = "assignees__id"
LABEL_ID = "labels__id"
STATE_ID = "state_id"
CYCLE_ID = "issue_cycle__cycle_id"
MODULE_ID = "issue_module__module_id"
def send_export_email(email, slug, csv_buffer):
"""Helper function to send export email."""
subject = "Your Export is ready"
html_content = render_to_string("emails/exports/analytics.html", {})
text_content = strip_tags(html_content)
csv_buffer.seek(0)
msg = EmailMultiAlternatives(subject, text_content, settings.EMAIL_FROM, [email])
msg.attach(f"{slug}-analytics.csv", csv_buffer.getvalue())
msg.send(fail_silently=False)
def get_assignee_details(slug, filters):
"""Fetch assignee details if required."""
return (
Issue.issue_objects.filter(
workspace__slug=slug, **filters, assignees__avatar__isnull=False
)
.distinct("assignees__id")
.order_by("assignees__id")
.values(
"assignees__avatar",
"assignees__display_name",
"assignees__first_name",
"assignees__last_name",
"assignees__id",
)
)
def get_label_details(slug, filters):
"""Fetch label details if required"""
return (
Issue.objects.filter(workspace__slug=slug, **filters, labels__id__isnull=False)
.distinct("labels__id")
.order_by("labels__id")
.values("labels__id", "labels__color", "labels__name")
)
def get_state_details(slug, filters):
return (
Issue.issue_objects.filter(
workspace__slug=slug,
**filters,
)
.distinct("state_id")
.order_by("state_id")
.values("state_id", "state__name", "state__color")
)
def get_module_details(slug, filters):
return (
Issue.issue_objects.filter(
workspace__slug=slug,
**filters,
issue_module__module_id__isnull=False,
)
.distinct("issue_module__module_id")
.order_by("issue_module__module_id")
.values(
"issue_module__module_id",
"issue_module__module__name",
)
)
def get_cycle_details(slug, filters):
return (
Issue.issue_objects.filter(
workspace__slug=slug,
**filters,
issue_cycle__cycle_id__isnull=False,
)
.distinct("issue_cycle__cycle_id")
.order_by("issue_cycle__cycle_id")
.values(
"issue_cycle__cycle_id",
"issue_cycle__cycle__name",
)
)
def generate_csv_from_rows(rows):
"""Generate CSV buffer from rows."""
csv_buffer = io.StringIO()
writer = csv.writer(csv_buffer, delimiter=",", quoting=csv.QUOTE_ALL)
[writer.writerow(row) for row in rows]
return csv_buffer
def generate_segmented_rows(
distribution,
x_axis,
y_axis,
segment,
key,
assignee_details,
label_details,
state_details,
cycle_details,
module_details,
):
segment_zero = list(
set(
item.get("segment") for sublist in distribution.values() for item in sublist
)
)
segmented = segment
row_zero = [
row_mapping.get(x_axis, "X-Axis"),
row_mapping.get(y_axis, "Y-Axis"),
] + segment_zero
rows = []
for item, data in distribution.items():
generated_row = [
item,
sum(obj.get(key) for obj in data if obj.get(key) is not None),
]
for segment in segment_zero:
value = next((x.get(key) for x in data if x.get("segment") == segment), "0")
generated_row.append(value)
if x_axis == ASSIGNEE_ID:
assignee = next(
(
user
for user in assignee_details
if str(user[ASSIGNEE_ID]) == str(item)
),
None,
)
if assignee:
generated_row[
0
] = f"{assignee['assignees__first_name']} {assignee['assignees__last_name']}"
if x_axis == LABEL_ID:
label = next(
(lab for lab in label_details if str(lab[LABEL_ID]) == str(item)),
None,
)
if label:
generated_row[0] = f"{label['labels__name']}"
if x_axis == STATE_ID:
state = next(
(sta for sta in state_details if str(sta[STATE_ID]) == str(item)),
None,
)
if state:
generated_row[0] = f"{state['state__name']}"
if x_axis == CYCLE_ID:
cycle = next(
(cyc for cyc in cycle_details if str(cyc[CYCLE_ID]) == str(item)),
None,
)
if cycle:
generated_row[0] = f"{cycle['issue_cycle__cycle__name']}"
if x_axis == MODULE_ID:
module = next(
(mod for mod in module_details if str(mod[MODULE_ID]) == str(item)),
None,
)
if module:
generated_row[0] = f"{module['issue_module__module__name']}"
rows.append(tuple(generated_row))
if segmented == ASSIGNEE_ID:
for index, segm in enumerate(row_zero[2:]):
assignee = next(
(
user
for user in assignee_details
if str(user[ASSIGNEE_ID]) == str(segm)
),
None,
)
if assignee:
row_zero[
index + 2
] = f"{assignee['assignees__first_name']} {assignee['assignees__last_name']}"
if segmented == LABEL_ID:
for index, segm in enumerate(row_zero[2:]):
label = next(
(lab for lab in label_details if str(lab[LABEL_ID]) == str(segm)),
None,
)
if label:
row_zero[index + 2] = label["labels__name"]
if segmented == STATE_ID:
for index, segm in enumerate(row_zero[2:]):
state = next(
(sta for sta in state_details if str(sta[STATE_ID]) == str(segm)),
None,
)
if state:
row_zero[index + 2] = state["state__name"]
if segmented == MODULE_ID:
for index, segm in enumerate(row_zero[2:]):
module = next(
(mod for mod in label_details if str(mod[MODULE_ID]) == str(segm)),
None,
)
if module:
row_zero[index + 2] = module["issue_module__module__name"]
if segmented == CYCLE_ID:
for index, segm in enumerate(row_zero[2:]):
cycle = next(
(cyc for cyc in cycle_details if str(cyc[CYCLE_ID]) == str(segm)),
None,
)
if cycle:
row_zero[index + 2] = cycle["issue_cycle__cycle__name"]
return [tuple(row_zero)] + rows
def generate_non_segmented_rows(
distribution,
x_axis,
y_axis,
key,
assignee_details,
label_details,
state_details,
cycle_details,
module_details,
):
rows = []
for item, data in distribution.items():
row = [item, data[0].get("count" if y_axis == "issue_count" else "estimate")]
if x_axis == ASSIGNEE_ID:
assignee = next(
(
user
for user in assignee_details
if str(user[ASSIGNEE_ID]) == str(item)
),
None,
)
if assignee:
row[
0
] = f"{assignee['assignees__first_name']} {assignee['assignees__last_name']}"
if x_axis == LABEL_ID:
label = next(
(lab for lab in label_details if str(lab[LABEL_ID]) == str(item)),
None,
)
if label:
row[0] = f"{label['labels__name']}"
if x_axis == STATE_ID:
state = next(
(sta for sta in state_details if str(sta[STATE_ID]) == str(item)),
None,
)
if state:
row[0] = f"{state['state__name']}"
if x_axis == CYCLE_ID:
cycle = next(
(cyc for cyc in cycle_details if str(cyc[CYCLE_ID]) == str(item)),
None,
)
if cycle:
row[0] = f"{cycle['issue_cycle__cycle__name']}"
if x_axis == MODULE_ID:
module = next(
(mod for mod in module_details if str(mod[MODULE_ID]) == str(item)),
None,
)
if module:
row[0] = f"{module['issue_module__module__name']}"
rows.append(tuple(row))
row_zero = [row_mapping.get(x_axis, "X-Axis"), row_mapping.get(y_axis, "Y-Axis")]
return [tuple(row_zero)] + rows
@shared_task
def analytic_export_task(email, data, slug):
@@ -43,134 +356,70 @@ def analytic_export_task(email, data, slug):
segment = data.get("segment", False)
distribution = build_graph_plot(
queryset=queryset, x_axis=x_axis, y_axis=y_axis, segment=segment
queryset, x_axis=x_axis, y_axis=y_axis, segment=segment
)
key = "count" if y_axis == "issue_count" else "estimate"
segmented = segment
assignee_details = (
get_assignee_details(slug, filters)
if x_axis == ASSIGNEE_ID or segment == ASSIGNEE_ID
else {}
)
assignee_details = {}
if x_axis in ["assignees__email"] or segment in ["assignees__email"]:
assignee_details = (
Issue.issue_objects.filter(workspace__slug=slug, **filters, assignees__avatar__isnull=False)
.order_by("assignees__id")
.distinct("assignees__id")
.values("assignees__avatar", "assignees__email", "assignees__first_name", "assignees__last_name")
)
label_details = (
get_label_details(slug, filters)
if x_axis == LABEL_ID or segment == LABEL_ID
else {}
)
state_details = (
get_state_details(slug, filters)
if x_axis == STATE_ID or segment == STATE_ID
else {}
)
cycle_details = (
get_cycle_details(slug, filters)
if x_axis == CYCLE_ID or segment == CYCLE_ID
else {}
)
module_details = (
get_module_details(slug, filters)
if x_axis == MODULE_ID or segment == MODULE_ID
else {}
)
if segment:
segment_zero = []
for item in distribution:
current_dict = distribution.get(item)
for current in current_dict:
segment_zero.append(current.get("segment"))
segment_zero = list(set(segment_zero))
row_zero = (
[
row_mapping.get(x_axis, "X-Axis"),
]
+ [
row_mapping.get(y_axis, "Y-Axis"),
]
+ segment_zero
rows = generate_segmented_rows(
distribution,
x_axis,
y_axis,
segment,
key,
assignee_details,
label_details,
state_details,
cycle_details,
module_details,
)
rows = []
for item in distribution:
generated_row = [
item,
]
data = distribution.get(item)
# Add y axis values
generated_row.append(sum(obj.get(key) for obj in data if obj.get(key, None) is not None))
for segment in segment_zero:
value = [x for x in data if x.get("segment") == segment]
if len(value):
generated_row.append(value[0].get(key))
else:
generated_row.append("0")
# x-axis replacement for names
if x_axis in ["assignees__email"]:
assignee = [user for user in assignee_details if str(user.get("assignees__email")) == str(item)]
if len(assignee):
generated_row[0] = str(assignee[0].get("assignees__first_name")) + " " + str(assignee[0].get("assignees__last_name"))
rows.append(tuple(generated_row))
# If segment is ["assignees__email"] then replace segment_zero rows with first and last names
if segmented in ["assignees__email"]:
for index, segm in enumerate(row_zero[2:]):
# find the name of the user
assignee = [user for user in assignee_details if str(user.get("assignees__email")) == str(segm)]
if len(assignee):
row_zero[index] = str(assignee[0].get("assignees__first_name")) + " " + str(assignee[0].get("assignees__last_name"))
rows = [tuple(row_zero)] + rows
csv_buffer = io.StringIO()
writer = csv.writer(csv_buffer, delimiter=",", quoting=csv.QUOTE_ALL)
# Write CSV data to the buffer
for row in rows:
writer.writerow(row)
subject = "Your Export is ready"
html_content = render_to_string("emails/exports/analytics.html", {})
text_content = strip_tags(html_content)
csv_buffer.seek(0)
msg = EmailMultiAlternatives(
subject, text_content, settings.EMAIL_FROM, [email]
)
msg.attach(f"{slug}-analytics.csv", csv_buffer.read())
msg.send(fail_silently=False)
else:
row_zero = [
row_mapping.get(x_axis, "X-Axis"),
row_mapping.get(y_axis, "Y-Axis"),
]
rows = []
for item in distribution:
row = [
item,
distribution.get(item)[0].get("count")
if y_axis == "issue_count"
else distribution.get(item)[0].get("estimate "),
]
# x-axis replacement to names
if x_axis in ["assignees__email"]:
assignee = [user for user in assignee_details if str(user.get("assignees__email")) == str(item)]
if len(assignee):
row[0] = str(assignee[0].get("assignees__first_name")) + " " + str(assignee[0].get("assignees__last_name"))
rows.append(tuple(row))
rows = [tuple(row_zero)] + rows
csv_buffer = io.StringIO()
writer = csv.writer(csv_buffer, delimiter=",", quoting=csv.QUOTE_ALL)
# Write CSV data to the buffer
for row in rows:
writer.writerow(row)
subject = "Your Export is ready"
html_content = render_to_string("emails/exports/analytics.html", {})
text_content = strip_tags(html_content)
csv_buffer.seek(0)
msg = EmailMultiAlternatives(
subject, text_content, settings.EMAIL_FROM, [email]
rows = generate_non_segmented_rows(
distribution,
x_axis,
y_axis,
segment,
key,
assignee_details,
label_details,
state_details,
cycle_details,
module_details,
)
msg.attach(f"{slug}-analytics.csv", csv_buffer.read())
msg.send(fail_silently=False)
csv_buffer = generate_csv_from_rows(rows)
send_export_email(email, slug, csv_buffer)
except Exception as e:
# Print logs if in DEBUG mode
if settings.DEBUG:
print(e)
capture_exception(e)
return
+375
View File
@@ -0,0 +1,375 @@
# Python imports
import csv
import io
import json
import boto3
import zipfile
from urllib.parse import urlparse, urlunparse
# Django imports
from django.conf import settings
from django.utils import timezone
# Third party imports
from celery import shared_task
from sentry_sdk import capture_exception
from botocore.client import Config
from openpyxl import Workbook
# Module imports
from plane.db.models import Issue, ExporterHistory
def dateTimeConverter(time):
if time:
return time.strftime("%a, %d %b %Y %I:%M:%S %Z%z")
def dateConverter(time):
if time:
return time.strftime("%a, %d %b %Y")
def create_csv_file(data):
csv_buffer = io.StringIO()
csv_writer = csv.writer(csv_buffer, delimiter=",", quoting=csv.QUOTE_ALL)
for row in data:
csv_writer.writerow(row)
csv_buffer.seek(0)
return csv_buffer.getvalue()
def create_json_file(data):
return json.dumps(data)
def create_xlsx_file(data):
workbook = Workbook()
sheet = workbook.active
for row in data:
sheet.append(row)
xlsx_buffer = io.BytesIO()
workbook.save(xlsx_buffer)
xlsx_buffer.seek(0)
return xlsx_buffer.getvalue()
def create_zip_file(files):
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zipf:
for filename, file_content in files:
zipf.writestr(filename, file_content)
zip_buffer.seek(0)
return zip_buffer
def upload_to_s3(zip_file, workspace_id, token_id, slug):
file_name = f"{workspace_id}/export-{slug}-{token_id[:6]}-{timezone.now()}.zip"
expires_in = 7 * 24 * 60 * 60
if settings.DOCKERIZED and settings.USE_MINIO:
s3 = boto3.client(
"s3",
endpoint_url=settings.AWS_S3_ENDPOINT_URL,
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
config=Config(signature_version="s3v4"),
)
s3.upload_fileobj(
zip_file,
settings.AWS_STORAGE_BUCKET_NAME,
file_name,
ExtraArgs={"ACL": "public-read", "ContentType": "application/zip"},
)
presigned_url = s3.generate_presigned_url(
"get_object",
Params={"Bucket": settings.AWS_STORAGE_BUCKET_NAME, "Key": file_name},
ExpiresIn=expires_in,
)
# Create the new url with updated domain and protocol
presigned_url = presigned_url.replace(
"http://plane-minio:9000/uploads/",
f"{settings.AWS_S3_URL_PROTOCOL}//{settings.AWS_S3_CUSTOM_DOMAIN}/",
)
else:
s3 = boto3.client(
"s3",
region_name=settings.AWS_REGION,
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
config=Config(signature_version="s3v4"),
)
s3.upload_fileobj(
zip_file,
settings.AWS_S3_BUCKET_NAME,
file_name,
ExtraArgs={"ACL": "public-read", "ContentType": "application/zip"},
)
presigned_url = s3.generate_presigned_url(
"get_object",
Params={"Bucket": settings.AWS_S3_BUCKET_NAME, "Key": file_name},
ExpiresIn=expires_in,
)
exporter_instance = ExporterHistory.objects.get(token=token_id)
if presigned_url:
exporter_instance.url = presigned_url
exporter_instance.status = "completed"
exporter_instance.key = file_name
else:
exporter_instance.status = "failed"
exporter_instance.save(update_fields=["status", "url", "key"])
def generate_table_row(issue):
return [
f"""{issue["project__identifier"]}-{issue["sequence_id"]}""",
issue["project__name"],
issue["name"],
issue["description_stripped"],
issue["state__name"],
issue["priority"],
f"{issue['created_by__first_name']} {issue['created_by__last_name']}"
if issue["created_by__first_name"] and issue["created_by__last_name"]
else "",
f"{issue['assignees__first_name']} {issue['assignees__last_name']}"
if issue["assignees__first_name"] and issue["assignees__last_name"]
else "",
issue["labels__name"],
issue["issue_cycle__cycle__name"],
dateConverter(issue["issue_cycle__cycle__start_date"]),
dateConverter(issue["issue_cycle__cycle__end_date"]),
issue["issue_module__module__name"],
dateConverter(issue["issue_module__module__start_date"]),
dateConverter(issue["issue_module__module__target_date"]),
dateTimeConverter(issue["created_at"]),
dateTimeConverter(issue["updated_at"]),
dateTimeConverter(issue["completed_at"]),
dateTimeConverter(issue["archived_at"]),
]
def generate_json_row(issue):
return {
"ID": f"""{issue["project__identifier"]}-{issue["sequence_id"]}""",
"Project": issue["project__name"],
"Name": issue["name"],
"Description": issue["description_stripped"],
"State": issue["state__name"],
"Priority": issue["priority"],
"Created By": f"{issue['created_by__first_name']} {issue['created_by__last_name']}"
if issue["created_by__first_name"] and issue["created_by__last_name"]
else "",
"Assignee": f"{issue['assignees__first_name']} {issue['assignees__last_name']}"
if issue["assignees__first_name"] and issue["assignees__last_name"]
else "",
"Labels": issue["labels__name"],
"Cycle Name": issue["issue_cycle__cycle__name"],
"Cycle Start Date": dateConverter(issue["issue_cycle__cycle__start_date"]),
"Cycle End Date": dateConverter(issue["issue_cycle__cycle__end_date"]),
"Module Name": issue["issue_module__module__name"],
"Module Start Date": dateConverter(issue["issue_module__module__start_date"]),
"Module Target Date": dateConverter(issue["issue_module__module__target_date"]),
"Created At": dateTimeConverter(issue["created_at"]),
"Updated At": dateTimeConverter(issue["updated_at"]),
"Completed At": dateTimeConverter(issue["completed_at"]),
"Archived At": dateTimeConverter(issue["archived_at"]),
}
def update_json_row(rows, row):
matched_index = next(
(
index
for index, existing_row in enumerate(rows)
if existing_row["ID"] == row["ID"]
),
None,
)
if matched_index is not None:
existing_assignees, existing_labels = (
rows[matched_index]["Assignee"],
rows[matched_index]["Labels"],
)
assignee, label = row["Assignee"], row["Labels"]
if assignee is not None and assignee not in existing_assignees:
rows[matched_index]["Assignee"] += f", {assignee}"
if label is not None and label not in existing_labels:
rows[matched_index]["Labels"] += f", {label}"
else:
rows.append(row)
def update_table_row(rows, row):
matched_index = next(
(index for index, existing_row in enumerate(rows) if existing_row[0] == row[0]),
None,
)
if matched_index is not None:
existing_assignees, existing_labels = rows[matched_index][7:9]
assignee, label = row[7:9]
if assignee is not None and assignee not in existing_assignees:
rows[matched_index][7] += f", {assignee}"
if label is not None and label not in existing_labels:
rows[matched_index][8] += f", {label}"
else:
rows.append(row)
def generate_csv(header, project_id, issues, files):
"""
Generate CSV export for all the passed issues.
"""
rows = [
header,
]
for issue in issues:
row = generate_table_row(issue)
update_table_row(rows, row)
csv_file = create_csv_file(rows)
files.append((f"{project_id}.csv", csv_file))
def generate_json(header, project_id, issues, files):
rows = []
for issue in issues:
row = generate_json_row(issue)
update_json_row(rows, row)
json_file = create_json_file(rows)
files.append((f"{project_id}.json", json_file))
def generate_xlsx(header, project_id, issues, files):
rows = [header]
for issue in issues:
row = generate_table_row(issue)
update_table_row(rows, row)
xlsx_file = create_xlsx_file(rows)
files.append((f"{project_id}.xlsx", xlsx_file))
@shared_task
def issue_export_task(provider, workspace_id, project_ids, token_id, multiple, slug):
try:
exporter_instance = ExporterHistory.objects.get(token=token_id)
exporter_instance.status = "processing"
exporter_instance.save(update_fields=["status"])
workspace_issues = (
(
Issue.objects.filter(
workspace__id=workspace_id,
project_id__in=project_ids,
project__project_projectmember__member=exporter_instance.initiated_by_id,
)
.select_related("project", "workspace", "state", "parent", "created_by")
.prefetch_related(
"assignees", "labels", "issue_cycle__cycle", "issue_module__module"
)
.values(
"id",
"project__identifier",
"project__name",
"project__id",
"sequence_id",
"name",
"description_stripped",
"priority",
"state__name",
"created_at",
"updated_at",
"completed_at",
"archived_at",
"issue_cycle__cycle__name",
"issue_cycle__cycle__start_date",
"issue_cycle__cycle__end_date",
"issue_module__module__name",
"issue_module__module__start_date",
"issue_module__module__target_date",
"created_by__first_name",
"created_by__last_name",
"assignees__first_name",
"assignees__last_name",
"labels__name",
)
)
.order_by("project__identifier", "sequence_id")
.distinct()
)
# CSV header
header = [
"ID",
"Project",
"Name",
"Description",
"State",
"Priority",
"Created By",
"Assignee",
"Labels",
"Cycle Name",
"Cycle Start Date",
"Cycle End Date",
"Module Name",
"Module Start Date",
"Module Target Date",
"Created At",
"Updated At",
"Completed At",
"Archived At",
]
EXPORTER_MAPPER = {
"csv": generate_csv,
"json": generate_json,
"xlsx": generate_xlsx,
}
files = []
if multiple:
for project_id in project_ids:
issues = workspace_issues.filter(project__id=project_id)
exporter = EXPORTER_MAPPER.get(provider)
if exporter is not None:
exporter(
header,
project_id,
issues,
files,
)
else:
exporter = EXPORTER_MAPPER.get(provider)
if exporter is not None:
exporter(
header,
workspace_id,
workspace_issues,
files,
)
zip_buffer = create_zip_file(files)
upload_to_s3(zip_buffer, workspace_id, token_id, slug)
except Exception as e:
exporter_instance = ExporterHistory.objects.get(token=token_id)
exporter_instance.status = "failed"
exporter_instance.reason = str(e)
exporter_instance.save(update_fields=["status", "reason"])
# Print logs if in DEBUG mode
if settings.DEBUG:
print(e)
capture_exception(e)
return
@@ -0,0 +1,49 @@
# Python imports
import boto3
from datetime import timedelta
# Django imports
from django.conf import settings
from django.utils import timezone
from django.db.models import Q
# Third party imports
from celery import shared_task
from botocore.client import Config
# Module imports
from plane.db.models import ExporterHistory
@shared_task
def delete_old_s3_link():
# Get a list of keys and IDs to process
expired_exporter_history = ExporterHistory.objects.filter(
Q(url__isnull=False) & Q(created_at__lte=timezone.now() - timedelta(days=8))
).values_list("key", "id")
if settings.DOCKERIZED and settings.USE_MINIO:
s3 = boto3.client(
"s3",
endpoint_url=settings.AWS_S3_ENDPOINT_URL,
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
config=Config(signature_version="s3v4"),
)
else:
s3 = boto3.client(
"s3",
region_name=settings.AWS_REGION,
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
config=Config(signature_version="s3v4"),
)
for file_name, exporter_id in expired_exporter_history:
# Delete object from S3
if file_name:
if settings.DOCKERIZED and settings.USE_MINIO:
s3.delete_object(Bucket=settings.AWS_STORAGE_BUCKET_NAME, Key=file_name)
else:
s3.delete_object(Bucket=settings.AWS_S3_BUCKET_NAME, Key=file_name)
ExporterHistory.objects.filter(id=exporter_id).update(url=None)
@@ -16,7 +16,7 @@ from plane.db.models import User
def forgot_password(first_name, email, uidb64, token, current_site):
try:
realtivelink = f"/reset-password/?uidb64={uidb64}&token={token}"
realtivelink = f"/accounts/reset-password/?uidb64={uidb64}&token={token}"
abs_url = current_site + realtivelink
from_email_string = settings.EMAIL_FROM
+15
View File
@@ -25,6 +25,7 @@ from plane.db.models import (
WorkspaceIntegration,
Label,
User,
IssueProperty,
)
from .workspace_invitation_task import workspace_invitation
from plane.bgtasks.user_welcome_task import send_welcome_slack
@@ -103,6 +104,20 @@ def service_importer(service, importer_id):
ignore_conflicts=True,
)
IssueProperty.objects.bulk_create(
[
IssueProperty(
project_id=importer.project_id,
workspace_id=importer.workspace_id,
user=user,
created_by=importer.created_by,
)
for user in workspace_users
],
batch_size=100,
ignore_conflicts=True,
)
# Check if sync config is on for github importers
if service == "github" and importer.config.get("sync", False):
name = importer.metadata.get("name", False)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,164 @@
# Python imports
import json
from datetime import timedelta
# Django imports
from django.utils import timezone
from django.db.models import Q
from django.conf import settings
# Third party imports
from celery import shared_task
from sentry_sdk import capture_exception
# Module imports
from plane.db.models import Issue, Project, State
from plane.bgtasks.issue_activites_task import issue_activity
@shared_task
def archive_and_close_old_issues():
archive_old_issues()
close_old_issues()
def archive_old_issues():
try:
# Get all the projects whose archive_in is greater than 0
projects = Project.objects.filter(archive_in__gt=0)
for project in projects:
project_id = project.id
archive_in = project.archive_in
# Get all the issues whose updated_at in less that the archive_in month
issues = Issue.issue_objects.filter(
Q(
project=project_id,
archived_at__isnull=True,
updated_at__lte=(timezone.now() - timedelta(days=archive_in * 30)),
state__group__in=["completed", "cancelled"],
),
Q(issue_cycle__isnull=True)
| (
Q(issue_cycle__cycle__end_date__lt=timezone.now().date())
& Q(issue_cycle__isnull=False)
),
Q(issue_module__isnull=True)
| (
Q(issue_module__module__target_date__lt=timezone.now().date())
& Q(issue_module__isnull=False)
),
).filter(
Q(issue_inbox__status=1)
| Q(issue_inbox__status=-1)
| Q(issue_inbox__status=2)
| Q(issue_inbox__isnull=True)
)
# Check if Issues
if issues:
# Set the archive time to current time
archive_at = timezone.now()
issues_to_update = []
for issue in issues:
issue.archived_at = archive_at
issues_to_update.append(issue)
# Bulk Update the issues and log the activity
if issues_to_update:
Issue.objects.bulk_update(
issues_to_update, ["archived_at"], batch_size=100
)
[
issue_activity.delay(
type="issue.activity.updated",
requested_data=json.dumps({"archived_at": str(archive_at)}),
actor_id=str(project.created_by_id),
issue_id=issue.id,
project_id=project_id,
current_instance=None,
subscriber=False,
epoch=int(timezone.now().timestamp())
)
for issue in issues_to_update
]
return
except Exception as e:
if settings.DEBUG:
print(e)
capture_exception(e)
return
def close_old_issues():
try:
# Get all the projects whose close_in is greater than 0
projects = Project.objects.filter(close_in__gt=0).select_related(
"default_state"
)
for project in projects:
project_id = project.id
close_in = project.close_in
# Get all the issues whose updated_at in less that the close_in month
issues = Issue.issue_objects.filter(
Q(
project=project_id,
archived_at__isnull=True,
updated_at__lte=(timezone.now() - timedelta(days=close_in * 30)),
state__group__in=["backlog", "unstarted", "started"],
),
Q(issue_cycle__isnull=True)
| (
Q(issue_cycle__cycle__end_date__lt=timezone.now().date())
& Q(issue_cycle__isnull=False)
),
Q(issue_module__isnull=True)
| (
Q(issue_module__module__target_date__lt=timezone.now().date())
& Q(issue_module__isnull=False)
),
).filter(
Q(issue_inbox__status=1)
| Q(issue_inbox__status=-1)
| Q(issue_inbox__status=2)
| Q(issue_inbox__isnull=True)
)
# Check if Issues
if issues:
if project.default_state is None:
close_state = State.objects.filter(group="cancelled").first()
else:
close_state = project.default_state
issues_to_update = []
for issue in issues:
issue.state = close_state
issues_to_update.append(issue)
# Bulk Update the issues and log the activity
if issues_to_update:
Issue.objects.bulk_update(issues_to_update, ["state"], batch_size=100)
[
issue_activity.delay(
type="issue.activity.updated",
requested_data=json.dumps({"closed_to": str(issue.state_id)}),
actor_id=str(project.created_by_id),
issue_id=issue.id,
project_id=project_id,
current_instance=None,
subscriber=False,
epoch=int(timezone.now().timestamp())
)
for issue in issues_to_update
]
return
except Exception as e:
if settings.DEBUG:
print(e)
capture_exception(e)
return
@@ -0,0 +1,102 @@
# Python imports
import json
# Django imports
from django.utils import timezone
# Module imports
from plane.db.models import IssueSubscriber, Project, IssueAssignee, Issue, Notification
# Third Party imports
from celery import shared_task
@shared_task
def notifications(type, issue_id, project_id, actor_id, subscriber, issue_activities_created):
issue_activities_created = (
json.loads(issue_activities_created) if issue_activities_created is not None else None
)
if type not in [
"cycle.activity.created",
"cycle.activity.deleted",
"module.activity.created",
"module.activity.deleted",
"issue_reaction.activity.created",
"issue_reaction.activity.deleted",
"comment_reaction.activity.created",
"comment_reaction.activity.deleted",
"issue_vote.activity.created",
"issue_vote.activity.deleted",
"issue_draft.activity.created",
"issue_draft.activity.updated",
"issue_draft.activity.deleted",
]:
# Create Notifications
bulk_notifications = []
issue_subscribers = list(
IssueSubscriber.objects.filter(project_id=project_id, issue_id=issue_id)
.exclude(subscriber_id=actor_id)
.values_list("subscriber", flat=True)
)
issue_assignees = list(
IssueAssignee.objects.filter(project_id=project_id, issue_id=issue_id)
.exclude(assignee_id=actor_id)
.values_list("assignee", flat=True)
)
issue_subscribers = issue_subscribers + issue_assignees
issue = Issue.objects.filter(pk=issue_id).first()
if subscriber:
# add the user to issue subscriber
try:
_ = IssueSubscriber.objects.get_or_create(
issue_id=issue_id, subscriber_id=actor_id
)
except Exception as e:
pass
project = Project.objects.get(pk=project_id)
for subscriber in list(set(issue_subscribers)):
for issue_activity in issue_activities_created:
bulk_notifications.append(
Notification(
workspace=project.workspace,
sender="in_app:issue_activities",
triggered_by_id=actor_id,
receiver_id=subscriber,
entity_identifier=issue_id,
entity_name="issue",
project=project,
title=issue_activity.get("comment"),
data={
"issue": {
"id": str(issue_id),
"name": str(issue.name),
"identifier": str(issue.project.identifier),
"sequence_id": issue.sequence_id,
"state_name": issue.state.name,
"state_group": issue.state.group,
},
"issue_activity": {
"id": str(issue_activity.get("id")),
"verb": str(issue_activity.get("verb")),
"field": str(issue_activity.get("field")),
"actor": str(issue_activity.get("actor_id")),
"new_value": str(issue_activity.get("new_value")),
"old_value": str(issue_activity.get("old_value")),
"issue_comment": str(
issue_activity.get("issue_comment").comment_stripped
if issue_activity.get("issue_comment") is not None
else ""
),
},
},
)
)
# Bulk create notifications
Notification.objects.bulk_create(bulk_notifications, batch_size=100)
+15
View File
@@ -1,6 +1,7 @@
import os
from celery import Celery
from plane.settings.redis import redis_instance
from celery.schedules import crontab
# Set the default Django settings module for the 'celery' program.
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "plane.settings.production")
@@ -13,5 +14,19 @@ app = Celery("plane")
# pickle the object when using Windows.
app.config_from_object("django.conf:settings", namespace="CELERY")
app.conf.beat_schedule = {
# Executes every day at 12 AM
"check-every-day-to-archive-and-close": {
"task": "plane.bgtasks.issue_automation_task.archive_and_close_old_issues",
"schedule": crontab(hour=0, minute=0),
},
"check-every-day-to-delete_exporter_history": {
"task": "plane.bgtasks.exporter_expired_task.delete_old_s3_link",
"schedule": crontab(hour=0, minute=0),
},
}
# Load task modules from all registered Django app configs.
app.autodiscover_tasks()
app.conf.beat_scheduler = 'django_celery_beat.schedulers.DatabaseScheduler'

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