Compare commits

..
1051 Commits
Author SHA1 Message Date
guru_sainathandGitHub 02111d779b promote: staging to production 2023-06-14 17:47:23 +05:30
guru_sainathandGitHub c3aa1cb06d chore: auth workflow in magic-link
chore: auth workflow in magic-link
2023-06-14 17:42:48 +05:30
guru_sainathandGitHub e73bd96dbc chore: auth workflow in magic-link (#1292) 2023-06-14 16:56:17 +05:30
guru_sainathandGitHub 8c9f4b9665 promote: develop to stage release
promote: develop to stage release
2023-06-14 14:57:30 +05:30
guru_sainathandGitHub be7706e62e chore: updating last_workspace_id under user (#1289)
* chore: onboarding steps workflow verification

* chore: onboarding onboarding variable update

* chore: role check in onboarding

* chore: updated last_workspace_id under user
2023-06-14 14:04:25 +05:30
guru_sainathandGitHub 4083f623a0 refactor: onboarding user role validation (#1287) 2023-06-14 13:39:49 +05:30
guru_sainathandGitHub 6f7b563712 refactor: onboarding workflow (#1286)
* chore: onboarding steps workflow verification

* chore: onboarding variable update
2023-06-14 13:17:35 +05:30
guru_sainathandGitHub 4b25b7244b promote: develop to staging 2023-06-13 15:40:12 +05:30
guru_sainathandGitHub f774603b7f chore: onboarding workflow in authentication (#1281) 2023-06-13 14:37:25 +05:30
pablohashescobarandGitHub 15b5db0cae dev: upgrade python runtime (#1256) 2023-06-12 10:02:16 +05:30
66807bef0d fix: social auth authentication workflow (#1264)
* fix: github login mutation

* dev: updated social auth workflow and handled multiple loads on user

* dev: mutaing user and updated analytics logout issue resolved

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-06-10 01:26:38 +05:30
Vamsi KuramaandGitHub 49f19c2c44 Merge pull request #1257 from makeplane/stage-release
promote: stage release to production
2023-06-09 16:27:25 +05:30
guru_sainathandGitHub 9529ddb393 Merge pull request #1262 from makeplane/develop
promote: develop to stage-relaease
2023-06-09 16:23:23 +05:30
Aaryan KhandelwalandGitHub e7af3da115 chore: update remove file function logic (#1259)
* chore: update remove file function logic

* fix: workspace file delete endpoint
2023-06-09 10:24:57 +05:30
guru_sainathandGitHub d09f410f21 Merge pull request #1252 from makeplane/develop
promote: develop to stage release
2023-06-08 00:19:08 +05:30
pablohashescobarandGitHub 98e6a1366c chore: update workspace invitation email redirection url (#1236)
* chore: update workspace invitation email redirection url

* dev: update workspace invitation mail
2023-06-08 00:14:53 +05:30
pablohashescobarandGitHub 754142afa2 fix: workspace and project member user deletion (#1241)
* fix: workspace and project member user deletion

* fix: workspace member deletion

* dev: add comments
2023-06-08 00:14:41 +05:30
Aaryan KhandelwalandGitHub 42fceb4dcd fix: user profile data mutation (#1243) 2023-06-07 19:03:49 +05:30
Aaryan KhandelwalandGitHub e949c4e130 chore: fetch only high priority issues for the active cycle (#1228) 2023-06-07 19:03:02 +05:30
Anmol Singh BhatiaandGitHub 78c1a64690 fix: assignee dropdown, sign in button, and onboarding flicker fix (#1242) 2023-06-07 17:45:57 +05:30
guru_sainathandGitHub 3d5fcbd4ce dev: route validation on non authenticated pages (#1238) 2023-06-07 13:47:56 +05:30
Anmol Singh BhatiaandGitHub f9cd1b1352 fix: last workspace id (#1237) 2023-06-07 13:18:45 +05:30
Peter Dave HelloandGitHub 18f66805cb Improve apk usages in Dockerfile (#1198) 2023-06-07 12:30:42 +05:30
pablohashescobarandGitHub 382a1343ea fix: file asset uploads in workspace (#1234) 2023-06-07 12:21:09 +05:30
pablohashescobarandGitHub c05eb9e240 fix: forgot password email subject and update template (#1233) 2023-06-07 09:09:59 +05:30
Anmol Singh BhatiaandGitHub 40d2990565 fix: onboarding ui fix (#1225) 2023-06-07 01:56:58 +05:30
Aaryan KhandelwalandGitHub 684df96969 chore: replace nextjs Image element (#1227) 2023-06-07 01:56:21 +05:30
1f3fdd5d0a feat: reset password page for self-hosted added (#1221)
* feat: reset password page for self-hosted added

* chore: change reset password workflow

* dev: update email template for reset password

* chore: updated restricted workspace slugs list

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
2023-06-06 21:36:13 +05:30
Anmol Singh BhatiaandGitHub 6f2a38ad66 fix: bug and auth fixes (#1224)
* fix: sign in and invitation page fixes

* fix: project and workspace services track event fix

* fix: user onboarding complete track event fix

* fix: issue track event fix

* fix: partial property , issue comment and mark as done issue track event fix

* fix: bulk delete , move to cycle or module and issue label track event fix

* fix: state , cycle and module track event fix

* fix: pages and block track event fix

* fix: integration , estimate , importer , analytics and gpt track event fix

* fix: view track event fix

* fix: build fix

* fix: build fix
2023-06-06 21:36:00 +05:30
pablohashescobarandGitHub c127353281 chore: workspace invite created detail (#1209)
* chore: workspace invite created detail

* dev: select related workspace member invite list
2023-06-06 19:15:56 +05:30
pablohashescobarandGitHub 705371eaf3 fix: file upload size limit (#1218) 2023-06-06 19:15:42 +05:30
pablohashescobarandGitHub fae9d8cdc1 chore: reset password url (#1220)
* chore: reset password url

* dev: update password reset endpoint

* dev: update reset password url
2023-06-06 19:15:20 +05:30
pablohashescobarandGitHub b6c0ddac50 chore: move minio endpoint url to environment configuration (#1210) 2023-06-06 08:21:57 +05:30
pablohashescobarandGitHub 557e96c68e fix: email tls when selfhosting (#1206) 2023-06-05 21:26:04 +05:30
7eae6b4c9e fix: user public authentication workflow updates (#1207)
* auth integration fixes

* auth integration fixes

* auth integration fixes

* auth integration fixes

* dev: update user api to return fallback workspace and improve the structure of the response

* dev: fix the issue keyerror and move onboarding logic to serializer method field

* dev: use-user-auth hook imlemented for route access validation and build issues resolved effected by user payload

* fix: global theme color fix

* style: new onboarding ui , fix: use-user-auth hook implemented

* fix: command palette, project invite modal and issue detail page mutation type fix

* fix: onboarding redirection fix

* dev: build isuue resolved

* fix: use user auth hook fix

* fix: sign in toast alert fix, sign out redirection fix and user theme error fix

* fix: user response fix

* fix: unAuthorizedStatus logic updated

* dev: Implemented SEO in app.tsx

* dev: User public auth workflow updates.

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: anmolsinghbhatia <anmolsinghbhatia@caravel.tech>
2023-06-05 17:48:29 +05:30
pablohashescobarandGitHub 77e05a3599 fix: project member role update (#1205) 2023-06-05 17:45:10 +05:30
pablohashescobarandGitHub 2511a284eb feat: plane proxy setup (#1181)
* feat: plane proxy setup

* dev: remove port mapping from web, api and minio containers
2023-06-05 12:53:04 +05:30
pablohashescobarandGitHub e3da80755c fix: minio settings (#1172) 2023-06-05 12:52:25 +05:30
pablohashescobarandGitHub 58d1d8f132 fix: issue search for blocking and blocked_by condition (#1182)
* fix: issue search for blocking and blocked_by condition

* fix: issue search endpoint blockers

* fix: rectify the filter parameters
2023-06-05 12:51:30 +05:30
pablohashescobarandGitHub 50060a0bf9 chore: update docker uploads (#1202) 2023-06-05 12:51:12 +05:30
pablohashescobarandGitHub bffc6a60e7 fix: workspace member role update (#1203) 2023-06-05 12:50:44 +05:30
pablohashescobarandGitHub 799cf230b7 Revert "Update README.md (#1189)" (#1193)
This reverts commit 37442f482b.
2023-06-04 15:51:06 +05:30
tarunratanandGitHub 37442f482b Update README.md (#1189)
change in readme of docker-compose
2023-06-04 09:49:13 +05:30
Nathanael DemaconandGitHub c234f2a087 docs: fix docker compose quick start (#1173)
* docs: add missing .env.example fetch in docker compose quick start

* docs: clone the repository instead of fetching particular files
2023-06-02 12:27:15 +05:30
Anmol Singh BhatiaandGitHub 857879f5ed fix : theme provider fix (#1164) 2023-06-01 14:40:27 +05:30
44f8ba407d Authentication Workflow fixes. Redirection fixes (#832)
* auth integration fixes

* auth integration fixes

* auth integration fixes

* auth integration fixes

* dev: update user api to return fallback workspace and improve the structure of the response

* dev: fix the issue keyerror and move onboarding logic to serializer method field

* dev: use-user-auth hook imlemented for route access validation and build issues resolved effected by user payload

* fix: global theme color fix

* style: new onboarding ui , fix: use-user-auth hook implemented

* fix: command palette, project invite modal and issue detail page mutation type fix

* fix: onboarding redirection fix

* dev: build isuue resolved

* fix: use user auth hook fix

* fix: sign in toast alert fix, sign out redirection fix and user theme error fix

* fix: user response fix

* fix: unAuthorizedStatus logic updated

---------

Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: gurusainath <gurusainath007@gmail.com>
Co-authored-by: anmolsinghbhatia <anmolsinghbhatia@caravel.tech>
2023-05-30 19:14:35 +05:30
Anmol Singh BhatiaandGitHub 33db616767 fix: default status set for module form (#1160) 2023-05-29 22:16:32 +05:30
Anmol Singh BhatiaandGitHub c949c4d244 fix: empty module mutation and issue view flicker fix (#1158) 2023-05-29 18:35:43 +05:30
Anmol Singh BhatiaandGitHub c8caa925b1 chore: page and cycle refactor (#1159)
* fix: release note modal fix

* chore: cycle and page endpoint refactor
2023-05-29 18:35:16 +05:30
Aaryan KhandelwalandGitHub 26ba4d71c3 fix: order by last updated, cycles empty state alignment (#1151)
* fix: order by last updated

* fix: cycles empty space alignment, chore: new meta tags

* chore: update meta tags
2023-05-29 15:38:35 +05:30
Aaryan KhandelwalandGitHub 022960d7e3 fix: pages layout and alignment issues (#1152) 2023-05-29 15:16:49 +05:30
guru_sainathandGitHub dce3c3f008 Merge pull request #1153 from makeplane/fix/cycles_layout
refactor: cycles list code and folder structure
2023-05-29 15:16:10 +05:30
pablohashescobarandGitHub ffc6077e9b chore: improve docker setup (#1150) 2023-05-29 12:11:16 +05:30
guru_sainathandGitHub 23d08a2ad1 Merge pull request #1156 from makeplane/hot-fix
promote: hot-fix to develop
2023-05-29 10:46:45 +05:30
Aaryan Khandelwal 1c98f2dca9 chore: update cycle fetch keys names 2023-05-29 02:38:16 +05:30
Aaryan Khandelwal 053ebc031d fix: cycles layout 2023-05-29 02:14:40 +05:30
pablohashescobarandGitHub c9dee593eb chore: total members in user workspace invites (#1143) 2023-05-28 18:11:59 +05:30
pablohashescobarandGitHub bc8430220b chore: email settings for docker setup (#1148) 2023-05-28 18:11:36 +05:30
MarksandGitHub 030558c026 Update text during the onboarding process (#1149)
* Update text during the onboarding process

A few of the phrases were worded in ways that took a bit longer to process. These changes should make onboarding more comfortable.

* Update text
2023-05-28 17:42:01 +05:30
pablohashescobarandGitHub d8c367c51e docs: update self hosting setup script (#1145) 2023-05-28 14:47:25 +05:30
EliandGitHub 1afb3ba4d2 chore: speedup replace-env-vars.sh (#1146) 2023-05-28 10:04:54 +05:30
pablohashescobarandGitHub 8252b1ccde chore: configuration for nginx port (#1144) 2023-05-28 09:56:15 +05:30
Anmol Singh BhatiaandGitHub 394f0bf555 chore: cycles endpoint updated and code refactor (#1135)
* chore: cycles endpoint updated and code refactor

* chore: incomplete cycle endpoint updated

* chore: code refactor
2023-05-26 15:38:56 +05:30
Anmol Singh BhatiaandGitHub 4ce0ac6ea1 chore: pages endpoint updated (#1137) 2023-05-26 15:38:44 +05:30
pablohashescobarandGitHub cd821a934d chore: update single click deployments (#1141)
* chore: update single click deployments

* dev: update environment variables
2023-05-26 14:04:15 +05:30
pablohashescobarandGitHub f80b3f1eb1 fix: issue ordering for priority and updated_by parameters (#1142) 2023-05-26 13:51:09 +05:30
pablohashescobarandGitHub b6321438ce chore: docker setup (#1136)
* chore: update docker environment variables and compose file for better readability

* dev: update single dockerfile

* dev: update WEB_URL configuration

* dev: move database settings to environment variable

* chore: remove port configuration from default compose file

* dev: update example env to add EMAIL_FROM and default values for AWS
2023-05-26 11:09:59 +05:30
pablohashescobarandGitHub 16604dd31b refactor: page views endpoint (#1130) 2023-05-25 14:13:54 +05:30
Aaryan KhandelwalandGitHub 11b28048bf chore: analytics tracker events (#1119)
* chore: added export analytics event tracker

* chore: add analytics view events

* chore: workspace analytics view track event added
2023-05-25 12:38:53 +05:30
Dakshesh JainandGitHub 7e5b26ea82 fix: project ordering messing up in projects page (#1122) 2023-05-25 12:38:15 +05:30
pablohashescobarandGitHub 5beb50fa76 fix: role updation (#1110) 2023-05-25 12:27:04 +05:30
pablohashescobarandGitHub af2d7d6f75 fix: project member delete when deleting user from workspace (#1123)
* fix: project member delete when deleting user from workspace

* fix: workspace and project member delete
2023-05-25 12:25:15 +05:30
pablohashescobarandGitHub e608b58e70 refactor: cycle views endpoint (#1128) 2023-05-25 12:24:39 +05:30
pablohashescobarandGitHub a16514ed11 fix: auto generated secret key to only generate hexadecimal characters (#1133) 2023-05-25 12:24:03 +05:30
Anmol Singh BhatiaandGitHub 74329a49cc fix: send code button behavior on enter key press (#1121) 2023-05-25 12:19:48 +05:30
Anmol Singh BhatiaandGitHub def391cb76 feat: storing my issue view display properties (#1124) 2023-05-25 12:19:37 +05:30
e526a01295 Clean up docker compose file for selfhosting (#1022)
* Clean up docker compose file

Removed nginx container (might want to put it back?)
Changed spacing to tabs for better readability
Changed the order, first the important stuff (plane) and later the database/redis
All containers should be in the same format (first container_name, then image, then restart, etc.).
Removed links because deprecated since compose version 2, all containers are in one docker network
Removed build from plane-api
Removed ports from redis and postgresql
Removed PGDATA directory because that's the default one
Renamed redis and db to plane-redis and plane-db

* Fixed spacing (again)

* Fix spacing (attempt 3)

* Pasting error - should be good now

* New compose download instructions

---------

Co-authored-by: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com>
2023-05-25 10:49:37 +05:30
0fb4a87454 fix: docker image uploads (#1108)
* dev: basic initial setup for images

* Update docker-compose.yml

* dev: minio setup

* dev: docker minio setup

* dev: update the asset view

* dev: setup minio with default configuration

* dev: update minio setup for creating buckets

* dev: update the permission sets

* dev: get variables from shell for create bucket

* dev: update image uploading setup for docker

* dev: environment variables update

* dev: web url for images

* dev: update image configuration

* dev: env update for email port

---------

Co-authored-by: Narayana <narayana.vadapalli1996@gmail.com>
2023-05-25 10:24:20 +05:30
Vihar KuramaandGitHub 0bd6e53b44 docs: updated Readme with new features and screenshots (#1132)
* update readme images with new features

* remove line-break on readme

* update plane analytics image
2023-05-24 21:42:40 +05:30
gurusainath f095594be9 update: Handled empty state in adding members to project. 2023-05-22 22:38:22 +05:30
guru_sainathandGitHub 2e638b28b6 dev: added tooltip to title and added info for issues (#1112)
* update: tooltip in the blocks

* dev: added tooltip to title and added info for issues
2023-05-22 22:09:52 +05:30
guru_sainathandGitHub aaffe37fbe fix: Showing status for accounts not created in the workspace members (#1111) 2023-05-22 22:09:38 +05:30
Vamsi KuramaandGitHub 5c632921f8 Merge pull request #1102 from makeplane/stage-release
promote: stage-release to master
2023-05-22 00:20:23 +05:30
guru_sainathandGitHub 7c9035182b Merge pull request #1105 from makeplane/hot-fix
promote: hot-fix to stage-release
2023-05-21 19:42:15 +05:30
gurusainath b5325f14aa fix: gantt build issue resolved 2023-05-21 19:38:33 +05:30
gurusainath 4238f89cec dev: gantt ui changes 2023-05-21 19:27:08 +05:30
guru_sainathandGitHub a44cddb0fc dev: dropdown overflow issue resolved in kanban (#1106) 2023-05-21 19:08:28 +05:30
guru_sainathandGitHub 83a0c8163f dev: redirection implementation on gantt blocks (#1104) 2023-05-21 18:37:26 +05:30
gurusainath c8c195eab4 Merge branch 'stage-release' of gurusainath:makeplane/plane into stage-release 2023-05-20 23:32:07 +05:30
guru_sainathandGitHub 0acee1fe66 Merge pull request #1103 from makeplane/develop
fix: updated discord link on readme (#1101)
2023-05-20 23:31:32 +05:30
Vihar KuramaandGitHub e5f6be54e0 fix: updated discord link on readme (#1101) 2023-05-20 23:30:05 +05:30
guru_sainathandGitHub 15a846ce06 Merge pull request #1100 from makeplane/hot-fix
promote: hotfix to stage-release
2023-05-20 23:28:29 +05:30
guru_sainathandGitHub a01e241523 styles: UI changes in the gantt blocks (#1099) 2023-05-20 23:17:44 +05:30
pablohashescobarandGitHub cba62f07c0 chore: analytic export mail (#1098) 2023-05-20 23:16:19 +05:30
gurusainath c1f8766571 styles: UI changes in the gantt blocks 2023-05-20 23:10:04 +05:30
guru_sainathandGitHub af13a1b00a Merge pull request #1097 from makeplane/fix/cycle_view
fix: cycle view & create label modal
2023-05-20 22:43:55 +05:30
anmolsinghbhatia 4439740768 fix: create label modal error message updated 2023-05-20 22:30:44 +05:30
anmolsinghbhatia 98a223e5e1 fix: cycle view board view fix , feat: on gantt view set all cycle as default tab 2023-05-20 22:24:05 +05:30
guru_sainathandGitHub e081395857 Merge pull request #1095 from makeplane/develop
promote: develop to stage-release
2023-05-20 20:24:27 +05:30
gurusainath 8b527f27d0 dev: migrations for the analytics fields 2023-05-20 20:18:22 +05:30
guru_sainathandGitHub ae67dc6074 update: changed icons for gantt chat in issues, modules and cycles (#1096) 2023-05-20 20:08:17 +05:30
e1e9a5ed96 feat: Gantt chart (#1062)
* dev: Helpers

* dev: views

* dev: Chart views Month, Year and Day

* dev: Chart Workflow updates

* update: scroll functionality implementation

* update: data vaidation

* update: date renders and issue filter in the month view

* update: new date render month view

* update: scroll enabled left in chart

* update: Item render from the date it created.

* update: width implementation in chat view

* dev: chart render functionality in the gantt chart

* update: month view fix

* dev: chart render issues resolved

* update: fixed allchat views

* update: updated week view default values

* update: integrated chart view in issues

* update: grabble and sidebar logic impleemntation and integrated gantt in issues

* update: Preview gantt chart in month view

* fix: mutation in gantt chart after creating a new issue

* chore: cycles and modules list gantt chart

* update: Ui changes on gantt view

* fix: gantt chart height, chore: remove link from issue

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-05-20 17:30:15 +05:30
Aaryan KhandelwalandGitHub 9ccc35d181 fix: make custom search select handle undefined options (#1094)
* fix: make search select handle undefined options

* fix: kanban y-scroll
2023-05-20 16:00:41 +05:30
pablohashescobarandGitHub 012486df11 chore: assignee names in analytics export (#1086)
* chore: assignee names in analytics export

* chore: update key as assignee name
2023-05-20 16:00:02 +05:30
Anmol Singh BhatiaandGitHub 1fed5f7846 style: active cycle stats tab sticky (#1092) 2023-05-20 03:08:23 +05:30
Aaryan KhandelwalandGitHub 8cbe6c4b36 fix: show only filtered states when state filter is selected (#1093) 2023-05-20 03:07:00 +05:30
Dakshesh JainandGitHub d9642eee82 fix: ai response not coming when using 'AI' button (#1091)
* fix: ai response not comming for page

* fix: ai response not coming when using 'AI' button
2023-05-19 20:33:35 +05:30
Anmol Singh BhatiaandGitHub ab273f6be3 fix: sidebar dropdown fix, feat: assignee name updated (#1089)
* fix: issue sidebar cycle and module dropdown width fix

* feat: issue sidebar, issue card and issue modal assignee full name added
2023-05-19 20:00:09 +05:30
Dakshesh JainandGitHub b1f26f322f style: added theme in color picker (#1088) 2023-05-19 19:46:09 +05:30
Dakshesh JainandGitHub 2e4f936dfa fix: ai response not comming for page (#1087) 2023-05-19 19:12:34 +05:30
Dakshesh JainandGitHub ddeafc0695 fix: inconsistency for create label, select label getting closed on select (#1085) 2023-05-19 19:12:24 +05:30
Anmol Singh BhatiaandGitHub 406f02737f feat: kanban board issue menu context (#1084) 2023-05-19 19:12:15 +05:30
Dakshesh JainandGitHub 8e9afd459a fix: create issue state not clearing on create more (#1082) 2023-05-19 19:11:56 +05:30
Anmol Singh BhatiaandGitHub 186b5b5500 feat: cycle gantt view option added (#1083) 2023-05-19 18:08:47 +05:30
Aaryan KhandelwalandGitHub f2c8bdba34 fix: assignee name in analytics tooltip (#1081) 2023-05-19 17:08:32 +05:30
pablohashescobarandGitHub e162c88f03 dev: project emoji and icons (#1076) 2023-05-19 16:46:56 +05:30
Aaryan KhandelwalandGitHub 7f5fdb9589 fix: icon picker not working (#1080)
* fix: icon picker not working

* fix: project icon in analytics sidebar
2023-05-19 16:35:51 +05:30
Anmol Singh BhatiaandGitHub e3a114cd69 fix: module and cycle create issue in calendar view mutation fix (#1079) 2023-05-19 16:15:06 +05:30
Anmol Singh BhatiaandGitHub ae1eb9527a style: ui improvement (#1077)
* style: assignee, sub-issue and due date display properties styling

* style: cycle view  indicator added
2023-05-19 15:22:09 +05:30
Dakshesh JainandGitHub 6da4247400 fix: Application Error on issues list page (#1064)
* fix: Application Error on issues list page

* fix: can't read property of undefined at renderTick
2023-05-19 15:13:55 +05:30
Dakshesh JainandGitHub 4ce5a450d9 fix: pages 'I'm feeling lucky' not working (#1058) 2023-05-19 15:13:33 +05:30
pablohashescobarandGitHub 06618006c2 fix: cycle date check endpoint (#1074) 2023-05-19 13:00:38 +05:30
pablohashescobarandGitHub bb79c9de96 chore: start date for issues (#1075) 2023-05-19 12:36:12 +05:30
Aaryan KhandelwalandGitHub 09cffd5498 fix: analytics colors when segmented by cycle, module, dates, and assignees (#1072)
* fix: show unique colors when segmented

* chore: modify random color generator function

* chore: modify random color generator function
2023-05-18 19:07:24 +05:30
Anmol Singh BhatiaandGitHub 5916d6e749 fix: bug and ui fix (#1073)
* fix: cycle and module sidebar scroll

* style: date picker theming

* style: workspace slug spacing

* fix: app sidebar z-index

* fix: favorite cycle mutation

* fix: cycle modal on error close

* feat: cycle view context

* style: active cycle stats scroll

* fix: active cycle favorite mutation

* feat: import export banner

* feat: cycle sidebar date picker logic updated

* fix: NaN in progress percentage fix

* fix: tooltip fix

* style: empty state for active cycle

* style: cycle badge width fix , all cycle empty state fix and cycle icon size fix
2023-05-18 19:07:01 +05:30
pablohashescobarandGitHub c3d520aefd fix: analytics segmented export (#1068)
* fix: analytics segmented export

* dev: fix none type

* fix: analytic export y axis count
2023-05-17 18:32:10 +05:30
Aaryan KhandelwalandGitHub d41250c1ce chore: delete label confirmation modal (#1069)
* fix: negative days displayed on upcoming issues on dashboard

* chore: show completed and cancelled states by default

* chore: delete label confirmation modal
2023-05-17 16:04:56 +05:30
pablohashescobarandGitHub 27626fb16f fix: default analytic estimate points and sorting for custom analytics (#1066) 2023-05-17 14:58:45 +05:30
Aaryan KhandelwalandGitHub ab695a309f fix: layout padding, tabs size and page heading font sizes (#1067) 2023-05-17 14:57:58 +05:30
Aaryan KhandelwalandGitHub 3427652c22 chore: update analytics sidebar and header content, fix: trash box positioning (#1065)
* fix: labels dropdown on issue details page theming

* style: trash box styling and positioning

* chore: empty state for scope and demand analytics, show assignee name in scope graph tooltip

* chore: empty state for analytics

* chore: modify analytics sidebar and header
2023-05-17 13:25:58 +05:30
559b0cc9c8 style: cycle new ui (#1052)
* style: cycles new ui

* chore: added progress bar for the high priority issues

* fix: build fix

* style: active cycle details, theming , padding and layout

* style: cycle list and card styling

* style: cycle card

* fix: tooltip text overflow

* fix: cycle mutation fix

* style: cycle list and card view improvement, chore: code refactor

* feat: view cycle button

* style: cycle list and board view improvement

* style: responsiveness added

* feat: active cycle stats component, chore: code refactor

* fix: active cycle divider fix, style: stats font color

* fix: tooltip fix

---------

Co-authored-by: kunal_17 <kunalvish17360@gmail.com>
2023-05-17 12:58:01 +05:30
pablohashescobarandGitHub c49b0d6151 fix: issue assignee multiple values (#1056)
* fix: issue assignee multiple values

* chore: return first name and last name

* dev: keys update and also send data when segmented
2023-05-17 00:46:41 +05:30
pablohashescobarandGitHub d99f85ce05 chore: analytics assignee email for default analytics (#1057) 2023-05-17 00:46:20 +05:30
pablohashescobarandGitHub e696a3741c chore: analytics data ordering (#1059) 2023-05-17 00:45:59 +05:30
Aaryan KhandelwalandGitHub d575e8ec6b chore: x-axis tick values for assignees (#1060)
* style: new custom analytics ui

* fix: x-axis assignee tick values

* chore: assignee names in the custom analytics table
2023-05-16 15:11:40 +05:30
pablohashescobarandGitHub c060f7db30 chore: user first name and last name for default analytics (#1054) 2023-05-16 10:50:26 +05:30
pablohashescobarandGitHub 84a0f6f77e chore: rename effort to estimate (#1053) 2023-05-16 10:50:14 +05:30
Aaryan KhandelwalandGitHub c6d78b5e6a style: new custom analytics ui (#1055) 2023-05-16 10:41:37 +05:30
Aaryan KhandelwalandGitHub 8c707cc544 fix: lower role user cannot update higher role user (#1048)
* fix: user role update

* chore: update project member update service type
2023-05-15 23:58:41 +05:30
pablohashescobarandGitHub abe021071c fix: workspace and project member update (#1046) 2023-05-15 19:38:37 +05:30
pablohashescobarandGitHub 8d6082183e chore: return assignee avatars when x axis in assignee_email (#1049) 2023-05-15 19:38:08 +05:30
pablohashescobarandGitHub 7b52fb885d remove: onboarding emails (#1050)
* remove: user onboarding mail

* remove: welcome emails
2023-05-15 19:37:40 +05:30
pablohashescobarandGitHub d28bc41fbd chore: project members, cycles and modules count (#1051) 2023-05-15 19:37:17 +05:30
pablohashescobarandGitHub 63075a6a0d fix: analytics export and send month and year when dimension in date (#1039)
* fix: send month and year when dimension in date

* fix: export csv email

* fix: export for segment and fix segment for date values
2023-05-15 11:37:07 +05:30
Aaryan KhandelwalandGitHub 9fdc56db0f chore: user cannot update their own role (#1041) 2023-05-15 11:35:19 +05:30
Aaryan KhandelwalandGitHub 290318603d fix: calendar view mutation (#1042) 2023-05-15 11:35:07 +05:30
Anmol Singh BhatiaandGitHub dbbd9add99 fix: ui and bug fix (#1043)
* style: calendar border added

* fix: calendar issue ellipsis position fix

* fix: help section overflow fix

* fix: module card date overflow fix

* style: page detail padding and position fix

* fix: cycle and module sidebar fix
2023-05-15 11:32:50 +05:30
Aaryan KhandelwalandGitHub 37bb183bf0 chore: update analytics x-axis, tooltip and table heading values (#1040)
* chore: global bar graph component

* chore: global pie graph component

* chore: global line graph component

* chore: removed unnecessary file

* chore: refactored global chart components to accept all props

* chore: function to convert response to chart data

* chore: global calendar graph component added

* chore: global scatter plot graph component

* feat: analytics boilerplate created

* chore: null value for segment and project

* chore: clean up file

* chore: change project query param key

* chore: export, refresh buttons, analytics table

* fix: analytics fetch key error

* chore: show only integer values in the y-axis

* chore: custom x-axis tick values and bar colors

* refactor: divide analytics page into granular components

* chore: convert analytics page to modal, save analytics modal

* fix: build error

* fix: modal overflow issues, analytics loading screen

* chore: custom tooltip, refactor: graphs folder structure

* refactor: folder structure, chore: x-axis tick values for larger data

* chore: code cleanup

* chore: remove unnecessary files

* fix: refresh analytics button on error

* feat: scope and demand analytics

* refactor: scope and demand and custom analytics folder structure

* fix: dynamic import type

* chore: minor updates

* feat: project, cycle and module level analytics

* style: project analytics modal

* fix: merge conflicts

* style: new scope and demand ui, user avatars in bar graph

* fix: default analytics types

* chore: new values when dimensioned by date

* chore: update table and tooltip content when dimensioned or segmented by dates
2023-05-15 11:22:06 +05:30
vamsi 512b8c104d dev: analytics migrations 2023-05-12 17:02:34 +05:30
Aaryan KhandelwalandGitHub bf865f399f fix: kanban board horizontal scroll (#1038)
* fix: kanban board horizontal scroll

* chore: droppable placeholder position
2023-05-12 12:41:31 +05:30
pablohashescobarandGitHub 6a78948113 fix: analytics (#1037)
* fix: most issue created by user keys

* fix: cycle and module filters for GET method
2023-05-12 12:22:42 +05:30
pablohashescobarandGitHub 6e9235e5fe fix: analytic query params (#1035)
* fix: query params for analytics

* fix: default analytics filters
2023-05-11 20:53:54 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
f2a68874f1 chore(deps): bump django in /apiserver/requirements (#1036)
Bumps [django](https://github.com/django/django) from 3.2.18 to 3.2.19.
- [Commits](https://github.com/django/django/compare/3.2.18...3.2.19)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-11 20:25:08 +05:30
Anmol Singh BhatiaandGitHub 9cb3794dde fix: page block padding fix (#1034) 2023-05-11 18:41:13 +05:30
Anmol Singh BhatiaandGitHub 1329145173 feat: custom theming (#1028)
* chore: custom theme types and constants

* feat: custom theming

* feat: preferences tab added in profile settings

* feat: remove unneccessary file

* feat:theme apply on page load

* fix: theme switch dropdown fix

* feat: color picker input, theme icon added, chore: code refactor

* style: color picker icon added

* fix: mutation fix

* fix: palette sequence fix

* chore: default custom theme palette updated

* style: join project and not authorized page theming

* fix: merge conflict

* fix: build fix and preferences tab layout fix
2023-05-11 18:40:17 +05:30
Anmol Singh BhatiaandGitHub 44d49b5500 style: page detail (#1030)
* style: page detail header styling

* style: page detail ui

* style: page block, create block styling
2023-05-11 18:03:37 +05:30
Aaryan KhandelwalandGitHub 1a534a3c19 feat: plane analytics (#1029)
* chore: global bar graph component

* chore: global pie graph component

* chore: global line graph component

* chore: removed unnecessary file

* chore: refactored global chart components to accept all props

* chore: function to convert response to chart data

* chore: global calendar graph component added

* chore: global scatter plot graph component

* feat: analytics boilerplate created

* chore: null value for segment and project

* chore: clean up file

* chore: change project query param key

* chore: export, refresh buttons, analytics table

* fix: analytics fetch key error

* chore: show only integer values in the y-axis

* chore: custom x-axis tick values and bar colors

* refactor: divide analytics page into granular components

* chore: convert analytics page to modal, save analytics modal

* fix: build error

* fix: modal overflow issues, analytics loading screen

* chore: custom tooltip, refactor: graphs folder structure

* refactor: folder structure, chore: x-axis tick values for larger data

* chore: code cleanup

* chore: remove unnecessary files

* fix: refresh analytics button on error

* feat: scope and demand analytics

* refactor: scope and demand and custom analytics folder structure

* fix: dynamic import type

* chore: minor updates

* feat: project, cycle and module level analytics

* style: project analytics modal

* fix: merge conflicts
2023-05-11 17:38:46 +05:30
Aaryan KhandelwalandGitHub d7928f853d chore: global graph components (#1014)
* chore: global bar graph component

* chore: global pie graph component

* chore: global line graph component

* chore: removed unnecessary file

* chore: refactored global chart components to accept all props

* chore: global calendar graph component added

* chore: global scatter plot graph component
2023-05-11 17:11:04 +05:30
Dakshesh JainandGitHub a0553722c9 fix: settings editor state according to ai response (#1032)
* fix: placeholder and ai response not getting appended in textarea

* fix: settings editor state according to ai response
2023-05-11 17:02:20 +05:30
Anmol Singh BhatiaandGitHub 34f4580b94 style: issue due date selector width fix (#1033) 2023-05-11 17:01:58 +05:30
pablohashescobarandGitHub a1d7a4ea55 fix: created by null for bulk operations (#1026) 2023-05-11 16:58:35 +05:30
pablohashescobarandGitHub abaa65b4b7 feat: analytics (#1018)
* dev: initialize plane analytics

* dev: plane analytics endpoint

* dev: update endpoint to give data with segments as well

* dev: analytics with count and effort paramters

* feat: analytics endpoints

* feat: saved analytics

* dev: remove print logs

* dev: rename x_axis to dimension in response

* dev: remove color queries

* dev: update query for None values

* feat: analytics export

* dev: update code structure send color when state or label and fix none count

* dev: uncomment try catch block

* dev: fix segment keyerror

* dev: default analytics endpoint

* dev: fix segmented results

* dev: default analytics endpoint and colors for segment

* dev: total issues and open issues by state

* dev: segment colors

* dev: fix total issue annotate

* dev: effort segmentation

* dev: total estimates and open estimates

* fix: effort when not segmented

* dev: send avatar for default analytics
2023-05-11 16:57:03 +05:30
pablohashescobarandGitHub fb165d080e feat: estimate points data in cycles list endpoint (#1015)
* feat: estimate points data in cycles list endpoint

* dev: prefetch for assignees

* dev: update sum for estimate points
2023-05-11 16:56:48 +05:30
Dakshesh JainandGitHub 083562b24c fix: placeholder and ai response not getting appended in textarea (#1031) 2023-05-11 13:41:16 +05:30
Anmol Singh BhatiaandGitHub 88d3fa549a style: page block hover effect (#1017) 2023-05-11 02:27:27 +05:30
Anmol Singh BhatiaandGitHub c7deb00f2a feat: calendar view display properties (#1024)
* fix: calendar mutation fix, chore: calendar type added

* feat: calendar view display property added

* feat: calendar header, single date and single issue component added, chore: code refactor

* chore: partialupdateissue function updated

* fix: dropdown overflow fix, style: issue card styling

* fix: calendar weekly view row height fix

* feat: calendar issue card ellipsis added, fix: edit and delete mutation fix

* style: plus icon , add issue button padding and onhover effect fix

* style: calendar issue card

* style: weekly view height
2023-05-11 02:27:14 +05:30
4f2b106852 fix: rich text editor (#1008)
* fix: undo/redo, placeholder overlapping with text, horizontal cursor

refractor: removed a lot of state-management that was not required

* fix: forwarding ref to remirror for getting extra helper functions

* fix: icon type error

* fix: value type not supported error on page block

* style: spacing, and UX for add link

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-05-11 02:15:49 +05:30
Aaryan KhandelwalandGitHub df96d40cfa fix: views issues mutation, sidebar link highlight (#1025)
* fix: views issues mutation, sidebar link highlight

* fix: show only specific states when type filter is set

* fix: delete comment mutation

* style: bulk delete issues modal

* fix: project settings features mutation
2023-05-11 02:15:39 +05:30
vamsi 4884ecd668 dev: migrations for estimate point values 2023-05-05 19:48:38 +05:30
vamsi c16c5b1cb2 Merge branch 'develop' of https://github.com/makeplane/plane into develop 2023-05-05 19:46:45 +05:30
guru_sainathandGitHub a69593a9e8 fix: handled token expiry validation (#1016) 2023-05-05 18:01:58 +05:30
a1de3f581f fix: layout height and overflow (#1004)
* fix: kanban height issue

* dev: Layout fixes

* dev: layout changes

* fix: layout overflow settings and fixed header

* style: filters padding fixed

* fix: hide filters if none are applied

---------

Co-authored-by: gurusainath <gurusainath007@gmail.com>
2023-05-05 17:07:29 +05:30
Rhea JainandGitHub 443878994a fix: breadcrumbs and tab updated (#1007) 2023-05-05 15:46:05 +05:30
Anmol Singh BhatiaandGitHub 86cb23777e fix: bug fixes (#1000)
* fix: issue sidebar cycle and module dropdown fix

* style: my issue page

* style: date picker theming

* fix: cycle modal

* style: date picker

* fix: info icon fix

* feat: integration banner

* feat: project integration banner

* fix: module card progress bar fix

* style: integration banner

* style: workspace sidebar

* fix: cycle date checker

* fix: calendar page view dropdown
2023-05-05 15:45:53 +05:30
Aaryan KhandelwalandGitHub 93c105c495 chore: track events for estimates and importers (#1012) 2023-05-05 15:45:38 +05:30
Aaryan KhandelwalandGitHub b34cf0c471 fix: ai button not working on creating a page block (#1013)
* fix: ai button not working on creating page block

* fix: build error
2023-05-05 15:45:10 +05:30
pablohashescobarandGitHub fd96c54b43 fix: cycle date check endpoint for updation (#1006)
* fix: cycle date check endpoint for updation

* dev: update the cycle date check endpoint to exclude current cycle when updating
2023-05-05 15:13:22 +05:30
pablohashescobarandGitHub 993cf3faba chore: add assignee avatar in cycle endpoint (#996)
* chore: add assignee avatar in cycle endpoint

* dev: update the structure to return avatar and firstname

* dev: return distinct users

* dev: update the structure to return id

* dev: update the prefetch queryset to distinct by id

* dev: remove id from distinct

* dev: add unique condition
2023-05-05 15:13:03 +05:30
pablohashescobarandGitHub 1bf1b63fff fix: estimate points update (#1003)
* fix: estimate points hub

* fix: estimate points update

* fix: estimate points bulk_update
2023-05-05 15:12:38 +05:30
pablohashescobarandGitHub 336220bd98 feat: return workspace and project details in estimate endpoints (#1009) 2023-05-05 15:12:22 +05:30
pablohashescobarandGitHub 5b0dc43bae docs: update readme (#1010)
docs: update readme
2023-05-05 15:12:01 +05:30
pablohashescobarandGitHub e0bec31586 feat: workspace detail for imports (#1011) 2023-05-05 15:11:45 +05:30
pablohashescobarandGitHub a2825208b8 docs: update self hosting section in readme (#1002)
docs: update self hosting section in readme
2023-05-03 23:53:53 +05:30
pablohashescobarandGitHub c3387ba974 fix: environment variables for the services (#1001) 2023-05-03 21:31:06 +05:30
pablohashescobarandGitHub baa9c30449 fix: single docker file (#999) 2023-05-03 16:48:04 +05:30
pablohashescobarandGitHub 849e2d658a dev:: update docker file for frontend (#998) 2023-05-03 16:39:13 +05:30
c7f1090914 fix: docker setup (#987)
* removing dependencies from .env

* dev: Passing the arguments from docker compose to DockerWeb in nextjs to define base environment variables

* dev: removed env from docker-compose and taking the env from shell

* dev: Updated docker file and used console in signin to test the env from docker

* dev: Docker setting env variables via shell

* removed env variables and args

* Update Dockerfile.web

* Update Dockerfile.web

* Update signin.tsx

* .

* .

* dev: Added BASE_URL from docker

* dev: Updated docker config

* dev: scripts for replacing variable during runtime

* dev: entrypoint script

* dev: update replace env script and update docker entrypoint command for frontend

* dev: update replace env script to not update process.env

* dev: update docker file to add missing variables as well

* fix: updated docker compose yml and web

* dev: create start script to run docker and update script for replacing variables

* dev: update setup script and env example script to create variables in the root of the project

* .

* dev: update docker compose hub

* dev: update docker compose hub command

* dev: update docker compose yml and env example

* dev: update docker compose hub

* dev: single docker

---------

Co-authored-by: Narayana <narayana.vadapalli1996@gmail.com>
Co-authored-by: gurusainath <gurusainath007@gmail.com>
2023-05-03 13:36:55 +05:30
Vamsi KuramaandGitHub 563bb12b9b Merge pull request #993 from makeplane/stage-release
promote: stage-release to prod
2023-05-03 01:03:31 +05:30
vamsi e3d43298df Merge branch 'stage-release' of https://github.com/makeplane/plane into stage-release 2023-05-03 00:57:04 +05:30
pablohashescobarandGitHub b36565298f Merge pull request #992 from makeplane/develop
promote: develop to stage-release
2023-05-03 00:55:14 +05:30
guru_sainathandGitHub b26e8bd956 update: UI for google auth button and loadin toggle in signin screen (#991) 2023-05-03 00:48:24 +05:30
pablohashescobarandGitHub e5703dbe70 Merge pull request #990 from makeplane/develop
promote: develop to stage-release
2023-05-02 23:37:25 +05:30
Anmol Singh BhatiaandGitHub 498d6d2b02 style: onboarding logo and style (#989) 2023-05-02 23:31:36 +05:30
pablohashescobarandGitHub 1d22817ede fix: import user invite emails (#986) 2023-05-02 20:09:54 +05:30
Anmol Singh BhatiaandGitHub 483c49d0ff fix: ui and bug fixes (#980)
* style: sub issue theming

* style: shortcut modal theming

* style: blocking and blocked by modal theming

* fix: filter labels dropdown width fix

* style: display properties

* chore: workspace invite

* style: invite co workers theming

* style: create workspace theming

* style: attachment theming

* style: workspace sidebar theming

* style: issue property theming

* style: create module modal lead icon

* style: label list modal theming

* delete attachment and member modal theming

* style: transfer issue modal

* style: delete estimate modal theming

* style: module form status

* style: delete state modal theming

* style: shortcut modal

* style: onboarding logo

* style: onboarding command menu
2023-05-02 20:00:33 +05:30
guru_sainathandGitHub 0fa9451633 fix: workspace integration github repos issue resolved (#988) 2023-05-02 18:15:24 +05:30
pablohashescobarandGitHub 46237c5431 Merge pull request #866 from makeplane/develop
promote: develop to stage-release
2023-05-02 01:57:04 +05:30
vamsi 20e400487f dev: migrations SlackProjectSync; new attributes to cycle, importer and module 2023-05-02 01:55:15 +05:30
pablohashescobarandGitHub 99fb3c9bfe chore: log cycle delete and cycle issue delete activities (#975)
* chore: log cycle delete and cycle issue delete activities

* dev: remove print logs
2023-05-02 01:21:48 +05:30
pablohashescobarandGitHub 88200a93bf feat: delete imported data as well when the import is deleted (#974) 2023-05-02 00:53:08 +05:30
pablohashescobarandGitHub b2ad071608 fix: bulk modules importer (#973) 2023-05-02 00:52:52 +05:30
pablohashescobarandGitHub 21992f540f chore: importer user invite email (#972) 2023-05-02 00:52:36 +05:30
pablohashescobarandGitHub 742731cbe6 chore: log module delete and module issue delete activities (#976) 2023-05-02 00:51:04 +05:30
pablohashescobarandGitHub c6878b9b0f fix: page access update (#977) 2023-05-02 00:50:41 +05:30
pablohashescobarandGitHub 887cac5612 feat: filter issues by estimate points (#978) 2023-05-02 00:50:10 +05:30
guru_sainathandGitHub 982566b5b4 Merge pull request #982 from makeplane/fix/slack-integration
fix: slack with project integration bug fixes
2023-05-02 00:47:36 +05:30
gurusainath 2dc5655886 fix: slack with project integration bug fixes 2023-05-02 00:40:47 +05:30
guru_sainathandGitHub 160b4a4390 Merge pull request #970 from makeplane/refactor/estimates_modal
refactor: estimates payload generate function
2023-04-29 14:17:29 +05:30
guru_sainathandGitHub f187d9512a Merge pull request #969 from makeplane/style/workspace_sidebar
style: workspace sidebar
2023-04-29 14:15:41 +05:30
Aaryan Khandelwal 53d3ea1979 refactor: estimates payload generate function 2023-04-29 03:27:03 +05:30
anmolsinghbhatia 13d76d4325 style: workspace sidebar 2023-04-28 20:51:47 +05:30
guru_sainathandGitHub 25338cc804 Merge pull request #968 from makeplane/style/onboarding_screens
style: onboarding screens, invitations page
2023-04-28 20:50:40 +05:30
Aaryan Khandelwal eaa750b025 fix: initial onboarding step 2023-04-28 20:47:29 +05:30
Aaryan Khandelwal 81d9c70026 style: onboarding screens, invitations page 2023-04-28 20:46:07 +05:30
guru_sainathandGitHub 93fb4fe1e9 Merge pull request #959 from makeplane/feat/product_update_modal
feat: product update modal
2023-04-28 19:38:33 +05:30
guru_sainathandGitHub b8e6d072cc Merge pull request #967 from makeplane/style/ui_fixes
style: command k item and cycle icon theming
2023-04-28 19:03:16 +05:30
anmolsinghbhatia 1220cebe50 style: command k item and cycle icon theming 2023-04-28 18:52:53 +05:30
anmolsinghbhatia 9464b5c00e style: product update modal 2023-04-28 18:29:01 +05:30
anmolsinghbhatia 3fae0f39c0 chore: product update interface 2023-04-28 18:28:43 +05:30
Kunal VishwakarmaandGitHub 73a757e337 fix: added the decoded url for slack integration popup (#958) 2023-04-28 17:55:57 +05:30
Kunal VishwakarmaandGitHub bb40b7feb5 chore: removed email span from invite member page (#956) 2023-04-28 17:49:38 +05:30
Aaryan KhandelwalandGitHub 3175ce9136 style: create project modal (#957) 2023-04-28 17:49:29 +05:30
Anmol Singh BhatiaandGitHub 0b9b4bb289 fix: cycle & module mutation , feat: cycle & module toast alerts (#962)
* fix: cycle remove issue mutation

* fix: module remove issue mutation

* feat: issue removed toast alert added
2023-04-28 17:49:16 +05:30
Anmol Singh BhatiaandGitHub f0f24b6fc4 style: module , cycle & icon styling (#963)
* style: target icon updated

* style: cycle card theming

* style: module card theming

* style: no current cycle message
2023-04-28 17:49:04 +05:30
Anmol Singh BhatiaandGitHub 429dffb055 fix: cycle & module sidebar default tab fix (#964) 2023-04-28 17:48:40 +05:30
guru_sainathandGitHub 6e5c85cd6e Merge pull request #961 from makeplane/style/issue_list_responsive
style: responsive design for issue list in 'list' view
2023-04-28 10:49:54 +05:30
Dakshesh Jain 2adcb163fb Merge branch 'develop' of https://github.com/makeplane/plane into style/issue_list_responsive 2023-04-25 19:44:19 +05:30
Dakshesh Jain 028a350cd1 style: issue list dropdown on next line 2023-04-25 19:43:52 +05:30
Anmol Singh BhatiaandGitHub d021a5696a fix: sub issues list fix (#960) 2023-04-25 19:00:20 +05:30
Dakshesh Jain a5c18e37c1 Merge branch 'develop' of https://github.com/makeplane/plane into style/issue_list_responsive 2023-04-25 18:36:24 +05:30
Dakshesh Jain 8e611664a8 style: made issue list view responsive 2023-04-25 18:35:58 +05:30
anmolsinghbhatia 3480b450f2 fix: build fix 2023-04-25 18:10:23 +05:30
anmolsinghbhatia a7d9591c44 feat: product updates tag and date added 2023-04-25 18:02:13 +05:30
anmolsinghbhatia 1364c842e0 feat: product updates button added 2023-04-25 17:33:53 +05:30
anmolsinghbhatia eb99b4adc9 feat: markdown to custom component 2023-04-25 16:54:12 +05:30
anmolsinghbhatia 6684dd4ab6 feat: product updates service added 2023-04-25 16:51:50 +05:30
Kunal VishwakarmaandGitHub 529ed4432c chore: fixed sidebar highlight not working (#952) 2023-04-25 12:10:05 +05:30
Kunal VishwakarmaandGitHub 8c1ad69f0c chore: changed pages empty state image (#954) 2023-04-25 12:09:29 +05:30
Kunal VishwakarmaandGitHub c23de32b03 chore: added create label inside of pages label select (#944)
* chore: added create label inside of pages label select

* chore: used footer instead of props
2023-04-25 12:08:56 +05:30
Rhea JainandGitHub 83ac1f4e4c chore: invite text update (#955) 2023-04-25 12:07:02 +05:30
Anmol Singh BhatiaandGitHub cee9695a4a feat: sub issues progress (#895) 2023-04-25 11:38:13 +05:30
Aaryan KhandelwalandGitHub c9f866e538 style: profile settings, activity theming (#951) 2023-04-24 18:53:30 +05:30
Aaryan KhandelwalandGitHub 7d96adcb70 chore: empty state for estimates (#950) 2023-04-24 18:53:18 +05:30
Aaryan KhandelwalandGitHub c5b034385f chore: added custom toggle switch everywhere (#949) 2023-04-24 18:53:07 +05:30
Aaryan KhandelwalandGitHub ff7f31c35b style: calendar view (#948)
* style: calendar view

* chore: add issue button positioning
2023-04-24 18:10:44 +05:30
Aaryan KhandelwalandGitHub 7234d6f68b style: workspace and project settings (#946) 2023-04-24 13:21:09 +05:30
Aaryan KhandelwalandGitHub d8a5b8d848 style: imports theming (#945) 2023-04-24 13:20:41 +05:30
Aaryan KhandelwalandGitHub 5412e09701 chore: empty input fields text (#943) 2023-04-24 11:28:05 +05:30
Aaryan KhandelwalandGitHub 7116acc331 style: auth pages theming (#942) 2023-04-24 11:20:14 +05:30
Aaryan KhandelwalandGitHub ae26b17cab style: filters list theming (#941) 2023-04-24 11:20:02 +05:30
Aaryan KhandelwalandGitHub 2ec8fbab34 style: modals theming (#940)
* style: existing issues list modal

* style: parent issues list modal

* style: issue modal

* style: cycle modal

* style: module modal

* style: view modal

* style: page modal

* style: delete modals
2023-04-24 11:19:53 +05:30
Aaryan KhandelwalandGitHub 213dc3f8e8 style: signin page theming (#938) 2023-04-24 11:19:43 +05:30
Aaryan KhandelwalandGitHub 4b02886c40 chore: remove outline button component (#937) 2023-04-23 01:43:58 +05:30
Aaryan KhandelwalandGitHub 8eddc4b304 fix: points not updating while editing estimate (#935) 2023-04-23 00:00:46 +05:30
Aaryan KhandelwalandGitHub 169a60723b style: project settings theming (#936)
* style: project and workspace members theming

* style: project features theming

* style: project settings states theming

* style: project settings labels theming

* style: project settings integrations theming
2023-04-22 23:46:19 +05:30
Kunal VishwakarmaandGitHub c80094581e feat: frontend slack integration (#932)
* feat: slack integration frontend

* feat: slack integration frontend complete

* Co-authored-by: Aaryan Khandelwal <aaryan610@users.noreply.github.com>
2023-04-22 21:54:50 +05:30
pablohashescobarandGitHub d99f669b89 dev: add s3 url for staging (#934) 2023-04-22 18:21:22 +05:30
Aaryan KhandelwalandGitHub 99dd1b9f0c chore: state delete validation (#930) 2023-04-22 18:19:35 +05:30
pablohashescobarandGitHub 48e77ea81b remove: state delete issue validation endpoint (#929) 2023-04-22 18:18:08 +05:30
pablohashescobarandGitHub 0be6738715 dev: back migration for integrations (#933) 2023-04-22 18:17:53 +05:30
Anmol Singh BhatiaandGitHub d041d8be6b fix: cycle date validation (#922) 2023-04-22 18:17:17 +05:30
pablohashescobarandGitHub e53847c59e feat: module view props (#882) 2023-04-22 18:16:16 +05:30
pablohashescobarandGitHub 16781a71fe feat: cycle view props (#881) 2023-04-22 18:16:05 +05:30
pablohashescobarandGitHub fb4535b294 feat: slack integration (#874)
* feat: init slack integration

* dev: create model and update existing view for slack

* dev: update slack sync model and create view to install slack

* dev: workspace integration query

* dev: update the metadata validation for access_token and team_id and save config to database

* dev: update validation for team_id

* dev: update validation

* dev: update validations

* dev: remove bot access token field from sync

* dev: handle integrity exception
2023-04-22 18:15:52 +05:30
pablohashescobarandGitHub 33a904bc3e chore: added estimate bulk endpoint for retrieving and updating (#919)
* chore: added estimate bulk endpoint for retrieving and updating

* chore: estimate endpoints

* fix: retrieve project estimate

* dev: handle integrity error check
2023-04-22 01:04:20 +05:30
pablohashescobarandGitHub 0d264838a9 remove: print exception instead use capture to log it (#926) 2023-04-22 01:04:07 +05:30
Aaryan KhandelwalandGitHub c638b6aba6 chore: new estimates workflow (#927)
* chore: new services for estimates

* chore: new estimates workflow
2023-04-22 00:59:57 +05:30
Aaryan KhandelwalandGitHub cb814dd68b chore: added new restricted workspace slugs (#928) 2023-04-22 00:57:16 +05:30
Aaryan KhandelwalandGitHub e17c824119 style: dashboard styling (#924) 2023-04-22 00:56:17 +05:30
Anmol Singh BhatiaandGitHub 68930c256f style: sidebar theming (#925)
* style: sidebar workspace dropdown theming

* style: progress chart and progress bar theming

* style: module and cycle sidebar theming
2023-04-22 00:15:45 +05:30
Anmol Singh BhatiaandGitHub e59137f6f2 style: cycle and module theming (#923)
* style: cycle theming

* style: module theming
2023-04-21 17:40:57 +05:30
Aaryan KhandelwalandGitHub 7f235739bd style: projects page theming (#921) 2023-04-21 17:40:49 +05:30
Vamsi KuramaandGitHub 20162050c3 Merge pull request #833 from kylewlacy/self-hosting-tweaks
Add a few more options for self-hosting
2023-04-21 16:47:36 +05:30
Aaryan KhandelwalandGitHub 06ad0d0f7a style: views theming (#920) 2023-04-21 16:30:16 +05:30
Aaryan KhandelwalandGitHub cfd7e1d154 fix: theming fixes (#914) 2023-04-21 15:48:06 +05:30
Anmol Singh BhatiaandGitHub fdf7ea1f82 feat: copy page link added (#915) 2023-04-21 15:46:28 +05:30
Anmol Singh BhatiaandGitHub 8f12d3d01b style: calendar theming (#918) 2023-04-21 15:45:57 +05:30
Aaryan KhandelwalandGitHub 62dc6c2f3f fix: comment box placeholder (#909) 2023-04-21 12:31:34 +05:30
Aaryan KhandelwalandGitHub 6f03022f65 chore: changed light mode colors (#913) 2023-04-21 10:46:04 +05:30
Aaryan KhandelwalandGitHub f2701a12ea style: pages theming (#912) 2023-04-21 02:15:21 +05:30
Anmol Singh BhatiaandGitHub 9129a6cde2 feat: calendar filters (#908)
* feat: hiding unnecessary filters for calendar view

* feat: filters for calendar view

* feat: module and cycle calendar view filters
2023-04-21 01:42:09 +05:30
Rhea JainandGitHub 2ba4594b29 chore: attachements button text update (#910) 2023-04-21 01:41:47 +05:30
Aaryan KhandelwalandGitHub 165d16e32b style: issue details page (#911) 2023-04-21 00:34:22 +05:30
Aaryan KhandelwalandGitHub 73388195ef chore: page block input (#907)
* fix: sidebar workspace dropdown logo

* chore: changed textarea to input for page block
2023-04-20 19:04:10 +05:30
pablohashescobarandGitHub 4dda4ec610 chore: single endpoint to create estimate and estimate points (#897) 2023-04-20 18:14:38 +05:30
pablohashescobarandGitHub e68a5382f9 feat: sub issue state distribution (#885)
* feat: sub issue state distribution

* dev: update the response structure to match consistency

* dev: update the query for sub issue distribution to include 0 issue groups as well
2023-04-20 18:14:24 +05:30
pablohashescobarandGitHub 5b6caadd6f chore: state delete validations endpoint (#880) 2023-04-20 18:14:05 +05:30
pablohashescobarandGitHub 73a8bbb31f feat: release information endpoint (#876)
* dev: init release notes

* feat: API endpoint to fetch get last 5 release information
2023-04-20 18:13:49 +05:30
Aaryan KhandelwalandGitHub 952d35dd79 style: list and kanban view theming (#906)
* fix: sidebar workspace dropdown logo

* style: list and kanban view theming
2023-04-20 18:13:21 +05:30
Anmol Singh BhatiaandGitHub 170b3d6eec fix: cycle and module sidebar fix (#905) 2023-04-20 18:09:01 +05:30
Anmol Singh BhatiaandGitHub d04a422054 feat: calendar add new issue (#901) 2023-04-20 14:11:11 +05:30
Aaryan KhandelwalandGitHub affc7655f7 fix: sidebar workspace dropdown logo (#903) 2023-04-20 13:56:09 +05:30
50c78628b3 feat: themes (#902)
* chore: add next theme and initial setup

* chore: add dark mode colors to layouts

* chore: user general setting page theming

* chore: dashboard theming

* chore: project page theming

* chore: workspace setting page theming

* chore: my issue page theming

* chore: cmdk theming

* chore: change hardcode bg and text color to theme

* chore: change color name according to the design

* style: fix card in the dashboard

* style: fix merge conflict design issues

* style: add light high contrast and dark high contrast

* style: fix cmd k menu color and selection

* feat: change theme from cmdk menu

* chore: add multiple theme field to custom theme

* chore: removed custom theming

* fix: build error

---------

Co-authored-by: Saheb Giri <iamsahebgiri@gmail.com>
2023-04-20 13:42:16 +05:30
3c2f5d12ed feat: themes (#902)
* chore: add next theme and initial setup

* chore: add dark mode colors to layouts

* chore: user general setting page theming

* chore: dashboard theming

* chore: project page theming

* chore: workspace setting page theming

* chore: my issue page theming

* chore: cmdk theming

* chore: change hardcode bg and text color to theme

* chore: change color name according to the design

* style: fix card in the dashboard

* style: fix merge conflict design issues

* style: add light high contrast and dark high contrast

* style: fix cmd k menu color and selection

* feat: change theme from cmdk menu

* chore: add multiple theme field to custom theme

* chore: removed custom theming

* fix: build error

---------

Co-authored-by: Saheb Giri <iamsahebgiri@gmail.com>
2023-04-20 13:41:24 +05:30
Rhea JainandGitHub 9f04933957 chore: upload button and github banner text update (#899)
* chore: upload button and github banner text update

* chore: attachments button text fix
2023-04-20 12:16:13 +05:30
Anmol Singh BhatiaandGitHub 8d37a3e58b feat: cycle list tab context (#900) 2023-04-20 12:11:33 +05:30
Aaryan KhandelwalandGitHub 4f61c5d552 fix: pages access mutation (#896) 2023-04-20 12:09:55 +05:30
Aaryan KhandelwalandGitHub 7149d20601 chore: change pages icon (#894) 2023-04-20 12:09:48 +05:30
Aaryan KhandelwalandGitHub d30a88832a fix: reset estimates modal form after create/update (#893) 2023-04-20 12:09:35 +05:30
Rhea JainandGitHub ebce364104 chore: changed loading text (#898) 2023-04-19 20:52:52 +05:30
Aaryan KhandelwalandGitHub c5206a7792 feat: separate filters for cycles and modules (#892) 2023-04-19 17:25:31 +05:30
Anmol Singh BhatiaandGitHub 390b837561 fix: invite button validation , style: workspace screen (#877)
* fix: invite button validation

* style: workspace screen tab height
2023-04-19 15:41:46 +05:30
Anmol Singh BhatiaandGitHub fb1932e309 fix : create issue modal (#875)
* fix: label list bug fix

* fix: assignee and label count removed

* fix: assignee and label fix
2023-04-19 15:41:17 +05:30
Dakshesh JainandGitHub ac125965eb fix: add tlds (#851) 2023-04-19 15:40:47 +05:30
Dakshesh JainandGitHub 63a36fb25d feat: jira importer (#879)
* feat: jira importer

* fix: yarn lock

* fix: displaying correct count of users that are been imported

* fix: showing workspace member in import users
2023-04-19 15:40:31 +05:30
pablohashescobarandGitHub 2b280935a1 chore: update state create endpoint to send error response on integrity error (#869)
* chore: update state create endpoint to send error response on integrity error

* dev: update status code for general exception
2023-04-18 12:25:22 +05:30
Kunal VishwakarmaandGitHub be5ef61428 fix: join project button (#873) 2023-04-18 10:55:32 +05:30
Aaryan KhandelwalandGitHub 1627a587ee refactor: workspace integrations code (#872) 2023-04-18 10:54:45 +05:30
Aaryan KhandelwalandGitHub 682a1477fb chore: create/update estimate points validation (#871) 2023-04-18 10:54:19 +05:30
Kyle Lacy ea87823478 Add $NEXT_PUBLIC_EXTRA_IMAGE_DOMAINS to add domains for Next Image plugin 2023-04-17 16:30:36 -07:00
Kyle Lacy b8c06b3121 Add $AWS_S3_ENDPOINT_URL to .env.example 2023-04-17 14:08:47 -07:00
Kyle Lacy ca2366aa9b Use env var to set AWS_S3_ENDPOINT_URL setting 2023-04-17 14:07:00 -07:00
Aaryan KhandelwalandGitHub acff6396f9 chore: create/update state duplicate name error (#870) 2023-04-18 01:15:39 +05:30
Aaryan KhandelwalandGitHub fa5c994ddc chore: remove redundant console logs (#868) 2023-04-18 01:15:26 +05:30
Aaryan KhandelwalandGitHub 5f20e65ca6 style: list view styling reduced (#867) 2023-04-18 01:15:10 +05:30
Kyle Lacy 792162ae66 Remove redundant $REDIS_BROKER_SSL setting
A fix was already pushed upstream that made this setting unnecessary
2023-04-17 12:37:58 -07:00
Kyle Lacy e22f552ea0 Merge branch 'develop' into self-hosting-tweaks 2023-04-17 12:30:24 -07:00
pablohashescobarandGitHub 396fbc4ebb fix: redis url for docker in production settings (#865) 2023-04-17 22:38:52 +05:30
Vamsi KuramaandGitHub d2a58bf04a Merge pull request #862 from makeplane/stage-release
promote: staging to production v0.5
2023-04-17 19:08:10 +05:30
Vamsi KuramaandGitHub c51407c85e Merge branch 'master' into stage-release 2023-04-17 19:07:33 +05:30
Vamsi KuramaandGitHub 3ed937378f Merge pull request #861 from makeplane/develop
promote: patches from develop to stage-release
2023-04-17 18:48:21 +05:30
Dakshesh JainandGitHub 0fa3a8c3e3 fix: removed useEffect for project detail fetch error (#860) 2023-04-17 18:38:07 +05:30
Aaryan KhandelwalandGitHub bd0cfef02f fix: delete import (#859) 2023-04-17 18:34:18 +05:30
Dakshesh JainandGitHub 7aa9e0bba1 fix: project authorization api fetch keys (#858) 2023-04-17 18:22:04 +05:30
Vamsi KuramaandGitHub 718f62a898 Merge pull request #857 from makeplane/develop
promote: develop to stage-release
2023-04-17 17:47:19 +05:30
Kunal VishwakarmaandGitHub d26d01ace4 chore: filters spacing (#856) 2023-04-17 17:42:02 +05:30
Vamsi KuramaandGitHub 60e44fc1a2 Merge pull request #847 from makeplane/chore/my_issues_endpoint
chore: my issues endpoint to return attachment and link count
2023-04-17 17:39:38 +05:30
Anmol Singh BhatiaandGitHub 45eaa23ed0 style: dashboard content (#855) 2023-04-17 17:36:02 +05:30
Aaryan KhandelwalandGitHub 600fedd5ba fix: undefined block content (#854) 2023-04-17 17:21:15 +05:30
Aaryan KhandelwalandGitHub 6de54089cd fix: show proper issues list if no filters are present (#853) 2023-04-17 17:10:36 +05:30
Aaryan KhandelwalandGitHub dc53708109 chore: show workspace members (#852) 2023-04-17 16:41:16 +05:30
Aaryan KhandelwalandGitHub f5351e4419 fix: remove filters function (#846) 2023-04-17 15:03:56 +05:30
Aaryan KhandelwalandGitHub 819508d5fc fix: remirror placeholder (#849) 2023-04-17 15:03:44 +05:30
Kunal VishwakarmaandGitHub 0beb654069 chore: worked on making issue list padding consistent (#850) 2023-04-17 15:01:22 +05:30
Anmol Singh BhatiaandGitHub 98cef0e1e8 fix: my issue page display property (#848) 2023-04-17 14:45:51 +05:30
Aaryan KhandelwalandGitHub 8a6036a20a fix: drop to delete box zindex (#843) 2023-04-17 14:13:32 +05:30
pablohashescobar 85b6c78e75 chore: my issues endpoint to return attachment and link count 2023-04-17 14:03:19 +05:30
Anmol Singh BhatiaandGitHub 3f401b0fc5 feat: page tab context (#845)
* feat: page list tab context added

* fix: build fix
2023-04-17 13:30:39 +05:30
Anmol Singh BhatiaandGitHub 365c758a25 fix: create update view modal fix (#842) 2023-04-17 12:24:30 +05:30
Aaryan KhandelwalandGitHub ac98381f23 chore: remove estimate option from my issues (#839) 2023-04-17 12:00:30 +05:30
Kunal VishwakarmaandGitHub 3e436179fe chore: added default state for issues (#840) 2023-04-17 11:54:22 +05:30
Aaryan KhandelwalandGitHub e23075b7b9 chore: no estimates option, estimates activity (#838) 2023-04-17 11:30:48 +05:30
pablohashescobarandGitHub 61761fedc5 chore: remove view filter validation while creating or updating view (#836) 2023-04-17 11:15:38 +05:30
Kunal VishwakarmaandGitHub 5a36a7931f fix: removed extra spaces form estimate points brackets (#837) 2023-04-17 11:15:06 +05:30
Aaryan KhandelwalandGitHub 363c5c8ec4 fix: join project mutation (#835)
* fix: join project mutation

* chore: remove imports
2023-04-17 10:27:20 +05:30
Kyle Lacy 5e5d1a4699 Update .env.example with newly-added env vars 2023-04-15 12:56:43 -07:00
Kyle Lacy e2294f9105 Add EMAIL_FROM setting to change sender email for messages 2023-04-15 12:51:23 -07:00
Kyle Lacy f757d8232b Add setting to disable extra TLS config for Celery broker connection 2023-04-15 12:32:54 -07:00
Kyle Lacy 6af54ebbe7 Fix typo in `.env.example 2023-04-15 12:32:00 -07:00
Kyle Lacy 3913cf571f Make SMTP port and TLS configurable 2023-04-15 12:00:02 -07:00
Aaryan KhandelwalandGitHub 8638170a98 fix: cycles and modules sidebar mutation (#831) 2023-04-14 19:40:00 +05:30
Vamsi KuramaandGitHub ebff5d8c54 Merge pull request #830 from makeplane/develop
promote: dev to stage-release
2023-04-14 17:06:19 +05:30
vamsi b7ce69c220 dev: migrations estimate points and themes 2023-04-14 17:04:02 +05:30
Vamsi KuramaandGitHub e4da207df5 Merge pull request #768 from makeplane/feat/workspace_themes
feat: workspace themes
2023-04-14 16:56:32 +05:30
Vamsi KuramaandGitHub 3a0c5bab76 Merge pull request #804 from makeplane/fix/magic_sign_in
fix: connection error when signing in with code
2023-04-14 16:56:20 +05:30
Vamsi KuramaandGitHub 1cd1505c7d Merge pull request #805 from makeplane/chore/celery_production_settings
chore: production settings for celery
2023-04-14 16:56:00 +05:30
Vamsi KuramaandGitHub bc7fab96c3 Merge pull request #812 from makeplane/fix/parent_issue_search
fix: parent issue search
2023-04-14 16:55:20 +05:30
Vamsi KuramaandGitHub a358260a22 Merge pull request #817 from makeplane/chore/issue_estimate_points
chore: set default value as null for estimate point
2023-04-14 16:54:43 +05:30
Dakshesh JainandGitHub 3b103da6a3 chore: added unsplash flag for self-hosted (#829)
* chore: added unsplash flag for self hosted

* fix: removed actual code and only using flag

* refactor: removed extra variable
2023-04-14 16:52:31 +05:30
Aaryan KhandelwalandGitHub 23b4145565 chore: added session recorder key to env (#827) 2023-04-14 16:51:24 +05:30
Anmol Singh BhatiaandGitHub 5848c326c7 fix: cycle and module sidebar fix (#828) 2023-04-14 16:44:06 +05:30
Aaryan KhandelwalandGitHub ce253b3cc9 refactor: drag function (#826) 2023-04-14 16:41:28 +05:30
Vamsi KuramaandGitHub 3817511024 Merge pull request #824 from makeplane/feat/session_recorder
feat: session recorder
2023-04-14 16:09:31 +05:30
Kunal VishwakarmaandGitHub f50872f2a9 fix: empty issue design (#821)
* fix: empty issue design

* chore: removed unused imports
2023-04-14 16:04:16 +05:30
Anmol Singh BhatiaandGitHub a0b8f7188f fix: cycle card (#825) 2023-04-14 16:03:11 +05:30
Aaryan Khandelwal 2950877767 feat: session recorder integrated 2023-04-14 15:27:14 +05:30
Aaryan KhandelwalandGitHub c7d930f89b chore: add env flag to enable session recorder conditionally (#822) 2023-04-14 15:17:35 +05:30
pablohashescobarandGitHub 81da8715d5 chore: update bug report template to include browser environment as well (#820)
* chore: update bug report template to include browser environment as well

* chore: update type to dropdown
2023-04-14 00:45:03 +05:30
pablohashescobar b4c8323886 chore: set default value as null for estimate point 2023-04-13 19:54:34 +05:30
Anmol Singh BhatiaandGitHub c3ffd233a6 style: workspace url (#816) 2023-04-13 19:33:22 +05:30
Aaryan KhandelwalandGitHub 3fa6185b63 fix: drag and drop function (#815)
* fix: kanban drag and drop

* fix: kanban board issue dnd mutation
2023-04-13 19:09:55 +05:30
Dakshesh JainandGitHub 6de94efc7d style: removed static 'app.plane.so' (#813) 2023-04-13 18:28:23 +05:30
pablohashescobar 0cd6d9d570 fix: parent issue search 2023-04-13 17:49:09 +05:30
Aaryan KhandelwalandGitHub 657241c9c1 feat: clarity script added (#807) 2023-04-13 16:17:21 +05:30
Anmol Singh BhatiaandGitHub dc9ce5101c fix: workspace url error message (#809) 2023-04-13 15:46:25 +05:30
Kunal VishwakarmaandGitHub 3457411c6a style: issue list (#798)
* style: issue list

* chore: changed the empty state images
2023-04-13 15:39:05 +05:30
Aaryan KhandelwalandGitHub b7a7508d5d fix: workspace dashboard duplicate keys (#803) 2023-04-13 15:38:43 +05:30
Dakshesh JainandGitHub 484a88d881 fix: unusual redirection on onboarding (#808) 2023-04-13 15:38:00 +05:30
pablohashescobarandGitHub cd69b06e5e fix: error message for jira importers (#794) 2023-04-13 00:34:53 +05:30
pablohashescobarandGitHub c4609b95cd fix: remove length check condition when updating issue property (#791) 2023-04-13 00:34:37 +05:30
pablohashescobarandGitHub 6eb7ec0697 fix: typo in url for bulk creating labels (#788) 2023-04-13 00:34:23 +05:30
pablohashescobarandGitHub e232d39f0e feat: track estimate points in issue activity (#762)
* feat: track estimate points in issue activity

* dev: update comment
2023-04-13 00:34:12 +05:30
pablohashescobarandGitHub 8a26fd0a97 fix: issue link activity (#761) 2023-04-13 00:34:02 +05:30
pablohashescobarandGitHub 537a82028e fix: attachment activity (#760) 2023-04-13 00:33:50 +05:30
pablohashescobarandGitHub 440ed08728 fix: issue attachment delete (#759) 2023-04-13 00:33:37 +05:30
pablohashescobar c199e76038 chore: production settings for celery 2023-04-13 00:31:06 +05:30
pablohashescobar b40fd4bbc2 fix: connection error when signing in with code 2023-04-12 19:55:31 +05:30
Anmol Singh BhatiaandGitHub 5190ea7280 fix: send code btn fix (#802) 2023-04-12 19:03:29 +05:30
Dakshesh JainandGitHub db488338fb fix: weird redirection in index page (#801) 2023-04-12 19:03:08 +05:30
Anmol Singh BhatiaandGitHub 9196fb4562 style: onboarding screen cards (#800) 2023-04-12 19:01:46 +05:30
Saheb GiriandGitHub 34ff8fecc5 feat: add create page option in cmdk menu (#799) 2023-04-12 19:01:19 +05:30
Anmol Singh BhatiaandGitHub d6dbfdc731 feat: page improvement (#797)
* feat: remove label icon added

* feat: block menu dropdown state added

* feat: page info icon added and  style: overflow title and label fix
2023-04-12 18:07:50 +05:30
Anmol Singh BhatiaandGitHub f2e8add29d feat: attachment and link display properties (#796)
* feat: attachment and link count added

* fix: build fix
2023-04-12 15:33:30 +05:30
Saheb GiriandGitHub 032ef831b2 feat: now user can edit view (#793)
* feat: now user can edit view

* fix: build error
2023-04-12 15:33:21 +05:30
Kunal VishwakarmaandGitHub 0f9812cf2c chore: added estimate delete modal (#792) 2023-04-12 15:03:04 +05:30
Aaryan KhandelwalandGitHub f734aad10d fix: minor pages bugs (#786)
* fix: dashboard workspace activity mutation

* fix/minor_pages_bugs
2023-04-11 23:19:47 +05:30
pablohashescobarandGitHub 6279a04267 feat: attachment and link count in issues list (#777) 2023-04-11 18:47:36 +05:30
pablohashescobarandGitHub ce26bed44a fix: jira importer info endpoint to get query params (#776) 2023-04-11 18:47:18 +05:30
Anmol Singh BhatiaandGitHub 88d2adddc7 style: create issue modal icon (#784) 2023-04-11 18:46:39 +05:30
Kunal VishwakarmaandGitHub e4e66b3ae4 style: pages UI (#769)
* style: pages ui

* chore: added toast alert and tooltip

* fix: fixed issues in pages block

* fix: ai buttons inside pages block
2023-04-11 18:18:49 +05:30
Aaryan KhandelwalandGitHub f1f716e8f6 fix: estimates bugs (#785)
* fix: dashboard workspace activity mutation

* fix: minor bugs
2023-04-11 18:17:47 +05:30
Anmol Singh BhatiaandGitHub 61b9e7a161 feat: issue activity logs (#782)
* feat: attachment, link and estimate activity log added in issue detail page

* feat: attachment, link and estimate activity log added in profile activity section
2023-04-11 18:14:36 +05:30
dfa3a7b78d feat: estimates (#783)
* chore: use estimate points hook created

* chore: user auth layer

* fix: build error

* chore: estimate crud and validation

* fix: build errors

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-04-11 17:54:01 +05:30
Anmol Singh BhatiaandGitHub d5c2965946 fix: workspace joining state fix (#780) 2023-04-11 17:13:06 +05:30
Kunal VishwakarmaandGitHub cfa283116b chore: added remove workspace image in workspace settings (#781) 2023-04-11 17:12:34 +05:30
Aaryan KhandelwalandGitHub 725c9375ea fix: onboarding loop (#775)
* fix: dashboard workspace activity mutation

* fix: onboarding loop
2023-04-11 12:25:21 +05:30
Anmol Singh BhatiaandGitHub 22c1f6f8e2 feat: workspace name validation added (#767) 2023-04-11 12:11:41 +05:30
Anmol Singh BhatiaandGitHub 748e5e7fb7 fix: project setting identifier (#766)
* fix: project setting identifier validation added

* fix: project setting identifier label fix
2023-04-11 12:11:15 +05:30
Aaryan KhandelwalandGitHub 7aa0ace555 fix: pages access (#754)
* fix: dashboard workspace activity mutation

* fix: page access operation
2023-04-11 12:10:22 +05:30
Anmol Singh BhatiaandGitHub f2c5bb5c03 fix: workspace join button fix (#774) 2023-04-11 10:12:33 +05:30
pablohashescobarandGitHub 800075b781 fix: worker script (#773) 2023-04-11 09:25:33 +05:30
vamsi 6865cf4b54 chore: ssl config for REDIS connections to celery 2023-04-11 00:04:03 +05:30
pablohashescobar bc457846fe chore: move theme setting in user level from workspace level 2023-04-10 23:19:01 +05:30
Saheb GiriandGitHub d411cd7576 style: redesign view (#770)
* style: add new design to the view item

* feat: add no of filters
2023-04-10 22:46:09 +05:30
pablohashescobarandGitHub 2dbe1dd401 fix: remove migrate command from worker script (#772) 2023-04-10 22:44:11 +05:30
pablohashescobarandGitHub ec3f891b4a chore: update doc redirection url in user welcome email (#771) 2023-04-10 21:29:54 +05:30
pablohashescobar b6c911f484 feat: workspace themes 2023-04-10 18:14:09 +05:30
Anmol Singh BhatiaandGitHub fc48fb97d1 style: attachment upload button (#765) 2023-04-10 18:07:40 +05:30
Vamsi KuramaandGitHub 3618f79f89 Merge pull request #764 from makeplane/fix/setup
dev: fix setup.sh causing tr Illegal byte sequence
2023-04-10 17:45:20 +05:30
vamsi ea563d20a3 dev: fix setup.sh causing tr Illegal byte sequence 2023-04-10 17:43:19 +05:30
Vihar KuramaandGitHub 805b8f47cc Update Readme.md - Removed status section 2023-04-10 15:15:34 +05:30
Vihar KuramaandGitHub 5f216fbbbc Merge pull request #753 from makeplane/docker-hub-hotfix
Create docker-compose-hub.yml
2023-04-10 14:26:51 +05:30
Kunal VishwakarmaandGitHub 33ea1cb9d3 chore: added ai generation inside block in pages (#732) 2023-04-10 12:33:12 +05:30
Vihar KuramaandGitHub 0caadd0f7a Merge pull request #758 from nothingneko/patch-1
Fix: Dead Discord Link on Readme
2023-04-10 11:11:45 +05:30
pablohashescobarandGitHub 62e736677a fix: cycle date check endpoint (#748) 2023-04-10 10:58:32 +05:30
pablohashescobarandGitHub bd9de0c213 feat: activity for issue attachments and links (#746) 2023-04-10 10:58:22 +05:30
pablohashescobarandGitHub c80968bb23 feat: bulk update estimate endpoint (#755) 2023-04-10 10:58:09 +05:30
Jaiden RiordanandGitHub 441203867b Fix dead Discord Link 2023-04-09 18:32:08 -05:00
vamsi eac4b21ead dev: migrations for estimate Estimate, IssueAttachment 2023-04-09 03:13:46 +05:30
Narayana VadapalliandGitHub 6fe35c9fe6 Create docker-compose-hub.yml 2023-04-09 00:21:40 +05:30
Aaryan KhandelwalandGitHub 08e77cb19e fix: home page redirection logic (#752)
* fix: dashboard workspace activity mutation

* fix: home page redirection logic

* chore: add homePageRedirect function back
2023-04-08 14:32:33 -04:00
Anmol Singh BhatiaandGitHub 03e74415f2 feat: upload button validation (#747) 2023-04-08 18:20:33 +05:30
Aaryan KhandelwalandGitHub c81bc4e5d2 style: loading screens (#750)
* fix: dashboard workspace activity mutation

* style: loading screens
2023-04-08 18:20:00 +05:30
Aaryan KhandelwalandGitHub 1026ae3eb1 chore: user auth layer (#749)
* chore: use estimate points hook created

* chore: user auth layer

* fix: build error
2023-04-08 18:05:54 +05:30
Aaryan KhandelwalandGitHub 3fe32606a9 chore: disable jira importer (#745)
* fix: dashboard workspace activity mutation

* chore: remove jira importer from settings
2023-04-08 15:46:19 +05:30
pablohashescobarandGitHub d88a95b1e9 fix: celery worker for issue activities (#744)
* dev: update celery configuration to root folder

* dev: update import for celery

* fix: worker to deserialize data
2023-04-08 15:46:05 +05:30
Aaryan KhandelwalandGitHub c21fb6e942 fix: issue attachments mutation (#743)
* fix: dashboard workspace activity mutation

* fix: attachment mutation for create and delete
2023-04-08 15:33:20 +05:30
Aaryan KhandelwalandGitHub 98e6d3de22 fix: dashboard workspace activity mutation (#742) 2023-04-08 15:10:19 +05:30
Kunal VishwakarmaandGitHub 9b1ae6bcd4 fix: inconsistency in styles (#734) 2023-04-08 13:55:30 +05:30
3947a86fa7 fix: new auth layer (#740)
* chore: made workspace authorization wrapper component

* chore: added todos

* chore: workspace pages new layout

* chore: project authorization wrapper

* chore: new project authorization wrapper

* fix: authorization for member roles

* chore: new auth screens ui

---------

Co-authored-by: Dakshesh Jain <dakshesh.jain14@gmail.com>
2023-04-08 13:46:46 +05:30
Aaryan KhandelwalandGitHub beedd57ee1 fix: fetch selected project members (#741)
* fix: fetch selected project members

* chore: remove old imports
2023-04-08 13:46:21 +05:30
Kunal VishwakarmaandGitHub 0a3d13706e fix: mutation issue in cycles (#739)
* fix: mutation issue in cycles

* fix: removed comments
2023-04-08 01:24:24 +05:30
Anmol Singh BhatiaandGitHub c093209338 fix: shortcut combination key bug fix (#733)
* fix: shortcut combination key bug fix

* refactor: code refactor
2023-04-07 17:01:52 +05:30
Kunal VishwakarmaandGitHub a68d94c33f fix: added image popover for settings cover (#737)
* fix: added popover for profile cover

* fix:removed comments
2023-04-07 15:33:11 +05:30
Aaryan KhandelwalandGitHub 35f9876981 refactor: import/export code (#735) 2023-04-07 13:27:57 +05:30
pablohashescobarandGitHub c0b732f1f1 chore: rename project name to project key (#731) 2023-04-06 22:58:13 +05:30
pablohashescobarandGitHub 6be775434d fix: project issue search endpoint (#729) 2023-04-06 22:58:03 +05:30
pablohashescobarandGitHub 687b05d221 dev: upgrade python version (#728) 2023-04-06 22:57:36 +05:30
pablohashescobarandGitHub 59a33587a0 feat: delete endpoint for importers (#725)
* feat: delete endpoint for importers

* fix: delete endpoint for importers
2023-04-06 22:57:20 +05:30
pablohashescobarandGitHub e46487c130 chore: add project details on importer service endpoint (#714)
* chore: add project details on importer service endpoint

* dev: add select related for imports
2023-04-06 22:57:06 +05:30
pablohashescobarandGitHub 5b72b1672f chore: add workspace and project details on label endpoints (#713) 2023-04-06 22:56:55 +05:30
pablohashescobarandGitHub ec818a5523 refactor: move all background task from rqworker to celery (#668)
* refactor: move all background task from rqworker to celery

* dev: update background job to take input in parameters rather than a single dict

* dev: update procfile for new worker

* dev: docker updates for new celery worker
2023-04-06 22:56:36 +05:30
pablohashescobarandGitHub 100c431ac3 fix: default assignee for issues (#712)
* fix: default assignee for issues

* fix: check for empty array as well
2023-04-06 22:56:24 +05:30
Anmol Singh BhatiaandGitHub ea06ee4529 fix: issue attachment improvement (#730)
* fix: invalid file error state bug fix

* style: icons updated
2023-04-06 19:16:50 +05:30
Aaryan KhandelwalandGitHub 35455c2bf7 chore: delete import (#727)
* chore: delete import

* chore: changed button text
2023-04-06 16:06:31 +05:30
Dakshesh JainandGitHub c49f614352 feat: update project cover image (#726) 2023-04-06 15:11:01 +05:30
Kunal VishwakarmaandGitHub 95fe4a3831 feat: added estimates (#721)
* feat: added estimates

* chore: added estimate to issue sidebar
2023-04-06 15:09:24 +05:30
Anmol Singh BhatiaandGitHub 14dd498d08 feat: issue attachments feature (#717)
* chore: issue attachment services added

* feat: attachment icons added

* chore: fetch-key and icons export

* feat: issue attachment upload section added

* feat: issue attachment list section added

* feat: date helper function added

* style: responsiveness added

* feat: attachment info added

* style: attachment overflow fix

* style: cursor pointer added

* chore: issue attachment interface

* style: uploading state added

* feat: delete issue attachment modal

* style: style improvement and refactor

* style: consistent icon added , chore: refactor the code

* fix: js icon import fix

* fix: build fix

* chore: refactor code
2023-04-06 15:07:11 +05:30
pablohashescobarandGitHub 86ec46db2c dev: add imports for back migration and add migration for views (#707) 2023-04-06 13:59:43 +05:30
pablohashescobarandGitHub 6a579f85ad feat: bulk create endpoint for estimate points (#708)
* feat: bulk create endpoint for estimate points

* dev: remove integrity logic and update url
2023-04-06 13:59:17 +05:30
pablohashescobarandGitHub 105428894f fix: add check if the users need to be imported (#716) 2023-04-06 13:58:56 +05:30
pablohashescobarandGitHub f2144c3e89 fix: issue search endpoint for parent issues (#705)
* fix: issue search endpoint for parent issues

* fix: parent search

* fix: search endpoint
2023-04-06 13:58:27 +05:30
Dakshesh JainandGitHub cf662f6e6c chore: new analytic events (#699)
* feat: tracking events for issues marked as DONE, issue property update, issue moved to cycle, issue moved to modules

* fix: changed events names

* chore: sync analytic

* chore: new analytic events
2023-04-06 12:08:52 +05:30
Aaryan KhandelwalandGitHub 65037b5031 style: disabled state for buttons (#724) 2023-04-06 01:03:42 +05:30
Aaryan KhandelwalandGitHub c9d8a8dbd1 feat: github importer (#722)
* chore: github importer first step completed

* refactor: github importer code refactored

* chore: github importer functionality completed

* fix: import data step saved data
2023-04-06 00:51:15 +05:30
pablohashescobarandGitHub 6b8b981e1d docs: update readme to include default email and password for self hosting setup (#706) 2023-04-05 15:03:40 +05:30
pablohashescobarandGitHub 1562939287 chore: add workspace details on comment serializer (#697) 2023-04-05 00:20:21 +05:30
pablohashescobarandGitHub cc07e2790d feat: issue estimations (#696)
* dev: initialize estimation

* dev: issue estimation field in issues and project settings

* dev: update issue estimation logic
2023-04-05 00:19:53 +05:30
pablohashescobarandGitHub 97386e9d07 feat: issue attachments (#677) 2023-04-05 00:17:55 +05:30
pablohashescobarandGitHub ff5cddeb95 feat: issue search endpoint (#667)
* feat: issue search endpoints

* dev: update issue search for blocker and blocked by
2023-04-05 00:17:16 +05:30
Vamsi KuramaandGitHub 3d6f2dd3dc Merge pull request #703 from makeplane/stage-release
promote: stage-release to production
2023-04-04 19:38:37 +05:30
vamsi 8cbf75ad6c Merge branch 'stage-release' of https://github.com/makeplane/plane into stage-release 2023-04-04 19:33:32 +05:30
vamsi 9f4f1cac42 Merge branch 'develop' of https://github.com/makeplane/plane into stage-release 2023-04-04 19:31:12 +05:30
Vamsi KuramaandGitHub 1c752d7019 Merge pull request #691 from makeplane/develop
promote: develop to stage release
2023-04-04 19:29:55 +05:30
Aaryan KhandelwalandGitHub 3519be9ce8 fix: remirror empty state (#702)
* fix: minor pages ui

* fix: remirror empty state
2023-04-04 19:27:13 +05:30
sphynxuxandGitHub 9ce158fc10 add screenshots, feature, docker compose steps on readme (#701) 2023-04-04 18:50:23 +05:30
Aaryan KhandelwalandGitHub 0036ac6afb fix: minor pages ui (#700) 2023-04-04 18:31:28 +05:30
pablohashescobarandGitHub 7f7ceec24c chore: return user role in user onboard endpoint (#682) 2023-04-04 18:07:17 +05:30
pablohashescobarandGitHub adf366b325 chore: gpt environment variables in the example file (#698) 2023-04-04 18:07:01 +05:30
Aaryan KhandelwalandGitHub 2660d646ad chore: minor pages UI (#695)
* chore: fix minor ui bugs in pages

* chore: shortcut to add new block

* chore: keyboard accessibility

* chore: block options position
2023-04-04 16:21:46 +05:30
Aaryan KhandelwalandGitHub dad36b404d fix: pages ai modal (#694) 2023-04-04 14:07:17 +05:30
Aaryan KhandelwalandGitHub 51be70d814 chore: remove github importer tab (#693) 2023-04-04 00:27:08 +05:30
Aaryan KhandelwalandGitHub 4af5921991 chore: restrict users from creating workspace with reserved slugs (#692) 2023-04-04 00:13:21 +05:30
pablohashescobarandGitHub 588247f1c1 dev: back migration for project member views (#663) 2023-04-04 00:00:03 +05:30
pablohashescobarandGitHub 1bb93f1f50 dev: back migration to update label colors (#664) 2023-04-03 23:58:35 +05:30
pablohashescobarandGitHub d990f0038b fix: module link create url validation (#678) 2023-04-03 23:58:24 +05:30
Dakshesh JainandGitHub 67952bc225 feat: added user role on onboarding event (#684)
* feat: added tracker for views

* feat: added user role on onboarding event
2023-04-03 23:57:19 +05:30
a1f0f43992 feat: icon picker (#689)
* feat: icon picker

* style: icon picker modal

---------

Co-authored-by: Dakshesh Jain <dakshesh.jain14@gmail.com>
2023-04-03 23:54:26 +05:30
Aaryan KhandelwalandGitHub 007ed0afa4 fix: cmdk hover style (#690) 2023-04-03 23:52:39 +05:30
Kunal VishwakarmaandGitHub 62cca1c7cd feat: issue redirect (#685)
* feat: open issue in new tab for list and kanban view

* fix: used a tag
2023-04-03 23:51:46 +05:30
Aaryan KhandelwalandGitHub 5ba7d271b7 style: revamp pages (#688)
* feat: dnd added for blocks

* chore: added access option to pages

* style: ui fixes

* fix: polishing

* fix: build error
2023-04-03 23:30:29 +05:30
Saheb GiriandGitHub f6f9caf9e6 fix: now user can navigate using arrow keys (#681) 2023-04-03 19:04:11 +05:30
Aaryan KhandelwalandGitHub 35e40a7bec chore: added placeholder to remirror (#686)
* chore: added placeholder to remirror

* fix: build error
2023-04-03 18:14:50 +05:30
Aaryan KhandelwalandGitHub 3e08186d72 fix: kanban empty issues (#687) 2023-04-03 18:04:45 +05:30
Dakshesh JainandGitHub 150553d420 fix: tracker not creating user alias (#683) 2023-04-03 16:37:19 +05:30
Aaryan KhandelwalandGitHub 1b30e4b57f fix: user cannot create duplicate links (#680) 2023-04-03 16:22:37 +05:30
Anmol Singh BhatiaandGitHub a63c551e75 feat: completed cycle transfer issue validation added (#676)
* feat: completed cycle transfer issue validation added

* style: transfer issue section

* style: issue transfer button
2023-04-03 15:57:52 +05:30
Dakshesh JainandGitHub cfe7c5e0b7 fix: search button not working on image picker popover (#679) 2023-04-03 15:57:08 +05:30
Aaryan KhandelwalandGitHub 12ad3892f8 fix: signin page type (#671) 2023-04-03 15:07:39 +05:30
Dakshesh JainandGitHub 353197f583 feat: added tracker for views (#675) 2023-04-03 15:04:48 +05:30
Kunal VishwakarmaandGitHub 0cf498651c feat: shortcuts (#674)
* feat: added shortcut for page and view

* fix: shortcut for view

* fix: onclick method for views and pages
2023-04-03 14:23:50 +05:30
Saheb GiriandGitHub a18af1cecf fix: missing deleted icon in user activity (#673) 2023-04-03 11:51:25 +05:30
Aaryan KhandelwalandGitHub 61875722e4 chore: add auto generate description option to create issue modals (#672) 2023-04-03 11:41:46 +05:30
Vamsi KuramaandGitHub 7fed2ec6ef Merge pull request #670 from makeplane/stage-release
promote: stage-release to production
2023-04-01 20:29:39 +05:30
Vamsi KuramaandGitHub 93ba04aebc Merge pull request #669 from makeplane/develop
promote: develop to stage-release
2023-04-01 20:19:02 +05:30
Alejandro Pinar RuizandGitHub bd3ea456c3 fix: add tags for frontend build action (#445) 2023-04-01 18:30:26 +05:30
Saheb GiriandGitHub c0bf7783b1 style: designed user activity. (#666) 2023-03-31 23:00:28 +05:30
Anmol Singh BhatiaandGitHub 9f34f41982 feat: list and kanban view group by header icon (#665) 2023-03-31 21:20:23 +05:30
Kunal VishwakarmaandGitHub c940641ba1 Fix: view list bugs (#654)
* fix: viewlist link

* fix/removed side effects

* fix: deadzones in the link
2023-03-31 19:27:07 +05:30
Kunal VishwakarmaandGitHub 567966459b style: added icons to ellipses in project settings label (#662) 2023-03-31 18:31:44 +05:30
Anmol Singh BhatiaandGitHub 6055f5c4ee fix: cycle list page mutation fix (#661)
* fix: cycle list page mutation fix

* fix: cycle mutation fix
2023-03-31 18:31:21 +05:30
Saheb GiriandGitHub abe34ad7b1 fix: add scroll into view when editing label in settings page (#660) 2023-03-31 18:31:01 +05:30
Anmol Singh BhatiaandGitHub d596e41d4d fix: project setting control fix (#658)
* fix: project setting control fix

* fix: project member endpoint fix
2023-03-31 18:30:39 +05:30
Aaryan KhandelwalandGitHub fae1534887 fix: issues list flicker (#659)
* fix: issues list flicker

* fix: useeffect dependencies
2023-03-31 18:29:24 +05:30
Saheb GiriandGitHub 448d8c63f3 fix: old labels must have black color in project settings (#657) 2023-03-31 18:03:42 +05:30
Kunal VishwakarmaandGitHub 844ae4869a fix: view list link (#653)
* fix: viewlist link

* fix/removed side effects

* fix: deadzones in the link
2023-03-31 17:54:05 +05:30
Anmol Singh BhatiaandGitHub afd7741d0c fix: cycle status bug fix (#656) 2023-03-31 17:52:20 +05:30
Saheb GiriandGitHub f074f9f003 fix: graphs issue (#655)
* fix: prevent y axis to have decimal

* fix: add padding and labels to line chart
2023-03-31 17:33:51 +05:30
Anmol Singh BhatiaandGitHub f6500914be style: transfer issue modal empty state added (#652) 2023-03-31 16:05:02 +05:30
Saheb GiriandGitHub a6f306209d fix: issue modal title textfield reset (#651) 2023-03-31 16:04:51 +05:30
Anmol Singh BhatiaandGitHub e5507651c3 fix: issue sidebar module , cycle and label dropdown fix (#650) 2023-03-31 16:04:36 +05:30
Aaryan KhandelwalandGitHub e3005b7776 fix: project identifier check in project settings (#649) 2023-03-31 16:04:17 +05:30
Aaryan KhandelwalandGitHub 480e2c4d7f refactor: cycles toggle favorite (#648) 2023-03-31 16:03:58 +05:30
Aaryan KhandelwalandGitHub 09e17858fe chore: remove edit and push issue from page block (#647) 2023-03-31 16:03:48 +05:30
Saheb GiriandGitHub 13b2a6fd53 fix: persist data on tab switch in workspace (#646)
* fix: persist data on tab switch in workspace

* fix: build fail
2023-03-31 16:03:35 +05:30
Kunal VishwakarmaandGitHub 4ab82b9616 fix: style and bugs (#644)
* fix: style and bugs

* fix: removed unnecessary classes
2023-03-31 16:03:25 +05:30
Saheb GiriandGitHub 09d73c5e04 fix: issue activity (#645)
- fix: set and remove activity logs are showing problems
- fix: removing due date is showing static value of 1 Jan.
- fix:  old labels to have black color.
- fix:  word 'Description' should be bold
2023-03-31 14:46:17 +05:30
Vamsi KuramaandGitHub ed60707bae Merge pull request #643 from makeplane/develop
promote: develop to stage release
2023-03-31 04:41:24 +05:30
Aaryan KhandelwalandGitHub b2c15125fc fix: image upload (#642) 2023-03-31 04:24:57 +05:30
Aaryan KhandelwalandGitHub a8f125cfa8 fix: blocked and blocking in issue details sidebar (#641) 2023-03-31 03:50:35 +05:30
pablohashescobarandGitHub 15ce3537ad fix: recent pages endpoint (#640)
* fix: recent pages endpoint for date

* fix: order by
2023-03-31 03:32:32 +05:30
Aaryan KhandelwalandGitHub 448f383ec9 chore: cycle sidebar content updated (#639) 2023-03-31 03:21:37 +05:30
Aaryan KhandelwalandGitHub 0c39f0c563 style: custom error page (#638) 2023-03-31 03:09:55 +05:30
Aaryan KhandelwalandGitHub 65ddcb6d79 chore: pages content update, empty state for recent pages (#637) 2023-03-31 02:51:39 +05:30
Aaryan KhandelwalandGitHub 0c94b494ed refactor: calendar view (#636) 2023-03-31 02:43:38 +05:30
Aaryan KhandelwalandGitHub e2921539d0 refactor: sidebar stats mutation (#635) 2023-03-31 02:20:44 +05:30
Aaryan KhandelwalandGitHub 66d07e340b fix: delete mutations for issues, cycles and modules (#634) 2023-03-31 02:17:35 +05:30
vamsi 29ea592c4a dev: upgrade redis requirements to 4.5.4 2023-03-31 02:08:29 +05:30
vamsi 05323d4697 dev: new migrations for Pages related attributes 2023-03-31 02:04:33 +05:30
Anmol Singh BhatiaandGitHub 8fcfebf0fc fix: invalid date toast message updated (#633) 2023-03-31 01:40:06 +05:30
Anmol Singh BhatiaandGitHub 5d0533a44f style: ellipsis consistent style (#632)
* style: ellipsis consistent style

* style: consistent ellipsis menu for module and cycle
2023-03-31 00:31:00 +05:30
Anmol Singh BhatiaandGitHub 3cf2172520 style: delete workspace modal (#631) 2023-03-31 00:25:42 +05:30
Saheb GiriandGitHub a8a5873d88 fix: add icons to dropdown and replace link icon (#630) 2023-03-30 20:45:15 +05:30
Kunal VishwakarmaandGitHub 73c7b1bddc fix: modal typo (#629)
* fix: modal width inconsistency

* fix: modal typo
2023-03-30 20:25:04 +05:30
Anmol Singh BhatiaandGitHub 4441651f81 style: empty state image updated (#628) 2023-03-30 19:34:50 +05:30
Saheb GiriandGitHub 64c936b9b5 style: module modal consistent design (#625) 2023-03-30 19:28:04 +05:30
Dakshesh JainandGitHub 66ed6a1dc8 feat: added tracker for page, page block, and gpt (#623)
* refactor: made events function generic

* feat: added tracker for page, page block, and gpt
2023-03-30 19:27:46 +05:30
Anmol Singh BhatiaandGitHub 50275fd2ad fix: sidebar pending issue stats fix (#627) 2023-03-30 19:26:41 +05:30
Kunal VishwakarmaandGitHub 112fe8e7e6 fix: modal width inconsistency (#626) 2023-03-30 19:02:05 +05:30
Anmol Singh BhatiaandGitHub 02e6439bd5 feat: completed cycle transfer issue (#624)
* feat: bulk transfer issue for completed cycle added

* feat: toast alert added for issue transfer
2023-03-30 18:59:53 +05:30
pablohashescobarandGitHub f9ee898d88 chore: add workspace details and project details in page responses (#615)
* chore: add workspace details and project details in page responses

* fix: typo in workspace queryset
2023-03-30 18:59:39 +05:30
pablohashescobarandGitHub f79fdbf782 chore: create activity when a block is converter into an issue (#609) 2023-03-30 18:58:51 +05:30
pablohashescobarandGitHub 1d7b65ad83 fix: transfer issues endpoint (#620) 2023-03-30 18:58:23 +05:30
Saheb GiriandGitHub 1558f51c23 fix: show divider only when filter is selected (#618) 2023-03-30 18:54:44 +05:30
Aaryan KhandelwalandGitHub bc7dc43171 chore: ai assistant limit error (#619) 2023-03-30 18:45:33 +05:30
Kunal VishwakarmaandGitHub 70a00a6309 style: issue modal scroll (#621) 2023-03-30 18:45:11 +05:30
Saheb GiriandGitHub 4d56adba43 style: create cycle modal makeover (#617) 2023-03-30 18:35:29 +05:30
Anmol Singh BhatiaandGitHub 624d9dfd39 fix: icon and ellipsis (#622)
* fix: consistent icon for end date for cycle card

* fix: sidebar ellipsis fix
2023-03-30 18:35:05 +05:30
Aaryan KhandelwalandGitHub 3e8c375d1c fix: old labels color set to black (#614) 2023-03-30 18:07:18 +05:30
Aaryan KhandelwalandGitHub 880813685b fix: sidebar dropdowns height (#616)
* fix: old labels color set to black

* fix: issue details sidebar select dropdowns height
2023-03-30 18:07:00 +05:30
Aaryan KhandelwalandGitHub 16abbe0b3e fix: project identifier check in project settings (#613) 2023-03-30 17:48:03 +05:30
Aaryan KhandelwalandGitHub b65fa89cdb fix: role input on my profile page (#612) 2023-03-30 17:47:49 +05:30
Aaryan KhandelwalandGitHub ed4aae47a2 style: my profile page (#608) 2023-03-30 17:04:41 +05:30
Aaryan KhandelwalandGitHub 5feaed3961 fix: fetch correct list of issues on the calendar view (#611) 2023-03-30 17:03:37 +05:30
Saheb GiriandGitHub be5c4140ff refactor: issue label and make design consistent (#610) 2023-03-30 16:59:07 +05:30
pablohashescobarandGitHub 9c4fcca6c1 fix: ordering in queryset (#598) 2023-03-30 16:33:16 +05:30
pablohashescobarandGitHub 5aad20e7ed fix: project update for identifier (#604) 2023-03-30 16:33:04 +05:30
pablohashescobarandGitHub 3a599b6436 chore: workspace detail on issue activity serializer (#607) 2023-03-30 16:32:48 +05:30
Kunal VishwakarmaandGitHub c5ccc29418 feat: added invitaion pending pills (#606) 2023-03-30 16:01:24 +05:30
Anmol Singh BhatiaandGitHub f5f90dab69 fix : module and cycle invalid date fix (#605)
* fix: module and cycle modal invalid date validation

* fix: cycle and module sidebar invalid date
2023-03-30 16:00:48 +05:30
Aaryan KhandelwalandGitHub a94e38c093 fix: minor bugs in the onboarding screens (#603) 2023-03-30 13:55:50 +05:30
Saheb GiriandGitHub 63b7c1ee47 style: icons consistency and minor fixes (#601)
* fix: change discord casing

* style: icon and style consistency in cmd k

- change discord icon color
- make link copy link consistent
-  bolt to rocket icon for shortcut

* style: make icon color throughout the activity log
2023-03-30 13:54:18 +05:30
Kunal VishwakarmaandGitHub 1866fd77bb fix: changed buttons to primary (#602) 2023-03-30 13:53:03 +05:30
Kunal VishwakarmaandGitHub 531b9e3d64 style: fixed height issue in module card (#599) 2023-03-30 12:09:42 +05:30
Anmol Singh BhatiaandGitHub 06fb3e9b58 fix: calendar view bugs (#600)
* fix: text turncate added for issue

* fix: next week btn fix, style:calendar cell height fix
2023-03-30 12:09:11 +05:30
Aaryan KhandelwalandGitHub fb01e6d22c fix: module status line break (#597) 2023-03-30 02:07:48 +05:30
pablohashescobarandGitHub 89bb439d62 chore: return user request count from logger (#596) 2023-03-30 02:04:41 +05:30
e6055da150 feat: calendar view (#561)
* feat:start and last day of month helper function

* feat: start and last day of week helper function

* feat: weekday and everyday interval helper function

* feat: calendar date formater helper function

* feat: monthly calendar view , feat: weekend date toggle, feat: calendar month and year picker

* feat: monthly , weekly view and weekend toggle

* feat: drag and drop added in calendar view

* fix: drag and drop mutation fix

* chore: refactoring , feat: calendar view option added

* fix: calendar view menu fix

* style: calendar view style improvement

* style: drag and drop styling

* fix:week day format fix

* chore: calendar constant added

* feat: calendar helper function added

* feat: month and year picker, month navigator, jump to today funtionality added

* feat: weekly navigation and jump to today fix, style: month year picker fix

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-03-30 02:01:53 +05:30
Aaryan KhandelwalandGitHub 952c64d449 fix: mutation error for project issues (#595) 2023-03-30 01:50:00 +05:30
Aaryan KhandelwalandGitHub dcf8b562d3 chore: cycle and module sidebar stats filter (#594) 2023-03-30 01:31:43 +05:30
6a40dd911f fix: cycles date check for null and incomplete cycles endpoint (#580)
Co-authored-by: Vamsi Kurama <vamsi.kurama@gmail.com>
2023-03-30 01:26:14 +05:30
pablohashescobarandGitHub 9064709d5d feat: user activities endpoint (#590) 2023-03-30 01:25:55 +05:30
6d8eda9814 chore: update API endpoints for analytics (#574)
* chore: return workspace details on project create and update

* chore: update endpoints to return workspace and project details

---------

Co-authored-by: Vamsi Kurama <vamsi.kurama@gmail.com>
2023-03-30 01:25:32 +05:30
pablohashescobarandGitHub 7eaec16381 chore: add cycle id and module id in issues list endpoint (#586) 2023-03-30 01:25:16 +05:30
pablohashescobarandGitHub d7ed237f78 feat: add API limit for AI assistance (#592) 2023-03-30 01:23:56 +05:30
kunalv17andGitHub 5c1607f39b style: finished module card design (#593)
* style: finished modulecard design

* fix: use assignee list component
2023-03-30 01:17:51 +05:30
Saheb GiriandGitHub c75ca8203f feat: add label to create view modal (#591) 2023-03-30 01:08:14 +05:30
Anmol Singh BhatiaandGitHub cfd97041b8 feat: incomplete cycle endpoint added , issue sidebar cycle fix (#582)
* feat: incomplete cycle endpoint added , issue sidebar cycle fix

* fix: fetch key
2023-03-29 19:21:15 +05:30
Saheb GiriandGitHub 7337707a4e style: redesign issue activity (#579)
* style: redesign the activity feed and refactor the code

* chore: remove console logs

* revert: back to remirror rich text editor

* style: make icons, text smaller and reduce the spacing between logs
2023-03-29 19:19:37 +05:30
Dakshesh JainandGitHub b441a2ce20 feat: added labels in filters (#585)
* feat: added labels in filters

* fix: added labels in fetch keys
2023-03-29 19:18:57 +05:30
Aaryan KhandelwalandGitHub 1509c8611d fix: mutation while adding issue to cycle or module (#589) 2023-03-29 19:13:53 +05:30
kunalv17andGitHub 4e9715a5b2 style: added divider to the pages block (#571)
* style: added hr to the pages block

* style: added divider and removed hr

* chore: removed index prop

* chore: removed index
2023-03-29 18:36:52 +05:30
Dakshesh JainandGitHub 10657d4796 fix: filter menu not closing on select other parent option (#587) 2023-03-29 18:18:45 +05:30
Saheb GiriandGitHub 653cc29290 style: issue filter design (#588)
* style: redesign issue filter pill

* style: redesign issue label on the view modal
2023-03-29 18:18:08 +05:30
Dakshesh JainandGitHub fa9c6581fd fix: using proper name for tracker key (#581)
* fix: transmitting selective data for analytics

* fix: using proper name for tracker key

* fix: using proper name for tracker key
2023-03-29 16:34:56 +05:30
Aaryan KhandelwalandGitHub 96910e1897 chore: ai for issue description (#575)
* feat: block sync

* chore: ai assistant for issue description
2023-03-29 16:30:40 +05:30
sphynxuxandGitHub 2f69761130 Merge pull request #583 from makeplane/chore/analytics_payload
chore: analytics payload
2023-03-29 16:29:17 +05:30
sphynxuxandGitHub a8f5a3eda1 Merge pull request #584 from makeplane/fix/parent_issue_modal
fix: design of select parent issue modal
2023-03-29 16:28:55 +05:30
Anmol Singh BhatiaandGitHub 248d094762 feat: cycle list page (#577)
* style: cycle list page

* fix:typo fix
2023-03-29 16:27:55 +05:30
pablohashescobar 1255f4756d fix: uuid serilaizable error 2023-03-29 16:25:06 +05:30
Aaryan Khandelwal 22f8eb9a68 fix: design of select parent issue modal 2023-03-29 16:24:04 +05:30
pablohashescobar 3ba9cddc2a chore: add user id when logging for analytics 2023-03-29 16:15:11 +05:30
kunalv17andGitHub e3cb0ed13e feat/added toggle for pages in settings features (#576)
* feat/added toggle for pages in settings features

* style:changed pages icon
2023-03-29 15:21:08 +05:30
pablohashescobar 2786f09e85 Merge branch 'develop' of github.com:makeplane/plane into develop 2023-03-29 14:52:38 +05:30
Dakshesh JainandGitHub 06daf68753 fix: transmitting selective data for analytics (#578) 2023-03-29 14:38:30 +05:30
Anmol Singh BhatiaandGitHub 2138ddf1f5 fix: profile page header fix (#572) 2023-03-29 13:17:31 +05:30
pablohashescobar f9fa345b25 Merge branch 'develop' of github.com:makeplane/plane into develop 2023-03-29 12:54:33 +05:30
Dakshesh JainandGitHub c3e1d33518 feat: jitsu tracker setup (#542)
* feat: jitsu tracker setup

also using it in create, update and delete project & workspace

* refactor: added some more check condition on track-event api

* fix: added env vars in turbo.json

* feat: added user onboard event, workspace invite and workspace invite accept events

* feat: add tracker for issues, state, modules and cycles

* fix: add @jitsu/nextjs in package.json
2023-03-29 12:24:19 +05:30
kunalv17andGitHub 9eb9b7bf6c style: dashboard color (#568) 2023-03-29 11:54:37 +05:30
Saheb GiriandGitHub c60b152a7c fix: add crisp chat with us option in cmdk (#570) 2023-03-29 11:53:09 +05:30
Anmol Singh BhatiaandGitHub e07ffc3a46 fix: module sidebar link section relocation (#569) 2023-03-29 11:52:39 +05:30
Saheb GiriandGitHub dd3bca9a32 fix: cmdk integration (#567)
* fix: issues not showing on cmd k

* fix: text overflows on longer issue title

* fix: add loading state whenever there is a network call

* fix: minor ux changes

* feat: replace loading with spinner
2023-03-29 00:51:47 +05:30
628591854d fix: search endpoint to return distint results (#566)
Co-authored-by: Vamsi Kurama <vamsi.kurama@gmail.com>
2023-03-29 00:23:35 +05:30
pablohashescobarandGitHub 05e9c0f76f fix: cycle validation for completed cycles (#559) 2023-03-29 00:23:16 +05:30
pablohashescobarandGitHub 3bebcc4714 chore: return gpt response in html (#555)
* chore: return gpt response in html

* chore: update the response br
2023-03-29 00:23:02 +05:30
pablohashescobarandGitHub d16d32cea8 feat: move issues from one cycle to another (#554)
* feat: move issues from one cycle to another

* fix: push method when updating

* fix: new cycle completed validation
2023-03-29 00:22:48 +05:30
Aaryan KhandelwalandGitHub b654d30aeb chore: minor fixes on pages (#557)
* feat: block sync

* chore: minor fixes on pages

* fix: remove dangerously set inner html

* fix: pages crud operations mutation

* fix: favorites mutation for recent pages

* fix: remove dangerously set inner html
2023-03-29 00:20:00 +05:30
Saheb GiriandGitHub c0a471e916 fix: don't allow decimal in the y axis of completed issues graph (#564) 2023-03-29 00:08:34 +05:30
Anmol Singh BhatiaandGitHub e2eeec8f79 feat: completed cycle message added (#565) 2023-03-28 18:48:57 +05:30
kunalv17andGitHub ccd03a4a45 refactor: constants fetchkeys strings to upper case (#563) 2023-03-28 18:28:26 +05:30
pablohashescobar 20bbe74b57 Merge branch 'develop' of github.com:makeplane/plane into develop 2023-03-28 17:47:45 +05:30
kunalv17andGitHub b7b8d3914a style: views list page, chore: views favotire (#562) 2023-03-28 16:48:46 +05:30
afb92ea850 feat: add global search through cmd k (#560)
* feat: cmdk integration

* feat: create view, cycle, project, module and workspace from command k

* feat: user can logout directly from command menu

* feat: user can visit sub page like various settings

* feat: change state of issue from command menu

* chore: add current issue state and minor UX improvements

* refactor: moved change issue state to new file

* feat: change issue priority from command k

* feat: delete issue from command k

* feat: copy issue url to clipboard

* fix: change placeholder when settings page is selected

* chore: remove logout option from cmd k

* feat: add help options to cmd k

* feat: assign issue to member from cmd k

* feat: now assign issue to yourself from cmd k

* chore: implement new cmd k design with icons

* feat: implemented global search feature in the cmd k

* feat: add keyboard acessibility to cmd k list items

* chore: remove console logs

* fix: pages icon in cmd list

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-03-28 14:49:27 +05:30
kunalv17andGitHub fc4d06fe0c style: changed the workspace cards height (#558) 2023-03-28 14:17:24 +05:30
pablohashescobar dd90f26d93 Merge branch 'master' of github.com:makeplane/plane into develop 2023-03-28 13:58:38 +05:30
Aaryan KhandelwalandGitHub fbbf97f3a6 chore: remove docs (#553)
* feat: block sync

* chore: remove docs from repo
2023-03-28 03:54:48 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
d9f31a1eb7 chore(deps): bump redis from 4.4.2 to 4.5.3 in /apiserver/requirements (#556)
Bumps [redis](https://github.com/redis/redis-py) from 4.4.2 to 4.5.3.
- [Release notes](https://github.com/redis/redis-py/releases)
- [Changelog](https://github.com/redis/redis-py/blob/master/CHANGES)
- [Commits](https://github.com/redis/redis-py/compare/v4.4.2...v4.5.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-28 03:41:58 +05:30
pablohashescobarandGitHub a06d59f77d feat: sync flag for syncing issues with page blocks (#547) 2023-03-28 01:52:46 +05:30
pablohashescobarandGitHub 722a053461 fix: date for earlier this week (#540) 2023-03-28 01:52:34 +05:30
pablohashescobarandGitHub d6818e74fd feat: page view toggle in project settings (#536) 2023-03-28 01:52:21 +05:30
Aaryan KhandelwalandGitHub 37aade5ef6 fix: order by last created (#550)
* feat: block sync

* fix: order by last created order
2023-03-28 01:52:13 +05:30
Aaryan KhandelwalandGitHub b9e42d116e fix: issue modal overflow (#549)
* feat: block sync

* fix: create issue modal overflow
2023-03-28 01:52:04 +05:30
Aaryan KhandelwalandGitHub e0928d6ec5 fix: default label color (#551)
* feat: block sync

* fix: default label color
2023-03-28 01:50:55 +05:30
Aaryan KhandelwalandGitHub 909ccd578b chore: crisp integration (#552)
* feat: block sync

* chore: crisp integration

* fix: chat with us icon
2023-03-28 01:50:36 +05:30
Aaryan KhandelwalandGitHub 08ee5dc6b1 feat: block sync (#548) 2023-03-28 00:36:20 +05:30
pablohashescobarandGitHub a5a96d9f66 fix: add slack sdk in requirements file (#546) 2023-03-28 00:33:14 +05:30
pablohashescobarandGitHub 691ea0c080 feat: slack bot setup (#545) 2023-03-27 23:37:00 +05:30
pablohashescobarandGitHub 8bd557a743 feat: gzip compressor for performance upgrades (#538) 2023-03-27 23:36:13 +05:30
pablohashescobarandGitHub 35b80b422d fix: gpt task and prompt (#537) 2023-03-27 23:36:00 +05:30
pablohashescobarandGitHub d6ffc3176e fix: global search endpoint to show entities within projects and add filtering through identifiers (#543) 2023-03-27 23:34:04 +05:30
Aaryan KhandelwalandGitHub 3503b22dd9 refactor: pages folder structure (#544)
* refactor: pages folder structure, mutation issues

* fix: block edit and push

* fix: block title placeholder
2023-03-27 23:19:05 +05:30
Aaryan KhandelwalandGitHub e13b679c28 fix: join project screen flicker (#541) 2023-03-27 16:53:31 +05:30
Aaryan KhandelwalandGitHub 21dd2e703b fix: module progress bar (#535) 2023-03-26 11:37:06 +05:30
Aaryan KhandelwalandGitHub 5dd5fe2d09 feat: gpt integration for page block description (#539) 2023-03-26 11:36:10 +05:30
pablohashescobarandGitHub 52d4828e1d fix: issue filters for start date and target date (#530) 2023-03-25 23:57:31 +05:30
pablohashescobarandGitHub c5baa6183c fix: pages endpoint for dates and access (#531)
* fix: pages endpoint for dates and access

* fix: recent pages endpoint

* fix: recent pages endpoint

* fix: date object in recent pages endpoint

* dev: update nomenclature
2023-03-25 23:57:16 +05:30
pablohashescobarandGitHub 69387ffd8c fix: page labels update (#532) 2023-03-25 23:57:06 +05:30
pablohashescobarandGitHub 32ab7951f7 perf: update issue serializer (#534) 2023-03-25 23:56:53 +05:30
Aaryan KhandelwalandGitHub 5d67029b5a feat: pages (#533)
* style: page details

* style: page blocks design

* chore: pages list end points

* feat: add blocks, push blocks to issues

* feat: page labels, color options

* feat: added labels to pages

* fix: update page mutation
2023-03-25 23:39:46 +05:30
pablohashescobarandGitHub 578d724e41 feat: global search endpoint for workspace (#529) 2023-03-25 11:16:33 +05:30
pablohashescobarandGitHub a3a792741f feat: GPT integration (#526)
* feat: GPT integration

* dev: move engine value to env variable
2023-03-25 11:14:26 +05:30
pablohashescobarandGitHub 31624f3ae6 fix: start date and target date issue filter (#525) 2023-03-25 11:14:16 +05:30
pablohashescobarandGitHub 5152deb2d0 fix: model ordering (#521)
* fix: model ordering

* fix: my pages and create by others pages
2023-03-25 11:14:05 +05:30
pablohashescobarandGitHub c6ba93da72 fix: dashboard issues filters (#519)
* fix: upcoming issues filters

* dev: update dashboard endpoint to return correct month
2023-03-25 11:13:14 +05:30
pablohashescobarandGitHub 28b3c999ae dev: update recent pages endpoint and return blocks on page listing (#517)
* dev: update recent pages endpoint and return blocks on page listing

* fix: yesterday's pages
2023-03-25 11:12:54 +05:30
82b9275609 fix: deselecting filter option when clicked again (#528)
* fix: filters dropdown overflowing issue

* filters dropdown z-index

* fix: deselecting filter option when clicked again

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-03-24 23:33:28 +05:30
Dakshesh JainandGitHub f2054b6945 style: removed color text from label modal (#527)
also closing color picker on color select
2023-03-24 23:32:33 +05:30
Dakshesh JainandGitHub f3583f6415 feat: added filter 'created_by' (#524)
* feat: clear filter button

* feat: added filter 'created_by'

* style: text align left for all the issues in filters dropdown
2023-03-24 23:31:56 +05:30
Aaryan KhandelwalandGitHub 02f423bcb6 fix: workspace dashboard (#522)
* chore: completed issues graph

* style: issue stats
2023-03-24 11:06:52 +05:30
Aaryan KhandelwalandGitHub 3d34741356 chore: update view button (#523) 2023-03-24 10:56:06 +05:30
sphynxuxandGitHub 7731ee5de4 Merge pull request #520 from makeplane/feat/custom_error_page
fix: removed unnecessary variable
2023-03-24 01:15:19 +05:30
Aaryan Khandelwal 0ba2c5456a fix: removed unnecessary variable 2023-03-24 01:14:17 +05:30
sphynxuxandGitHub a0ce3f0be6 Merge pull request #518 from makeplane/feat/custom_error_page
feat: custom error page
2023-03-24 01:12:29 +05:30
Aaryan KhandelwalandGitHub 472767ab67 feat: group by created by option (#516) 2023-03-24 01:11:42 +05:30
Aaryan Khandelwal ad60b8774e feat: custom error page 2023-03-24 01:11:33 +05:30
pablohashescobarandGitHub 6c6f9a5bfd fix: dashboard endpoint for overdue and upcoming issues (#502)
* fix: dashboard endpoint for overdue and upcoming issues

* dev: update for upcoming issue to get target date null issues as well

* dev: update the filter
2023-03-24 00:13:48 +05:30
pablohashescobarandGitHub 3056727190 dev: endpoints for my, other, recent and favorite pages, add sort order and color for pages (#499)
* dev: endpoints for my, other, recent and favorite pages, add sort order and color for pages

* dev: fix state attribute error while saving page blocks
2023-03-24 00:13:26 +05:30
pablohashescobarandGitHub 053ae2063e fix: issue filtering keyerror (#494) 2023-03-24 00:13:05 +05:30
pablohashescobarandGitHub 2501c819d3 fix: keyerror on comment create and update (#492) 2023-03-24 00:12:56 +05:30
Aaryan KhandelwalandGitHub 765cfdbf7e fix: kanban loading state (#514) 2023-03-23 23:36:52 +05:30
guru_sainathandGitHub 2f2caaaf6e Feat: Github importer to sync issues, users, and labels with workspace projects. (#509)
* Dev: Github integration with issues and layout integration

* dev: Github Integration route and UI configuration
2023-03-23 23:27:11 +05:30
Aaryan KhandelwalandGitHub 7892a563b7 fix: minor ui fixes (#515)
* fix: sidebar arror positioning

* chore: show empty groups default value as true
2023-03-23 23:25:30 +05:30
Aaryan KhandelwalandGitHub f3b7fc6eb5 fix: filters dropdown overflowing issue (#513)
* fix: filters dropdown overflowing issue

* filters dropdown z-index
2023-03-23 23:25:08 +05:30
Aaryan KhandelwalandGitHub f01f2fb9bd style: my profiles page responsiveness (#512) 2023-03-23 22:55:58 +05:30
Aaryan KhandelwalandGitHub 19434342d3 fix: delete image preview after modal close (#511) 2023-03-23 22:55:12 +05:30
Aaryan KhandelwalandGitHub 6962d7718f chore: updated my issues text (#507) 2023-03-23 22:54:53 +05:30
Anmol Singh BhatiaandGitHub b6a3615f66 feat: completed cycle validation , fix: quick action and kanban fix (#505)
* feat: completed cycle card validation

* fix: unique key to hidden group

* feat: completed cycle sidebar validation

* fix: remove console log from progress chart hover

* feat: kanban and list view completed cycle validation

* feat: quick action validation

* refactor: code refactor

* fix: sidebar draft cycle status
2023-03-23 22:42:08 +05:30
Anmol Singh BhatiaandGitHub 5191fc5f7c style: create workspace bg fix (#510) 2023-03-23 22:41:30 +05:30
Aaryan KhandelwalandGitHub 567afa6d39 fix: kanban drag and drop (#508) 2023-03-23 18:52:18 +05:30
Dakshesh JainandGitHub feb0e40559 refactor: state with group 'completed' or 'cancelled' are collapsed by default (#506) 2023-03-23 18:10:28 +05:30
4a81b988b4 feat: implemented new pages design with bare minimum functionality (#503)
* chore: add page types and page api service

* chore: add create, list, update and delete on pages

* chore: add create, delete and patch page blocks

* feat: add and remove pages to favorite

* fix: made neccessary changes

- used tailwind for hover events
- add error toast alert
- used partial for patch request

* fix: replace absolute positiong with a flex box

* fix: design list page view to match with ui

* feat: add top large textarea for page title and description

* refactor: add page label with types

* feat: add pages grid layout

* feat: add tabs and masonry layout

* fix: build errors

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-03-23 16:12:14 +05:30
Aaryan KhandelwalandGitHub c755907d99 chore: new workspace endpoint (#504)
* chore: new workspace dashboard endpoint

* chore: overdue and upcoming issues
2023-03-23 15:54:59 +05:30
Aaryan KhandelwalandGitHub ad2fa91a2b chore: new global select filters component (#501) 2023-03-23 12:01:50 +05:30
Saheb GiriandGitHub 10e5ba7b3e feat: add pages and page blocks (#495)
* chore: add page types and page api service

* chore: add create, list, update and delete on pages

* chore: add create, delete and patch page blocks

* feat: add and remove pages to favorite

* fix: made neccessary changes

- used tailwind for hover events
- add error toast alert
- used partial for patch request

* fix: replace absolute positiong with a flex box
2023-03-23 11:01:06 +05:30
Aaryan KhandelwalandGitHub d477c19ad9 feat: show empty states toggle button in the views dropdown (#500)
* feat: show empty states toggle button in views dropdown

* refactor: empty state toggle naming convention, feat: hidden groups in section in the kanban board
2023-03-23 02:13:52 +05:30
Aaryan KhandelwalandGitHub 79249c5c9b fix: states order (#498)
* fix: order of states in the kanban board

* fix: state name in list view
2023-03-23 01:00:50 +05:30
pablohashescobarandGitHub 4e3c9397ea feat: page labels and favorites (#487)
* dev: initiate page labels

* dev: page labels

* dev: my pages endpoint
2023-03-22 23:41:30 +05:30
Aaryan KhandelwalandGitHub cd26b2e096 Revert "fix: sidebar and shortcut modal (#493)" (#497)
This reverts commit f615f8ac0c.
2023-03-22 23:40:25 +05:30
pablohashescobarandGitHub 27b1308227 fix: file asset uploading and deleting (#496) 2023-03-22 23:38:47 +05:30
Anmol Singh BhatiaandGitHub f615f8ac0c fix: sidebar and shortcut modal (#493)
* fix: shortcut modal cmd and ctrl fix

* fix: sidebar collapse state ordering
2023-03-22 19:08:04 +05:30
Dakshesh JainandGitHub 031f6443a0 style: kanban loader (#491) 2023-03-22 19:06:36 +05:30
Dakshesh JainandGitHub e48d98cea6 feat: showing progress while page is being loaded (#485) 2023-03-22 19:05:36 +05:30
Dakshesh JainandGitHub a830808f9d refactor: views & filter (#490)
* fix: not saving filters on views detail page

* refactor: using issues endpoint to get issues in views detail page

feat: showing toast alert on saving view
2023-03-22 18:18:19 +05:30
Aaryan KhandelwalandGitHub 9a97c97336 chore: new modules and cycles response (#489) 2023-03-22 18:10:38 +05:30
Aaryan KhandelwalandGitHub 2e346158ba fix: minor ui fixes (#488) 2023-03-22 16:58:32 +05:30
Aaryan KhandelwalandGitHub 283950c8e2 style: views (#486) 2023-03-22 14:47:13 +05:30
vamsi 818d1147d5 dev: new migrations for added fields and introducing Pages 2023-03-22 01:39:53 +05:30
pablohashescobarandGitHub c4594bff01 feat: cycles and modules issues state group percentages (#484)
* dev: state group issue percentage on cycle list

* dev: add issue percentage fields for modules and query updates on cycle apis
2023-03-22 01:36:52 +05:30
pablohashescobarandGitHub 5e81600e38 feat: jira issue importer (#476)
* dev: initialize jira importer

* dev: create service import for jira

* dev: update task to create all users for project and workspace and also create assignees when importing bulk assignees

* dev: create bulk modules import endpoint for jira epics

* dev: create bulk module issues when importing modules
2023-03-22 01:36:38 +05:30
pablohashescobarandGitHub 846e73e3b8 feat: add issue filters for cycles and modules (#475) 2023-03-22 01:36:06 +05:30
pablohashescobarandGitHub 4dc76eac19 fix: project url max length (#471) 2023-03-22 01:35:53 +05:30
pablohashescobarandGitHub f7dbc5e9c0 feat: page and page-blocks (#468)
* dev: initiate paper models

* feat: page and page-blocks

* dev: page id filter for page blocks
2023-03-22 01:34:10 +05:30
pablohashescobarandGitHub 025c8b3835 feat: sub issue toggle (#464) 2023-03-22 01:33:56 +05:30
pablohashescobarandGitHub 9339138c0e feat: user workspace dashboard endpoint (#461)
* dev: initiate new dashboard endpoint

* fix: updated issue week for this count and pending issues

* dev: add try catch block

* dev: add exception

* dev: issue state distribution endpoint

* dev: move from state name to state group

* dev: add overdue issues and upcoming issues
2023-03-22 01:33:44 +05:30
pablohashescobarandGitHub 988b27f909 refactor: users endpoint to return invites and assigned issues count (#457) 2023-03-22 01:33:31 +05:30
pablohashescobarandGitHub a3bacbfef1 fix: workspace invitation datetime import (#456)
* chore: update the endpoint to return last 6 months data

* dev: add datetime import
2023-03-22 01:33:19 +05:30
pablohashescobarandGitHub 698718aa8d chore: rename analytics env variable keys (#455) 2023-03-22 01:33:06 +05:30
pablohashescobarandGitHub 7fd775ab05 chore: update the endpoint to return last 6 months data (#453) 2023-03-22 01:32:51 +05:30
pablohashescobarandGitHub e5f8c94de8 feat: add flag for toggling issue views (#452) 2023-03-22 01:32:37 +05:30
Dakshesh JainandGitHub 9c388d8e50 refactor: setting filters as null if value is an empty array (#483)
* refractor: added params to fetch key

* feat: create views directly from views list page

fix: selected filter not showing up in multi-level dropdown, refactor: arranged imports

* refactor: setting filters as null if value is an empty array
2023-03-21 17:11:04 +05:30
Dakshesh JainandGitHub bf09673d09 refactor: making unsplash api request from api folder (#482)
* refractor: added params to fetch key

* feat: create views directly from views list page

fix: selected filter not showing up in multi-level dropdown, refactor: arranged imports

* refactor: making unsplash api request from api folder

to hide acces key from client side
2023-03-21 16:31:01 +05:30
Dakshesh JainandGitHub 5869c91d70 fix: added redirection to sign-in page if fetching user was a fail in the context (#481)
* refractor: added params to fetch key

* feat: create views directly from views list page

fix: selected filter not showing up in multi-level dropdown, refactor: arranged imports

* fix: added redirection to sign-in page if fetching user was a fail in the context
2023-03-21 12:48:17 +05:30
Dakshesh JainandGitHub 53df658b60 refactor: added params to filters, and removed manual mutation (#480)
* refractor: added params to fetch key

* feat: create views directly from views list page

fix: selected filter not showing up in multi-level dropdown, refactor: arranged imports

* refactor: added params to filters, and removed manual mutation
2023-03-21 12:47:47 +05:30
Anmol Singh BhatiaandGitHub 505b14e3a6 fix: ui fixes and improvement (#479)
* fix: list view longer title tooltip fix

* fix: module and cycle sidebar ui improvement
2023-03-21 12:47:10 +05:30
Anmol Singh BhatiaandGitHub b96d40f106 style: auth screens (#478)
* style: sign in page

* style: github and google sign

* style: sign with code and password

* style: not a member and not authorized for project setting

* style: join project icon

* chore: comment removed
2023-03-21 12:46:12 +05:30
Dakshesh JainandGitHub 68150a9d2b feat: create views directly from the views list page (#472)
* refractor: added params to fetch key

* feat: create views directly from views list page

fix: selected filter not showing up in multi-level dropdown, refactor: arranged imports
2023-03-18 11:34:49 +05:30
Dakshesh JainandGitHub e6b0012fe2 fix: mutation on issue create (#473)
* refractor: added params to fetch key

* feat: create views directly from views list page

fix: selected filter not showing up in multi-level dropdown, refactor: arranged imports

* fix: mutation on project create
2023-03-18 11:34:29 +05:30
Anmol Singh BhatiaandGitHub 5739d95ab4 style: onboarding, chore: refactoring (#474)
* style: onboarding screens

* style: onboarding card component and refactoring

* fix: onboarding card text fix

* fix: merge conflict fix
2023-03-18 11:34:09 +05:30
pablohashescobarandGitHub 350e183375 dev: update importer task to create user automatically and adding it to project and workspace (#467) 2023-03-17 22:57:10 +05:30
Aaryan KhandelwalandGitHub e7ef6275cd style: new buttons added (#470) 2023-03-17 10:40:38 +05:30
Aaryan KhandelwalandGitHub 4de0abfc22 fix: minor ui fixes (#469) 2023-03-17 10:39:06 +05:30
Aaryan KhandelwalandGitHub 5f796e732a fix: bot activity and comment log name (#463)
* fix: github bot activity log details

* fix: bot comment details

* fix: updated bot logs

* refactor: bot name logic
2023-03-17 10:38:01 +05:30
Dakshesh JainandGitHub 0fb9a14f15 refractor: added params to fetch key (#465) 2023-03-16 18:15:08 +05:30
Aaryan KhandelwalandGitHub 23c468786d style: filter issues dropdown (#466) 2023-03-16 18:14:07 +05:30
Dakshesh JainandGitHub 0f06589b83 style: made new issue filter dropdown (#462) 2023-03-16 16:27:18 +05:30
Aaryan KhandelwalandGitHub a84abc60b2 style: workspace dashboard (#460)
* style: workspace dashboard

* feat: activity graph

* chore: change tile colors for activity graph

* fix: activity graph tiles order, color

* style: activity intensity
2023-03-16 15:53:49 +05:30
Aaryan KhandelwalandGitHub 27324ddd93 fix: github description not appearing (#459) 2023-03-16 15:53:25 +05:30
Aaryan KhandelwalandGitHub d413dd1169 chore: update user profile stats (#458) 2023-03-16 15:53:09 +05:30
Dakshesh JainandGitHub ef0e326ca0 feat: issues filter using views (#448)
* fix: made basic UI for views, binded services and logic for views

* feat: views list, delete view, and conditionally updating filters or my view props
2023-03-16 14:07:19 +05:30
Aaryan KhandelwalandGitHub 96ad751e11 style: new workspace dashboard design (#454)
* style: workspace dashboard

* feat: activity graph
2023-03-16 01:36:21 +05:30
vamsi 8370511a66 dev: new migrations Importer, IssueView, IssueViewFavorite and a few alterations 2023-03-16 00:41:50 +05:30
pablohashescobarandGitHub 836dc4027b feat: jitsu events for sign in and sign up (#423)
* feat: jitsu events for sign in and sign up

* dev: update event data
2023-03-15 23:25:38 +05:30
pablohashescobarandGitHub 88754e6fc0 dev: separate endpoints for workspace assets and user assets (#420) 2023-03-15 23:25:23 +05:30
pablohashescobarandGitHub b6ee197b40 feat: issue filter views (#418)
* dev: views initiated

* dev: refactor filtering logic

* dev: move state grouping filter to util function

* dev: view issues create endpoint and update on filters for time

* dev: rename views to issue views

* dev: rename in serilaizer and views

* dev: update issue filters

* dev: update filter

* feat: create issue favorites

* dev: update query keys

* dev: update create and update method
2023-03-15 23:25:09 +05:30
pablohashescobarandGitHub 46f6b61928 refactor: grouper function to fix priority keys (#415) 2023-03-15 23:24:55 +05:30
pablohashescobarandGitHub 5d8f2b6b75 feat: github importer (#425)
* dev: init github importer

* dev: add endpoint for creating import

* dev: create endpoint to bulk create issues

* dev: bulk issue importer

* dev: bulk create endpoints for labels and updates in issue bulk create endpoint to create labels and links

* dev: add comments in bluk create

* dev: status import endpoint and user invitaion workflow

* dev: initiate github repo sync

* dev: bulk issue sync endpoint and fix key issue in bg task

* dev: update endpoints for service imports

* dev: update labels logic

* dev: update importer task

* dev: bulk issue activities

* dev: update importer task for mapped users

* dev: update importer endpoint to send github token

* dev: update bulk import endpoint

* fix: workspace get query

* dev: update bulk import endpoints
2023-03-15 23:24:44 +05:30
pablohashescobarandGitHub d3ca8560fc fix: project list ordering (#427) 2023-03-15 23:24:26 +05:30
pablohashescobarandGitHub ab9e0cf559 refactor: update response structure for cycle issue and module issues (#432) 2023-03-15 23:21:37 +05:30
pablohashescobarandGitHub c07cfee018 refactor: update favorites for project, cycle and module (#434)
* dev: refactor favorites

* dev: fix typo
2023-03-15 23:21:23 +05:30
pablohashescobarandGitHub ed8f0b8473 feat: user activity graph (#437)
* feat: user activity graph

* dev: issue completed and activity graph
2023-03-15 23:21:08 +05:30
pablohashescobarandGitHub 0082a98d53 dev: add cycle date validation (#442) 2023-03-15 23:20:49 +05:30
pablohashescobarandGitHub d6aadb115d refactor: users endpoint to return invites and assigned issues count (#449) 2023-03-15 23:20:31 +05:30
Aaryan KhandelwalandGitHub 4639ab3d9c fix: workspace dropdown (#447)
* fix: workspace dropdown links

* fix: module progress round off
2023-03-15 18:50:37 +05:30
Aaryan KhandelwalandGitHub c11bf7c7de feat: progress bar for module (#446) 2023-03-15 18:00:40 +05:30
Anmol Singh BhatiaandGitHub ae8902e815 style: sidebar icon color (#444) 2023-03-15 17:41:52 +05:30
Anmol Singh BhatiaandGitHub 164072e3cc fix: date checker edge case fix (#443) 2023-03-15 17:41:24 +05:30
928ebdf632 fix: mutation for issue update on both kanban & list (#436)
* refactor: issues filter logic

* fix: removed fetch logic from hooks

* feat: filter by assignee and label

* chore: remove filter buttons

* feat: filter options

* fix: mutation for issue update on both kanban & list

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-03-15 11:44:44 +05:30
Anmol Singh BhatiaandGitHub 636e8e6c60 style: empty state global component (#435) 2023-03-15 11:01:54 +05:30
Aaryan KhandelwalandGitHub bfab4865cd chore: update user file assets endpoint (#438)
* chore: new service for user assets

* chore: update user file assets endpoint
2023-03-15 11:00:42 +05:30
Aaryan KhandelwalandGitHub dbd6de0988 feat: views template created (#439) 2023-03-15 11:00:05 +05:30
Aaryan KhandelwalandGitHub 32d37ec45e refactor: custom search select component (#440) 2023-03-15 10:59:43 +05:30
Aaryan KhandelwalandGitHub bcd2ac1317 feat: issue filters dropdown created (#441) 2023-03-15 10:59:23 +05:30
Dakshesh JainandGitHub 0117ccfca2 fix: made project list authenticated (#428) 2023-03-14 12:21:06 +05:30
Aaryan KhandelwalandGitHub 0ba81a10f1 style: made the paddings and text sizes smaller (#433) 2023-03-14 12:18:14 +05:30
Aaryan KhandelwalandGitHub 3f5bbf336c fix: truncate text function (#431) 2023-03-14 12:16:38 +05:30
Aaryan KhandelwalandGitHub d6d51c2f43 fix: modules sidebar lead select not working (#429) 2023-03-14 12:16:26 +05:30
Anmol Singh BhatiaandGitHub d5d64e09d4 style: design (#430)
* style: shortcut modal

* style: feature setting module icon

* style: delete issue modal

* style: delete project modal

* style: sidebar prompt for chart and other info

* fix: create issue modal state icon

* fix: workspace dropdown
2023-03-13 23:38:43 +05:30
Narayana VadapalliandGitHub 7d7683ae6f Merge pull request #424 from pinarruiz/feature/upload-images
Github actions to push images
2023-03-13 10:20:37 +05:30
Narayana VadapalliandGitHub fcd64de8af Update push-image-frontend.yml 2023-03-13 10:19:17 +05:30
Narayana VadapalliandGitHub b2765d47b4 Update push-image-backend.yml 2023-03-13 10:15:05 +05:30
Alejandro Pinar Ruiz aca0c251b8 Github actions to push images 2023-03-12 20:35:47 +01:00
pablohashescobarandGitHub 6de6522a41 chore: permissions for api endpoints (#419) 2023-03-11 23:51:06 +05:30
Vihar KuramaandGitHub bff89ee4c6 Merge pull request #422 from makeplane/feat/issue_templates
feat: new issue templates
2023-03-11 23:50:16 +05:30
pablohashescobar 7744d9b69a feat: new issue templates 2023-03-11 23:49:02 +05:30
Aaryan KhandelwalandGitHub 441cf39d2c refactor: global workspace form (#421) 2023-03-11 17:23:23 +05:30
Anmol Singh BhatiaandGitHub 4a7f80712b style: workspace sidebar (#417) 2023-03-10 16:05:10 +05:30
Aaryan KhandelwalandGitHub c7923f6d44 feat: added load more button to github repos dropdown (#414) 2023-03-10 16:03:49 +05:30
Anmol Singh BhatiaandGitHub 4fad685ec8 style: workspace dropdown (#416)
* style: workspace dropdown

* style: workspace dropdown hover fix
2023-03-10 12:32:29 +05:30
Anmol Singh BhatiaandGitHub 704b7d02ef style : ui fixes (#412)
* fix: kanban view vertical scroll fix

* fix: delete option remove from my issue page

* fix: my issue filter key renamed with id

* fix: sidebar ellipsis alignment

* fix: cycle card favorite icon alignment

* style: icon added in card options

* fix: progress icon alignment

* style: my issue page list view
2023-03-09 22:50:34 +05:30
Anmol Singh BhatiaandGitHub 4e9149a27c style: empty cycle state (#410) 2023-03-09 22:49:03 +05:30
pablohashescobarandGitHub 0416e07f46 refactor: self hosting setup (#411)
* merge-commit: self hosted updates

* dev: updates in self hosting setup

* dev: update script to get the instance IP

* dev: update script to generate backend secret key
2023-03-09 20:49:12 +05:30
Anmol Singh BhatiaandGitHub e3e57df4a2 style: list view (#409)
* style: list view

* style: list board header spacing fix
2023-03-09 16:05:25 +05:30
Vamsi KuramaandGitHub 2e1c113fdd Merge pull request #402 from makeplane/develop
dev: promote to staging
2023-03-08 20:30:28 +05:30
vamsi 981a246db1 Merge branch 'develop' of https://github.com/makeplane/plane into develop 2023-03-08 20:29:42 +05:30
vamsi 8641e35a61 Merge branch 'stage-release' into develop 2023-03-08 20:28:36 +05:30
+4 20aa3ce318 dev: promote the develop to stage-release (#399) (#403)
* chore: new link endpoints

* chore: added created by info for link

* chore: cannot have empty state group

* feat: filtering for cycle and module issue and updated grouper function for grouping in modules and cycles (#342)

* docs: github integration (#346)

* fix: add pagination for github repositories endpoint (#345)

* fix: remove bot accounts from list api (#344)

* refactor: create new endpoints for date checking getting current upcoming and past cycles (#343)

* refactor: create new endpoints for date checking getting current upcoming and past cycles

* refactor: rename endpoint to match consistency

* fix: remove project slug (#340)

* refactor: update links to different endpoints (#338)

* chore: cycle validation services and constants added

* style: kanban board

* chore:  cycle type and services updated

* chore: completed cycle dynamic importing and refactor

* feat: cycle modal date validation

* fix: build fix

* style: redesigned sidebar, added new icons and spacing changes

* style: changed app header color to white

* feat: cover image selector for project create

* style/projects_page

* style: added dragging state design

* fix: cycle form date

* chore: draft cycle services and types

* feat: draft tab and cycle sidebar update

* style: projects list page

* fix: image aspect ratio

* style: assignee drop down label

* style: new primary button design

* style: assignee dropdown

* style: assignee dropdown stlye fix

* style: state dropdown redesign

* style: dropdown ui consisteny

* style: priority dropdown redesign

* style: label dropdown redesign

* style: issue dropdown re-order

* style: state Icon

* style: date dropdown redesign

* fix: dropdown issue label

* style: transsition

* style: color fixed

* chore: labels list file and function rename

* style: redesigned create project modal

style: changed image picker to pop-over instread of modal

* fix: upload button on workspace settings page not working, UX of workspace settings image upload

* feat: date range status function added

* style: project settings pages

* fix: merge conflicts

* fix: mutation fix and date range helper fn added

* style: workspace settings pages

* style: dropdowns, feat: favorite projects in sidebar

* feat: global component for combobox with new design

* feat: custom context menu for issues in kanban board

* refactor: global context menu component

* chore: updated context menu component

* chore: updated sidebar selects

* style: kanban horizontal scrollbar added (#372)

* style: new cycle list (#374)

* feat: short date helper function

* feat: linear progress indicator added

* style: new cyce list and cycle card design

* feat: short date function improve

* feat: linear progress indicator improvement

* style: cycle card and progress indicator

* fix: helper date function and progress indicator fix

* fix: build error

---------



* chore: updated project favorites endpoints (#375)

* feat: favorite cycle and style: style improvements (#376)

* style: consistent btn

* style: caret direction for disclosure

* fix: progress tooltip value rounded

* chore: favorite cycle serivces

* chore: favorite cycle type and constant

* feat: favorite cycle feat added

* refactor: favorite services and type

* fix: build fix

* refactor: sidebar projects menu (#377)

* feat: add endpoint for draft cycles and add validation for creating draft cycles (#355)

* feat: add endpoint for draft cycles and add validation for creating draft cycles

* fix: key error in cycle create endpoint

* feat: delete file assets from storage (#373)

* chore: rename past cycle to completed cycle (#347)

* fix: workspace member listing endpoint (#348)

* fix: module issue viewset typo (#349)

* feat: add project to favourites (#352)

* feat: add project to favourites

* feat: add project is_favourite attribute to list endpoints

* refactor: updated destroy endpoint to send project_id

* chore: nomenclature update

* feat: add cover image to project (#353)

* fix: cycle date filtering for current and upcoming cycle (#357)

* fix: update filtering for completed cycles

* fix: filter updated for upcoming cycles

* fix: cycle and module issue filtering (#363)

* feat: already exisiting  url validation (#368)

* feat: cycle favourites for user (#369)

* feat: cycle favourites for user

* chore: update nomenclature

* chore: update on nomenclature

* feat: add favorites for completed and current cycle endpoints

* feat: module favourites for user (#370)

* feat: added floating toolbar on text selection (#378)

style: re-designed create-issue modal

* dev: migrations added for ProjectFavorite, ModuleFavorite, CycleFavorite including a bunch of other attribs

* chore: cycles loading, fix: cycles favorite mutation (#379)

* style: cycle sidebar, fix: cycle card bug fix   (#383)

* style: new cycle sidebar

* style: other information section

* style: progress bar bg fix

* fix: cycle card bug fix

* style: progress chart

* style: chart tooltip

* style : module sidebar (#385)

* style: new cycle sidebar

* style: other information section

* style: progress bar bg fix

* fix: cycle card bug fix

* style: progress chart

* style: chart tooltip

* style: module link tab added in sidebar stats

* style: lead and member select

* fix: text selection moving when typing in between (#384)

* feat: added floating toolbar on text selection (#386)

style: re-designed create-issue modal

* style :module list (#387)

* chore: module favorite type and services

* style: module list

* style: module list and card

* fix: link fix

* style: truncate (#388)

* style: truncate

* fix: truncate text added to cycle and module card

* fix: custom menu link item (#390)

* fix: ui fixes (#392)

* fix: ui fixes

* chore: kanban issue title length

* style: ui fix (#393)

* style: truncate

* fix: truncate text added to cycle and module card

* fix: progress percentage

* feat: cycle card tooltip

* fix: sidebar fix

* fix: edit module mutation error (#394)

* fix: issue details mutation (#389)

* fix: ui improvement (#395)

* fix: current cycle date updation

* fix: sidebar overflow fix , date helper fn added

* chore: update module dropdowns (#396)

* fix: project member filter for bot accounts (#391)

* fix: make api token only view once (#382)

* dev: add back migration for project cover images (#381)

* fix: rename db host name for docker setup (#380)

* dev: promote to staging (#397)













* Revert "dev: promote to staging (#397)" (#398)

This reverts commit f7405ba1d6.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
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: Anmol Singh Bhatia <anmolsinghbhatia1001@gmail.com>
Co-authored-by: vamsi <vamsi.kurama@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: Narayana <narayana.vadapalli1996@gmail.com>
Co-authored-by: sphynxux <122926002+sphynxux@users.noreply.github.com>
Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@caravel.tech>
Co-authored-by: Dakshesh Jain <dakshesh.jain14@gmail.com>
2023-03-08 19:58:36 +05:30
Vamsi KuramaandGitHub aa240b90b0 Merge pull request #401 from makeplane/fix/order_by_text
fix: order by text
2023-03-08 19:22:57 +05:30
Aaryan Khandelwal 674ecd33ef fix: order by text 2023-03-08 19:15:28 +05:30
Vamsi KuramaandGitHub 523308a768 Merge pull request #400 from makeplane/fix/kanban_overlay
fix: drag overlay z-index, sidebar dropdowns
2023-03-08 19:12:32 +05:30
Aaryan Khandelwal 223a204a97 fix: drag overlay z-index, sidebar dropdowns 2023-03-08 19:08:57 +05:30
+5 c9252c9713 dev: promote the develop to stage-release (#399)
* chore: new link endpoints

* chore: added created by info for link

* chore: cannot have empty state group

* feat: filtering for cycle and module issue and updated grouper function for grouping in modules and cycles (#342)

* docs: github integration (#346)

* fix: add pagination for github repositories endpoint (#345)

* fix: remove bot accounts from list api (#344)

* refactor: create new endpoints for date checking getting current upcoming and past cycles (#343)

* refactor: create new endpoints for date checking getting current upcoming and past cycles

* refactor: rename endpoint to match consistency

* fix: remove project slug (#340)

* refactor: update links to different endpoints (#338)

* chore: cycle validation services and constants added

* style: kanban board

* chore:  cycle type and services updated

* chore: completed cycle dynamic importing and refactor

* feat: cycle modal date validation

* fix: build fix

* style: redesigned sidebar, added new icons and spacing changes

* style: changed app header color to white

* feat: cover image selector for project create

* style/projects_page

* style: added dragging state design

* fix: cycle form date

* chore: draft cycle services and types

* feat: draft tab and cycle sidebar update

* style: projects list page

* fix: image aspect ratio

* style: assignee drop down label

* style: new primary button design

* style: assignee dropdown

* style: assignee dropdown stlye fix

* style: state dropdown redesign

* style: dropdown ui consisteny

* style: priority dropdown redesign

* style: label dropdown redesign

* style: issue dropdown re-order

* style: state Icon

* style: date dropdown redesign

* fix: dropdown issue label

* style: transsition

* style: color fixed

* chore: labels list file and function rename

* style: redesigned create project modal

style: changed image picker to pop-over instread of modal

* fix: upload button on workspace settings page not working, UX of workspace settings image upload

* feat: date range status function added

* style: project settings pages

* fix: merge conflicts

* fix: mutation fix and date range helper fn added

* style: workspace settings pages

* style: dropdowns, feat: favorite projects in sidebar

* feat: global component for combobox with new design

* feat: custom context menu for issues in kanban board

* refactor: global context menu component

* chore: updated context menu component

* chore: updated sidebar selects

* style: kanban horizontal scrollbar added (#372)

* style: new cycle list (#374)

* feat: short date helper function

* feat: linear progress indicator added

* style: new cyce list and cycle card design

* feat: short date function improve

* feat: linear progress indicator improvement

* style: cycle card and progress indicator

* fix: helper date function and progress indicator fix

* fix: build error

---------

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

* chore: updated project favorites endpoints (#375)

* feat: favorite cycle and style: style improvements (#376)

* style: consistent btn

* style: caret direction for disclosure

* fix: progress tooltip value rounded

* chore: favorite cycle serivces

* chore: favorite cycle type and constant

* feat: favorite cycle feat added

* refactor: favorite services and type

* fix: build fix

* refactor: sidebar projects menu (#377)

* feat: add endpoint for draft cycles and add validation for creating draft cycles (#355)

* feat: add endpoint for draft cycles and add validation for creating draft cycles

* fix: key error in cycle create endpoint

* feat: delete file assets from storage (#373)

* chore: rename past cycle to completed cycle (#347)

* fix: workspace member listing endpoint (#348)

* fix: module issue viewset typo (#349)

* feat: add project to favourites (#352)

* feat: add project to favourites

* feat: add project is_favourite attribute to list endpoints

* refactor: updated destroy endpoint to send project_id

* chore: nomenclature update

* feat: add cover image to project (#353)

* fix: cycle date filtering for current and upcoming cycle (#357)

* fix: update filtering for completed cycles

* fix: filter updated for upcoming cycles

* fix: cycle and module issue filtering (#363)

* feat: already exisiting  url validation (#368)

* feat: cycle favourites for user (#369)

* feat: cycle favourites for user

* chore: update nomenclature

* chore: update on nomenclature

* feat: add favorites for completed and current cycle endpoints

* feat: module favourites for user (#370)

* feat: added floating toolbar on text selection (#378)

style: re-designed create-issue modal

* dev: migrations added for ProjectFavorite, ModuleFavorite, CycleFavorite including a bunch of other attribs

* chore: cycles loading, fix: cycles favorite mutation (#379)

* style: cycle sidebar, fix: cycle card bug fix   (#383)

* style: new cycle sidebar

* style: other information section

* style: progress bar bg fix

* fix: cycle card bug fix

* style: progress chart

* style: chart tooltip

* style : module sidebar (#385)

* style: new cycle sidebar

* style: other information section

* style: progress bar bg fix

* fix: cycle card bug fix

* style: progress chart

* style: chart tooltip

* style: module link tab added in sidebar stats

* style: lead and member select

* fix: text selection moving when typing in between (#384)

* feat: added floating toolbar on text selection (#386)

style: re-designed create-issue modal

* style :module list (#387)

* chore: module favorite type and services

* style: module list

* style: module list and card

* fix: link fix

* style: truncate (#388)

* style: truncate

* fix: truncate text added to cycle and module card

* fix: custom menu link item (#390)

* fix: ui fixes (#392)

* fix: ui fixes

* chore: kanban issue title length

* style: ui fix (#393)

* style: truncate

* fix: truncate text added to cycle and module card

* fix: progress percentage

* feat: cycle card tooltip

* fix: sidebar fix

* fix: edit module mutation error (#394)

* fix: issue details mutation (#389)

* fix: ui improvement (#395)

* fix: current cycle date updation

* fix: sidebar overflow fix , date helper fn added

* chore: update module dropdowns (#396)

* fix: project member filter for bot accounts (#391)

* fix: make api token only view once (#382)

* dev: add back migration for project cover images (#381)

* fix: rename db host name for docker setup (#380)

* dev: promote to staging (#397)

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
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: Anmol Singh Bhatia <anmolsinghbhatia1001@gmail.com>
Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
Co-authored-by: Narayana <narayana.vadapalli1996@gmail.com>

* Revert "dev: promote to staging (#397)" (#398)

This reverts commit f7405ba1d6.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
Co-authored-by: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com>
Co-authored-by: sphynxux <122926002+sphynxux@users.noreply.github.com>
Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@caravel.tech>
Co-authored-by: Dakshesh Jain <dakshesh.jain14@gmail.com>
Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia1001@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Narayana <narayana.vadapalli1996@gmail.com>
2023-03-08 01:07:00 +05:30
Vamsi KuramaandGitHub 303f266fc7 Revert "dev: promote to staging (#397)" (#398)
This reverts commit f7405ba1d6.
2023-03-08 01:05:38 +05:30
+2 f7405ba1d6 dev: promote to staging (#397)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
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: Anmol Singh Bhatia <anmolsinghbhatia1001@gmail.com>
Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
Co-authored-by: Narayana <narayana.vadapalli1996@gmail.com>
2023-03-08 01:03:11 +05:30
pablohashescobarandGitHub cf72a187fe fix: rename db host name for docker setup (#380) 2023-03-08 01:00:52 +05:30
pablohashescobarandGitHub 2967fb1bee dev: add back migration for project cover images (#381) 2023-03-08 01:00:34 +05:30
pablohashescobarandGitHub 1a04eda613 fix: make api token only view once (#382) 2023-03-08 01:00:10 +05:30
pablohashescobarandGitHub 0bd3e8ae95 fix: project member filter for bot accounts (#391) 2023-03-08 00:59:48 +05:30
Aaryan KhandelwalandGitHub cc498096f3 chore: update module dropdowns (#396) 2023-03-07 22:56:22 +05:30
Anmol Singh BhatiaandGitHub afe2b029c0 fix: ui improvement (#395)
* fix: current cycle date updation

* fix: sidebar overflow fix , date helper fn added
2023-03-07 22:38:49 +05:30
Dakshesh JainandGitHub 30a91a6b91 fix: issue details mutation (#389) 2023-03-07 22:22:06 +05:30
Aaryan KhandelwalandGitHub f725ea5b15 fix: edit module mutation error (#394) 2023-03-07 22:21:50 +05:30
Anmol Singh BhatiaandGitHub cf94b92be2 style: ui fix (#393)
* style: truncate

* fix: truncate text added to cycle and module card

* fix: progress percentage

* feat: cycle card tooltip

* fix: sidebar fix
2023-03-07 20:48:19 +05:30
Aaryan KhandelwalandGitHub 88fca3c67c fix: ui fixes (#392)
* fix: ui fixes

* chore: kanban issue title length
2023-03-07 20:27:16 +05:30
Aaryan KhandelwalandGitHub cadb67a018 fix: custom menu link item (#390) 2023-03-07 20:02:03 +05:30
Anmol Singh BhatiaandGitHub d18765a613 style: truncate (#388)
* style: truncate

* fix: truncate text added to cycle and module card
2023-03-07 19:32:29 +05:30
Anmol Singh BhatiaandGitHub 61102952d0 style :module list (#387)
* chore: module favorite type and services

* style: module list

* style: module list and card

* fix: link fix
2023-03-07 18:46:56 +05:30
Dakshesh JainandGitHub d8bf9b4c2a feat: added floating toolbar on text selection (#386)
style: re-designed create-issue modal
2023-03-07 16:09:37 +05:30
Dakshesh JainandGitHub 388d5b054a fix: text selection moving when typing in between (#384) 2023-03-07 16:09:17 +05:30
Anmol Singh BhatiaandGitHub 09eab9e6bf style : module sidebar (#385)
* style: new cycle sidebar

* style: other information section

* style: progress bar bg fix

* fix: cycle card bug fix

* style: progress chart

* style: chart tooltip

* style: module link tab added in sidebar stats

* style: lead and member select
2023-03-07 15:04:02 +05:30
Anmol Singh BhatiaandGitHub 0246e0585b style: cycle sidebar, fix: cycle card bug fix (#383)
* style: new cycle sidebar

* style: other information section

* style: progress bar bg fix

* fix: cycle card bug fix

* style: progress chart

* style: chart tooltip
2023-03-07 13:43:09 +05:30
Aaryan KhandelwalandGitHub 64978969a0 chore: cycles loading, fix: cycles favorite mutation (#379) 2023-03-07 11:04:51 +05:30
vamsi b54a1f221f dev: migrations added for ProjectFavorite, ModuleFavorite, CycleFavorite including a bunch of other attribs 2023-03-07 03:09:04 +05:30
Dakshesh JainandGitHub 82f8b6d387 feat: added floating toolbar on text selection (#378)
style: re-designed create-issue modal
2023-03-06 22:49:06 +05:30
pablohashescobarandGitHub d28fe930a6 feat: module favourites for user (#370) 2023-03-06 19:00:00 +05:30
pablohashescobarandGitHub cb8b6b43dc feat: cycle favourites for user (#369)
* feat: cycle favourites for user

* chore: update nomenclature

* chore: update on nomenclature

* feat: add favorites for completed and current cycle endpoints
2023-03-06 18:59:47 +05:30
pablohashescobarandGitHub 79d7b6fec3 feat: already exisiting url validation (#368) 2023-03-06 18:58:10 +05:30
pablohashescobarandGitHub 39f54d8265 fix: cycle and module issue filtering (#363) 2023-03-06 18:57:58 +05:30
pablohashescobarandGitHub 3d57edfcf8 fix: cycle date filtering for current and upcoming cycle (#357)
* fix: update filtering for completed cycles

* fix: filter updated for upcoming cycles
2023-03-06 18:57:46 +05:30
pablohashescobarandGitHub ae64b53cf3 feat: add cover image to project (#353) 2023-03-06 18:57:20 +05:30
pablohashescobarandGitHub 689eaad0f0 feat: add project to favourites (#352)
* feat: add project to favourites

* feat: add project is_favourite attribute to list endpoints

* refactor: updated destroy endpoint to send project_id

* chore: nomenclature update
2023-03-06 18:57:07 +05:30
pablohashescobarandGitHub 697e7f13b5 fix: module issue viewset typo (#349) 2023-03-06 18:56:53 +05:30
pablohashescobarandGitHub cee8a6a8cd fix: workspace member listing endpoint (#348) 2023-03-06 18:56:41 +05:30
pablohashescobarandGitHub f5e96b8078 chore: rename past cycle to completed cycle (#347) 2023-03-06 18:56:21 +05:30
pablohashescobarandGitHub cecd025a78 feat: delete file assets from storage (#373) 2023-03-06 18:56:05 +05:30
pablohashescobarandGitHub 3a81a6c186 feat: add endpoint for draft cycles and add validation for creating draft cycles (#355)
* feat: add endpoint for draft cycles and add validation for creating draft cycles

* fix: key error in cycle create endpoint
2023-03-06 18:45:20 +05:30
Aaryan KhandelwalandGitHub 27653907f9 refactor: sidebar projects menu (#377) 2023-03-06 18:38:01 +05:30
Anmol Singh BhatiaandGitHub 626aae696f feat: favorite cycle and style: style improvements (#376)
* style: consistent btn

* style: caret direction for disclosure

* fix: progress tooltip value rounded

* chore: favorite cycle serivces

* chore: favorite cycle type and constant

* feat: favorite cycle feat added

* refactor: favorite services and type

* fix: build fix
2023-03-06 16:36:22 +05:30
Aaryan KhandelwalandGitHub d6badcd9b8 chore: updated project favorites endpoints (#375) 2023-03-06 11:37:18 +05:30
786816ed41 style: new cycle list (#374)
* feat: short date helper function

* feat: linear progress indicator added

* style: new cyce list and cycle card design

* feat: short date function improve

* feat: linear progress indicator improvement

* style: cycle card and progress indicator

* fix: helper date function and progress indicator fix

* fix: build error

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-03-06 11:36:48 +05:30
Anmol Singh BhatiaandGitHub fef72ccc70 style: kanban horizontal scrollbar added (#372) 2023-03-06 11:13:08 +05:30
Aaryan KhandelwalandGitHub a4dc4d1f15 Merge pull request #371 from makeplane/style/dropdowns
style: consistent dropdowns, feat: custom context menu
2023-03-06 10:39:50 +05:30
Aaryan Khandelwal 4f4f3ebbde chore: updated sidebar selects 2023-03-05 23:24:50 +05:30
Aaryan Khandelwal 6d99557de5 chore: updated context menu component 2023-03-05 20:22:01 +05:30
Aaryan Khandelwal a4da4bf889 refactor: global context menu component 2023-03-05 03:24:24 +05:30
Aaryan Khandelwal 0a681937fd feat: custom context menu for issues in kanban board 2023-03-04 19:33:24 +05:30
Aaryan Khandelwal a875c608d4 feat: global component for combobox with new design 2023-03-04 19:10:35 +05:30
Aaryan Khandelwal 4d598fd6b6 style: dropdowns, feat: favorite projects in sidebar 2023-03-04 17:47:03 +05:30
Dakshesh JainandGitHub 067859b4bd Merge pull request #367 from makeplane/style/workspace_settings
style: workspace settings pages
2023-03-03 18:16:53 +05:30
Aaryan Khandelwal 1d8b4296fb style: workspace settings pages 2023-03-03 14:30:21 +05:30
Aaryan KhandelwalandGitHub 433276c833 Merge pull request #359 from makeplane/style/projects_page
style: projects page
2023-03-03 14:06:59 +05:30
Aaryan KhandelwalandGitHub 03a8ad336e Merge pull request #366 from makeplane/style/project_settings
style: project settings pages
2023-03-03 14:04:04 +05:30
Aaryan Khandelwal e281feddf5 fix: merge conflicts 2023-03-03 13:55:18 +05:30
Aaryan KhandelwalandGitHub f290a417bc Merge pull request #358 from makeplane/feat/cycle_validations
feat: cycle validations
2023-03-03 13:53:09 +05:30
Anmol Singh Bhatia f965734f3b fix: mutation fix and date range helper fn added 2023-03-03 13:49:19 +05:30
Aaryan Khandelwal 530edbe149 fix: merge conflicts 2023-03-03 13:38:40 +05:30
Aaryan Khandelwal ad7b691b2b Merge branch 'develop' of https://github.com/makeplane/plane into style/project_settings 2023-03-03 13:32:42 +05:30
Aaryan KhandelwalandGitHub 76b615d964 Merge pull request #365 from makeplane/fix/workspace_settings_logo
fix: upload button on workspace settings page not working
2023-03-03 13:32:25 +05:30
Aaryan KhandelwalandGitHub 2b01ae6500 Merge pull request #364 from makeplane/style/create_project
style: redesigned create project modal
2023-03-03 13:31:45 +05:30
Aaryan Khandelwal db547cc51a style: project settings pages 2023-03-03 13:29:36 +05:30
Anmol Singh Bhatia 02e4e58f19 feat: date range status function added 2023-03-03 11:32:00 +05:30
Dakshesh Jain 6bf26617a3 fix: upload button on workspace settings page not working, UX of workspace settings image upload 2023-03-02 18:39:51 +05:30
Dakshesh Jain 63d0a0dea7 Merge branch 'develop' of https://github.com/makeplane/plane into style/create_project 2023-03-02 17:09:28 +05:30
Dakshesh Jain e96a755a2e style: redesigned create project modal
style: changed image picker to pop-over instread of modal
2023-03-02 17:06:21 +05:30
Aaryan KhandelwalandGitHub aeadf0ebbf Merge pull request #362 from makeplane/style/issue_dropdown
style: new dropdown
2023-03-02 17:01:19 +05:30
Anmol Singh Bhatia c6d54a0ad2 chore: labels list file and function rename 2023-03-02 15:43:50 +05:30
Anmol Singh Bhatia 1fe1596f9d style: color fixed 2023-03-02 14:23:38 +05:30
Anmol Singh Bhatia 6bf608a37a style: transsition 2023-03-02 13:36:20 +05:30
Anmol Singh Bhatia 5ae1f63985 fix: dropdown issue label 2023-03-02 13:29:55 +05:30
Aaryan KhandelwalandGitHub f5db3dc07b Merge pull request #351 from makeplane/style/app_header
style: changed app header color to white
2023-03-02 13:16:23 +05:30
Aaryan KhandelwalandGitHub 2e6cd2638d Merge pull request #350 from makeplane/style/app_sidebar
style: redesigned sidebar, added new icons and spacing changes
2023-03-02 13:15:29 +05:30
Anmol Singh Bhatia 1f1fa19432 style: date dropdown redesign 2023-03-02 12:33:12 +05:30
Aaryan KhandelwalandGitHub ea509211a0 Merge pull request #354 from makeplane/feat/unsplash_modal
feat: cover image selector for project create
2023-03-02 12:00:16 +05:30
Aaryan KhandelwalandGitHub 032f39d9ec Merge pull request #360 from makeplane/style/button
style: new primary button design
2023-03-02 11:59:20 +05:30
Anmol Singh Bhatia 9a88803a3f style: state Icon 2023-03-02 11:32:18 +05:30
Anmol Singh Bhatia 216c565afc Merge branch 'develop' of github.com:makeplane/plane into style/issue_dropdown 2023-03-02 11:02:01 +05:30
Anmol Singh Bhatia 700769665f style: issue dropdown re-order 2023-03-02 10:37:09 +05:30
Anmol Singh Bhatia 1476896005 style: label dropdown redesign 2023-03-02 10:36:19 +05:30
Anmol Singh Bhatia cb2f0633f7 style: priority dropdown redesign 2023-03-02 09:38:54 +05:30
Anmol Singh Bhatia fdbad4ff1a style: dropdown ui consisteny 2023-03-02 09:38:13 +05:30
Anmol Singh Bhatia d8e1710a9b style: state dropdown redesign 2023-03-02 09:01:47 +05:30
Anmol Singh Bhatia 9a52031d59 style: assignee dropdown stlye fix 2023-03-02 09:01:16 +05:30
Anmol Singh Bhatia 217d6ea51c style: assignee dropdown 2023-03-02 08:03:41 +05:30
Dakshesh Jain c5d7d4f751 style: new primary button design 2023-03-01 18:43:24 +05:30
Anmol Singh Bhatia c897f04926 style: assignee drop down label 2023-03-01 16:10:48 +05:30
Aaryan KhandelwalandGitHub 99cf2d4e8d Merge pull request #356 from makeplane/style/kanban_board
style: kanban board
2023-03-01 15:07:21 +05:30
Aaryan Khandelwal 35af45ddd7 fix: image aspect ratio 2023-03-01 14:18:02 +05:30
Aaryan Khandelwal 8f0ef7bf13 fix: merge conflicts 2023-03-01 14:14:24 +05:30
Aaryan Khandelwal 6afcf1f0e3 style: projects list page 2023-03-01 14:11:27 +05:30
Anmol Singh Bhatia 8a941d0d14 feat: draft tab and cycle sidebar update 2023-03-01 11:58:30 +05:30
Anmol Singh Bhatia 7ab6eb7b48 chore: draft cycle services and types 2023-03-01 11:56:14 +05:30
Anmol Singh Bhatia a840cea9e9 fix: cycle form date 2023-03-01 11:55:04 +05:30
Aaryan Khandelwal 8589ce777f style: added dragging state design 2023-03-01 11:30:49 +05:30
Aaryan Khandelwal f7e0e257a4 Merge branch 'develop' of https://github.com/makeplane/plane into style/kanban_board 2023-03-01 11:30:17 +05:30
Aaryan KhandelwalandGitHub 2c0b27d838 Merge pull request #341 from makeplane/chore/delete_state_options
chore: cannot have empty state group
2023-03-01 10:48:40 +05:30
Aaryan Khandelwal 76b8b9eaef style/projects_page 2023-02-28 23:50:21 +05:30
Dakshesh Jain b660b1d814 feat: cover image selector for project create 2023-02-28 20:54:55 +05:30
Dakshesh Jain dea21cd660 style: changed app header color to white 2023-02-28 17:09:49 +05:30
Dakshesh Jain 73567dc7fc style: redesigned sidebar, added new icons and spacing changes 2023-02-28 16:48:02 +05:30
Anmol Singh Bhatia 7d42262e72 fix: build fix 2023-02-28 15:17:14 +05:30
Anmol Singh Bhatia 19e9f510bc feat: cycle modal date validation 2023-02-28 14:55:19 +05:30
Anmol Singh Bhatia 17e09d70e2 chore: completed cycle dynamic importing and refactor 2023-02-28 14:53:10 +05:30
Anmol Singh Bhatia 443c9300dd chore: cycle type and services updated 2023-02-28 14:47:32 +05:30
Aaryan Khandelwal 0cd3bb5956 style: kanban board 2023-02-28 14:42:46 +05:30
Anmol Singh Bhatia d480325829 chore: cycle validation services and constants added 2023-02-28 10:31:52 +05:30
pablohashescobarandGitHub 1b369feb6a refactor: update links to different endpoints (#338) 2023-02-28 02:09:22 +05:30
pablohashescobarandGitHub 7b4d7f12f5 fix: remove project slug (#340) 2023-02-28 02:09:09 +05:30
pablohashescobarandGitHub 1255552ebe refactor: create new endpoints for date checking getting current upcoming and past cycles (#343)
* refactor: create new endpoints for date checking getting current upcoming and past cycles

* refactor: rename endpoint to match consistency
2023-02-28 02:08:55 +05:30
pablohashescobarandGitHub 1ff0970ed6 fix: remove bot accounts from list api (#344) 2023-02-28 02:08:34 +05:30
pablohashescobarandGitHub 90b8d66946 fix: add pagination for github repositories endpoint (#345) 2023-02-28 02:08:17 +05:30
sphynxuxandGitHub 07295ac314 docs: github integration (#346) 2023-02-28 02:07:12 +05:30
pablohashescobarandGitHub ec4332ea6b feat: filtering for cycle and module issue and updated grouper function for grouping in modules and cycles (#342) 2023-02-27 15:32:15 +05:30
Aaryan KhandelwalandGitHub 3af3bb0fb5 Merge pull request #339 from makeplane/chore/new_link_endpoint
chore: new link endpoints
2023-02-27 15:20:00 +05:30
Aaryan Khandelwal 522952fa59 chore: cannot have empty state group 2023-02-24 16:22:49 +05:30
Aaryan Khandelwal 9dd5b15cd3 chore: added created by info for link 2023-02-24 15:53:25 +05:30
Aaryan Khandelwal df836d55d5 chore: new link endpoints 2023-02-24 15:50:15 +05:30
Vamsi KuramaandGitHub 397a3cec4f Merge pull request #336 from makeplane/develop
release: bug-fixes and ui/ux improvements for 23 Feb 2023
2023-02-24 00:03:37 +05:30
Vamsi KuramaandGitHub 3c6752807d Merge pull request #335 from makeplane/style/ui_consistency
style: kanban dropdowns
2023-02-23 23:58:06 +05:30
pablohashescobarandGitHub 517600ac89 fix: add filter for workspace integrations (#325)
* fix: add filter for workspace integrations

* fix: update url for delete

* fix: remove github installation when deleted

* fix: delete old repos

* fix: add filter on repository endpoints
2023-02-23 23:56:48 +05:30
Aaryan Khandelwal 90c913ce03 fix: merge conflicts 2023-02-23 22:36:20 +05:30
Aaryan Khandelwal b4c4271f66 style: kanban dropdowns, github integration loaders 2023-02-23 22:34:36 +05:30
Dakshesh JainandGitHub b53b0bc3f0 fix: create issue modal close on escape click (#333) 2023-02-23 19:56:18 +05:30
Anmol Singh BhatiaandGitHub 443c187cde feat: sidebar select option truncate (#334) 2023-02-23 19:54:28 +05:30
Anmol Singh BhatiaandGitHub 946dddb6b2 fix: kanban assignees tooltip (#332) 2023-02-23 19:04:08 +05:30
Anmol Singh BhatiaandGitHub 69e8b504de fix: ui fix (#331)
* fix: project card id removed

* feat: my issue page copy issue option
2023-02-23 18:12:43 +05:30
36a733cd06 style: github integration ui (#329)
* fix: ellipsis added to issue title

* feat: toolttip added

* feat: assignees tooltip added

* fix: build fix

* fix: build fix

* fix: build error

* fix: minor bugs and ux improvements

* style: github integration ui

* chore: updated .env.example file

---------

Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@caravel.tech>
2023-02-23 18:12:07 +05:30
sriram veeraghantaandGitHub 2b3cb839ad environmental example variables fixes (#330) 2023-02-23 18:05:32 +05:30
Dakshesh JainandGitHub 98d4473501 fix: issue view not updating order_by value (#324) 2023-02-23 17:50:47 +05:30
Dakshesh JainandGitHub a550f4b161 refractor: added proper types to getServerSideProps context (#321)
* fix: redirection after login

* refractor: added proper types to getServerSideProps context
2023-02-23 17:50:37 +05:30
Dakshesh JainandGitHub 1e63c5b1b3 style: added direction for multi-level drop-down (#328)
* feat: made new multi-level select listbox

* refractor: changeds Multi-level-select component and added direction props

* style: added direction for multi-level drop-down
2023-02-23 17:02:26 +05:30
Anmol Singh BhatiaandGitHub 4caa4e33b1 fix: ui improvements (#327)
* fix: kanban board header scroll fix

* style: enable scrollbar style added

* fix: emoji picker overflow

* fix: delete project modal text overflow

* fix: cycle card ellipsis

* fix: tooltip position updated and custom class added

* fix: assignees tooltip overflow

* fix: module card

* fix: my issue page  tooltip and responsive title  added

* fix: home page tooltip and responsiveness
2023-02-23 16:46:52 +05:30
Dakshesh JainandGitHub 6a10faca68 feat: made new multi-level select listbox (#326) 2023-02-23 15:36:46 +05:30
sriram veeraghantaandGitHub ad5a8be0e2 Merge pull request #323 from makeplane/develop
release: Stage Release 23rd Feb 2023
2023-02-23 10:58:57 +05:30
92f717962c fix: minor bugs and ux improvements (#322)
* fix: ellipsis added to issue title

* feat: toolttip added

* feat: assignees tooltip added

* fix: build fix

* fix: build fix

* fix: build error

* fix: minor bugs and ux improvements

---------

Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@caravel.tech>
2023-02-23 10:54:54 +05:30
vamsi 702cfeb4ee dev: added new migrations 2023-02-23 01:04:44 +05:30
vamsi 649748f801 Merge branch 'develop' of https://github.com/makeplane/plane into develop 2023-02-23 01:02:34 +05:30
vamsi 2fffad130e Merge branch 'master' of https://github.com/makeplane/plane into develop 2023-02-23 01:01:52 +05:30
pablohashescobarandGitHub 937222fdd4 feat: assignee and label details in cycle and module issues (#319) 2023-02-23 01:01:09 +05:30
a9802f816e feat: github integration (#315)
* feat: initiate integrations

* feat: initiate github integration create models for the same

* feat: github integration views

* fix: update workspace integration view to create bot users

* refactor: rename repository model

* refactor: update github repo sync endpoint to create repo and sync in one go

* refactor: update issue activities to post the updates to segway hook

* refactor: update endpoints to get project id and add actor as a member of project in repo sync

* fix: make is bot as a read only field

* fix: remove github repo imports

* fix: url mapping

* feat: repo views

* refactor: update webhook request endpoint

* refactor: rename repositories table to github_repositories

* fix: workpace integration actor

* feat: label for github integration

* refactor: issue activity on create issue

* refactor: repo create endpoint and add db constraints for repo sync and issues

* feat: create api token on workpsace integration and avatar_url for integrations

* refactor: add uuid primary key for Audit model

* refactor: remove id from auditfield to maintain integrity and make avatar blank if none supplied

* feat: track comments on an issue

* feat: comment syncing from plane to github

* fix: prevent activities created by bot to be sent to webhook

* feat: github app installation id retrieve

* feat: github app installation id saved into db

* feat: installation_id for the github integragation and unique provider and project base integration for repo

* refactor: remove actor logic from activity task

* feat: saving github metadata using installation id in workspace integration table

* feat: github repositories endpoint

* feat: github and project repos synchronisation

* feat: delete issue and delete comment activity

* refactor: remove print logs

* FIX: reading env names for github app while installation

* refactor: update bot user firstname with title

* fix: add is_bot value in field

---------

Co-authored-by: venplane <venkatesh@plane.so>
2023-02-22 19:40:57 +05:30
Dakshesh JainandGitHub c1a78cc230 fix: redirection after login (#320) 2023-02-22 17:54:27 +05:30
d29f34566c fix : tooltip fix (#318)
* fix: ellipsis added to issue title

* feat: toolttip added

* feat: assignees tooltip added

* fix: build fix

* fix: build fix

* fix: build error

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-02-22 13:24:59 +05:30
Anmol Singh BhatiaandGitHub d8c10b6bc0 feat: issues tooltip , fix: ui improvement (#317)
* fix: ellipsis added to issue title

* feat: toolttip added

* feat: assignees tooltip added

* fix: build fix
2023-02-22 11:42:17 +05:30
sriram veeraghantaandGitHub 2cadb3784b env fixes (#316) 2023-02-22 00:53:22 +05:30
pablohashescobarandGitHub f17ecd597b Merge pull request #278 from makeplane/feat/multiple_sub_issues
feat: assign multiple sub issues
2023-02-21 23:52:33 +05:30
pablohashescobarandGitHub 33ed2f6c35 Merge pull request #287 from makeplane/fix/state_ordering
fix: state ordering in group
2023-02-21 23:52:21 +05:30
pablohashescobarandGitHub a904c4a7de Merge pull request #302 from makeplane/feat/issue_sorting_grouping
feat: updated issue grouping and filtering
2023-02-21 23:52:08 +05:30
pablohashescobarandGitHub 71f9ae41f3 feat: created_by details for links (#313) 2023-02-21 23:50:54 +05:30
+2 1b94c7b640 Merge pull request #314 from makeplane/develop
* chore: update all backend dependencies to the latest version

* feat: record issue completed at date when the issues are moved to fompleted group (#262)

* feat: cycle status (#265)

* feat: cycle status and dates added in sidebar

* feat: update status added

* chore: update python runtime

* feat: label grouping in dropdowns, default state in project settings (#266)

* feat: label grouping in dropdowns, default state in project settings

* feat: label disclosure default open

* refactor: label setting page

* chore: tooltip component updated

* chore: tooltip component updated

* feat/state_sequence_change

* fix: remirror buttons (#267)

* feat: burndown chart (#268)

* chore: recharts dependencie added

* chore: tpye added for issue completed at

* feat: date range helper fn added

* feat: progress chart added

* feat: ideal task line added in progress chart

* feat: chart legends added

* fix: state reordering (#269)

* fix: state reordering

* refactor: remove unnecessary argument

* refactor: mutation after setting default

* feat: drag and drop an issue to delete (#270)

* feat: drag and drop an issue to delete

* style: repositioned trash box

* feat : cycle sidebar revamp (#271)

* feat: range date picker added

* feat: cycle status ui improved

* feat : sidebar progress improvement (#272)

* feat: progress chart render validation

* fix: sidebar stats tab

* feat: sidebar active tab context

* chore: removed minor bugs (#273)

* fix: ui bug (#274)

* fix: shortcut search fix
shortcut modal ui fixes
shortcut search fix
email us label change
* fix: email us label updated

* feat: default state for project (#264)

* build: add channels requirement for the asgi configuration (#225)

* refactor: combine sign in and sign up endpoint to a single endpoint (#263)

* feat: state grouping and ordering list (#253)

* feat: state grouping and ordering list

* fix: state grouping in state list endpoint

* dev: added migrations for new models schema changes

* fix: mac text copy fix (#277)

* feat: state description in settings (#275)

* chore: removed minor bugs

* feat: state description in settings

* feat: group by assignee

* refactor: update django admin panel heading (#276)

* feat: create label option in create issue modal (#281)

* refactor: issue details page (#282)

* fix: shortcut search  (#283)

* fix: search case innsensitive

* style: email icon updated

* feat: module sidebar date and status updated (#285)

* feat: bulk assign sub-issues (#284)

* fix: consistent dropdowns, refactor: ui components (#286)

* build(deps): bump django in /apiserver/requirements (#289)

Bumps [django](https://github.com/django/django) from 3.2.17 to 3.2.18.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.2.17...3.2.18)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix: workspace name and breadcrumb title , refactor: command palette (#290)

* refactor: command pallette

* fix: workspace name trim

* fix: breadcrumb title responsiveness added

* feat: copy link option (#292)

* feat: copy issue link added in issue card

* feat: copy cycle link added

* feat: ellipsis added in module card

* fix: origin path and handlecopytext added

* fix: remirror image not updating (#294)

* feat: resend login magic code  (#291)

* feat: resend login code on signing page after 30 seconds

* feat: handling error on code send

* refractor: isResendDisabled varible for resend button

* dev: timer count-down hook

* refractor: using new timer hook in sign in page

* feat: issue links (#288)

* feat: links for issues

* fix: add issue link in serilaizer

* feat: links can be added to issues

---------

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

* fix: default label color (#295)

* fix: colors of old labels can now be changed

* fix: black color for labels with no color

* fix: ui changes (#297)

* fix: module card height and invalid date

* fix: issue details page title resizing fix

* refractor: use local storage hook (#293)

* feat: resend login code on signing page after 30 seconds

* refractor: use local storage hook

* refractor: properly using new local storage hook on modules sidebar

* fix: assignee and labels field while editing an issue (#296)

* fix: assignee and labels field while editing an issue

* chore: remove unused declarations

* fix: issue title resizing fix (#300)

* fix: issue title resizing fix

* fix: header ui fix and invalid date label updated

* fix: try/catch for invalid values stored in local storage (#301)

* fix: create issue modal closing on clicking on Grammarly recommendation (#299)

fixed it by not closing modal on outside click

* style: not showing pointer & theme color on resend code button disabled (#298)

* fix: new project issues response (#303)

* refactor/cycles_folder_structure (#304)

* fix: ui changes (#306)

* fix: sidebar date range

* fix: renamed key with id in filters

* fix: replace progress bar

* chore: react progress bar package removed

* fix: progress chart legends position

* fix: progress chart legends alignment fix

* feat: manual ordering of issues (#305)

* feat: global component for links list (#307)

* Feat: Dockerizing using nginx reverse proxy (#280)

* minor docker fixes

* eslint config changes

* dockerfile changes to backend and frontend

* oauth enabled env flag

* sentry enabled env flag

* build: get alternatives for environment variables and static file storage

* build: automatically generate random secret key if not provided

* build: update docker compose for next url env add channels to requirements for asgi server and save files in local machine for docker environment

* build: update nginx conf for backend base url update backend dockerfile to make way for static file uploads

* feat: create a default user with given values else default values

* chore: update docker python version and other dependency version in docker

* build: update local settings file to run it in docker

* fix: update script to run in default production setting

* fix: env variable changes and env setup shell script added

* Added Single Dockerfile to run the Entire plane application

* docs build fixes

---------

Co-authored-by: Narayana <narayana.vadapalli1996@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>

* feat: edit module (#309)

* feat: edit module

* fix: build fix

* refactor: dnd function (#308)

* refactor: manual ordering bugs (#312)

* refactor: create issue modal input fields (#310)

* style: showing user sign-in progress on sign-in with code (#311)

* style: not showing pointer & theme color on resend code button disabled

* style: showing user sign-in progress on sign-in with code

* style: showing error from server on sign-in with code fail

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
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: Anmol Singh Bhatia <anmolsinghbhatia1001@gmail.com>
Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
Co-authored-by: vamsi <vamsi.kurama@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
Co-authored-by: Narayana <narayana.vadapalli1996@gmail.com>
2023-02-21 19:17:32 +05:30
Dakshesh JainandGitHub 5cbb3ecd4d style: showing user sign-in progress on sign-in with code (#311)
* style: not showing pointer & theme color on resend code button disabled

* style: showing user sign-in progress on sign-in with code

* style: showing error from server on sign-in with code fail
2023-02-21 19:14:12 +05:30
Aaryan KhandelwalandGitHub c0263acb8a refactor: create issue modal input fields (#310) 2023-02-21 19:12:38 +05:30
Aaryan KhandelwalandGitHub 8d6a357a7f refactor: manual ordering bugs (#312) 2023-02-21 19:11:54 +05:30
Aaryan KhandelwalandGitHub 8c15a1519f refactor: dnd function (#308) 2023-02-21 14:56:32 +05:30
Anmol Singh BhatiaandGitHub 3d28cde91d feat: edit module (#309)
* feat: edit module

* fix: build fix
2023-02-21 12:45:04 +05:30
bdca84bd09 Feat: Dockerizing using nginx reverse proxy (#280)
* minor docker fixes

* eslint config changes

* dockerfile changes to backend and frontend

* oauth enabled env flag

* sentry enabled env flag

* build: get alternatives for environment variables and static file storage

* build: automatically generate random secret key if not provided

* build: update docker compose for next url env add channels to requirements for asgi server and save files in local machine for docker environment

* build: update nginx conf for backend base url update backend dockerfile to make way for static file uploads

* feat: create a default user with given values else default values

* chore: update docker python version and other dependency version in docker

* build: update local settings file to run it in docker

* fix: update script to run in default production setting

* fix: env variable changes and env setup shell script added

* Added Single Dockerfile to run the Entire plane application

* docs build fixes

---------

Co-authored-by: Narayana <narayana.vadapalli1996@gmail.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
2023-02-21 11:31:43 +05:30
Aaryan KhandelwalandGitHub 33e2986062 feat: global component for links list (#307) 2023-02-21 11:23:50 +05:30
Aaryan KhandelwalandGitHub 818fe3ecf7 feat: manual ordering of issues (#305) 2023-02-20 19:19:46 +05:30
Anmol Singh BhatiaandGitHub 202096500e fix: ui changes (#306)
* fix: sidebar date range

* fix: renamed key with id in filters

* fix: replace progress bar

* chore: react progress bar package removed

* fix: progress chart legends position

* fix: progress chart legends alignment fix
2023-02-20 19:00:40 +05:30
Aaryan KhandelwalandGitHub e5934e0b07 refactor/cycles_folder_structure (#304) 2023-02-20 11:23:04 +05:30
Aaryan KhandelwalandGitHub 77c319c748 fix: new project issues response (#303) 2023-02-18 21:19:04 +05:30
pablohashescobar d50cc14972 fix: typo in model aggregation key 2023-02-18 18:21:42 +05:30
pablohashescobar 495ac0ca00 feat: improved grouper with grouping function 2023-02-18 15:43:47 +05:30
pablohashescobar 236c660cc7 feat: sort order during create 2023-02-18 12:35:42 +05:30
pablohashescobar eba0f02aeb feat: back migration script to populate random sort_order values 2023-02-18 12:29:46 +05:30
pablohashescobar 2505417dbd feat: updated issue grouping and filtering 2023-02-18 12:22:17 +05:30
Dakshesh JainandGitHub 393638c700 style: not showing pointer & theme color on resend code button disabled (#298) 2023-02-17 20:10:44 +05:30
Dakshesh JainandGitHub 11a36b4398 fix: create issue modal closing on clicking on Grammarly recommendation (#299)
fixed it by not closing modal on outside click
2023-02-17 20:10:21 +05:30
Dakshesh JainandGitHub d71cf567e9 fix: try/catch for invalid values stored in local storage (#301) 2023-02-17 20:10:02 +05:30
Anmol Singh BhatiaandGitHub fcb932dc5d fix: issue title resizing fix (#300)
* fix: issue title resizing fix

* fix: header ui fix and invalid date label updated
2023-02-17 19:58:27 +05:30
Aaryan KhandelwalandGitHub c979599e53 fix: assignee and labels field while editing an issue (#296)
* fix: assignee and labels field while editing an issue

* chore: remove unused declarations
2023-02-17 19:07:36 +05:30
Dakshesh JainandGitHub a0d176c952 refractor: use local storage hook (#293)
* feat: resend login code on signing page after 30 seconds

* refractor: use local storage hook

* refractor: properly using new local storage hook on modules sidebar
2023-02-17 18:52:16 +05:30
Anmol Singh BhatiaandGitHub 8c39717068 fix: ui changes (#297)
* fix: module card height and invalid date

* fix: issue details page title resizing fix
2023-02-17 18:51:45 +05:30
Aaryan KhandelwalandGitHub 45319d81db fix: default label color (#295)
* fix: colors of old labels can now be changed

* fix: black color for labels with no color
2023-02-17 17:06:30 +05:30
7c1f357bed feat: issue links (#288)
* feat: links for issues

* fix: add issue link in serilaizer

* feat: links can be added to issues

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
2023-02-17 17:04:12 +05:30
Dakshesh JainandGitHub a66b2fd73d feat: resend login magic code (#291)
* feat: resend login code on signing page after 30 seconds

* feat: handling error on code send

* refractor: isResendDisabled varible for resend button

* dev: timer count-down hook

* refractor: using new timer hook in sign in page
2023-02-17 16:58:00 +05:30
Aaryan KhandelwalandGitHub 4b068398bd fix: remirror image not updating (#294) 2023-02-17 16:57:31 +05:30
Anmol Singh BhatiaandGitHub 1665863bd9 feat: copy link option (#292)
* feat: copy issue link added in issue card

* feat: copy cycle link added

* feat: ellipsis added in module card

* fix: origin path and handlecopytext added
2023-02-17 14:04:34 +05:30
Anmol Singh BhatiaandGitHub a28be95002 fix: workspace name and breadcrumb title , refactor: command palette (#290)
* refactor: command pallette

* fix: workspace name trim

* fix: breadcrumb title responsiveness added
2023-02-16 18:01:14 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
6ed5c05164 build(deps): bump django in /apiserver/requirements (#289)
Bumps [django](https://github.com/django/django) from 3.2.17 to 3.2.18.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.2.17...3.2.18)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-16 16:04:26 +05:30
Aaryan KhandelwalandGitHub 667dafbda4 fix: consistent dropdowns, refactor: ui components (#286) 2023-02-16 12:03:52 +05:30
pablohashescobar 2d2751c58d fix: state ordering in group 2023-02-15 17:53:37 +05:30
Aaryan KhandelwalandGitHub ec37bb9d23 feat: bulk assign sub-issues (#284) 2023-02-15 14:45:28 +05:30
Anmol Singh BhatiaandGitHub f21135d955 feat: module sidebar date and status updated (#285) 2023-02-15 14:44:19 +05:30
Anmol Singh BhatiaandGitHub 9b51a918cf fix: shortcut search (#283)
* fix: search case innsensitive

* style: email icon updated
2023-02-15 12:28:22 +05:30
Aaryan KhandelwalandGitHub ab0ce2f649 refactor: issue details page (#282) 2023-02-15 12:21:41 +05:30
pablohashescobar 9a5d7b1049 Merge branch 'develop' of github.com:makeplane/plane into feat/multiple_sub_issues 2023-02-14 20:08:39 +05:30
pablohashescobar b8c1305883 fix: error validation for empty length 2023-02-14 20:08:04 +05:30
Aaryan KhandelwalandGitHub 6f0539f01d feat: create label option in create issue modal (#281) 2023-02-14 20:05:32 +05:30
pablohashescobar c9dce08842 feat: assign multiple sub issues 2023-02-14 15:33:53 +05:30
pablohashescobarandGitHub fcba332589 refactor: update django admin panel heading (#276) 2023-02-14 14:50:16 +05:30
Aaryan KhandelwalandGitHub e53ff4c02e feat: state description in settings (#275)
* chore: removed minor bugs

* feat: state description in settings

* feat: group by assignee
2023-02-14 14:46:48 +05:30
Anmol Singh BhatiaandGitHub 9c8c7f1dda fix: mac text copy fix (#277) 2023-02-14 14:35:14 +05:30
vamsi 7950f191e7 dev: added migrations for new models schema changes 2023-02-14 01:19:59 +05:30
pablohashescobarandGitHub 92d5749997 feat: state grouping and ordering list (#253)
* feat: state grouping and ordering list

* fix: state grouping in state list endpoint
2023-02-14 01:16:35 +05:30
pablohashescobarandGitHub af1d49bbf5 refactor: combine sign in and sign up endpoint to a single endpoint (#263) 2023-02-14 01:14:56 +05:30
pablohashescobarandGitHub 0477db69a0 build: add channels requirement for the asgi configuration (#225) 2023-02-14 01:14:24 +05:30
pablohashescobarandGitHub 7a3c00aba4 Merge pull request #226 from makeplane/chore/backend_dependencies
chore: update all backend dependencies to the latest version
2023-02-14 01:14:05 +05:30
pablohashescobarandGitHub 97ffdc8124 feat: default state for project (#264) 2023-02-14 01:12:32 +05:30
Anmol Singh BhatiaandGitHub c6f0990605 fix: ui bug (#274)
* fix: shortcut search fix
shortcut modal ui fixes
shortcut search fix
email us label change
* fix: email us label updated
2023-02-13 20:19:46 +05:30
Aaryan KhandelwalandGitHub 214e860e67 chore: removed minor bugs (#273) 2023-02-13 19:38:58 +05:30
Anmol Singh BhatiaandGitHub 8fb34fe1e3 feat : sidebar progress improvement (#272)
* feat: progress chart render validation

* fix: sidebar stats tab

* feat: sidebar active tab context
2023-02-13 13:14:23 +05:30
Anmol Singh BhatiaandGitHub ebf294af55 feat : cycle sidebar revamp (#271)
* feat: range date picker added

* feat: cycle status ui improved
2023-02-13 10:33:02 +05:30
Aaryan KhandelwalandGitHub d0afa486c7 feat: drag and drop an issue to delete (#270)
* feat: drag and drop an issue to delete

* style: repositioned trash box
2023-02-13 10:32:02 +05:30
Aaryan KhandelwalandGitHub 0a88b3ed84 fix: state reordering (#269)
* fix: state reordering

* refactor: remove unnecessary argument

* refactor: mutation after setting default
2023-02-13 10:30:44 +05:30
Anmol Singh BhatiaandGitHub bb4ffec7e8 feat: burndown chart (#268)
* chore: recharts dependencie added

* chore: tpye added for issue completed at

* feat: date range helper fn added

* feat: progress chart added

* feat: ideal task line added in progress chart

* feat: chart legends added
2023-02-10 18:40:02 +05:30
Aaryan KhandelwalandGitHub af22dc9c58 fix: remirror buttons (#267) 2023-02-10 18:39:23 +05:30
Aaryan KhandelwalandGitHub a403c0c346 feat: label grouping in dropdowns, default state in project settings (#266)
* feat: label grouping in dropdowns, default state in project settings

* feat: label disclosure default open

* refactor: label setting page

* chore: tooltip component updated

* chore: tooltip component updated

* feat/state_sequence_change
2023-02-10 18:02:18 +05:30
pablohashescobar 37c28b251d chore: update python runtime 2023-02-09 19:07:08 +05:30
7c06be19fc feat: cycle status (#265)
* feat: cycle status and dates added in sidebar

* feat: update status added

---------

Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia1001@gmail.com>
2023-02-09 17:54:47 +05:30
pablohashescobarandGitHub 9e9a6f4cce feat: record issue completed at date when the issues are moved to fompleted group (#262) 2023-02-09 10:41:43 +05:30
sriram veeraghantaandGitHub 605ab26bb0 Merge pull request #261 from makeplane/develop
Merge pull request #260 from makeplane/stage-release
2023-02-09 00:21:51 +05:30
sriram veeraghantaandGitHub a94a3e2726 Merge pull request #260 from makeplane/stage-release
Branch Sync
2023-02-09 00:21:12 +05:30
sriram veeraghantaandGitHub 394c73885d Merge pull request #259 from makeplane/stage-release-develop
release: Stage Release
2023-02-09 00:19:42 +05:30
sriramveeraghanta 7f406ceb39 fix: merge conflicts resolved 2023-02-09 00:12:59 +05:30
56030b1c2c fix: github auth login (#250)
* fix: added PROJECT_ISSUES_LIST on the imports (#221)

* fix: github signin by parsing email

* refactor: changed variable names

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
Co-authored-by: Vamsi Kurama <vamsi.kurama@gmail.com>
2023-02-09 00:01:45 +05:30
Aaryan KhandelwalandGitHub 4ffa31fd02 fix: create issue modal bugs (#257) 2023-02-08 23:58:53 +05:30
Aaryan KhandelwalandGitHub 8e3541b947 fix: mutation of states (#256)
* feat: label grouping, fix: new states response

* fix: mutation of states
2023-02-08 23:58:17 +05:30
sriram veeraghantaandGitHub bd399d6d1a sentry changes (#255) 2023-02-08 20:44:35 +05:30
Aaryan KhandelwalandGitHub 166520dfda feat: label grouping, fix: new states response (#254) 2023-02-08 18:51:03 +05:30
c978632938 feat: sidebar progress (#252)
* feat: cycle assignees and labels progress added

* fix: build fix

* feat: sidebar progress stats added and refactor

* refactor: progress stats and cycle sidebar

* feat: module sidebar progress added

* feat: sidebar progress no assignee added

* feat: states tab added

---------

Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia1001@gmail.com>
2023-02-08 18:50:08 +05:30
d3b73dc32f release: Stage Release (#251)
* feat: manual ordering for issues in kanban

* refactor: issues folder structure

* refactor: modules and states folder structure

* refactor: datepicker code

* fix: create issue modal bug

* feat: custom progress bar added

* refactor: created global component for kanban board

* refactor: update cycle and module issue create

* refactor: return modules created

* refactor: integrated global kanban view everywhere

* refactor: integrated global list view everywhere

* refactor: removed unnecessary api calls

* refactor: update nomenclature for consistency

* refactor: global select component for issue view

* refactor: track cycles and modules for issue

* fix: tracking new cycles and modules in activities

* feat: segregate api token workspace

* fix: workpsace id during token creation

* refactor: update model association to cascade on delete

* feat: sentry integrated (#235)

* feat: sentry integrated

* fix: removed unnecessary env variable

* fix: update remirror description to save empty string and empty paragraph (#237)

* Update README.md

* fix: description and comment_json default value to remove warnings

* feat: link option in remirror (#240)

* feat: link option in remirror

* fix: removed link import from remirror toolbar

* feat: module and cycle settings under project

* fix:  module issue assignment

* fix: module issue updation and activity logging

* fix: typo while creating module issues

* fix: string comparison for update operation

* fix: ui fixes (#246)

* style: shortcut command label bg color change

* sidebar shortcut ui fix

---------

Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia1001@gmail.com>

* fix: update empty passwords to hashed string and add hashing for magic sign in

* refactor: remove print logs from back migrations

* build(deps): bump django in /apiserver/requirements

Bumps [django](https://github.com/django/django) from 3.2.16 to 3.2.17.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.2.16...3.2.17)

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

Signed-off-by: dependabot[bot] <support@github.com>

* feat: cycles and modules toggle in settings, refactor: folder structure (#247)

* feat: link option in remirror

* fix: removed link import from remirror toolbar

* refactor: constants folder

* refactor: layouts folder structure

* fix: issue view context

* feat: cycles and modules toggle in settings

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia1001@gmail.com>
Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
Co-authored-by: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com>
Co-authored-by: sphynxux <122926002+sphynxux@users.noreply.github.com>
Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-08 10:15:18 +05:30
Aaryan KhandelwalandGitHub 76cc634a46 feat: cycles and modules toggle in settings, refactor: folder structure (#247)
* feat: link option in remirror

* fix: removed link import from remirror toolbar

* refactor: constants folder

* refactor: layouts folder structure

* fix: issue view context

* feat: cycles and modules toggle in settings
2023-02-08 10:13:07 +05:30
pablohashescobarandGitHub 4e27e93739 Merge pull request #243 from makeplane/fix/description_comment_migration_warnings
fix: description and comment_json default value to remove warnings
2023-02-08 01:47:46 +05:30
pablohashescobarandGitHub 8e1ba90a34 Merge pull request #244 from makeplane/feat/module_cycle_settings
feat: module and cycle settings under project
2023-02-08 01:47:23 +05:30
pablohashescobarandGitHub a67690186a Merge pull request #245 from makeplane/fix/module_issue
fix:  module issue assignment
2023-02-08 01:46:41 +05:30
pablohashescobarandGitHub 5785ab9e96 Merge pull request #248 from makeplane/fix/password_empty
fix: update empty passwords to hashed string and add hashing for magic sign in
2023-02-08 01:46:13 +05:30
pablohashescobarandGitHub e77defc622 chore: update django version to 3.2.17
chore: update django version to 3.2.17
2023-02-08 01:03:04 +05:30
dependabot[bot]andGitHub 27849ee079 build(deps): bump django in /apiserver/requirements
Bumps [django](https://github.com/django/django) from 3.2.16 to 3.2.17.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.2.16...3.2.17)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-07 19:30:23 +00:00
pablohashescobar d36e8af571 refactor: remove print logs from back migrations 2023-02-08 00:58:51 +05:30
pablohashescobar 343718cd2a fix: update empty passwords to hashed string and add hashing for magic sign in 2023-02-08 00:45:56 +05:30
d5bf1f7a91 fix: ui fixes (#246)
* style: shortcut command label bg color change

* sidebar shortcut ui fix

---------

Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia1001@gmail.com>
2023-02-07 19:28:09 +05:30
pablohashescobar 1d44071a77 fix: string comparison for update operation 2023-02-07 14:26:28 +05:30
pablohashescobar 51b3815b72 fix: typo while creating module issues 2023-02-07 14:10:06 +05:30
pablohashescobar 8801a291af fix: module issue updation and activity logging 2023-02-07 14:05:09 +05:30
pablohashescobar f09c9b67f6 fix: module issue assignment 2023-02-07 13:25:06 +05:30
pablohashescobar 8872f3f627 feat: module and cycle settings under project 2023-02-07 13:20:15 +05:30
Aaryan KhandelwalandGitHub 859fef24f4 feat: link option in remirror (#240)
* feat: link option in remirror

* fix: removed link import from remirror toolbar
2023-02-07 11:20:41 +05:30
pablohashescobar 32d83ac2c9 fix: description and comment_json default value to remove warnings 2023-02-07 02:29:36 +05:30
sphynxuxandGitHub f308fe2ce1 Merge pull request #242 from makeplane/sphynxux-patch-1
content: updated the discord link on Readme
2023-02-07 01:32:08 +05:30
sphynxuxandGitHub 240be69c7f Update README.md 2023-02-07 01:31:15 +05:30
pablohashescobarandGitHub 2c4dbc84a2 Merge pull request #231 from makeplane/feat/issue_manual_ordering
feat: manual ordering for issues in kanban
2023-02-07 01:22:02 +05:30
pablohashescobarandGitHub c7ad9f3da1 Merge pull request #236 from makeplane/refactor/cycle_modules
refactor: update cycle and module create operation
2023-02-07 01:20:13 +05:30
pablohashescobarandGitHub b93abb4b37 fix: update remirror description to save empty string and empty paragraph (#237) 2023-02-07 01:19:42 +05:30
pablohashescobarandGitHub aaaf75c5da Merge pull request #239 from makeplane/refactor/color_nomenclature
refactor: update nomenclature for consistency
2023-02-07 01:15:14 +05:30
pablohashescobarandGitHub d256472776 Merge pull request #241 from makeplane/feat/api_token_workspace
feat: segregate api token workspace
2023-02-07 01:13:55 +05:30
Aaryan KhandelwalandGitHub 21e042c852 feat: sentry integrated (#235)
* feat: sentry integrated

* fix: removed unnecessary env variable
2023-02-07 01:07:01 +05:30
pablohashescobar cd41a05022 refactor: update model association to cascade on delete 2023-02-06 23:20:21 +05:30
pablohashescobar f69b76c77d fix: workpsace id during token creation 2023-02-06 22:52:20 +05:30
pablohashescobar 0b7f0640c9 feat: segregate api token workspace 2023-02-06 20:22:08 +05:30
pablohashescobar a576a2ab59 fix: tracking new cycles and modules in activities 2023-02-06 19:25:11 +05:30
sriram veeraghantaandGitHub e1b7e8d139 Merge pull request #233 from makeplane/feat/progress_bar
feat: custom progress bar added
2023-02-06 19:15:43 +05:30
sriram veeraghantaandGitHub c7e006d2c1 Merge pull request #232 from makeplane/refactor/folder_structure
refactor: folder structure
2023-02-06 19:14:59 +05:30
pablohashescobar 67a1052b7b refactor: track cycles and modules for issue 2023-02-06 15:31:05 +05:30
Aaryan Khandelwal adbe16f8ae refactor: global select component for issue view 2023-02-06 15:18:57 +05:30
pablohashescobar eec82eca2f refactor: update nomenclature for consistency 2023-02-06 13:56:02 +05:30
Aaryan Khandelwal 0e07c1e19f refactor: removed unnecessary api calls 2023-02-05 22:01:23 +05:30
Aaryan Khandelwal d673aedf48 refactor: integrated global list view everywhere 2023-02-05 16:57:37 +05:30
Aaryan Khandelwal 85b7f39ed3 refactor: integrated global kanban view everywhere 2023-02-04 20:08:13 +05:30
pablohashescobar 7207d92d62 refactor: return modules created 2023-02-03 19:03:58 +05:30
pablohashescobar a4f095fb59 refactor: update cycle and module issue create 2023-02-03 19:03:27 +05:30
Aaryan Khandelwal 58eda658c8 refactor: created global component for kanban board 2023-02-03 16:03:27 +05:30
Anmol Singh Bhatia aa805b2b16 feat: custom progress bar added 2023-02-03 02:35:25 +05:30
Aaryan Khandelwal 4f85773a48 fix: create issue modal bug 2023-02-02 19:03:54 +05:30
Aaryan Khandelwal 563921d0cf refactor: datepicker code 2023-02-02 18:33:46 +05:30
Aaryan Khandelwal b2eab805e9 refactor: modules and states folder structure 2023-02-02 18:04:13 +05:30
Aaryan Khandelwal 8b1bf53831 refactor: issues folder structure 2023-02-02 15:00:35 +05:30
pablohashescobar c4fff45429 feat: manual ordering for issues in kanban 2023-02-02 13:29:17 +05:30
Vamsi KuramaandGitHub 8118974a37 Merge pull request #230 from makeplane/stage-release
Production release v0.2.1-dev
2023-02-01 23:34:41 +05:30
sriram veeraghantaandGitHub 6966666bf5 Merge pull request #229 from makeplane/develop
Stage Release
2023-02-01 20:34:06 +05:30
Aaryan KhandelwalandGitHub 7e92efee23 fix: header buttons not working (#228)
fix:

header buttons not working.
sub-issues mutation.
customized the datepicker.
mutation in the list and kanban view.
some icons not displaying.
fixed routing and added toast alert after creating a workspace.
workspace logo display design in workspace settings.
delete issue mutation error in cycles and modules.

feat:

added authorization to issue details page.
2023-02-01 20:33:18 +05:30
848fb2b960 fix: shortcut modal fix (#227)
* fix: shortcut btn alignment

* style: shortcut command to uppercase

* fix: update shortcut for uppercase

* fix: issue url shortcut fix

---------

Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia1001@gmail.com>
2023-02-01 20:31:40 +05:30
pablohashescobar c60e771e9c chore: update all backend dependencies to the latest version 2023-02-01 15:08:52 +05:30
sriram veeraghantaandGitHub 155c392467 build fixes (#223) 2023-02-01 00:28:50 +05:30
911 changed files with 70610 additions and 35000 deletions
+68
View File
@@ -0,0 +1,68 @@
# 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
# Database Settings
PGUSER="plane"
PGPASSWORD="plane"
PGHOST="plane-db"
PGDATABASE="plane"
# 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"
# 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_KEY=""
GPT_ENGINE=""
# 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"
# Auto generated and Required that will be generated from setup.sh
+3 -3
View File
@@ -1,10 +1,10 @@
module.exports = {
root: true,
// This tells ESLint to load the config from the package `config`
// extends: ["custom"],
// This tells ESLint to load the config from the package `eslint-config-custom`
extends: ["custom"],
settings: {
next: {
rootDir: ["apps/*/"],
rootDir: ["apps/*"],
},
},
};
+65
View File
@@ -0,0 +1,65 @@
name: Bug report
description: Create a bug report to help us improve Plane
title: "[bug]: "
labels: [bug, need testing]
body:
- type: markdown
attributes:
value: |
Thank you for taking the time to fill out this bug report.
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the bug you encountered
options:
- label: I have searched the existing issues
required: true
- type: textarea
attributes:
label: Current behavior
description: A concise description of what you're experiencing and what you expect
placeholder: |
When I do <X>, <Y> happens and I see the error message attached below:
```...```
What I expect is <Z>
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce
description: Add steps to reproduce this behaviour, include console or network logs and screenshots
placeholder: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required: true
- type: dropdown
id: env
attributes:
label: Environment
options:
- Production
- Deploy preview
validations:
required: true
type: dropdown
id: browser
attributes:
label: Browser
options:
- Google Chrome
- Mozilla Firefox
- Safari
- Other
- type: dropdown
id: version
attributes:
label: Version
options:
- Cloud
- Self-hosted
- Local
validations:
required: true
@@ -0,0 +1,28 @@
name: Feature request
description: Suggest a feature to improve Plane
title: "[feature]: "
labels: [feature]
body:
- type: markdown
attributes:
value: |
Thank you for taking the time to request a feature for Plane
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue related to this feature request already exists
options:
- label: I have searched the existing issues
required: true
- type: textarea
attributes:
label: Summary
description: One paragraph description of the feature
validations:
required: true
- type: textarea
attributes:
label: Why should this be worked on?
description: A concise description of the problems or use cases for this feature request
validations:
required: true
+6
View File
@@ -0,0 +1,6 @@
contact_links:
- name: Help and support
about: Reach out to us on our Discord server or GitHub discussions.
- name: Dedicated support
url: mailto:support@plane.so
about: Write to us if you'd like dedicated support using Plane
+77
View File
@@ -0,0 +1,77 @@
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
@@ -0,0 +1,77 @@
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 }}
+8
View File
@@ -62,3 +62,11 @@ yarn-error.log
*.sln
package-lock.json
.vscode
# Sentry
.sentryclirc
# lock files
package-lock.json
pnpm-lock.yaml
pnpm-workspace.yaml
+125
View File
@@ -0,0 +1,125 @@
FROM node:18-alpine AS builder
RUN apk add --no-cache libc6-compat
# Set working directory
WORKDIR /app
ENV NEXT_PUBLIC_API_BASE_URL=http://NEXT_PUBLIC_API_BASE_URL_PLACEHOLDER
RUN yarn global add turbo
COPY . .
RUN turbo prune --scope=app --docker
# Add lockfile and package.json's of isolated subworkspace
FROM node:18-alpine AS installer
RUN apk add --no-cache libc6-compat
WORKDIR /app
ARG NEXT_PUBLIC_API_BASE_URL=http://localhost:8000
# First install the dependencies (as they change less often)
COPY .gitignore .gitignore
COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/yarn.lock ./yarn.lock
RUN yarn install
# 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
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL \
BUILT_NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
RUN /usr/local/bin/replace-env-vars.sh http://NEXT_PUBLIC_WEBAPP_URL_PLACEHOLDER ${NEXT_PUBLIC_API_BASE_URL}
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
ENV DJANGO_SETTINGS_MODULE plane.settings.production
ENV DOCKERIZED 1
WORKDIR /code
RUN apk --no-cache add \
"libpq~=15" \
"libxslt~=1.1" \
"nodejs-current~=19" \
"xmlsec~=1.2" \
"nginx" \
"nodejs" \
"npm" \
"supervisor"
COPY apiserver/requirements.txt ./
COPY apiserver/requirements ./requirements
RUN apk add --no-cache libffi-dev
RUN apk add --no-cache --virtual .build-deps \
"bash~=5.2" \
"g++~=12.2" \
"gcc~=12.2" \
"cargo~=1.64" \
"git~=2" \
"make~=4.3" \
"postgresql13-dev~=13" \
"libc-dev" \
"linux-headers" \
&& \
pip install -r requirements.txt --compile --no-cache-dir \
&& \
apk del .build-deps
# Add in Django deps and generate Django's static files
COPY apiserver/manage.py manage.py
COPY apiserver/plane plane/
COPY apiserver/templates templates/
COPY apiserver/gunicorn.config.py ./
RUN apk --no-cache add "bash~=5.2"
COPY apiserver/bin ./bin/
RUN chmod +x ./bin/takeoff ./bin/worker
RUN chmod -R 777 /code
# Expose container port and run entry point script
WORKDIR /app
# Don't run production as root
RUN addgroup --system --gid 1001 plane
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 --chown=captain:plane /app/apps/app/.next/standalone ./
COPY --from=installer --chown=captain:plane /app/apps/app/.next/static ./apps/app/.next/static
ENV NEXT_TELEMETRY_DISABLED 1
# RUN rm /etc/nginx/conf.d/default.conf
#######################################################################
COPY nginx/nginx-single-docker-image.conf /etc/nginx/http.d/default.conf
#######################################################################
COPY nginx/supervisor.conf /code/supervisor.conf
ARG NEXT_PUBLIC_API_BASE_URL=http://localhost:8000
ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL \
BUILT_NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
USER root
COPY replace-env-vars.sh /usr/local/bin/
COPY start.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/replace-env-vars.sh
RUN chmod +x /usr/local/bin/start.sh
EXPOSE 80
CMD ["supervisord","-c","/code/supervisor.conf"]
+139 -22
View File
@@ -2,50 +2,167 @@
<p align="center">
<a href="https://plane.so">
<img src="https://res.cloudinary.com/dgxawjvpo/image/upload/v1673379660/Plane/plane-logo_0m83xue7R_f0v9r9.png" alt="Plane Logo" width="350">
<img src="https://res.cloudinary.com/toolspacedev/image/upload/v1680596414/Plane/Plane_Icon_Blue_on_White_150x150_muysa3.jpg" alt="Plane Logo" width="70">
</a>
</p>
<h3 align="center"><b>Plane</b></h3>
<p align="center"><b>Open-source, self-hosted project planning tool</b></p>
<p align="center">
<a href="https://discord.com/invite/29tPNhaV">
<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" />
</a>
<img alt="Discord" src="https://img.shields.io/github/commit-activity/m/makeplane/plane?style=for-the-badge" />
</p>
<br />
Plane is an open-source project planning tool that is designed to help individuals and teams streamline their issues, sprints, and product roadmaps. It is easy to use and can be accessed by anyone, making it an ideal choice for a wide range of projects and organizations.
<br /> <br />
<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"
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"
alt="Plane Screens"
width="100%"
/>
</a>
</p>
> 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/29tPNhaV) or GitHub issues, and we will use your feedback to improve on our upcoming releases.
Meet [Plane](https://plane.so). An open-source software development tool to manage issues, sprints, and product roadmaps with peace of mind 🧘‍♀️.
## Getting Started
Visit https://app.plane.so to get started with Plane.
> 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.
## Documentation
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).
## ⚡️ Quick start with Docker Compose
### Docker Compose Setup
- Clone the repository
```bash
git clone https://github.com/makeplane/plane
cd plane
chmod +x setup.sh
```
- Run setup.sh
```bash
./setup.sh http://localhost
```
> If running in a cloud env replace localhost with public facing IP address of the VM
- 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>
## 🚀 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.
## 📸 Screenshots
<p>
<a href="https://plane.so" target="_blank">
<img
src="https://ik.imagekit.io/killbluedog/Plane_Views_Dark_Mode.png?updatedAt=1684943050275"
alt="Plane Views"
width="100%"
/>
</a>
</p>
<p>
<a href="https://plane.so" target="_blank">
<img
src="https://ik.imagekit.io/killbluedog/Plane_Issue_Detail_Dark_Mode.png?updatedAt=1684943050202"
alt="Plane Issue Details"
width="100%"
/>
</a>
</p>
<p>
<a href="https://plane.so" target="_blank">
<img
src="https://ik.imagekit.io/killbluedog/Plane_Cycles___Modules_Dark_Mode.png?updatedAt=1684943050281"
alt="Plane Cycles and Modules"
width="100%"
/>
</a>
</p>
<p>
<a href="https://plane.so" target="_blank">
<img
src="https://ik.imagekit.io/killbluedog/Plane_Analytics_Dark_Mode.png?updatedAt=1684944596824"
alt="Plane Analytics"
width="100%"
/>
</a>
</p>
<p>
<a href="https://plane.so" target="_blank">
<img
src="https://ik.imagekit.io/killbluedog/Plane_Pages_Dark_Mode.png?updatedAt=1684943050202"
alt="Plane Pages"
width="100%"
/>
</a>
</p>
</p>
<p>
<a href="https://plane.so" target="_blank">
<img
src="https://ik.imagekit.io/killbluedog/Plane_Commad_K_Dark_Mode.png?updatedAt=1684943050312"
alt="Plane Command Menu"
width="100%"
/>
</a>
</p>
</p>
## 📚Documentation
For full documentation, visit [docs.plane.so](https://docs.plane.so/)
To see how to Contribute, visit [here](https://github.com/makeplane/plane/blob/master/CONTRIBUTING.md).
## Status
- [x] Early Community Previews: We are open-sourcing and sharing the development version of Plane
- [ ] Alpha: We are testing Plane with a closed set of customers
- [ ] Public Alpha: Anyone can sign up over at [app.plane.so](https://app.plane.so). But go easy on us, there are a few hiccups
- [ ] Public Beta: Stable enough for most non-enterprise use-cases
- [ ] Public: Production-ready
## Community
## ❤️ Community
The Plane community can be found on GitHub Discussions, where you can ask questions, voice ideas, and share your projects.
To chat with other community members you can join the [Plane Discord](https://discord.com/invite/q9HKAdau).
To chat with other community members you can join the [Plane Discord](https://discord.com/invite/A92xrEGCge).
Our [Code of Conduct](https://github.com/makeplane/plane/blob/master/CODE_OF_CONDUCT.md) applies to all Plane community channels.
## Security
## ⛓️ Security
If you believe you have found a security vulnerability in Plane, we encourage you to responsibly disclose this and not open a public issue. We will investigate all legitimate reports. Email security@plane.so to disclose any security vulnerabilities.
If you believe you have found a security vulnerability in Plane, we encourage you to responsibly disclose this and not open a public issue. We will investigate all legitimate reports. Email security@plane.so to disclose any security vulnerabilities.
-18
View File
@@ -1,18 +0,0 @@
# Backend
SECRET_KEY="<-- django secret -->"
EMAIL_HOST="<-- email smtp -->"
EMAIL_HOST_USER="<-- email host user -->"
EMAIL_HOST_PASSWORD="<-- email host password -->"
AWS_REGION="<-- aws region -->"
AWS_ACCESS_KEY_ID="<-- aws access key -->"
AWS_SECRET_ACCESS_KEY="<-- aws secret acess key -->"
AWS_S3_BUCKET_NAME="<-- aws s3 bucket name -->"
SENTRY_DSN="<-- sentry dsn -->"
WEB_URL="<-- frontend web url -->"
GITHUB_CLIENT_SECRET="<-- github secret -->"
DISABLE_COLLECTSTATIC=1
DOCKERIZED=0 //True if running docker compose else 0
+13 -12
View File
@@ -1,4 +1,4 @@
FROM python:3.8.14-alpine3.16 AS backend
FROM python:3.11.1-alpine3.17 AS backend
# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
@@ -7,20 +7,20 @@ ENV PIP_DISABLE_PIP_VERSION_CHECK=1
WORKDIR /code
RUN apk --update --no-cache add \
"libpq~=14" \
RUN apk --no-cache add \
"libpq~=15" \
"libxslt~=1.1" \
"nodejs-current~=18" \
"nodejs-current~=19" \
"xmlsec~=1.2"
COPY requirements.txt ./
COPY requirements ./requirements
RUN apk add libffi-dev
RUN apk --update --no-cache --virtual .build-deps add \
"bash~=5.1" \
"g++~=11.2" \
"gcc~=11.2" \
"cargo~=1.60" \
RUN apk add --no-cache libffi-dev
RUN apk add --no-cache --virtual .build-deps \
"bash~=5.2" \
"g++~=12.2" \
"gcc~=12.2" \
"cargo~=1.64" \
"git~=2" \
"make~=4.3" \
"postgresql13-dev~=13" \
@@ -46,15 +46,16 @@ COPY templates templates/
COPY gunicorn.config.py ./
USER root
RUN apk --update --no-cache add "bash~=5.1"
RUN apk --no-cache add "bash~=5.2"
COPY ./bin ./bin/
RUN chmod +x ./bin/takeoff ./bin/worker
RUN chmod -R 777 /code
USER captain
# Expose container port and run entry point script
EXPOSE 8000
CMD [ "./bin/takeoff" ]
# CMD [ "./bin/takeoff" ]
+1 -1
View File
@@ -1,2 +1,2 @@
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: python manage.py rqworker
worker: celery -A plane worker -l info
+169 -5
View File
@@ -1,11 +1,22 @@
# All the python scripts that are used for back migrations
import uuid
import random
from django.contrib.auth.hashers import make_password
from plane.db.models import ProjectIdentifier
from plane.db.models import Issue, IssueComment
from plane.db.models import (
Issue,
IssueComment,
User,
Project,
ProjectMember,
Label,
Integration,
)
# Update description and description html values for old descriptions
def update_description():
try:
issues = Issue.objects.all()
updated_issues = []
@@ -25,7 +36,6 @@ def update_description():
def update_comments():
try:
issue_comments = IssueComment.objects.all()
updated_issue_comments = []
@@ -44,9 +54,11 @@ def update_comments():
def update_project_identifiers():
try:
project_identifiers = ProjectIdentifier.objects.filter(workspace_id=None).select_related("project", "project__workspace")
project_identifiers = ProjectIdentifier.objects.filter(
workspace_id=None
).select_related("project", "project__workspace")
updated_identifiers = []
for identifier in project_identifiers:
identifier.workspace_id = identifier.project.workspace_id
updated_identifiers.append(identifier)
@@ -58,3 +70,155 @@ def update_project_identifiers():
except Exception as e:
print(e)
print("Failed")
def update_user_empty_password():
try:
users = User.objects.filter(password="")
updated_users = []
for user in users:
user.password = make_password(uuid.uuid4().hex)
user.is_password_autoset = True
updated_users.append(user)
User.objects.bulk_update(updated_users, ["password"], batch_size=50)
print("Success")
except Exception as e:
print(e)
print("Failed")
def updated_issue_sort_order():
try:
issues = Issue.objects.all()
updated_issues = []
for issue in issues:
issue.sort_order = issue.sequence_id * random.randint(100, 500)
updated_issues.append(issue)
Issue.objects.bulk_update(updated_issues, ["sort_order"], batch_size=100)
print("Success")
except Exception as e:
print(e)
print("Failed")
def update_project_cover_images():
try:
project_cover_images = [
"https://images.unsplash.com/photo-1677432658720-3d84f9d657b4?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
"https://images.unsplash.com/photo-1661107564401-57497d8fe86f?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1332&q=80",
"https://images.unsplash.com/photo-1677352241429-dc90cfc7a623?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1332&q=80",
"https://images.unsplash.com/photo-1677196728306-eeafea692454?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1331&q=80",
"https://images.unsplash.com/photo-1660902179734-c94c944f7830?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1255&q=80",
"https://images.unsplash.com/photo-1672243775941-10d763d9adef?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
"https://images.unsplash.com/photo-1677040628614-53936ff66632?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
"https://images.unsplash.com/photo-1676920410907-8d5f8dd4b5ba?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1332&q=80",
"https://images.unsplash.com/photo-1676846328604-ce831c481346?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1155&q=80",
"https://images.unsplash.com/photo-1676744843212-09b7e64c3a05?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
"https://images.unsplash.com/photo-1676798531090-1608bedeac7b?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
"https://images.unsplash.com/photo-1597088758740-56fd7ec8a3f0?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1169&q=80",
"https://images.unsplash.com/photo-1676638392418-80aad7c87b96?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=774&q=80",
"https://images.unsplash.com/photo-1649639194967-2fec0b4ea7bc?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
"https://images.unsplash.com/photo-1675883086902-b453b3f8146e?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=774&q=80",
"https://images.unsplash.com/photo-1675887057159-40fca28fdc5d?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1173&q=80",
"https://images.unsplash.com/photo-1675373980203-f84c5a672aa5?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
"https://images.unsplash.com/photo-1675191475318-d2bf6bad1200?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1332&q=80",
"https://images.unsplash.com/photo-1675456230532-2194d0c4bcc0?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
"https://images.unsplash.com/photo-1675371788315-60fa0ef48267?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1332&q=80",
]
projects = Project.objects.all()
updated_projects = []
for project in projects:
project.cover_image = project_cover_images[random.randint(0, 19)]
updated_projects.append(project)
Project.objects.bulk_update(updated_projects, ["cover_image"], batch_size=100)
print("Success")
except Exception as e:
print(e)
print("Failed")
def update_user_view_property():
try:
project_members = ProjectMember.objects.all()
updated_project_members = []
for project_member in project_members:
project_member.default_props = {
"filters": {"type": None},
"orderBy": "-created_at",
"collapsed": True,
"issueView": "list",
"filterIssue": None,
"groupByProperty": None,
"showEmptyGroups": True,
}
updated_project_members.append(project_member)
ProjectMember.objects.bulk_update(
updated_project_members, ["default_props"], batch_size=100
)
print("Success")
except Exception as e:
print(e)
print("Failed")
def update_label_color():
try:
labels = Label.objects.filter(color="")
updated_labels = []
for label in labels:
label.color = "#" + "%06x" % random.randint(0, 0xFFFFFF)
updated_labels.append(label)
Label.objects.bulk_update(updated_labels, ["color"], batch_size=100)
print("Success")
except Exception as e:
print(e)
print("Failed")
def create_slack_integration():
try:
_ = Integration.objects.create(provider="slack", network=2, title="Slack")
print("Success")
except Exception as e:
print(e)
print("Failed")
def update_integration_verified():
try:
integrations = Integration.objects.all()
updated_integrations = []
for integration in integrations:
integration.verified = True
updated_integrations.append(integration)
Integration.objects.bulk_update(
updated_integrations, ["verified"], batch_size=10
)
print("Success")
except Exception as e:
print(e)
print("Failed")
def update_start_date():
try:
issues = Issue.objects.filter(state__group__in=["started", "completed"])
updated_issues = []
for issue in issues:
issue.start_date = issue.created_at.date()
updated_issues.append(issue)
Issue.objects.bulk_update(updated_issues, ["start_date"], batch_size=500)
print("Success")
except Exception as e:
print(e)
print("Failed")
+4
View File
@@ -2,4 +2,8 @@
set -e
python manage.py wait_for_db
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 -
+28
View File
@@ -0,0 +1,28 @@
import os, sys
import uuid
sys.path.append("/code")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "plane.settings.production")
import django
django.setup()
from plane.db.models import User
def populate():
default_email = os.environ.get("DEFAULT_EMAIL", "captain@plane.so")
default_password = os.environ.get("DEFAULT_PASSWORD", "password123")
if not User.objects.filter(email=default_email).exists():
user = User.objects.create(email=default_email, username=uuid.uuid4().hex)
user.set_password(default_password)
user.save()
print("User created")
print("Success")
if __name__ == "__main__":
populate()
+1 -2
View File
@@ -2,5 +2,4 @@
set -e
python manage.py wait_for_db
python manage.py migrate
python manage.py rqworker
celery -A plane worker -l info
+3
View File
@@ -0,0 +1,3 @@
from .celery import app as celery_app
__all__ = ('celery_app',)
+36 -5
View File
@@ -10,6 +10,8 @@ from .workspace import (
WorkSpaceMemberSerializer,
TeamSerializer,
WorkSpaceMemberInviteSerializer,
WorkspaceLiteSerializer,
WorkspaceThemeSerializer,
)
from .project import (
ProjectSerializer,
@@ -17,11 +19,13 @@ from .project import (
ProjectMemberSerializer,
ProjectMemberInviteSerializer,
ProjectIdentifierSerializer,
ProjectFavoriteSerializer,
ProjectLiteSerializer,
)
from .state import StateSerializer
from .state import StateSerializer, StateLiteSerializer
from .shortcut import ShortCutSerializer
from .view import ViewSerializer
from .cycle import CycleSerializer, CycleIssueSerializer
from .view import IssueViewSerializer, IssueViewFavoriteSerializer
from .cycle import CycleSerializer, CycleIssueSerializer, CycleFavoriteSerializer
from .asset import FileAssetSerializer
from .issue import (
IssueCreateSerializer,
@@ -36,8 +40,35 @@ from .issue import (
IssueSerializer,
IssueFlatSerializer,
IssueStateSerializer,
IssueLinkSerializer,
IssueLiteSerializer,
IssueAttachmentSerializer,
)
from .module import ModuleWriteSerializer, ModuleSerializer, ModuleIssueSerializer
from .module import (
ModuleWriteSerializer,
ModuleSerializer,
ModuleIssueSerializer,
ModuleLinkSerializer,
ModuleFavoriteSerializer,
)
from .api_token import APITokenSerializer
from .api_token import APITokenSerializer
from .integration import (
IntegrationSerializer,
WorkspaceIntegrationSerializer,
GithubIssueSyncSerializer,
GithubRepositorySerializer,
GithubRepositorySyncSerializer,
GithubCommentSyncSerializer,
SlackProjectSyncSerializer,
)
from .importer import ImporterSerializer
from .page import PageSerializer, PageBlockSerializer, PageFavoriteSerializer
from .estimate import EstimateSerializer, EstimatePointSerializer, EstimateReadSerializer
from .analytic import AnalyticViewSerializer
@@ -0,0 +1,30 @@
from .base import BaseSerializer
from plane.db.models import AnalyticView
from plane.utils.issue_filters import issue_filters
class AnalyticViewSerializer(BaseSerializer):
class Meta:
model = AnalyticView
fields = "__all__"
read_only_fields = [
"workspace",
"query",
]
def create(self, validated_data):
query_params = validated_data.get("query_dict", {})
if bool(query_params):
validated_data["query"] = issue_filters(query_params, "POST")
else:
validated_data["query"] = dict()
return AnalyticView.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)
+7 -1
View File
@@ -5,4 +5,10 @@ from plane.db.models import APIToken
class APITokenSerializer(BaseSerializer):
class Meta:
model = APIToken
fields = "__all__"
fields = [
"label",
"user",
"user_type",
"workspace",
"created_at",
]
+48 -3
View File
@@ -5,12 +5,45 @@ from rest_framework import serializers
from .base import BaseSerializer
from .user import UserLiteSerializer
from .issue import IssueStateSerializer
from plane.db.models import Cycle, CycleIssue
from .workspace import WorkspaceLiteSerializer
from .project import ProjectLiteSerializer
from plane.db.models import Cycle, CycleIssue, CycleFavorite
class CycleSerializer(BaseSerializer):
owned_by = UserLiteSerializer(read_only=True)
is_favorite = serializers.BooleanField(read_only=True)
total_issues = serializers.IntegerField(read_only=True)
cancelled_issues = serializers.IntegerField(read_only=True)
completed_issues = serializers.IntegerField(read_only=True)
started_issues = serializers.IntegerField(read_only=True)
unstarted_issues = serializers.IntegerField(read_only=True)
backlog_issues = serializers.IntegerField(read_only=True)
assignees = serializers.SerializerMethodField(read_only=True)
total_estimates = serializers.IntegerField(read_only=True)
completed_estimates = serializers.IntegerField(read_only=True)
started_estimates = serializers.IntegerField(read_only=True)
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
def get_assignees(self, obj):
members = [
{
"avatar": assignee.avatar,
"first_name": assignee.first_name,
"id": assignee.id,
}
for issue_cycle in obj.issue_cycle.all()
for assignee in issue_cycle.issue.assignees.all()
]
# Use a set comprehension to return only the unique objects
unique_objects = {frozenset(item.items()) for item in members}
# Convert the set back to a list of dictionaries
unique_list = [dict(item) for item in unique_objects]
return unique_list
class Meta:
model = Cycle
@@ -23,7 +56,6 @@ class CycleSerializer(BaseSerializer):
class CycleIssueSerializer(BaseSerializer):
issue_detail = IssueStateSerializer(read_only=True, source="issue")
sub_issues_count = serializers.IntegerField(read_only=True)
@@ -35,3 +67,16 @@ class CycleIssueSerializer(BaseSerializer):
"project",
"cycle",
]
class CycleFavoriteSerializer(BaseSerializer):
cycle_detail = CycleSerializer(source="cycle", read_only=True)
class Meta:
model = CycleFavorite
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"user",
]
@@ -0,0 +1,44 @@
# Module imports
from .base import BaseSerializer
from plane.db.models import Estimate, EstimatePoint
from plane.api.serializers import WorkspaceLiteSerializer, ProjectLiteSerializer
class EstimateSerializer(BaseSerializer):
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
class Meta:
model = Estimate
fields = "__all__"
read_only_fields = [
"workspace",
"project",
]
class EstimatePointSerializer(BaseSerializer):
class Meta:
model = EstimatePoint
fields = "__all__"
read_only_fields = [
"estimate",
"workspace",
"project",
]
class EstimateReadSerializer(BaseSerializer):
points = EstimatePointSerializer(read_only=True, many=True)
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
class Meta:
model = Estimate
fields = "__all__"
read_only_fields = [
"points",
"name",
"description",
]
@@ -0,0 +1,16 @@
# Module imports
from .base import BaseSerializer
from .user import UserLiteSerializer
from .project import ProjectLiteSerializer
from .workspace import WorkspaceLiteSerializer
from plane.db.models import Importer
class ImporterSerializer(BaseSerializer):
initiated_by_detail = UserLiteSerializer(source="initiated_by", read_only=True)
project_detail = ProjectLiteSerializer(source="project", read_only=True)
workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
class Meta:
model = Importer
fields = "__all__"
@@ -0,0 +1,8 @@
from .base import IntegrationSerializer, WorkspaceIntegrationSerializer
from .github import (
GithubRepositorySerializer,
GithubRepositorySyncSerializer,
GithubIssueSyncSerializer,
GithubCommentSyncSerializer,
)
from .slack import SlackProjectSyncSerializer
@@ -0,0 +1,20 @@
# Module imports
from plane.api.serializers import BaseSerializer
from plane.db.models import Integration, WorkspaceIntegration
class IntegrationSerializer(BaseSerializer):
class Meta:
model = Integration
fields = "__all__"
read_only_fields = [
"verified",
]
class WorkspaceIntegrationSerializer(BaseSerializer):
integration_detail = IntegrationSerializer(read_only=True, source="integration")
class Meta:
model = WorkspaceIntegration
fields = "__all__"
@@ -0,0 +1,45 @@
# Module imports
from plane.api.serializers import BaseSerializer
from plane.db.models import (
GithubIssueSync,
GithubRepository,
GithubRepositorySync,
GithubCommentSync,
)
class GithubRepositorySerializer(BaseSerializer):
class Meta:
model = GithubRepository
fields = "__all__"
class GithubRepositorySyncSerializer(BaseSerializer):
repo_detail = GithubRepositorySerializer(source="repository")
class Meta:
model = GithubRepositorySync
fields = "__all__"
class GithubIssueSyncSerializer(BaseSerializer):
class Meta:
model = GithubIssueSync
fields = "__all__"
read_only_fields = [
"project",
"workspace",
"repository_sync",
]
class GithubCommentSyncSerializer(BaseSerializer):
class Meta:
model = GithubCommentSync
fields = "__all__"
read_only_fields = [
"project",
"workspace",
"repository_sync",
"issue_sync",
]
@@ -0,0 +1,14 @@
# Module imports
from plane.api.serializers import BaseSerializer
from plane.db.models import SlackProjectSync
class SlackProjectSyncSerializer(BaseSerializer):
class Meta:
model = SlackProjectSync
fields = "__all__"
read_only_fields = [
"project",
"workspace",
"workspace_integration",
]
+129 -31
View File
@@ -1,13 +1,16 @@
# Django imports
from django.utils import timezone
# Third Party imports
from rest_framework import serializers
# Module imports
from .base import BaseSerializer
from .user import UserLiteSerializer
from .state import StateSerializer
from .state import StateSerializer, StateLiteSerializer
from .user import UserLiteSerializer
from .project import ProjectSerializer
from .workspace import WorkSpaceSerializer
from .project import ProjectSerializer, ProjectLiteSerializer
from .workspace import WorkspaceLiteSerializer
from plane.db.models import (
User,
Issue,
@@ -24,6 +27,8 @@ from plane.db.models import (
Cycle,
Module,
ModuleIssue,
IssueLink,
IssueAttachment,
)
@@ -40,28 +45,17 @@ class IssueFlatSerializer(BaseSerializer):
"start_date",
"target_date",
"sequence_id",
"sort_order",
]
# Issue Serializer with state details
class IssueStateSerializer(BaseSerializer):
state_detail = StateSerializer(read_only=True, source="state")
project_detail = ProjectSerializer(read_only=True, source="project")
class Meta:
model = Issue
fields = "__all__"
##TODO: Find a better way to write this serializer
## Find a better approach to save manytomany?
class IssueCreateSerializer(BaseSerializer):
state_detail = StateSerializer(read_only=True, source="state")
created_by_detail = UserLiteSerializer(read_only=True, source="created_by")
project_detail = ProjectSerializer(read_only=True, source="project")
workspace_detail = WorkSpaceSerializer(read_only=True, source="workspace")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
assignees_list = serializers.ListField(
child=serializers.PrimaryKeyRelatedField(queryset=User.objects.all()),
@@ -109,7 +103,7 @@ class IssueCreateSerializer(BaseSerializer):
project = self.context["project"]
issue = Issue.objects.create(**validated_data, project=project)
if blockers is not None:
if blockers is not None and len(blockers):
IssueBlocker.objects.bulk_create(
[
IssueBlocker(
@@ -125,7 +119,7 @@ class IssueCreateSerializer(BaseSerializer):
batch_size=10,
)
if assignees is not None:
if assignees is not None and len(assignees):
IssueAssignee.objects.bulk_create(
[
IssueAssignee(
@@ -140,8 +134,19 @@ class IssueCreateSerializer(BaseSerializer):
],
batch_size=10,
)
else:
# Then assign it to default assignee
if project.default_assignee is not None:
IssueAssignee.objects.create(
assignee=project.default_assignee,
issue=issue,
project=project,
workspace=project.workspace,
created_by=issue.created_by,
updated_by=issue.updated_by,
)
if labels is not None:
if labels is not None and len(labels):
IssueLabel.objects.bulk_create(
[
IssueLabel(
@@ -157,7 +162,7 @@ class IssueCreateSerializer(BaseSerializer):
batch_size=10,
)
if blocks is not None:
if blocks is not None and len(blocks):
IssueBlocker.objects.bulk_create(
[
IssueBlocker(
@@ -176,7 +181,6 @@ class IssueCreateSerializer(BaseSerializer):
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)
@@ -250,12 +254,13 @@ class IssueCreateSerializer(BaseSerializer):
batch_size=10,
)
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")
class Meta:
model = IssueActivity
@@ -263,10 +268,10 @@ class IssueActivitySerializer(BaseSerializer):
class IssueCommentSerializer(BaseSerializer):
actor_detail = UserLiteSerializer(read_only=True, source="actor")
issue_detail = IssueFlatSerializer(read_only=True, source="issue")
project_detail = ProjectSerializer(read_only=True, source="project")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
class Meta:
model = IssueComment
@@ -309,6 +314,9 @@ class IssuePropertySerializer(BaseSerializer):
class LabelSerializer(BaseSerializer):
workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
project_detail = ProjectLiteSerializer(source="project", read_only=True)
class Meta:
model = Label
fields = "__all__"
@@ -318,8 +326,17 @@ class LabelSerializer(BaseSerializer):
]
class IssueLabelSerializer(BaseSerializer):
class LabelLiteSerializer(BaseSerializer):
class Meta:
model = Label
fields = [
"id",
"name",
"color",
]
class IssueLabelSerializer(BaseSerializer):
# label_details = LabelSerializer(read_only=True, source="label")
class Meta:
@@ -332,7 +349,6 @@ class IssueLabelSerializer(BaseSerializer):
class BlockedIssueSerializer(BaseSerializer):
blocked_issue_detail = IssueFlatSerializer(source="block", read_only=True)
class Meta:
@@ -341,7 +357,6 @@ class BlockedIssueSerializer(BaseSerializer):
class BlockerIssueSerializer(BaseSerializer):
blocker_issue_detail = IssueFlatSerializer(source="blocked_by", read_only=True)
class Meta:
@@ -350,7 +365,6 @@ class BlockerIssueSerializer(BaseSerializer):
class IssueAssigneeSerializer(BaseSerializer):
assignee_details = UserLiteSerializer(read_only=True, source="assignee")
class Meta:
@@ -373,7 +387,6 @@ class CycleBaseSerializer(BaseSerializer):
class IssueCycleDetailSerializer(BaseSerializer):
cycle_detail = CycleBaseSerializer(read_only=True, source="cycle")
class Meta:
@@ -404,7 +417,6 @@ class ModuleBaseSerializer(BaseSerializer):
class IssueModuleDetailSerializer(BaseSerializer):
module_detail = ModuleBaseSerializer(read_only=True, source="module")
class Meta:
@@ -420,6 +432,62 @@ class IssueModuleDetailSerializer(BaseSerializer):
]
class IssueLinkSerializer(BaseSerializer):
created_by_detail = UserLiteSerializer(read_only=True, source="created_by")
class Meta:
model = IssueLink
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
"issue",
]
# Validation if url already exists
def create(self, validated_data):
if IssueLink.objects.filter(
url=validated_data.get("url"), issue_id=validated_data.get("issue_id")
).exists():
raise serializers.ValidationError(
{"error": "URL already exists for this Issue"}
)
return IssueLink.objects.create(**validated_data)
class IssueAttachmentSerializer(BaseSerializer):
class Meta:
model = IssueAttachment
fields = "__all__"
read_only_fields = [
"created_by",
"updated_by",
"created_at",
"updated_at",
"workspace",
"project",
"issue",
]
# 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)
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)
class Meta:
model = Issue
fields = "__all__"
class IssueSerializer(BaseSerializer):
project_detail = ProjectSerializer(read_only=True, source="project")
state_detail = StateSerializer(read_only=True, source="state")
@@ -432,6 +500,8 @@ class IssueSerializer(BaseSerializer):
blocker_issues = BlockerIssueSerializer(read_only=True, 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)
class Meta:
@@ -445,3 +515,31 @@ class IssueSerializer(BaseSerializer):
"created_at",
"updated_at",
]
class IssueLiteSerializer(BaseSerializer):
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
state_detail = StateLiteSerializer(read_only=True, source="state")
label_details = LabelLiteSerializer(read_only=True, source="labels", many=True)
assignee_details = UserLiteSerializer(read_only=True, source="assignees", many=True)
sub_issues_count = serializers.IntegerField(read_only=True)
cycle_id = serializers.UUIDField(read_only=True)
module_id = serializers.UUIDField(read_only=True)
attachment_count = serializers.IntegerField(read_only=True)
link_count = serializers.IntegerField(read_only=True)
class Meta:
model = Issue
fields = "__all__"
read_only_fields = [
"start_date",
"target_date",
"completed_at",
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
+44 -59
View File
@@ -4,30 +4,29 @@ from rest_framework import serializers
# Module imports
from .base import BaseSerializer
from .user import UserLiteSerializer
from .project import ProjectSerializer
from .project import ProjectSerializer, ProjectLiteSerializer
from .workspace import WorkspaceLiteSerializer
from .issue import IssueStateSerializer
from plane.db.models import User, Module, ModuleMember, ModuleIssue, ModuleLink
class LinkCreateSerializer(serializers.Serializer):
url = serializers.CharField(required=True)
title = serializers.CharField(required=False)
from plane.db.models import (
User,
Module,
ModuleMember,
ModuleIssue,
ModuleLink,
ModuleFavorite,
)
class ModuleWriteSerializer(BaseSerializer):
members_list = serializers.ListField(
child=serializers.PrimaryKeyRelatedField(queryset=User.objects.all()),
write_only=True,
required=False,
)
links_list = serializers.ListField(
child=LinkCreateSerializer(),
write_only=True,
required=False,
)
project_detail = ProjectLiteSerializer(source="project", read_only=True)
workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
class Meta:
model = Module
@@ -42,9 +41,7 @@ class ModuleWriteSerializer(BaseSerializer):
]
def create(self, validated_data):
members = validated_data.pop("members_list", None)
links = validated_data.pop("links_list", None)
project = self.context["project"]
@@ -67,30 +64,10 @@ class ModuleWriteSerializer(BaseSerializer):
ignore_conflicts=True,
)
if links is not None:
ModuleLink.objects.bulk_create(
[
ModuleLink(
module=module,
project=project,
workspace=project.workspace,
created_by=module.created_by,
updated_by=module.updated_by,
title=link.get("title", None),
url=link.get("url", None),
)
for link in links
],
batch_size=10,
ignore_conflicts=True,
)
return module
def update(self, instance, validated_data):
members = validated_data.pop("members_list", None)
links = validated_data.pop("links_list", None)
if members is not None:
ModuleMember.objects.filter(module=instance).delete()
@@ -110,25 +87,6 @@ class ModuleWriteSerializer(BaseSerializer):
ignore_conflicts=True,
)
if links is not None:
ModuleLink.objects.filter(module=instance).delete()
ModuleLink.objects.bulk_create(
[
ModuleLink(
module=instance,
project=instance.project,
workspace=instance.project.workspace,
created_by=instance.created_by,
updated_by=instance.updated_by,
title=link.get("title", None),
url=link.get("url", None),
)
for link in links
],
batch_size=10,
ignore_conflicts=True,
)
return super().update(instance, validated_data)
@@ -147,7 +105,6 @@ class ModuleFlatSerializer(BaseSerializer):
class ModuleIssueSerializer(BaseSerializer):
module_detail = ModuleFlatSerializer(read_only=True, source="module")
issue_detail = IssueStateSerializer(read_only=True, source="issue")
sub_issues_count = serializers.IntegerField(read_only=True)
@@ -167,7 +124,6 @@ class ModuleIssueSerializer(BaseSerializer):
class ModuleLinkSerializer(BaseSerializer):
created_by_detail = UserLiteSerializer(read_only=True, source="created_by")
class Meta:
@@ -180,16 +136,32 @@ class ModuleLinkSerializer(BaseSerializer):
"updated_by",
"created_at",
"updated_at",
"module",
]
# Validation if url already exists
def create(self, validated_data):
if ModuleLink.objects.filter(
url=validated_data.get("url"), module_id=validated_data.get("module_id")
).exists():
raise serializers.ValidationError(
{"error": "URL already exists for this Issue"}
)
return ModuleLink.objects.create(**validated_data)
class ModuleSerializer(BaseSerializer):
project_detail = ProjectSerializer(read_only=True, source="project")
lead_detail = UserLiteSerializer(read_only=True, source="lead")
members_detail = UserLiteSerializer(read_only=True, many=True, source="members")
issue_module = ModuleIssueSerializer(read_only=True, many=True)
link_module = ModuleLinkSerializer(read_only=True, many=True)
is_favorite = serializers.BooleanField(read_only=True)
total_issues = serializers.IntegerField(read_only=True)
cancelled_issues = serializers.IntegerField(read_only=True)
completed_issues = serializers.IntegerField(read_only=True)
started_issues = serializers.IntegerField(read_only=True)
unstarted_issues = serializers.IntegerField(read_only=True)
backlog_issues = serializers.IntegerField(read_only=True)
class Meta:
model = Module
@@ -202,3 +174,16 @@ class ModuleSerializer(BaseSerializer):
"created_at",
"updated_at",
]
class ModuleFavoriteSerializer(BaseSerializer):
module_detail = ModuleFlatSerializer(source="module", read_only=True)
class Meta:
model = ModuleFavorite
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"user",
]
+105
View File
@@ -0,0 +1,105 @@
# Third party imports
from rest_framework import serializers
# Module imports
from .base import BaseSerializer
from .issue import IssueFlatSerializer, LabelSerializer
from .workspace import WorkspaceLiteSerializer
from .project import ProjectLiteSerializer
from plane.db.models import Page, PageBlock, PageFavorite, PageLabel, Label
class PageBlockSerializer(BaseSerializer):
issue_detail = IssueFlatSerializer(source="issue", read_only=True)
project_detail = ProjectLiteSerializer(source="project", read_only=True)
workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
class Meta:
model = PageBlock
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"page",
]
class PageSerializer(BaseSerializer):
is_favorite = serializers.BooleanField(read_only=True)
label_details = LabelSerializer(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)
project_detail = ProjectLiteSerializer(source="project", read_only=True)
workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
class Meta:
model = Page
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"owned_by",
]
def create(self, validated_data):
labels = validated_data.pop("labels_list", None)
project_id = self.context["project_id"]
owned_by_id = self.context["owned_by_id"]
page = Page.objects.create(
**validated_data, project_id=project_id, owned_by_id=owned_by_id
)
if labels is not None:
PageLabel.objects.bulk_create(
[
PageLabel(
label=label,
page=page,
project_id=project_id,
workspace_id=page.workspace_id,
created_by_id=page.created_by_id,
updated_by_id=page.updated_by_id,
)
for label in labels
],
batch_size=10,
)
return page
def update(self, instance, validated_data):
labels = validated_data.pop("labels_list", None)
if labels is not None:
PageLabel.objects.filter(page=instance).delete()
PageLabel.objects.bulk_create(
[
PageLabel(
label=label,
page=instance,
project_id=instance.project_id,
workspace_id=instance.workspace_id,
created_by_id=instance.created_by_id,
updated_by_id=instance.updated_by_id,
)
for label in labels
],
batch_size=10,
)
return super().update(instance, validated_data)
class PageFavoriteSerializer(BaseSerializer):
page_detail = PageSerializer(source="page", read_only=True)
class Meta:
model = PageFavorite
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"user",
]
+33 -8
View File
@@ -6,17 +6,20 @@ from rest_framework import serializers
# Module imports
from .base import BaseSerializer
from plane.api.serializers.workspace import WorkSpaceSerializer
from plane.api.serializers.workspace import WorkSpaceSerializer, WorkspaceLiteSerializer
from plane.api.serializers.user import UserLiteSerializer
from plane.db.models import (
Project,
ProjectMember,
ProjectMemberInvite,
ProjectIdentifier,
ProjectFavorite,
)
class ProjectSerializer(BaseSerializer):
workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
class Meta:
model = Project
fields = "__all__"
@@ -44,7 +47,6 @@ class ProjectSerializer(BaseSerializer):
return project
def update(self, instance, validated_data):
identifier = validated_data.get("identifier", "").strip().upper()
# If identifier is not passed update the project and return
@@ -56,12 +58,15 @@ class ProjectSerializer(BaseSerializer):
project_identifier = ProjectIdentifier.objects.filter(
name=identifier, workspace_id=instance.workspace_id
).first()
if project_identifier is None:
project = super().update(instance, validated_data)
_ = ProjectIdentifier.objects.update(name=identifier, project=project)
project_identifier = ProjectIdentifier.objects.filter(
project=project
).first()
if project_identifier is not None:
project_identifier.name = identifier
project_identifier.save()
return project
# If found check if the project_id to be updated and identifier project id is same
if project_identifier.project_id == instance.id:
# If same pass update
@@ -73,10 +78,13 @@ class ProjectSerializer(BaseSerializer):
class ProjectDetailSerializer(BaseSerializer):
workspace = WorkSpaceSerializer(read_only=True)
default_assignee = UserLiteSerializer(read_only=True)
project_lead = UserLiteSerializer(read_only=True)
is_favorite = serializers.BooleanField(read_only=True)
total_members = serializers.IntegerField(read_only=True)
total_cycles = serializers.IntegerField(read_only=True)
total_modules = serializers.IntegerField(read_only=True)
class Meta:
model = Project
@@ -84,7 +92,6 @@ class ProjectDetailSerializer(BaseSerializer):
class ProjectMemberSerializer(BaseSerializer):
workspace = WorkSpaceSerializer(read_only=True)
project = ProjectSerializer(read_only=True)
member = UserLiteSerializer(read_only=True)
@@ -95,7 +102,6 @@ class ProjectMemberSerializer(BaseSerializer):
class ProjectMemberInviteSerializer(BaseSerializer):
project = ProjectSerializer(read_only=True)
workspace = WorkSpaceSerializer(read_only=True)
@@ -108,3 +114,22 @@ class ProjectIdentifierSerializer(BaseSerializer):
class Meta:
model = ProjectIdentifier
fields = "__all__"
class ProjectFavoriteSerializer(BaseSerializer):
project_detail = ProjectSerializer(source="project", read_only=True)
class Meta:
model = ProjectFavorite
fields = "__all__"
read_only_fields = [
"workspace",
"user",
]
class ProjectLiteSerializer(BaseSerializer):
class Meta:
model = Project
fields = ["id", "identifier", "name"]
read_only_fields = fields
+17
View File
@@ -1,10 +1,15 @@
# Module imports
from .base import BaseSerializer
from .workspace import WorkspaceLiteSerializer
from .project import ProjectLiteSerializer
from plane.db.models import State
class StateSerializer(BaseSerializer):
workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace")
project_detail = ProjectLiteSerializer(read_only=True, source="project")
class Meta:
model = State
fields = "__all__"
@@ -12,3 +17,15 @@ class StateSerializer(BaseSerializer):
"workspace",
"project",
]
class StateLiteSerializer(BaseSerializer):
class Meta:
model = State
fields = [
"id",
"name",
"color",
"group",
]
read_only_fields = fields
+7
View File
@@ -21,9 +21,14 @@ class UserSerializer(BaseSerializer):
"last_login_uagent",
"token_updated_at",
"is_onboarded",
"is_bot",
]
extra_kwargs = {"password": {"write_only": True}}
# If the user has already filled first name or last name then he is onboarded
def get_is_onboarded(self, obj):
return bool(obj.first_name) or bool(obj.last_name)
class UserLiteSerializer(BaseSerializer):
class Meta:
@@ -34,7 +39,9 @@ class UserLiteSerializer(BaseSerializer):
"last_name",
"email",
"avatar",
"is_bot",
]
read_only_fields = [
"id",
"is_bot",
]
+44 -4
View File
@@ -1,14 +1,54 @@
# Third party imports
from rest_framework import serializers
# Module imports
from .base import BaseSerializer
from plane.db.models import View
from .workspace import WorkspaceLiteSerializer
from .project import ProjectLiteSerializer
from plane.db.models import IssueView, IssueViewFavorite
from plane.utils.issue_filters import issue_filters
class ViewSerializer(BaseSerializer):
class IssueViewSerializer(BaseSerializer):
is_favorite = serializers.BooleanField(read_only=True)
project_detail = ProjectLiteSerializer(source="project", read_only=True)
workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
class Meta:
model = View
model = IssueView
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"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 IssueView.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 IssueViewFavoriteSerializer(BaseSerializer):
view_detail = IssueViewSerializer(source="issue_view", read_only=True)
class Meta:
model = IssueViewFavorite
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"user",
]
+32 -6
View File
@@ -5,12 +5,18 @@ from rest_framework import serializers
from .base import BaseSerializer
from .user import UserLiteSerializer
from plane.db.models import User, Workspace, WorkspaceMember, Team, TeamMember
from plane.db.models import Workspace, WorkspaceMember, Team, WorkspaceMemberInvite
from plane.db.models import (
User,
Workspace,
WorkspaceMember,
Team,
TeamMember,
WorkspaceMemberInvite,
WorkspaceTheme,
)
class WorkSpaceSerializer(BaseSerializer):
owner = UserLiteSerializer(read_only=True)
total_members = serializers.IntegerField(read_only=True)
@@ -28,7 +34,6 @@ class WorkSpaceSerializer(BaseSerializer):
class WorkSpaceMemberSerializer(BaseSerializer):
member = UserLiteSerializer(read_only=True)
workspace = WorkSpaceSerializer(read_only=True)
@@ -38,8 +43,9 @@ class WorkSpaceMemberSerializer(BaseSerializer):
class WorkSpaceMemberInviteSerializer(BaseSerializer):
workspace = WorkSpaceSerializer(read_only=True)
total_members = serializers.IntegerField(read_only=True)
created_by_detail = UserLiteSerializer(read_only=True, source="created_by")
class Meta:
model = WorkspaceMemberInvite
@@ -47,7 +53,6 @@ class WorkSpaceMemberInviteSerializer(BaseSerializer):
class TeamSerializer(BaseSerializer):
members_detail = UserLiteSerializer(read_only=True, source="members", many=True)
members = serializers.ListField(
child=serializers.PrimaryKeyRelatedField(queryset=User.objects.all()),
@@ -93,3 +98,24 @@ class TeamSerializer(BaseSerializer):
return super().update(instance, validated_data)
else:
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
fields = "__all__"
read_only_fields = [
"workspace",
"actor",
]
+601 -11
View File
@@ -5,7 +5,6 @@ from django.urls import path
from plane.api.views import (
# Authentication
SignUpEndpoint,
SignInEndpoint,
SignOutEndpoint,
MagicSignInEndpoint,
@@ -22,6 +21,7 @@ from plane.api.views import (
# User
UserEndpoint,
UpdateUserOnBoardedEndpoint,
UserActivityEndpoint,
## End User
# Workspaces
WorkSpaceViewSet,
@@ -39,9 +39,14 @@ from plane.api.views import (
AddTeamToProjectEndpoint,
UserLastProjectWithWorkspaceEndpoint,
UserWorkspaceInvitationEndpoint,
UserActivityGraphEndpoint,
UserIssueCompletedGraphEndpoint,
UserWorkspaceDashboardEndpoint,
WorkspaceThemeViewSet,
## End Workspaces
# File Assets
FileAssetEndpoint,
UserAssetsEndpoint,
## End File Assets
# Projects
ProjectViewSet,
@@ -53,6 +58,7 @@ from plane.api.views import (
ProjectJoinEndpoint,
UserProjectInvitationsViewset,
ProjectIdentifierEndpoint,
ProjectFavoritesViewSet,
## End Projects
# Issues
IssueViewSet,
@@ -61,32 +67,86 @@ from plane.api.views import (
IssueCommentViewSet,
UserWorkSpaceIssues,
BulkDeleteIssuesEndpoint,
BulkImportIssuesEndpoint,
ProjectUserViewsEndpoint,
TimeLineIssueViewSet,
IssuePropertyViewSet,
LabelViewSet,
SubIssuesEndpoint,
IssueLinkViewSet,
BulkCreateIssueLabelsEndpoint,
IssueAttachmentEndpoint,
## End Issues
# States
StateViewSet,
## End States
# Estimates
ProjectEstimatePointEndpoint,
BulkEstimatePointEndpoint,
## End Estimates
# Shortcuts
ShortCutViewSet,
## End Shortcuts
# Views
ViewViewSet,
IssueViewViewSet,
ViewIssuesEndpoint,
IssueViewFavoriteViewSet,
## End Views
# Cycles
CycleViewSet,
CycleIssueViewSet,
CycleDateCheckEndpoint,
CycleFavoriteViewSet,
TransferCycleIssueEndpoint,
## End Cycles
# Modules
ModuleViewSet,
ModuleIssueViewSet,
ModuleFavoriteViewSet,
ModuleLinkViewSet,
BulkImportModulesEndpoint,
## End Modules
# Pages
PageViewSet,
PageBlockViewSet,
PageFavoriteViewSet,
CreateIssueFromPageBlockEndpoint,
## End Pages
# Api Tokens
ApiTokenEndpoint,
## End Api Tokens
# Integrations
IntegrationViewSet,
WorkspaceIntegrationViewSet,
GithubRepositoriesEndpoint,
GithubRepositorySyncViewSet,
GithubIssueSyncViewSet,
GithubCommentSyncViewSet,
BulkCreateGithubIssueSyncEndpoint,
SlackProjectSyncViewSet,
## End Integrations
# Importer
ServiceIssueImportSummaryEndpoint,
ImportServiceEndpoint,
UpdateServiceImportStatusEndpoint,
## End importer
# Search
GlobalSearchEndpoint,
IssueSearchEndpoint,
## End Search
# Gpt
GPTIntegrationEndpoint,
## End Gpt
# Release Notes
ReleaseNotesEndpoint,
## End Release Notes
# Analytics
AnalyticsEndpoint,
AnalyticViewViewset,
SavedAnalyticEndpoint,
ExportAnalyticsEndpoint,
DefaultAnalyticsEndpoint,
## End Analytics
)
@@ -95,7 +155,6 @@ urlpatterns = [
path("social-auth/", OauthEndpoint.as_view(), name="oauth"),
# Auth
path("sign-in/", SignInEndpoint.as_view(), name="sign-in"),
path("sign-up/", SignUpEndpoint.as_view(), name="sign-up"),
path("sign-out/", SignOutEndpoint.as_view(), name="sign-out"),
# Magic Sign In/Up
path(
@@ -111,7 +170,7 @@ urlpatterns = [
),
# Password Manipulation
path(
"password-reset/<uidb64>/<token>/",
"reset-password/<uidb64>/<token>/",
ResetPasswordEndpoint.as_view(),
name="password-reset",
),
@@ -138,6 +197,7 @@ urlpatterns = [
UpdateUserOnBoardedEndpoint.as_view(),
name="change-password",
),
path("users/activities/", UserActivityEndpoint.as_view(), name="user-activities"),
# user workspaces
path(
"users/me/workspaces/",
@@ -161,6 +221,23 @@ urlpatterns = [
name="workspace",
),
# 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",
),
## User Graph
path(
"users/me/invitations/workspaces/<str:slug>/<uuid:pk>/join/",
JoinWorkspaceEndpoint.as_view(),
@@ -230,7 +307,6 @@ urlpatterns = [
"workspaces/<str:slug>/members/<uuid:pk>/",
WorkSpaceMemberViewSet.as_view(
{
"put": "update",
"patch": "partial_update",
"delete": "destroy",
"get": "retrieve",
@@ -275,6 +351,27 @@ urlpatterns = [
WorkspaceMemberUserViewsEndpoint.as_view(),
name="workspace-member-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",
),
## End Workspaces ##
# Projects
path(
@@ -319,7 +416,6 @@ urlpatterns = [
ProjectMemberViewSet.as_view(
{
"get": "retrieve",
"put": "update",
"patch": "partial_update",
"delete": "destroy",
}
@@ -366,6 +462,25 @@ urlpatterns = [
ProjectMemberUserEndpoint.as_view(),
name="project-view",
),
path(
"workspaces/<str:slug>/user-favorite-projects/",
ProjectFavoritesViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project",
),
path(
"workspaces/<str:slug>/user-favorite-projects/<uuid:project_id>/",
ProjectFavoritesViewSet.as_view(
{
"delete": "destroy",
}
),
name="project",
),
# End Projects
# States
path(
@@ -391,6 +506,34 @@ urlpatterns = [
name="project-state",
),
# End States ##
# Estimates
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",
),
# End Estimates ##
# Shortcuts
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/shortcuts/",
@@ -418,7 +561,7 @@ urlpatterns = [
# Views
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/views/",
ViewViewSet.as_view(
IssueViewViewSet.as_view(
{
"get": "list",
"post": "create",
@@ -428,7 +571,7 @@ urlpatterns = [
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/views/<uuid:pk>/",
ViewViewSet.as_view(
IssueViewViewSet.as_view(
{
"get": "retrieve",
"put": "update",
@@ -438,6 +581,30 @@ urlpatterns = [
),
name="project-view",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/views/<uuid:view_id>/issues/",
ViewIssuesEndpoint.as_view(),
name="project-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",
),
## End Views
## Cycles
path(
@@ -484,6 +651,35 @@ urlpatterns = [
),
name="project-cycle",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/date-check/",
CycleDateCheckEndpoint.as_view(),
name="project-cycle",
),
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",
),
## End Cycles
# Issue
path(
@@ -535,9 +731,20 @@ urlpatterns = [
),
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/",
@@ -549,6 +756,38 @@ urlpatterns = [
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",
),
## End Issues
## Issue Activity
path(
@@ -633,7 +872,22 @@ urlpatterns = [
path(
"workspaces/<str:slug>/file-assets/",
FileAssetEndpoint.as_view(),
name="File Assets",
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",
),
## End File Assets
## Modules
@@ -681,9 +935,345 @@ urlpatterns = [
),
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",
),
## End Modules
# Pages
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",
),
## End Pages
# API Tokens
path("api-tokens/", ApiTokenEndpoint.as_view(), name="api-token"),
path("api-tokens/<uuid:pk>/", ApiTokenEndpoint.as_view(), name="api-token"),
path("api-tokens/", ApiTokenEndpoint.as_view(), name="api-tokens"),
path("api-tokens/<uuid:pk>/", ApiTokenEndpoint.as_view(), name="api-tokens"),
## End API Tokens
# Integrations
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
## End Integrations
# Importer
path(
"workspaces/<str:slug>/importers/<str:service>/",
ServiceIssueImportSummaryEndpoint.as_view(),
name="importer",
),
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",
),
## End Importer
# Search
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/search/",
GlobalSearchEndpoint.as_view(),
name="global-search",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/search-issues/",
IssueSearchEndpoint.as_view(),
name="project-issue-search",
),
## End Search
# Gpt
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
# Analytics
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",
),
## End Analytics
]
+72 -6
View File
@@ -11,10 +11,12 @@ from .project import (
ProjectJoinEndpoint,
ProjectUserViewsEndpoint,
ProjectMemberUserEndpoint,
ProjectFavoritesViewSet,
)
from .people import (
UserEndpoint,
UpdateUserOnBoardedEndpoint,
UserActivityEndpoint,
)
from .oauth import OauthEndpoint
@@ -35,12 +37,22 @@ from .workspace import (
UserLastProjectWithWorkspaceEndpoint,
WorkspaceMemberUserEndpoint,
WorkspaceMemberUserViewsEndpoint,
UserActivityGraphEndpoint,
UserIssueCompletedGraphEndpoint,
UserWorkspaceDashboardEndpoint,
WorkspaceThemeViewSet,
)
from .state import StateViewSet
from .shortcut import ShortCutViewSet
from .view import ViewViewSet
from .cycle import CycleViewSet, CycleIssueViewSet
from .asset import FileAssetEndpoint
from .view import IssueViewViewSet, ViewIssuesEndpoint, IssueViewFavoriteViewSet
from .cycle import (
CycleViewSet,
CycleIssueViewSet,
CycleDateCheckEndpoint,
CycleFavoriteViewSet,
TransferCycleIssueEndpoint,
)
from .asset import FileAssetEndpoint, UserAssetsEndpoint
from .issue import (
IssueViewSet,
WorkSpaceIssuesEndpoint,
@@ -52,6 +64,9 @@ from .issue import (
BulkDeleteIssuesEndpoint,
UserWorkSpaceIssues,
SubIssuesEndpoint,
IssueLinkViewSet,
BulkCreateIssueLabelsEndpoint,
IssueAttachmentEndpoint,
)
from .auth_extended import (
@@ -64,13 +79,64 @@ from .auth_extended import (
from .authentication import (
SignUpEndpoint,
SignInEndpoint,
SignOutEndpoint,
MagicSignInEndpoint,
MagicSignInGenerateEndpoint,
)
from .module import ModuleViewSet, ModuleIssueViewSet
from .module import (
ModuleViewSet,
ModuleIssueViewSet,
ModuleLinkViewSet,
ModuleFavoriteViewSet,
)
from .api_token import ApiTokenEndpoint
from .api_token import ApiTokenEndpoint
from .integration import (
WorkspaceIntegrationViewSet,
IntegrationViewSet,
GithubIssueSyncViewSet,
GithubRepositorySyncViewSet,
GithubCommentSyncViewSet,
GithubRepositoriesEndpoint,
BulkCreateGithubIssueSyncEndpoint,
SlackProjectSyncViewSet,
)
from .importer import (
ServiceIssueImportSummaryEndpoint,
ImportServiceEndpoint,
UpdateServiceImportStatusEndpoint,
BulkImportIssuesEndpoint,
BulkImportModulesEndpoint,
)
from .page import (
PageViewSet,
PageBlockViewSet,
PageFavoriteViewSet,
CreateIssueFromPageBlockEndpoint,
)
from .search import GlobalSearchEndpoint, IssueSearchEndpoint
from .gpt import GPTIntegrationEndpoint
from .estimate import (
ProjectEstimatePointEndpoint,
BulkEstimatePointEndpoint,
)
from .release import ReleaseNotesEndpoint
from .analytic import (
AnalyticsEndpoint,
AnalyticViewViewset,
SavedAnalyticEndpoint,
ExportAnalyticsEndpoint,
DefaultAnalyticsEndpoint,
)
+295
View File
@@ -0,0 +1,295 @@
# Django imports
from django.db.models import (
Count,
Sum,
F,
)
from django.db.models.functions import ExtractMonth
# Third party imports
from rest_framework import status
from rest_framework.response import Response
from sentry_sdk import capture_exception
# Module imports
from plane.api.views import BaseAPIView, BaseViewSet
from plane.api.permissions import WorkSpaceAdminPermission
from plane.db.models import Issue, AnalyticView, Workspace, State, Label
from plane.api.serializers import AnalyticViewSerializer
from plane.utils.analytics_plot import build_graph_plot
from plane.bgtasks.analytic_plot_export import analytic_export_task
from plane.utils.issue_filters import issue_filters
class AnalyticsEndpoint(BaseAPIView):
permission_classes = [
WorkSpaceAdminPermission,
]
def get(self, request, slug):
try:
x_axis = request.GET.get("x_axis", False)
y_axis = request.GET.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)
filters = issue_filters(request.GET, "GET")
queryset = 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(
workspace__slug=slug, project_id__in=filters.get("project__in")
).values(key, "color")
if filters.get("project__in", False)
else State.objects.filter(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.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")
)
return Response(
{
"total": total_issues,
"distribution": distribution,
"extras": {"colors": colors, "assignee_details": assignee_details},
},
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 AnalyticViewViewset(BaseViewSet):
permission_classes = [
WorkSpaceAdminPermission,
]
model = AnalyticView
serializer_class = AnalyticViewSerializer
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"))
)
class SavedAnalyticEndpoint(BaseAPIView):
permission_classes = [
WorkSpaceAdminPermission,
]
def get(self, request, slug, analytic_id):
try:
analytic_view = AnalyticView.objects.get(
pk=analytic_id, workspace__slug=slug
)
filter = analytic_view.query
queryset = Issue.objects.filter(**filter)
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()
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"},
status=status.HTTP_400_BAD_REQUEST,
)
class ExportAnalyticsEndpoint(BaseAPIView):
permission_classes = [
WorkSpaceAdminPermission,
]
def post(self, request, slug):
try:
x_axis = request.data.get("x_axis", False)
y_axis = request.data.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,
)
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,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
class DefaultAnalyticsEndpoint(BaseAPIView):
permission_classes = [
WorkSpaceAdminPermission,
]
def get(self, request, slug):
try:
filters = issue_filters(request.GET, "GET")
queryset = Issue.objects.filter(workspace__slug=slug, **filters)
total_issues = queryset.count()
total_issues_classified = (
queryset.annotate(state_group=F("state__group"))
.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_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")
)
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]
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]
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")
)
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"]
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,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
+12 -4
View File
@@ -15,16 +15,24 @@ 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)
if not workspace:
return Response(
{"error": "Workspace is required"}, status=status.HTTP_200_OK
)
api_token = APIToken.objects.create(
label=label,
user=request.user,
label=label, user=request.user, workspace_id=workspace
)
serializer = APITokenSerializer(api_token)
return Response(serializer.data, status=status.HTTP_201_CREATED)
# Token will be only vissible while creating
return Response(
{"api_token": serializer.data, "token": api_token.token},
status=status.HTTP_201_CREATED,
)
except Exception as e:
capture_exception(e)
+75 -11
View File
@@ -3,23 +3,23 @@ from rest_framework import status
from rest_framework.response import Response
from rest_framework.parsers import MultiPartParser, FormParser
from sentry_sdk import capture_exception
from django.conf import settings
# Module imports
from .base import BaseAPIView
from plane.db.models import FileAsset
from plane.db.models import FileAsset, Workspace
from plane.api.serializers import FileAssetSerializer
class FileAssetEndpoint(BaseAPIView):
parser_classes = (MultiPartParser, FormParser)
"""
A viewset for viewing and editing task instances.
"""
def get(self, request, slug):
files = FileAsset.objects.filter(workspace__slug=slug)
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)
@@ -27,14 +27,59 @@ class FileAssetEndpoint(BaseAPIView):
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,
)
if request.user.last_workspace_id is None:
return Response(
{"error": "Workspace id is required"},
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,
)
serializer.save(workspace_id=request.user.last_workspace_id)
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
)
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:
@@ -43,3 +88,22 @@ class FileAssetEndpoint(BaseAPIView):
{"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,
)
+1 -1
View File
@@ -84,7 +84,7 @@ class ForgotPasswordEndpoint(BaseAPIView):
)
return Response(
{"messgae": "Check your email to reset your password"},
{"message": "Check your email to reset your password"},
status=status.HTTP_200_OK,
)
return Response(
+150 -113
View File
@@ -3,12 +3,14 @@ import uuid
import random
import string
import json
import requests
# Django imports
from django.utils import timezone
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.conf import settings
from django.contrib.auth.hashers import make_password
# Third party imports
from rest_framework.response import Response
@@ -34,74 +36,6 @@ def get_tokens_for_user(user):
)
class SignUpEndpoint(BaseAPIView):
permission_classes = (AllowAny,)
def post(self, request):
try:
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 not None:
return Response(
{"error": "Email ID is already taken"},
status=status.HTTP_400_BAD_REQUEST,
)
user = User.objects.create(email=email)
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,
}
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 or contact the support team."
},
status=status.HTTP_400_BAD_REQUEST,
)
class SignInEndpoint(BaseAPIView):
permission_classes = (AllowAny,)
@@ -127,50 +61,111 @@ class SignInEndpoint(BaseAPIView):
status=status.HTTP_400_BAD_REQUEST,
)
user = User.objects.get(email=email)
user = User.objects.filter(email=email).first()
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,
)
# Sign up Process
if user is None:
user = User.objects.create(email=email, username=uuid.uuid4().hex)
user.set_password(password)
serialized_user = UserSerializer(user).data
# 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()
# 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()
serialized_user = UserSerializer(user).data
access_token, refresh_token = get_tokens_for_user(user)
access_token, refresh_token = get_tokens_for_user(user)
data = {
"access_token": access_token,
"refresh_token": refresh_token,
"user": serialized_user,
}
data = {
"access_token": access_token,
"refresh_token": refresh_token,
"user": serialized_user,
}
return Response(data, status=status.HTTP_200_OK)
# 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)
# Sign in Process
else:
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 User.DoesNotExist:
return Response(
{
"error": "Sorry, we could not find a user with the provided credentials. Please try again."
},
status=status.HTTP_403_FORBIDDEN,
)
except Exception as e:
capture_exception(e)
return Response(
@@ -216,14 +211,12 @@ class SignOutEndpoint(BaseAPIView):
class MagicSignInGenerateEndpoint(BaseAPIView):
permission_classes = [
AllowAny,
]
def post(self, request):
try:
email = request.data.get("email", False)
if not email:
@@ -269,7 +262,6 @@ class MagicSignInGenerateEndpoint(BaseAPIView):
ri.set(key, json.dumps(value), ex=expiry)
else:
value = {"current_attempt": 0, "email": email, "token": token}
expiry = 600
@@ -293,14 +285,12 @@ class MagicSignInGenerateEndpoint(BaseAPIView):
class MagicSignInEndpoint(BaseAPIView):
permission_classes = [
AllowAny,
]
def post(self, request):
try:
user_token = request.data.get("token", "").strip().lower()
key = request.data.get("key", False)
@@ -313,20 +303,67 @@ class MagicSignInEndpoint(BaseAPIView):
ri = redis_instance()
if ri.exists(key):
data = json.loads(ri.get(key))
token = data["token"]
email = data["email"]
if str(token) == str(user_token):
if User.objects.filter(email=email).exists():
user = User.objects.get(email=email)
# Send event to Jitsu for tracking
if settings.ANALYTICS_BASE_API:
_ = requests.post(
settings.ANALYTICS_BASE_API,
headers={
"Content-Type": "application/json",
"X-Auth-Token": settings.ANALYTICS_SECRET_KEY,
},
json={
"event_id": uuid.uuid4().hex,
"event_data": {
"medium": "code",
},
"user": {"email": email, "id": str(user.id)},
"device_ctx": {
"ip": request.META.get("REMOTE_ADDR"),
"user_agent": request.META.get(
"HTTP_USER_AGENT"
),
},
"event_type": "SIGN_IN",
},
)
else:
user = User.objects.create(
email=email, username=uuid.uuid4().hex
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()
+2 -2
View File
@@ -10,7 +10,7 @@ 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
@@ -39,7 +39,7 @@ class BaseViewSet(ModelViewSet, BasePaginator):
try:
return self.model.objects.all()
except Exception as e:
print(e)
capture_exception(e)
raise APIException("Please check the view", status.HTTP_400_BAD_REQUEST)
def dispatch(self, request, *args, **kwargs):
+570 -24
View File
@@ -1,5 +1,23 @@
# Python imports
import json
# Django imports
from django.db.models import OuterRef, Func, F
from django.db import IntegrityError
from django.db.models import (
OuterRef,
Func,
F,
Q,
Exists,
OuterRef,
Count,
Prefetch,
Sum,
)
from django.core import serializers
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views.decorators.gzip import gzip_page
# Third party imports
from rest_framework.response import Response
@@ -7,14 +25,29 @@ from rest_framework import status
from sentry_sdk import capture_exception
# Module imports
from . import BaseViewSet
from plane.api.serializers import CycleSerializer, CycleIssueSerializer
from . import BaseViewSet, BaseAPIView
from plane.api.serializers import (
CycleSerializer,
CycleIssueSerializer,
CycleFavoriteSerializer,
IssueStateSerializer,
)
from plane.api.permissions import ProjectEntityPermission
from plane.db.models import Cycle, CycleIssue, Issue
from plane.db.models import (
User,
Cycle,
CycleIssue,
Issue,
CycleFavorite,
IssueLink,
IssueAttachment,
)
from plane.bgtasks.issue_activites_task import issue_activity
from plane.utils.grouper import group_results
from plane.utils.issue_filters import issue_filters
class CycleViewSet(BaseViewSet):
serializer_class = CycleSerializer
model = Cycle
permission_classes = [
@@ -26,7 +59,35 @@ class CycleViewSet(BaseViewSet):
project_id=self.kwargs.get("project_id"), owned_by=self.request.user
)
def perform_destroy(self, instance):
cycle_issues = list(
CycleIssue.objects.filter(cycle_id=self.kwargs.get("pk")).values_list(
"issue", flat=True
)
)
issue_activity.delay(
type="cycle.activity.deleted",
requested_data=json.dumps(
{
"cycle_id": str(self.kwargs.get("pk")),
"issues": [str(issue_id) for issue_id in cycle_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=None,
)
return super().perform_destroy(instance)
def get_queryset(self):
subquery = CycleFavorite.objects.filter(
user=self.request.user,
cycle_id=OuterRef("pk"),
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
)
return self.filter_queryset(
super()
.get_queryset()
@@ -36,12 +97,193 @@ class CycleViewSet(BaseViewSet):
.select_related("project")
.select_related("workspace")
.select_related("owned_by")
.annotate(is_favorite=Exists(subquery))
.annotate(total_issues=Count("issue_cycle"))
.annotate(
completed_issues=Count(
"issue_cycle__issue__state__group",
filter=Q(issue_cycle__issue__state__group="completed"),
)
)
.annotate(
cancelled_issues=Count(
"issue_cycle__issue__state__group",
filter=Q(issue_cycle__issue__state__group="cancelled"),
)
)
.annotate(
started_issues=Count(
"issue_cycle__issue__state__group",
filter=Q(issue_cycle__issue__state__group="started"),
)
)
.annotate(
unstarted_issues=Count(
"issue_cycle__issue__state__group",
filter=Q(issue_cycle__issue__state__group="unstarted"),
)
)
.annotate(
backlog_issues=Count(
"issue_cycle__issue__state__group",
filter=Q(issue_cycle__issue__state__group="backlog"),
)
)
.annotate(total_estimates=Sum("issue_cycle__issue__estimate_point"))
.annotate(
completed_estimates=Sum(
"issue_cycle__issue__estimate_point",
filter=Q(issue_cycle__issue__state__group="completed"),
)
)
.annotate(
started_estimates=Sum(
"issue_cycle__issue__estimate_point",
filter=Q(issue_cycle__issue__state__group="started"),
)
)
.prefetch_related(
Prefetch(
"issue_cycle__issue__assignees",
queryset=User.objects.only("avatar", "first_name", "id").distinct(),
)
)
.order_by("-is_favorite", "name")
.distinct()
)
def list(self, request, slug, project_id):
try:
queryset = self.get_queryset()
cycle_view = request.GET.get("cycle_view", False)
if not cycle_view:
return Response(
{"error": "Cycle View parameter is required"},
status=status.HTTP_400_BAD_REQUEST,
)
# All Cycles
if cycle_view == "all":
return Response(
CycleSerializer(queryset, many=True).data, status=status.HTTP_200_OK
)
# Current Cycle
if cycle_view == "current":
queryset = queryset.filter(
start_date__lte=timezone.now(),
end_date__gte=timezone.now(),
)
return Response(
CycleSerializer(queryset, many=True).data, status=status.HTTP_200_OK
)
# Upcoming Cycles
if cycle_view == "upcoming":
queryset = queryset.filter(start_date__gt=timezone.now())
return Response(
CycleSerializer(queryset, many=True).data, status=status.HTTP_200_OK
)
# Completed Cycles
if cycle_view == "completed":
queryset = queryset.filter(end_date__lt=timezone.now())
return Response(
CycleSerializer(queryset, many=True).data, status=status.HTTP_200_OK
)
# Draft Cycles
if cycle_view == "draft":
queryset = queryset.filter(
end_date=None,
start_date=None,
)
return Response(
CycleSerializer(queryset, many=True).data, status=status.HTTP_200_OK
)
# Incomplete Cycles
if cycle_view == "incomplete":
queryset = queryset.filter(
Q(end_date__gte=timezone.now().date()) | Q(end_date__isnull=True),
)
return Response(
CycleSerializer(queryset, many=True).data, status=status.HTTP_200_OK
)
return 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,
)
def create(self, request, slug, project_id):
try:
if (
request.data.get("start_date", None) is None
and request.data.get("end_date", None) is None
) or (
request.data.get("start_date", None) is not None
and request.data.get("end_date", None) is not None
):
serializer = CycleSerializer(data=request.data)
if serializer.is_valid():
serializer.save(
project_id=project_id,
owned_by=request.user,
)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
return Response(
{
"error": "Both start date and end date are either required or are to be null"
},
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 partial_update(self, request, slug, project_id, pk):
try:
cycle = Cycle.objects.get(
workspace__slug=slug, project_id=project_id, pk=pk
)
if cycle.end_date is not None and cycle.end_date < timezone.now().date():
return Response(
{
"error": "The Cycle has already been completed so it cannot be edited"
},
status=status.HTTP_400_BAD_REQUEST,
)
serializer = CycleSerializer(cycle, 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 Cycle.DoesNotExist:
return Response(
{"error": "Cycle 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,
)
class CycleIssueViewSet(BaseViewSet):
serializer_class = CycleIssueSerializer
model = CycleIssue
@@ -49,12 +291,33 @@ class CycleIssueViewSet(BaseViewSet):
ProjectEntityPermission,
]
filterset_fields = [
"issue__labels__id",
"issue__assignees__id",
]
def perform_create(self, serializer):
serializer.save(
project_id=self.kwargs.get("project_id"),
cycle_id=self.kwargs.get("cycle_id"),
)
def perform_destroy(self, instance):
issue_activity.delay(
type="cycle.activity.deleted",
requested_data=json.dumps(
{
"cycle_id": str(self.kwargs.get("cycle_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()
@@ -77,9 +340,68 @@ class CycleIssueViewSet(BaseViewSet):
.distinct()
)
@method_decorator(gzip_page)
def list(self, request, slug, project_id, cycle_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.objects.filter(issue_cycle__cycle_id=cycle_id)
.annotate(
sub_issues_count=Issue.objects.filter(parent=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(bridge_id=F("issue_cycle__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")
)
)
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,
)
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, cycle_id):
try:
issues = request.data.get("issues", [])
if not len(issues):
@@ -91,29 +413,83 @@ class CycleIssueViewSet(BaseViewSet):
workspace__slug=slug, project_id=project_id, pk=cycle_id
)
issues = Issue.objects.filter(
pk__in=issues, workspace__slug=slug, project_id=project_id
)
if cycle.end_date is not None and cycle.end_date < timezone.now().date():
return Response(
{
"error": "The Cycle has already been completed so no new issues can be added"
},
status=status.HTTP_400_BAD_REQUEST,
)
# Delete old records in order to maintain the database integrity
CycleIssue.objects.filter(issue_id__in=issues).delete()
# Get all CycleIssues already created
cycle_issues = list(CycleIssue.objects.filter(issue_id__in=issues))
update_cycle_issue_activity = []
record_to_create = []
records_to_update = []
for issue in issues:
cycle_issue = [
cycle_issue
for cycle_issue in cycle_issues
if str(cycle_issue.issue_id) in issues
]
# Update only when cycle changes
if len(cycle_issue):
if cycle_issue[0].cycle_id != cycle_id:
update_cycle_issue_activity.append(
{
"old_cycle_id": str(cycle_issue[0].cycle_id),
"new_cycle_id": str(cycle_id),
"issue_id": str(cycle_issue[0].issue_id),
}
)
cycle_issue[0].cycle_id = cycle_id
records_to_update.append(cycle_issue[0])
else:
record_to_create.append(
CycleIssue(
project_id=project_id,
workspace=cycle.workspace,
created_by=request.user,
updated_by=request.user,
cycle=cycle,
issue_id=issue,
)
)
CycleIssue.objects.bulk_create(
[
CycleIssue(
project_id=project_id,
workspace=cycle.workspace,
created_by=request.user,
updated_by=request.user,
cycle=cycle,
issue=issue,
)
for issue in issues
],
record_to_create,
batch_size=10,
ignore_conflicts=True,
)
return Response({"message": "Success"}, status=status.HTTP_200_OK)
CycleIssue.objects.bulk_update(
records_to_update,
["cycle"],
batch_size=10,
)
# Capture Issue Activity
issue_activity.delay(
type="cycle.activity.created",
requested_data=json.dumps({"cycles_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_cycle_issues": update_cycle_issue_activity,
"created_cycle_issues": serializers.serialize(
"json", record_to_create
),
}
),
)
# Return all Cycle Issues
return Response(
CycleIssueSerializer(self.get_queryset(), many=True).data,
status=status.HTTP_200_OK,
)
except Cycle.DoesNotExist:
return Response(
@@ -125,3 +501,173 @@ class CycleIssueViewSet(BaseViewSet):
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
class CycleDateCheckEndpoint(BaseAPIView):
permission_classes = [
ProjectEntityPermission,
]
def post(self, request, slug, project_id):
try:
start_date = request.data.get("start_date", False)
end_date = request.data.get("end_date", False)
cycle_id = request.data.get("cycle_id")
if not start_date or not end_date:
return Response(
{"error": "Start date and end date both are required"},
status=status.HTTP_400_BAD_REQUEST,
)
cycles = Cycle.objects.filter(
Q(workspace__slug=slug)
& Q(project_id=project_id)
& (
Q(start_date__lte=start_date, end_date__gte=start_date)
| Q(start_date__lte=end_date, end_date__gte=end_date)
| Q(start_date__gte=start_date, end_date__lte=end_date)
)
).exclude(pk=cycle_id)
if cycles.exists():
return Response(
{
"error": "You have a cycle already on the given dates, if you want to create your draft cycle you can do that by removing dates",
"cycles": CycleSerializer(cycles, many=True).data,
"status": False,
}
)
else:
return Response({"status": True}, 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 CycleFavoriteViewSet(BaseViewSet):
permission_classes = [
ProjectEntityPermission,
]
serializer_class = CycleFavoriteSerializer
model = CycleFavorite
def get_queryset(self):
return self.filter_queryset(
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(user=self.request.user)
.select_related("cycle", "cycle__owned_by")
)
def create(self, request, slug, project_id):
try:
serializer = CycleFavoriteSerializer(data=request.data)
if serializer.is_valid():
serializer.save(user=request.user, project_id=project_id)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except IntegrityError as e:
if "already exists" in str(e):
return Response(
{"error": "The cycle 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,
)
def destroy(self, request, slug, project_id, cycle_id):
try:
cycle_favorite = CycleFavorite.objects.get(
project=project_id,
user=request.user,
workspace__slug=slug,
cycle_id=cycle_id,
)
cycle_favorite.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except CycleFavorite.DoesNotExist:
return Response(
{"error": "Cycle 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,
)
class TransferCycleIssueEndpoint(BaseAPIView):
permission_classes = [
ProjectEntityPermission,
]
def post(self, request, slug, project_id, cycle_id):
try:
new_cycle_id = request.data.get("new_cycle_id", False)
if not new_cycle_id:
return Response(
{"error": "New Cycle Id is required"},
status=status.HTTP_400_BAD_REQUEST,
)
new_cycle = Cycle.objects.get(
workspace__slug=slug, project_id=project_id, pk=new_cycle_id
)
if (
new_cycle.end_date is not None
and new_cycle.end_date < timezone.now().date()
):
return Response(
{
"error": "The cycle where the issues are transferred is already completed"
},
status=status.HTTP_400_BAD_REQUEST,
)
cycle_issues = CycleIssue.objects.filter(
cycle_id=cycle_id,
project_id=project_id,
workspace__slug=slug,
issue__state__group__in=["backlog", "unstarted", "started"],
)
updated_cycles = []
for cycle_issue in cycle_issues:
cycle_issue.cycle_id = new_cycle_id
updated_cycles.append(cycle_issue)
cycle_issues = CycleIssue.objects.bulk_update(
updated_cycles, ["cycle_id"], batch_size=100
)
return Response({"message": "Success"}, status=status.HTTP_200_OK)
except Cycle.DoesNotExist:
return Response(
{"error": "New Cycle 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,
)
+253
View File
@@ -0,0 +1,253 @@
# Django imports
from django.db import IntegrityError
# 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 BaseViewSet, BaseAPIView
from plane.api.permissions import ProjectEntityPermission
from plane.db.models import Project, Estimate, EstimatePoint
from plane.api.serializers import (
EstimateSerializer,
EstimatePointSerializer,
EstimateReadSerializer,
)
class ProjectEstimatePointEndpoint(BaseAPIView):
permission_classes = [
ProjectEntityPermission,
]
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(
estimate_id=project.estimate_id,
project_id=project_id,
workspace__slug=slug,
)
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):
permission_classes = [
ProjectEntityPermission,
]
model = Estimate
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,
)
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
)
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"},
status=status.HTTP_400_BAD_REQUEST,
)
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,
)
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)
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"},
status=status.HTTP_400_BAD_REQUEST,
)
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,
)
+101
View File
@@ -0,0 +1,101 @@
# 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,
)
+580
View File
@@ -0,0 +1,580 @@
# Python imports
import uuid
# Third party imports
from rest_framework import status
from rest_framework.response import Response
from sentry_sdk import capture_exception
# Django imports
from django.db.models import Max
# Module imports
from plane.api.views import BaseAPIView
from plane.db.models import (
WorkspaceIntegration,
Importer,
APIToken,
Project,
State,
IssueSequence,
Issue,
IssueActivity,
IssueComment,
IssueLink,
IssueLabel,
Workspace,
IssueAssignee,
Module,
ModuleLink,
ModuleIssue,
Label,
)
from plane.api.serializers import (
ImporterSerializer,
IssueFlatSerializer,
ModuleSerializer,
)
from plane.utils.integrations.github import get_github_repo_details
from plane.utils.importers.jira import jira_project_issue_summary
from plane.bgtasks.importer_task import service_importer
from plane.utils.html_processor import strip_tags
class ServiceIssueImportSummaryEndpoint(BaseAPIView):
def get(self, request, slug, service):
try:
if service == "github":
workspace_integration = WorkspaceIntegration.objects.get(
integration__provider="github", workspace__slug=slug
)
access_tokens_url = workspace_integration.metadata["access_tokens_url"]
owner = request.GET.get("owner")
repo = request.GET.get("repo")
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,
)
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,
)
class ImportServiceEndpoint(BaseAPIView):
def post(self, request, slug, service):
try:
project_id = request.data.get("project_id", False)
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": "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,
)
service_importer.delay(service, importer.id)
serializer = ImporterSerializer(importer)
return Response(serializer.data, status=status.HTTP_201_CREATED)
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,
)
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,
)
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,
)
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,
)
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,
)
def delete(self, request, slug, service, pk):
try:
importer = Importer.objects.get(
pk=pk, service=service, workspace__slug=slug
)
# Delete all imported Issues
imported_issues = importer.imported_data.get("issues", [])
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()
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,
)
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,
)
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
)
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 default state
default_state = State.objects.filter(
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(project_id=project_id).first()
# 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
# 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
)
# 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)
]
)
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"},
status=status.HTTP_400_BAD_REQUEST,
)
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 = 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(
[
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
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
@@ -0,0 +1,9 @@
from .base import IntegrationViewSet, WorkspaceIntegrationViewSet
from .github import (
GithubRepositorySyncViewSet,
GithubIssueSyncViewSet,
BulkCreateGithubIssueSyncEndpoint,
GithubCommentSyncViewSet,
GithubRepositoriesEndpoint,
)
from .slack import SlackProjectSyncViewSet
@@ -0,0 +1,229 @@
# Python improts
import uuid
# Django imports
from django.db import IntegrityError
from django.contrib.auth.hashers import make_password
# 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.views import BaseViewSet
from plane.db.models import (
Integration,
WorkspaceIntegration,
Workspace,
User,
WorkspaceMember,
APIToken,
)
from plane.api.serializers import IntegrationSerializer, WorkspaceIntegrationSerializer
from plane.utils.integrations.github import (
get_github_metadata,
delete_github_installation,
)
from plane.api.permissions import WorkSpaceAdminPermission
class IntegrationViewSet(BaseViewSet):
serializer_class = IntegrationSerializer
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,
)
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:
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"},
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,
)
integration.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except Integration.DoesNotExist:
return Response(
{"error": "Integration Does not exist"},
status=status.HTTP_404_NOT_FOUND,
)
class WorkspaceIntegrationViewSet(BaseViewSet):
serializer_class = WorkspaceIntegrationSerializer
model = WorkspaceIntegration
permission_classes = [
WorkSpaceAdminPermission,
]
def get_queryset(self):
return (
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.select_related("integration")
)
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):
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"},
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,
)
def destroy(self, request, slug, pk):
try:
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,
)
@@ -0,0 +1,231 @@
# Third party imports
from rest_framework import status
from rest_framework.response import Response
from sentry_sdk import capture_exception
# Module imports
from plane.api.views import BaseViewSet, BaseAPIView
from plane.db.models import (
GithubIssueSync,
GithubRepositorySync,
GithubRepository,
WorkspaceIntegration,
ProjectMember,
Label,
GithubCommentSync,
Project,
)
from plane.api.serializers import (
GithubIssueSyncSerializer,
GithubRepositorySyncSerializer,
GithubCommentSyncSerializer,
)
from plane.utils.integrations.github import get_github_repos
from plane.api.permissions import ProjectBasePermission, ProjectEntityPermission
class GithubRepositoriesEndpoint(BaseAPIView):
permission_classes = [
ProjectBasePermission,
]
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
)
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:
return Response(
{"error": "Workspace Integration Does not exists"},
status=status.HTTP_400_BAD_REQUEST,
)
class GithubRepositorySyncViewSet(BaseViewSet):
permission_classes = [
ProjectBasePermission,
]
serializer_class = GithubRepositorySyncSerializer
model = GithubRepositorySync
def perform_create(self, serializer):
serializer.save(project_id=self.kwargs.get("project_id"))
def get_queryset(self):
return (
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
)
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)
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
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"},
status=status.HTTP_400_BAD_REQUEST,
)
class GithubIssueSyncViewSet(BaseViewSet):
permission_classes = [
ProjectEntityPermission,
]
serializer_class = GithubIssueSyncSerializer
model = GithubIssueSync
def perform_create(self, serializer):
serializer.save(
project_id=self.kwargs.get("project_id"),
repository_sync_id=self.kwargs.get("repo_sync_id"),
)
class BulkCreateGithubIssueSyncEndpoint(BaseAPIView):
def post(self, request, slug, project_id, repo_sync_id):
try:
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,
)
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,
)
class GithubCommentSyncViewSet(BaseViewSet):
permission_classes = [
ProjectEntityPermission,
]
serializer_class = GithubCommentSyncSerializer
model = GithubCommentSync
def perform_create(self, serializer):
serializer.save(
project_id=self.kwargs.get("project_id"),
issue_sync_id=self.kwargs.get("issue_sync_id"),
)
@@ -0,0 +1,59 @@
# Django import
from django.db import IntegrityError
# Third party imports
from rest_framework import status
from rest_framework.response import Response
from sentry_sdk import capture_exception
# Module imports
from plane.api.views import BaseViewSet, BaseAPIView
from plane.db.models import SlackProjectSync, WorkspaceIntegration, ProjectMember
from plane.api.serializers import SlackProjectSyncSerializer
from plane.api.permissions import ProjectBasePermission, ProjectEntityPermission
class SlackProjectSyncViewSet(BaseViewSet):
permission_classes = [
ProjectBasePermission,
]
serializer_class = SlackProjectSyncSerializer
model = SlackProjectSync
def create(self, request, slug, project_id, workspace_integration_id):
try:
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(
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,
)
+448 -160
View File
@@ -1,14 +1,31 @@
# Python imports
import json
from itertools import groupby, chain
import random
from itertools import chain
# Django imports
from django.db.models import Prefetch, OuterRef, Func, F
from django.db.models import (
Prefetch,
OuterRef,
Func,
F,
Q,
Count,
Case,
Value,
CharField,
When,
)
from django.core.serializers.json import DjangoJSONEncoder
from django.utils.decorators import method_decorator
from django.views.decorators.gzip import gzip_page
from django.db.models.functions import Coalesce
from django.conf import settings
# Third Party imports
from rest_framework.response import Response
from rest_framework import status
from rest_framework.parsers import MultiPartParser, FormParser
from sentry_sdk import capture_exception
# Module imports
@@ -22,6 +39,10 @@ from plane.api.serializers import (
LabelSerializer,
IssueSerializer,
LabelSerializer,
IssueFlatSerializer,
IssueLinkSerializer,
IssueLiteSerializer,
IssueAttachmentSerializer,
)
from plane.api.permissions import (
ProjectEntityPermission,
@@ -36,11 +57,13 @@ from plane.db.models import (
TimelineIssue,
IssueProperty,
Label,
IssueBlocker,
CycleIssue,
ModuleIssue,
IssueLink,
IssueAttachment,
State,
)
from plane.bgtasks.issue_activites_task import issue_activity
from plane.utils.grouper import group_results
from plane.utils.issue_filters import issue_filters
class IssueViewSet(BaseViewSet):
@@ -75,24 +98,39 @@ class IssueViewSet(BaseViewSet):
self.get_queryset().filter(pk=self.kwargs.get("pk", None)).first()
)
if current_instance is not None:
issue_activity.delay(
{
"type": "issue.activity",
"requested_data": requested_data,
"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(
IssueSerializer(current_instance).data, cls=DjangoJSONEncoder
),
},
type="issue.activity.updated",
requested_data=requested_data,
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(
IssueSerializer(current_instance).data, cls=DjangoJSONEncoder
),
)
return super().perform_update(serializer)
def get_queryset(self):
def perform_destroy(self, instance):
current_instance = (
self.get_queryset().filter(pk=self.kwargs.get("pk", None)).first()
)
if current_instance is not None:
issue_activity.delay(
type="issue.activity.deleted",
requested_data=json.dumps(
{"issue_id": str(self.kwargs.get("pk", None))}
),
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(
IssueSerializer(current_instance).data, cls=DjangoJSONEncoder
),
)
return super().perform_destroy(instance)
def get_queryset(self):
return (
super()
.get_queryset()
@@ -110,81 +148,71 @@ class IssueViewSet(BaseViewSet):
.select_related("parent")
.prefetch_related("assignees")
.prefetch_related("labels")
.prefetch_related(
Prefetch(
"blocked_issues",
queryset=IssueBlocker.objects.select_related("blocked_by", "block"),
)
)
.prefetch_related(
Prefetch(
"blocker_issues",
queryset=IssueBlocker.objects.select_related("block", "blocked_by"),
)
)
.prefetch_related(
Prefetch(
"issue_cycle",
queryset=CycleIssue.objects.select_related("cycle", "issue"),
),
)
.prefetch_related(
Prefetch(
"issue_module",
queryset=ModuleIssue.objects.select_related(
"module", "issue"
).prefetch_related("module__members"),
),
)
)
def grouper(self, issue, group_by):
group_by = issue.get(group_by, "")
if isinstance(group_by, list):
if len(group_by):
return group_by[0]
else:
return ""
else:
return group_by
@method_decorator(gzip_page)
def list(self, request, slug, project_id):
try:
issue_queryset = self.get_queryset()
filters = issue_filters(request.query_params, "GET")
show_sub_issues = request.GET.get("show_sub_issues", "true")
# Custom ordering for priority
priority_order = ["urgent", "high", "medium", "low", None]
order_by_param = request.GET.get("order_by", "-created_at")
issue_queryset = (
self.get_queryset()
.filter(**filters)
.annotate(cycle_id=F("issue_cycle__id"))
.annotate(module_id=F("issue_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")
)
)
if order_by_param == "priority":
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")
else:
issue_queryset = issue_queryset.order_by(order_by_param)
issue_queryset = (
issue_queryset
if show_sub_issues == "true"
else issue_queryset.filter(parent__isnull=True)
)
issues = IssueLiteSerializer(issue_queryset, many=True).data
## Grouping the results
group_by = request.GET.get("group_by", False)
# TODO: Move this group by from ittertools to ORM for better performance - nk
if group_by:
issue_dict = dict()
return Response(
group_results(issues, group_by), status=status.HTTP_200_OK
)
issues = IssueSerializer(issue_queryset, many=True).data
for key, value in groupby(
issues, lambda issue: self.grouper(issue, group_by)
):
issue_dict[str(key)] = list(value)
return Response(issue_dict, status=status.HTTP_200_OK)
return Response(
{
"next_cursor": str(0),
"prev_cursor": str(0),
"next_page_results": False,
"prev_page_results": False,
"count": issue_queryset.count(),
"total_pages": 1,
"extra_stats": {},
"results": IssueSerializer(issue_queryset, many=True).data,
},
status=status.HTTP_200_OK,
)
return Response(issues, status=status.HTTP_200_OK)
except Exception as e:
print(e)
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
@@ -202,15 +230,14 @@ class IssueViewSet(BaseViewSet):
serializer.save()
# Track the issue
IssueActivity.objects.create(
issue_id=serializer.data["id"],
project_id=project_id,
workspace_id=serializer["workspace"],
comment=f"{request.user.email} created the issue",
verb="created",
actor=request.user,
issue_activity.delay(
type="issue.activity.created",
requested_data=json.dumps(self.request.data, cls=DjangoJSONEncoder),
actor_id=str(request.user.id),
issue_id=str(serializer.data.get("id", None)),
project_id=str(project_id),
current_instance=None,
)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@@ -219,8 +246,20 @@ class IssueViewSet(BaseViewSet):
{"error": "Project was not found"}, status=status.HTTP_404_NOT_FOUND
)
def retrieve(self, request, slug, project_id, pk=None):
try:
issue = Issue.objects.get(
workspace__slug=slug, project_id=project_id, pk=pk
)
return Response(IssueSerializer(issue).data, status=status.HTTP_200_OK)
except Issue.DoesNotExist:
return Response(
{"error": "Issue Does not exist"}, status=status.HTTP_404_NOT_FOUND
)
class UserWorkSpaceIssues(BaseAPIView):
@method_decorator(gzip_page)
def get(self, request, slug):
try:
issues = (
@@ -237,36 +276,23 @@ class UserWorkSpaceIssues(BaseAPIView):
.select_related("parent")
.prefetch_related("assignees")
.prefetch_related("labels")
.prefetch_related(
Prefetch(
"blocked_issues",
queryset=IssueBlocker.objects.select_related(
"blocked_by", "block"
),
.order_by("-created_at")
.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")
)
)
.prefetch_related(
Prefetch(
"blocker_issues",
queryset=IssueBlocker.objects.select_related(
"block", "blocked_by"
),
)
)
.prefetch_related(
Prefetch(
"issue_cycle",
queryset=CycleIssue.objects.select_related("cycle", "issue"),
),
)
.prefetch_related(
Prefetch(
"issue_module",
queryset=ModuleIssue.objects.select_related("module", "issue"),
),
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
)
serializer = IssueSerializer(issues, many=True)
serializer = IssueLiteSerializer(issues, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
except Exception as e:
capture_exception(e)
@@ -277,15 +303,17 @@ class UserWorkSpaceIssues(BaseAPIView):
class WorkSpaceIssuesEndpoint(BaseAPIView):
permission_classes = [
WorkSpaceAdminPermission,
]
@method_decorator(gzip_page)
def get(self, request, slug):
try:
issues = Issue.objects.filter(workspace__slug=slug).filter(
project__project_projectmember__member=self.request.user
issues = (
Issue.objects.filter(workspace__slug=slug)
.filter(project__project_projectmember__member=self.request.user)
.order_by("-created_at")
)
serializer = IssueSerializer(issues, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
@@ -298,18 +326,21 @@ class WorkSpaceIssuesEndpoint(BaseAPIView):
class IssueActivityEndpoint(BaseAPIView):
permission_classes = [
ProjectEntityPermission,
]
@method_decorator(gzip_page)
def get(self, request, slug, project_id, issue_id):
try:
issue_activities = (
IssueActivity.objects.filter(issue_id=issue_id)
.filter(project__project_projectmember__member=self.request.user)
.select_related("actor")
).order_by("created_by")
.filter(
~Q(field="comment"),
project__project_projectmember__member=self.request.user,
)
.select_related("actor", "workspace")
).order_by("created_at")
issue_comments = (
IssueComment.objects.filter(issue_id=issue_id)
.filter(project__project_projectmember__member=self.request.user)
@@ -333,7 +364,6 @@ class IssueActivityEndpoint(BaseAPIView):
class IssueCommentViewSet(BaseViewSet):
serializer_class = IssueCommentSerializer
model = IssueComment
permission_classes = [
@@ -351,6 +381,54 @@ class IssueCommentViewSet(BaseViewSet):
issue_id=self.kwargs.get("issue_id"),
actor=self.request.user if self.request.user is not None else None,
)
issue_activity.delay(
type="comment.activity.created",
requested_data=json.dumps(serializer.data, cls=DjangoJSONEncoder),
actor_id=str(self.request.user.id),
issue_id=str(self.kwargs.get("issue_id")),
project_id=str(self.kwargs.get("project_id")),
current_instance=None,
)
def perform_update(self, serializer):
requested_data = json.dumps(self.request.data, cls=DjangoJSONEncoder)
current_instance = (
self.get_queryset().filter(pk=self.kwargs.get("pk", None)).first()
)
if current_instance is not None:
issue_activity.delay(
type="comment.activity.updated",
requested_data=requested_data,
actor_id=str(self.request.user.id),
issue_id=str(self.kwargs.get("issue_id", None)),
project_id=str(self.kwargs.get("project_id", None)),
current_instance=json.dumps(
IssueCommentSerializer(current_instance).data,
cls=DjangoJSONEncoder,
),
)
return super().perform_update(serializer)
def perform_destroy(self, instance):
current_instance = (
self.get_queryset().filter(pk=self.kwargs.get("pk", None)).first()
)
if current_instance is not None:
issue_activity.delay(
type="comment.activity.deleted",
requested_data=json.dumps(
{"comment_id": str(self.kwargs.get("pk", None))}
),
actor_id=str(self.request.user.id),
issue_id=str(self.kwargs.get("issue_id", None)),
project_id=str(self.kwargs.get("project_id", None)),
current_instance=json.dumps(
IssueCommentSerializer(current_instance).data,
cls=DjangoJSONEncoder,
),
)
return super().perform_destroy(instance)
def get_queryset(self):
return self.filter_queryset(
@@ -436,7 +514,6 @@ class IssuePropertyViewSet(BaseViewSet):
def create(self, request, slug, project_id):
try:
issue_property, created = IssueProperty.objects.get_or_create(
user=request.user,
project_id=project_id,
@@ -463,7 +540,6 @@ class IssuePropertyViewSet(BaseViewSet):
class LabelViewSet(BaseViewSet):
serializer_class = LabelSerializer
model = Label
permission_classes = [
@@ -485,19 +561,18 @@ class LabelViewSet(BaseViewSet):
.select_related("project")
.select_related("workspace")
.select_related("parent")
.order_by("name")
.distinct()
)
class BulkDeleteIssuesEndpoint(BaseAPIView):
permission_classes = [
ProjectEntityPermission,
]
def delete(self, request, slug, project_id):
try:
issue_ids = request.data.get("issue_ids", [])
if not len(issue_ids):
@@ -527,14 +602,13 @@ class BulkDeleteIssuesEndpoint(BaseAPIView):
class SubIssuesEndpoint(BaseAPIView):
permission_classes = [
ProjectEntityPermission,
]
@method_decorator(gzip_page)
def get(self, request, slug, project_id, issue_id):
try:
sub_issues = (
Issue.objects.filter(
parent_id=issue_id, workspace__slug=slug, project_id=project_id
@@ -545,38 +619,252 @@ class SubIssuesEndpoint(BaseAPIView):
.select_related("parent")
.prefetch_related("assignees")
.prefetch_related("labels")
.prefetch_related(
Prefetch(
"blocked_issues",
queryset=IssueBlocker.objects.select_related(
"blocked_by", "block"
),
)
)
.prefetch_related(
Prefetch(
"blocker_issues",
queryset=IssueBlocker.objects.select_related(
"block", "blocked_by"
),
)
)
.prefetch_related(
Prefetch(
"issue_cycle",
queryset=CycleIssue.objects.select_related("cycle", "issue"),
),
)
.prefetch_related(
Prefetch(
"issue_module",
queryset=ModuleIssue.objects.select_related("module", "issue"),
),
)
)
serializer = IssueSerializer(sub_issues, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
state_distribution = (
State.objects.filter(workspace__slug=slug, project_id=project_id)
.annotate(
state_count=Count(
"state_issue",
filter=Q(state_issue__parent_id=issue_id),
)
)
.order_by("group")
.values("group", "state_count")
)
result = {item["group"]: item["state_count"] for item in state_distribution}
serializer = IssueLiteSerializer(
sub_issues,
many=True,
)
return Response(
{
"sub_issues": serializer.data,
"state_distribution": result,
},
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,
)
# Assign multiple sub issues
def post(self, request, slug, project_id, issue_id):
try:
parent_issue = Issue.objects.get(pk=issue_id)
sub_issue_ids = request.data.get("sub_issue_ids", [])
if not len(sub_issue_ids):
return Response(
{"error": "Sub Issue IDs are required"},
status=status.HTTP_400_BAD_REQUEST,
)
sub_issues = Issue.objects.filter(id__in=sub_issue_ids)
for sub_issue in sub_issues:
sub_issue.parent = parent_issue
_ = Issue.objects.bulk_update(sub_issues, ["parent"], batch_size=10)
updated_sub_issues = Issue.objects.filter(id__in=sub_issue_ids)
return Response(
IssueFlatSerializer(updated_sub_issues, many=True).data,
status=status.HTTP_200_OK,
)
except Issue.DoesNotExist:
return Response(
{"Parent Issue 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,
)
class IssueLinkViewSet(BaseViewSet):
permission_classes = [
ProjectEntityPermission,
]
model = IssueLink
serializer_class = IssueLinkSerializer
def perform_create(self, serializer):
serializer.save(
project_id=self.kwargs.get("project_id"),
issue_id=self.kwargs.get("issue_id"),
)
issue_activity.delay(
type="link.activity.created",
requested_data=json.dumps(serializer.data, cls=DjangoJSONEncoder),
actor_id=str(self.request.user.id),
issue_id=str(self.kwargs.get("issue_id")),
project_id=str(self.kwargs.get("project_id")),
current_instance=None,
)
def perform_update(self, serializer):
requested_data = json.dumps(self.request.data, cls=DjangoJSONEncoder)
current_instance = (
self.get_queryset().filter(pk=self.kwargs.get("pk", None)).first()
)
if current_instance is not None:
issue_activity.delay(
type="link.activity.updated",
requested_data=requested_data,
actor_id=str(self.request.user.id),
issue_id=str(self.kwargs.get("issue_id", None)),
project_id=str(self.kwargs.get("project_id", None)),
current_instance=json.dumps(
IssueLinkSerializer(current_instance).data,
cls=DjangoJSONEncoder,
),
)
return super().perform_update(serializer)
def perform_destroy(self, instance):
current_instance = (
self.get_queryset().filter(pk=self.kwargs.get("pk", None)).first()
)
if current_instance is not None:
issue_activity.delay(
type="link.activity.deleted",
requested_data=json.dumps(
{"link_id": str(self.kwargs.get("pk", None))}
),
actor_id=str(self.request.user.id),
issue_id=str(self.kwargs.get("issue_id", None)),
project_id=str(self.kwargs.get("project_id", None)),
current_instance=json.dumps(
IssueLinkSerializer(current_instance).data,
cls=DjangoJSONEncoder,
),
)
return super().perform_destroy(instance)
def get_queryset(self):
return (
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(issue_id=self.kwargs.get("issue_id"))
.filter(project__project_projectmember__member=self.request.user)
.order_by("-created_at")
.distinct()
)
class BulkCreateIssueLabelsEndpoint(BaseAPIView):
def post(self, request, slug, project_id):
try:
label_data = request.data.get("label_data", [])
project = Project.objects.get(pk=project_id)
labels = Label.objects.bulk_create(
[
Label(
name=label.get("name", "Migrated"),
description=label.get("description", "Migrated Issue"),
color="#" + "%06x" % random.randint(0, 0xFFFFFF),
project_id=project_id,
workspace_id=project.workspace_id,
created_by=request.user,
updated_by=request.user,
)
for label in label_data
],
batch_size=50,
ignore_conflicts=True,
)
return Response(
{"labels": LabelSerializer(labels, 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"},
status=status.HTTP_400_BAD_REQUEST,
)
class IssueAttachmentEndpoint(BaseAPIView):
serializer_class = IssueAttachmentSerializer
permission_classes = [
ProjectEntityPermission,
]
model = IssueAttachment
parser_classes = (MultiPartParser, FormParser)
def post(self, request, slug, project_id, issue_id):
try:
serializer = IssueAttachmentSerializer(data=request.data)
if serializer.is_valid():
serializer.save(project_id=project_id, issue_id=issue_id)
issue_activity.delay(
type="attachment.activity.created",
requested_data=None,
actor_id=str(self.request.user.id),
issue_id=str(self.kwargs.get("issue_id", None)),
project_id=str(self.kwargs.get("project_id", None)),
current_instance=json.dumps(
serializer.data,
cls=DjangoJSONEncoder,
),
)
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, slug, project_id, issue_id, pk):
try:
issue_attachment = IssueAttachment.objects.get(pk=pk)
issue_attachment.asset.delete(save=False)
issue_attachment.delete()
issue_activity.delay(
type="attachment.activity.deleted",
requested_data=None,
actor_id=str(self.request.user.id),
issue_id=str(self.kwargs.get("issue_id", None)),
project_id=str(self.kwargs.get("project_id", None)),
current_instance=None,
)
return Response(status=status.HTTP_204_NO_CONTENT)
except IssueAttachment.DoesNotExist:
return Response(
{"error": "Issue Attachment does not exist"},
status=status.HTTP_400_BAD_REQUEST,
)
def get(self, request, slug, project_id, issue_id):
try:
issue_attachments = IssueAttachment.objects.filter(
issue_id=issue_id, workspace__slug=slug, project_id=project_id
)
serilaizer = IssueAttachmentSerializer(issue_attachments, many=True)
return Response(serilaizer.data, status=status.HTTP_200_OK)
except Exception as e:
capture_exception(e)
return Response(
+311 -30
View File
@@ -1,6 +1,12 @@
# Python imports
import json
# Django Imports
from django.db import IntegrityError
from django.db.models import Prefetch, F, OuterRef, Func
from django.db.models import Prefetch, F, OuterRef, Func, Exists, Count, Q
from django.core import serializers
from django.utils.decorators import method_decorator
from django.views.decorators.gzip import gzip_page
# Third party imports
from rest_framework.response import Response
@@ -13,6 +19,9 @@ from plane.api.serializers import (
ModuleWriteSerializer,
ModuleSerializer,
ModuleIssueSerializer,
ModuleLinkSerializer,
ModuleFavoriteSerializer,
IssueStateSerializer,
)
from plane.api.permissions import ProjectEntityPermission
from plane.db.models import (
@@ -21,11 +30,16 @@ from plane.db.models import (
Project,
Issue,
ModuleLink,
ModuleFavorite,
IssueLink,
IssueAttachment,
)
from plane.bgtasks.issue_activites_task import issue_activity
from plane.utils.grouper import group_results
from plane.utils.issue_filters import issue_filters
class ModuleViewSet(BaseViewSet):
model = Module
permission_classes = [
ProjectEntityPermission,
@@ -39,31 +53,84 @@ class ModuleViewSet(BaseViewSet):
)
def get_queryset(self):
subquery = ModuleFavorite.objects.filter(
user=self.request.user,
module_id=OuterRef("pk"),
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
)
return (
super()
.get_queryset()
.filter(project_id=self.kwargs.get("project_id"))
.filter(workspace__slug=self.kwargs.get("slug"))
.annotate(is_favorite=Exists(subquery))
.select_related("project")
.select_related("workspace")
.select_related("lead")
.prefetch_related("members")
.prefetch_related(
Prefetch(
"issue_module",
queryset=ModuleIssue.objects.select_related(
"module", "issue", "issue__state", "issue__project"
).prefetch_related("issue__assignees", "issue__labels"),
)
)
.prefetch_related(
Prefetch(
"link_module",
queryset=ModuleLink.objects.select_related("module", "created_by"),
)
)
.annotate(total_issues=Count("issue_module"))
.annotate(
completed_issues=Count(
"issue_module__issue__state__group",
filter=Q(issue_module__issue__state__group="completed"),
)
)
.annotate(
cancelled_issues=Count(
"issue_module__issue__state__group",
filter=Q(issue_module__issue__state__group="cancelled"),
)
)
.annotate(
started_issues=Count(
"issue_module__issue__state__group",
filter=Q(issue_module__issue__state__group="started"),
)
)
.annotate(
unstarted_issues=Count(
"issue_module__issue__state__group",
filter=Q(issue_module__issue__state__group="unstarted"),
)
)
.annotate(
backlog_issues=Count(
"issue_module__issue__state__group",
filter=Q(issue_module__issue__state__group="backlog"),
)
)
.order_by("-is_favorite", "name")
)
def perform_destroy(self, instance):
module_issues = list(
ModuleIssue.objects.filter(module_id=self.kwargs.get("pk")).values_list(
"issue", flat=True
)
)
issue_activity.delay(
type="module.activity.deleted",
requested_data=json.dumps(
{
"module_id": str(self.kwargs.get("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)),
current_instance=None,
)
return super().perform_destroy(instance)
def create(self, request, slug, project_id):
try:
project = Project.objects.get(workspace__slug=slug, pk=project_id)
@@ -95,13 +162,12 @@ class ModuleViewSet(BaseViewSet):
class ModuleIssueViewSet(BaseViewSet):
serializer_class = ModuleIssueSerializer
model = ModuleIssue
filterset_fields = [
"issue__id",
"workspace__id",
"issue__labels__id",
"issue__assignees__id",
]
permission_classes = [
@@ -114,6 +180,22 @@ 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()
@@ -137,6 +219,66 @@ class ModuleIssueViewSet(BaseViewSet):
.distinct()
)
@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.objects.filter(issue_module__module_id=module_id)
.annotate(
sub_issues_count=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")
)
)
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,
)
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, module_id):
try:
issues = request.data.get("issues", [])
@@ -148,29 +290,75 @@ class ModuleIssueViewSet(BaseViewSet):
workspace__slug=slug, project_id=project_id, pk=module_id
)
issues = Issue.objects.filter(
pk__in=issues, workspace__slug=slug, project_id=project_id
)
module_issues = list(ModuleIssue.objects.filter(issue_id__in=issues))
# Delete old records in order to maintain the database integrity
ModuleIssue.objects.filter(issue_id__in=issues).delete()
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
]
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(
[
ModuleIssue(
module=module,
issue=issue,
project_id=project_id,
workspace=module.workspace,
created_by=request.user,
updated_by=request.user,
)
for issue in issues
],
record_to_create,
batch_size=10,
ignore_conflicts=True,
)
return Response({"message": "Success"}, status=status.HTTP_200_OK)
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
),
}
),
)
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
@@ -181,3 +369,96 @@ class ModuleIssueViewSet(BaseViewSet):
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
class ModuleLinkViewSet(BaseViewSet):
permission_classes = [
ProjectEntityPermission,
]
model = ModuleLink
serializer_class = ModuleLinkSerializer
def perform_create(self, serializer):
serializer.save(
project_id=self.kwargs.get("project_id"),
module_id=self.kwargs.get("module_id"),
)
def get_queryset(self):
return (
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(module_id=self.kwargs.get("module_id"))
.filter(project__project_projectmember__member=self.request.user)
.order_by("-created_at")
.distinct()
)
class ModuleFavoriteViewSet(BaseViewSet):
permission_classes = [
ProjectEntityPermission,
]
serializer_class = ModuleFavoriteSerializer
model = ModuleFavorite
def get_queryset(self):
return self.filter_queryset(
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(user=self.request.user)
.select_related("module")
)
def create(self, request, slug, project_id):
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,
)
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,
)
+56 -11
View File
@@ -5,6 +5,7 @@ import os
# Django imports
from django.utils import timezone
from django.conf import settings
# Third Party modules
from rest_framework.response import Response
@@ -13,7 +14,7 @@ from rest_framework.permissions import AllowAny
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
@@ -34,7 +35,6 @@ def get_tokens_for_user(user):
def validate_google_token(token, client_id):
try:
id_info = id_token.verify_oauth2_token(
token, google_auth_request.Request(), client_id
)
@@ -48,7 +48,7 @@ def validate_google_token(token, client_id):
}
return data
except Exception as e:
print(e)
capture_exception(e)
raise exceptions.AuthenticationFailed("Error with Google connection.")
@@ -106,9 +106,19 @@ def get_user_data(access_token: str) -> dict:
resp = requests.get(url=url, headers=headers)
userData = resp.json()
user_data = resp.json()
return userData
response = requests.get(
url="https://api.github.com/user/emails", headers=headers
).json()
[
user_data.update({"email": item.get("email")})
for item in response
if item.get("primary") is True
]
return user_data
class OauthEndpoint(BaseAPIView):
@@ -116,7 +126,6 @@ class OauthEndpoint(BaseAPIView):
def post(self, request):
try:
medium = request.data.get("medium", False)
id_token = request.data.get("credential", False)
client_id = request.data.get("clientId", False)
@@ -138,7 +147,6 @@ class OauthEndpoint(BaseAPIView):
email = data.get("email", None)
if email == None:
return Response(
{
"error": "Something went wrong. Please try again later or contact the support team."
@@ -153,7 +161,6 @@ class OauthEndpoint(BaseAPIView):
mobile_number = uuid.uuid4().hex
email_verified = True
else:
return Response(
{
"error": "Something went wrong. Please try again later or contact the support team."
@@ -198,7 +205,26 @@ class OauthEndpoint(BaseAPIView):
"last_login_at": timezone.now(),
},
)
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": f"oauth-{medium}",
},
"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",
},
)
return Response(data, status=status.HTTP_200_OK)
except User.DoesNotExist:
@@ -247,6 +273,26 @@ class OauthEndpoint(BaseAPIView):
"user": serialized_user,
"permissions": [],
}
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": f"oauth-{medium}",
},
"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",
},
)
SocialLoginConnection.objects.update_or_create(
medium=medium,
@@ -259,8 +305,7 @@ class OauthEndpoint(BaseAPIView):
)
return Response(data, status=status.HTTP_201_CREATED)
except Exception as e:
print(e)
capture_exception(e)
return Response(
{
"error": "Something went wrong. Please try again later or contact the support team."
+321
View File
@@ -0,0 +1,321 @@
# Python imports
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
# Third party imports
from rest_framework import status
from rest_framework.response import Response
from sentry_sdk import capture_exception
# Module imports
from .base import BaseViewSet, BaseAPIView
from plane.api.permissions import ProjectEntityPermission
from plane.db.models import (
Page,
PageBlock,
PageFavorite,
Issue,
IssueAssignee,
IssueActivity,
)
from plane.api.serializers import (
PageSerializer,
PageBlockSerializer,
PageFavoriteSerializer,
IssueLiteSerializer,
)
class PageViewSet(BaseViewSet):
serializer_class = PageSerializer
model = Page
permission_classes = [
ProjectEntityPermission,
]
search_fields = [
"name",
]
def get_queryset(self):
subquery = PageFavorite.objects.filter(
user=self.request.user,
page_id=OuterRef("pk"),
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
)
return self.filter_queryset(
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(project__project_projectmember__member=self.request.user)
.filter(Q(owned_by=self.request.user) | Q(access=0))
.select_related("project")
.select_related("workspace")
.select_related("owned_by")
.annotate(is_favorite=Exists(subquery))
.order_by(self.request.GET.get("order_by", "-created_at"))
.prefetch_related("labels")
.order_by("name", "-is_favorite")
.prefetch_related(
Prefetch(
"blocks",
queryset=PageBlock.objects.select_related(
"page", "issue", "workspace", "project"
),
)
)
.distinct()
)
def perform_create(self, serializer):
serializer.save(
project_id=self.kwargs.get("project_id"), owned_by=self.request.user
)
def create(self, request, slug, project_id):
try:
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,
)
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:
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"},
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)
if not page_view:
return Response({"error": "Page View parameter is required"}, status=status.HTTP_400_BAD_REQUEST)
# All Pages
if page_view == "all":
return Response(PageSerializer(queryset, many=True).data, status=status.HTTP_200_OK)
# Recent pages
if page_view == "recent":
current_time = date.today()
day_before = current_time - timedelta(days=1)
todays_pages = queryset.filter(updated_at__date=date.today())
yesterdays_pages = queryset.filter(updated_at__date=day_before)
earlier_this_week = queryset.filter( updated_at__date__range=(
(timezone.now() - timedelta(days=7)),
(timezone.now() - timedelta(days=2)),
))
return Response(
{
"today": PageSerializer(todays_pages, many=True).data,
"yesterday": PageSerializer(yesterdays_pages, many=True).data,
"earlier_this_week": PageSerializer(earlier_this_week, many=True).data,
},
status=status.HTTP_200_OK,
)
# Favorite Pages
if page_view == "favorite":
queryset = queryset.filter(is_favorite=True)
return Response(PageSerializer(queryset, many=True).data, status=status.HTTP_200_OK)
# My pages
if page_view == "created_by_me":
queryset = queryset.filter(owned_by=request.user)
return Response(PageSerializer(queryset, many=True).data, status=status.HTTP_200_OK)
# Created by other Pages
if page_view == "created_by_other":
queryset = queryset.filter(~Q(owned_by=request.user), access=0)
return Response(PageSerializer(queryset, many=True).data, status=status.HTTP_200_OK)
return Response({"error": "No matching view found"}, status=status.HTTP_400_BAD_REQUEST)
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
model = PageBlock
permission_classes = [
ProjectEntityPermission,
]
def get_queryset(self):
return self.filter_queryset(
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(page_id=self.kwargs.get("page_id"))
.filter(project__project_projectmember__member=self.request.user)
.select_related("project")
.select_related("workspace")
.select_related("page")
.select_related("issue")
.order_by("sort_order")
.distinct()
)
def perform_create(self, serializer):
serializer.save(
project_id=self.kwargs.get("project_id"),
page_id=self.kwargs.get("page_id"),
)
class PageFavoriteViewSet(BaseViewSet):
permission_classes = [
ProjectEntityPermission,
]
serializer_class = PageFavoriteSerializer
model = PageFavorite
def get_queryset(self):
return self.filter_queryset(
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(user=self.request.user)
.select_related("page", "page__owned_by")
)
def create(self, request, slug, project_id):
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,
)
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,
)
class CreateIssueFromPageBlockEndpoint(BaseAPIView):
permission_classes = [
ProjectEntityPermission,
]
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
)
_ = 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",
)
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,
)
+98 -4
View File
@@ -7,10 +7,20 @@ 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
from plane.db.models import (
User,
Workspace,
WorkspaceMemberInvite,
Issue,
IssueActivity,
WorkspaceMember,
)
from plane.utils.paginator import BasePaginator
class UserEndpoint(BaseViewSet):
serializer_class = UserSerializer
@@ -21,13 +31,61 @@ class UserEndpoint(BaseViewSet):
def retrieve(self, request):
try:
workspace = Workspace.objects.get(pk=request.user.last_workspace_id)
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.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(
{"user": UserSerializer(request.user).data, "slug": workspace.slug}
serialized_data,
status=status.HTTP_200_OK,
)
except Workspace.DoesNotExist:
return Response({"user": UserSerializer(request.user).data, "slug": None})
# 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.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,
@@ -40,6 +98,20 @@ class UpdateUserOnBoardedEndpoint(BaseAPIView):
user = User.objects.get(pk=request.user.id)
user.is_onboarded = request.data.get("is_onboarded", False)
user.save()
if user.last_workspace_id is not None:
user_role = WorkspaceMember.objects.filter(
workspace_id=user.last_workspace_id, member=request.user.id
).first()
return Response(
{
"message": "Updated successfully",
"role": user_role.company_role
if user_role is not None
else None,
},
status=status.HTTP_200_OK,
)
return Response(
{"message": "Updated successfully"}, status=status.HTTP_200_OK
)
@@ -49,3 +121,25 @@ class UpdateUserOnBoardedEndpoint(BaseAPIView):
{"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,
)
+249 -30
View File
@@ -5,7 +5,7 @@ from datetime import datetime
# Django imports
from django.core.exceptions import ValidationError
from django.db import IntegrityError
from django.db.models import Q
from django.db.models import Q, Exists, OuterRef, Func, F
from django.core.validators import validate_email
from django.conf import settings
@@ -22,6 +22,7 @@ from plane.api.serializers import (
ProjectMemberSerializer,
ProjectDetailSerializer,
ProjectMemberInviteSerializer,
ProjectFavoriteSerializer,
)
from plane.api.permissions import ProjectBasePermission
@@ -35,16 +36,20 @@ from plane.db.models import (
WorkspaceMember,
State,
TeamMember,
ProjectFavorite,
ProjectIdentifier,
Module,
Cycle,
CycleFavorite,
ModuleFavorite,
PageFavorite,
IssueViewFavorite,
Page,
IssueAssignee,
ModuleMember,
)
from plane.db.models import (
Project,
ProjectMember,
Workspace,
ProjectMemberInvite,
User,
ProjectIdentifier,
)
from plane.bgtasks.project_invitation_task import project_invitation
@@ -62,6 +67,11 @@ class ProjectViewSet(BaseViewSet):
return ProjectDetailSerializer
def get_queryset(self):
subquery = ProjectFavorite.objects.filter(
user=self.request.user,
project_id=OuterRef("pk"),
workspace__slug=self.kwargs.get("slug"),
)
return self.filter_queryset(
super()
.get_queryset()
@@ -70,12 +80,52 @@ class ProjectViewSet(BaseViewSet):
.select_related(
"workspace", "workspace__owner", "default_assignee", "project_lead"
)
.annotate(is_favorite=Exists(subquery))
.distinct()
)
def list(self, request, slug):
try:
subquery = ProjectFavorite.objects.filter(
user=self.request.user,
project_id=OuterRef("pk"),
workspace__slug=self.kwargs.get("slug"),
)
projects = (
self.get_queryset()
.annotate(is_favorite=Exists(subquery))
.order_by("-is_favorite", "name")
.annotate(
total_members=ProjectMember.objects.filter(
project_id=OuterRef("id")
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
total_cycles=Cycle.objects.filter(project_id=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
total_modules=Module.objects.filter(project_id=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
)
return Response(ProjectDetailSerializer(projects, 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,
)
def create(self, request, slug):
try:
workspace = Workspace.objects.get(slug=slug)
serializer = ProjectSerializer(
@@ -84,18 +134,19 @@ class ProjectViewSet(BaseViewSet):
if serializer.is_valid():
serializer.save()
## Add the user as Administrator to the project
# Add the user as Administrator to the project
ProjectMember.objects.create(
project_id=serializer.data["id"], member=request.user, role=20
)
## Default states
# Default states
states = [
{
"name": "Backlog",
"color": "#5e6ad2",
"sequence": 15000,
"group": "backlog",
"default": True,
},
{
"name": "Todo",
@@ -132,6 +183,8 @@ class ProjectViewSet(BaseViewSet):
sequence=state["sequence"],
workspace=serializer.instance.workspace,
group=state["group"],
default=state.get("default", False),
created_by=request.user,
)
for state in states
]
@@ -148,6 +201,12 @@ class ProjectViewSet(BaseViewSet):
{"name": "The project name is already taken"},
status=status.HTTP_410_GONE,
)
else:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_410_GONE,
)
except Workspace.DoesNotExist as e:
return Response(
{"error": "Workspace does not exist"}, status=status.HTTP_404_NOT_FOUND
@@ -188,7 +247,7 @@ class ProjectViewSet(BaseViewSet):
{"name": "The project name is already taken"},
status=status.HTTP_410_GONE,
)
except (Project.DoesNotExist or Workspace.DoesNotExist) as e:
except Project.DoesNotExist or Workspace.DoesNotExist as e:
return Response(
{"error": "Project does not exist"}, status=status.HTTP_404_NOT_FOUND
)
@@ -206,14 +265,12 @@ class ProjectViewSet(BaseViewSet):
class InviteProjectEndpoint(BaseAPIView):
permission_classes = [
ProjectBasePermission,
]
def post(self, request, slug, project_id):
try:
email = request.data.get("email", False)
role = request.data.get("role", False)
@@ -287,7 +344,6 @@ class InviteProjectEndpoint(BaseAPIView):
class UserProjectInvitationsViewset(BaseViewSet):
serializer_class = ProjectMemberInviteSerializer
model = ProjectMemberInvite
@@ -301,7 +357,6 @@ class UserProjectInvitationsViewset(BaseViewSet):
def create(self, request):
try:
invitations = request.data.get("invitations")
project_invitations = ProjectMemberInvite.objects.filter(
pk__in=invitations, accepted=True
@@ -313,12 +368,13 @@ class UserProjectInvitationsViewset(BaseViewSet):
workspace=invitation.project.workspace,
member=request.user,
role=invitation.role,
created_by=request.user,
)
for invitation in project_invitations
]
)
## Delete joined project invites
# Delete joined project invites
project_invitations.delete()
return Response(status=status.HTTP_200_OK)
@@ -331,7 +387,6 @@ class UserProjectInvitationsViewset(BaseViewSet):
class ProjectMemberViewSet(BaseViewSet):
serializer_class = ProjectMemberSerializer
model = ProjectMember
permission_classes = [
@@ -349,21 +404,126 @@ class ProjectMemberViewSet(BaseViewSet):
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(member__is_bot=False)
.select_related("project")
.select_related("member")
.select_related("workspace", "workspace__owner")
)
def partial_update(self, request, slug, project_id, pk):
try:
project_member = ProjectMember.objects.get(
pk=pk, workspace__slug=slug, project_id=project_id
)
if request.user.id == project_member.member_id:
return Response(
{"error": "You cannot update your own role"},
status=status.HTTP_400_BAD_REQUEST,
)
# Check while updating user roles
requested_project_member = ProjectMember.objects.get(
project_id=project_id, workspace__slug=slug, member=request.user
)
if (
"role" in request.data
and int(request.data.get("role", project_member.role))
> requested_project_member.role
):
return Response(
{
"error": "You cannot update a role that is higher than your own role"
},
status=status.HTTP_400_BAD_REQUEST,
)
serializer = ProjectMemberSerializer(
project_member, 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 ProjectMember.DoesNotExist:
return Response(
{"error": "Project Member 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,
)
def destroy(self, request, slug, project_id, pk):
try:
project_member = ProjectMember.objects.get(
workspace__slug=slug, project_id=project_id, pk=pk
)
# check requesting user role
requesting_project_member = ProjectMember.objects.get(
workspace__slug=slug, member=request.user, project_id=project_id
)
if requesting_project_member.role < project_member.role:
return Response(
{"error": "You cannot remove a user having role higher than yourself"},
status=status.HTTP_400_BAD_REQUEST,
)
# Remove all favorites
ProjectFavorite.objects.filter(
workspace__slug=slug, project_id=project_id, user=project_member.member
).delete()
CycleFavorite.objects.filter(
workspace__slug=slug, project_id=project_id, user=project_member.member
).delete()
ModuleFavorite.objects.filter(
workspace__slug=slug, project_id=project_id, user=project_member.member
).delete()
PageFavorite.objects.filter(
workspace__slug=slug, project_id=project_id, user=project_member.member
).delete()
IssueViewFavorite.objects.filter(
workspace__slug=slug, project_id=project_id, user=project_member.member
).delete()
# Also remove issue from issue assigned
IssueAssignee.objects.filter(
workspace__slug=slug,
project_id=project_id,
assignee=project_member.member,
).delete()
# Remove if module member
ModuleMember.objects.filter(
workspace__slug=slug,
project_id=project_id,
member=project_member.member,
).delete()
# Delete owned Pages
Page.objects.filter(
workspace__slug=slug,
project_id=project_id,
owned_by=project_member.member,
).delete()
project_member.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except ProjectMember.DoesNotExist:
return Response(
{"error": "Project Member does not exist"}, status=status.HTTP_400
)
except Exception as e:
capture_exception(e)
return Response({"error": "Something went wrong please try again later"})
class AddMemberToProjectEndpoint(BaseAPIView):
permission_classes = [
ProjectBasePermission,
]
def post(self, request, slug, project_id):
try:
member_id = request.data.get("member_id", False)
role = request.data.get("role", False)
@@ -412,13 +572,11 @@ class AddMemberToProjectEndpoint(BaseAPIView):
class AddTeamToProjectEndpoint(BaseAPIView):
permission_classes = [
ProjectBasePermission,
]
def post(self, request, slug, project_id):
try:
team_members = TeamMember.objects.filter(
workspace__slug=slug, team__in=request.data.get("teams", [])
@@ -438,6 +596,7 @@ class AddTeamToProjectEndpoint(BaseAPIView):
project_id=project_id,
member_id=member,
workspace=workspace,
created_by=request.user,
)
)
@@ -467,7 +626,6 @@ class AddTeamToProjectEndpoint(BaseAPIView):
class ProjectMemberInvitationsViewset(BaseViewSet):
serializer_class = ProjectMemberInviteSerializer
model = ProjectMemberInvite
@@ -489,7 +647,6 @@ class ProjectMemberInvitationsViewset(BaseViewSet):
class ProjectMemberInviteDetailViewSet(BaseViewSet):
serializer_class = ProjectMemberInviteSerializer
model = ProjectMemberInvite
@@ -509,14 +666,12 @@ class ProjectMemberInviteDetailViewSet(BaseViewSet):
class ProjectIdentifierEndpoint(BaseAPIView):
permission_classes = [
ProjectBasePermission,
]
def get(self, request, slug):
try:
name = request.GET.get("name", "").strip().upper()
if name == "":
@@ -541,7 +696,6 @@ class ProjectIdentifierEndpoint(BaseAPIView):
def delete(self, request, slug):
try:
name = request.data.get("name", "").strip().upper()
if name == "":
@@ -590,6 +744,7 @@ class ProjectJoinEndpoint(BaseAPIView):
if workspace_role >= 15
else (15 if workspace_role == 10 else workspace_role),
workspace=workspace,
created_by=request.user,
)
for project_id in project_ids
],
@@ -616,7 +771,6 @@ class ProjectJoinEndpoint(BaseAPIView):
class ProjectUserViewsEndpoint(BaseAPIView):
def post(self, request, slug, project_id):
try:
project = Project.objects.get(pk=project_id, workspace__slug=slug)
project_member = ProjectMember.objects.filter(
@@ -655,7 +809,6 @@ class ProjectUserViewsEndpoint(BaseAPIView):
class ProjectMemberUserEndpoint(BaseAPIView):
def get(self, request, slug, project_id):
try:
project_member = ProjectMember.objects.get(
project_id=project_id, workspace__slug=slug, member=request.user
)
@@ -674,3 +827,69 @@ class ProjectMemberUserEndpoint(BaseAPIView):
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
class ProjectFavoritesViewSet(BaseViewSet):
serializer_class = ProjectFavoriteSerializer
model = ProjectFavorite
def get_queryset(self):
return self.filter_queryset(
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(user=self.request.user)
.select_related(
"project", "project__project_lead", "project__default_assignee"
)
.select_related("workspace", "workspace__owner")
)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
def create(self, request, slug):
try:
serializer = ProjectFavoriteSerializer(data=request.data)
if serializer.is_valid():
serializer.save(user=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except IntegrityError as e:
print(str(e))
if "already exists" in str(e):
return Response(
{"error": "The project 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_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 destroy(self, request, slug, project_id):
try:
project_favorite = ProjectFavorite.objects.get(
project=project_id, user=request.user, workspace__slug=slug
)
project_favorite.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except ProjectFavorite.DoesNotExist:
return Response(
{"error": "Project 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,
)
+21
View File
@@ -0,0 +1,21 @@
# 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,
)
+259
View File
@@ -0,0 +1,259 @@
# Python imports
import re
# Django imports
from django.db.models import Q
# Third party imports
from rest_framework import status
from rest_framework.response import Response
from sentry_sdk import capture_exception
# Module imports
from .base import BaseAPIView
from plane.db.models import Workspace, Project, Issue, Cycle, Module, Page, IssueView
from plane.utils.issue_search import search_issues
class GlobalSearchEndpoint(BaseAPIView):
"""Endpoint to search across multiple fields in the workspace and
also show related workspace if found
"""
def filter_workspaces(self, query, slug, project_id):
fields = ["name"]
q = Q()
for field in fields:
q |= Q(**{f"{field}__icontains": query})
return (
Workspace.objects.filter(q, workspace_member__member=self.request.user)
.distinct()
.values("name", "id", "slug")
)
def filter_projects(self, query, slug, project_id):
fields = ["name"]
q = Q()
for field in fields:
q |= Q(**{f"{field}__icontains": query})
return (
Project.objects.filter(
q,
Q(project_projectmember__member=self.request.user) | Q(network=2),
workspace__slug=slug,
)
.distinct()
.values("name", "id", "identifier", "workspace__slug")
)
def filter_issues(self, query, slug, project_id):
fields = ["name", "sequence_id"]
q = Q()
for field in fields:
if field == "sequence_id":
sequences = re.findall(r"\d+\.\d+|\d+", query)
for sequence_id in sequences:
q |= Q(**{"sequence_id": sequence_id})
else:
q |= Q(**{f"{field}__icontains": query})
return (
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",
)
)
def filter_cycles(self, query, slug, project_id):
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",
)
)
def filter_modules(self, query, slug, project_id):
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",
)
)
def filter_pages(self, query, slug, project_id):
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",
)
)
def filter_views(self, query, slug, project_id):
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",
)
)
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,
)
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)
return Response({"results": results}, 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 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)
issue_id = request.query_params.get("issue_id", False)
issues = Issue.objects.filter(
workspace__slug=slug,
project_id=project_id,
project__project_projectmember__member=self.request.user,
)
if query:
issues = search_issues(query, issues)
if parent == "true" and issue_id:
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.objects.filter(parent__isnull=False).values_list(
"parent_id", flat=True
)
)
if blocker_blocked_by == "true" and issue_id:
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),
)
return Response(
issues.values(
"name",
"id",
"sequence_id",
"project__identifier",
"project_id",
"workspace__slug",
),
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:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
+79 -3
View File
@@ -1,12 +1,23 @@
# Python imports
from itertools import groupby
# Django imports
from django.db import IntegrityError
# Third party imports
from rest_framework.response import Response
from rest_framework import status
from sentry_sdk import capture_exception
# Module imports
from . import BaseViewSet
from . import BaseViewSet, BaseAPIView
from plane.api.serializers import StateSerializer
from plane.api.permissions import ProjectEntityPermission
from plane.db.models import State
from plane.db.models import State, Issue
class StateViewSet(BaseViewSet):
serializer_class = StateSerializer
model = State
permission_classes = [
@@ -27,3 +38,68 @@ class StateViewSet(BaseViewSet):
.select_related("workspace")
.distinct()
)
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,
)
def list(self, request, slug, project_id):
try:
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)
return Response(state_dict, 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 destroy(self, request, slug, project_id, pk):
try:
state = State.objects.get(
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.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)
+133 -7
View File
@@ -1,14 +1,31 @@
# Django imports
from django.db import IntegrityError
from django.db.models import Prefetch, OuterRef, Exists
# Third party imports
from rest_framework.response import Response
from rest_framework import status
from sentry_sdk import capture_exception
# Module imports
from . import BaseViewSet
from plane.api.serializers import ViewSerializer
from . import BaseViewSet, BaseAPIView
from plane.api.serializers import (
IssueViewSerializer,
IssueLiteSerializer,
IssueViewFavoriteSerializer,
)
from plane.api.permissions import ProjectEntityPermission
from plane.db.models import View
from plane.db.models import (
IssueView,
Issue,
IssueViewFavorite,
)
from plane.utils.issue_filters import issue_filters
class ViewViewSet(BaseViewSet):
serializer_class = ViewSerializer
model = View
class IssueViewViewSet(BaseViewSet):
serializer_class = IssueViewSerializer
model = IssueView
permission_classes = [
ProjectEntityPermission,
]
@@ -17,6 +34,12 @@ class ViewViewSet(BaseViewSet):
serializer.save(project_id=self.kwargs.get("project_id"))
def get_queryset(self):
subquery = IssueViewFavorite.objects.filter(
user=self.request.user,
view_id=OuterRef("pk"),
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
)
return self.filter_queryset(
super()
.get_queryset()
@@ -25,5 +48,108 @@ class ViewViewSet(BaseViewSet):
.filter(project__project_projectmember__member=self.request.user)
.select_related("project")
.select_related("workspace")
.annotate(is_favorite=Exists(subquery))
.order_by("-is_favorite", "name")
.distinct()
)
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.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
def get_queryset(self):
return self.filter_queryset(
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(user=self.request.user)
.select_related("view")
)
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,
)
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,
)
+335 -27
View File
@@ -1,6 +1,7 @@
# Python imports
import jwt
from datetime import datetime
from datetime import date, datetime
from dateutil.relativedelta import relativedelta
# Django imports
from django.db import IntegrityError
@@ -10,8 +11,16 @@ from django.utils import timezone
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.contrib.sites.shortcuts import get_current_site
from django.db.models import CharField, Count, OuterRef, Func, F
from django.db.models.functions import Cast
from django.db.models import (
CharField,
Count,
OuterRef,
Func,
F,
Q,
)
from django.db.models.functions import ExtractWeek, Cast, ExtractDay
from django.db.models.fields import DateField
# Third party modules
from rest_framework import status
@@ -27,6 +36,7 @@ from plane.api.serializers import (
WorkSpaceMemberInviteSerializer,
UserLiteSerializer,
ProjectMemberSerializer,
WorkspaceThemeSerializer,
)
from plane.api.views.base import BaseAPIView
from . import BaseViewSet
@@ -37,13 +47,23 @@ from plane.db.models import (
WorkspaceMemberInvite,
Team,
ProjectMember,
IssueActivity,
Issue,
WorkspaceTheme,
IssueAssignee,
ProjectFavorite,
CycleFavorite,
ModuleMember,
ModuleFavorite,
PageFavorite,
Page,
IssueViewFavorite,
)
from plane.api.permissions import WorkSpaceBasePermission, WorkSpaceAdminPermission
from plane.bgtasks.workspace_invitation_task import workspace_invitation
class WorkSpaceViewSet(BaseViewSet):
model = Workspace
serializer_class = WorkSpaceSerializer
permission_classes = [
@@ -60,7 +80,9 @@ class WorkSpaceViewSet(BaseViewSet):
lookup_field = "slug"
def get_queryset(self):
return self.filter_queryset(super().get_queryset().select_related("owner"))
return self.filter_queryset(
super().get_queryset().select_related("owner")
).order_by("name")
def create(self, request):
try:
@@ -101,7 +123,6 @@ class WorkSpaceViewSet(BaseViewSet):
class UserWorkSpacesEndpoint(BaseAPIView):
search_fields = [
"name",
]
@@ -111,7 +132,6 @@ class UserWorkSpacesEndpoint(BaseAPIView):
def get(self, request):
try:
member_count = (
WorkspaceMember.objects.filter(workspace=OuterRef("id"))
.order_by()
@@ -133,7 +153,6 @@ class UserWorkSpacesEndpoint(BaseAPIView):
return Response(serializer.data, status=status.HTTP_200_OK)
except Exception as e:
print(e)
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
@@ -163,14 +182,12 @@ class WorkSpaceAvailabilityCheckEndpoint(BaseAPIView):
class InviteWorkspaceEndpoint(BaseAPIView):
permission_classes = [
WorkSpaceAdminPermission,
]
def post(self, request, slug):
try:
emails = request.data.get("emails", False)
# Check if email is provided
if not emails or not len(emails):
@@ -214,6 +231,7 @@ class InviteWorkspaceEndpoint(BaseAPIView):
algorithm="HS256",
),
role=email.get("role", 10),
created_by=request.user,
)
)
except ValidationError:
@@ -267,7 +285,6 @@ class JoinWorkspaceEndpoint(BaseAPIView):
def post(self, request, slug, pk):
try:
workspace_invite = WorkspaceMemberInvite.objects.get(
pk=pk, workspace__slug=slug
)
@@ -286,7 +303,6 @@ class JoinWorkspaceEndpoint(BaseAPIView):
workspace_invite.save()
if workspace_invite.accepted:
# Check if the user created account after invitation
user = User.objects.filter(email=email).first()
@@ -325,7 +341,6 @@ class JoinWorkspaceEndpoint(BaseAPIView):
status=status.HTTP_404_NOT_FOUND,
)
except Exception as e:
print(e)
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
@@ -334,7 +349,6 @@ class JoinWorkspaceEndpoint(BaseAPIView):
class WorkspaceInvitationsViewset(BaseViewSet):
serializer_class = WorkSpaceMemberInviteSerializer
model = WorkspaceMemberInvite
@@ -347,12 +361,11 @@ class WorkspaceInvitationsViewset(BaseViewSet):
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.select_related("workspace", "workspace__owner")
.select_related("workspace", "workspace__owner", "created_by")
)
class UserWorkspaceInvitationsEndpoint(BaseViewSet):
serializer_class = WorkSpaceMemberInviteSerializer
model = WorkspaceMemberInvite
@@ -361,12 +374,12 @@ class UserWorkspaceInvitationsEndpoint(BaseViewSet):
super()
.get_queryset()
.filter(email=self.request.user.email)
.select_related("workspace", "workspace__owner")
.select_related("workspace", "workspace__owner", "created_by")
.annotate(total_members=Count("workspace__workspace_member"))
)
def create(self, request):
try:
invitations = request.data.get("invitations")
workspace_invitations = WorkspaceMemberInvite.objects.filter(
pk__in=invitations
@@ -378,6 +391,7 @@ class UserWorkspaceInvitationsEndpoint(BaseViewSet):
workspace=invitation.workspace,
member=request.user,
role=invitation.role,
created_by=request.user,
)
for invitation in workspace_invitations
],
@@ -397,7 +411,6 @@ class UserWorkspaceInvitationsEndpoint(BaseViewSet):
class WorkSpaceMemberViewSet(BaseViewSet):
serializer_class = WorkSpaceMemberSerializer
model = WorkspaceMember
@@ -414,14 +427,123 @@ class WorkSpaceMemberViewSet(BaseViewSet):
return self.filter_queryset(
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(workspace__slug=self.kwargs.get("slug"), member__is_bot=False)
.select_related("workspace", "workspace__owner")
.select_related("member")
)
def partial_update(self, request, slug, pk):
try:
workspace_member = WorkspaceMember.objects.get(pk=pk, workspace__slug=slug)
if request.user.id == workspace_member.member_id:
return Response(
{"error": "You cannot update your own role"},
status=status.HTTP_400_BAD_REQUEST,
)
# Get the requested user role
requested_workspace_member = WorkspaceMember.objects.get(
workspace__slug=slug, member=request.user
)
# Check if role is being updated
# One cannot update role higher than his own role
if (
"role" in request.data
and int(request.data.get("role", workspace_member.role))
> requested_workspace_member.role
):
return Response(
{
"error": "You cannot update a role that is higher than your own role"
},
status=status.HTTP_400_BAD_REQUEST,
)
serializer = WorkSpaceMemberSerializer(
workspace_member, 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 WorkspaceMember.DoesNotExist:
return Response(
{"error": "Workspace Member 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,
)
def destroy(self, request, slug, pk):
try:
# Check the user role who is deleting the user
workspace_member = WorkspaceMember.objects.get(workspace__slug=slug, pk=pk)
# check requesting user role
requesting_workspace_member = WorkspaceMember.objects.get(
workspace__slug=slug, member=request.user
)
if requesting_workspace_member.role < workspace_member.role:
return Response(
{"error": "You cannot remove a user having role higher than you"},
status=status.HTTP_400_BAD_REQUEST,
)
# Delete the user also from all the projects
ProjectMember.objects.filter(
workspace__slug=slug, member=workspace_member.member
).delete()
# Remove all favorites
ProjectFavorite.objects.filter(
workspace__slug=slug, user=workspace_member.member
).delete()
CycleFavorite.objects.filter(
workspace__slug=slug, user=workspace_member.member
).delete()
ModuleFavorite.objects.filter(
workspace__slug=slug, user=workspace_member.member
).delete()
PageFavorite.objects.filter(
workspace__slug=slug, user=workspace_member.member
).delete()
IssueViewFavorite.objects.filter(
workspace__slug=slug, user=workspace_member.member
).delete()
# Also remove issue from issue assigned
IssueAssignee.objects.filter(
workspace__slug=slug, assignee=workspace_member.member
).delete()
# Remove if module member
ModuleMember.objects.filter(
workspace__slug=slug, member=workspace_member.member
).delete()
# Delete owned Pages
Page.objects.filter(
workspace__slug=slug, owned_by=workspace_member.member
).delete()
workspace_member.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except WorkspaceMember.DoesNotExist:
return Response(
{"error": "Workspace Member 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,
)
class TeamMemberViewSet(BaseViewSet):
serializer_class = TeamSerializer
model = Team
permission_classes = [
@@ -443,9 +565,7 @@ class TeamMemberViewSet(BaseViewSet):
)
def create(self, request, slug):
try:
members = list(
WorkspaceMember.objects.filter(
workspace__slug=slug, member__id__in=request.data.get("members", [])
@@ -456,7 +576,6 @@ class TeamMemberViewSet(BaseViewSet):
)
if len(members) != len(request.data.get("members", [])):
users = list(set(request.data.get("members", [])).difference(members))
users = User.objects.filter(pk__in=users)
@@ -493,7 +612,6 @@ class TeamMemberViewSet(BaseViewSet):
class UserWorkspaceInvitationEndpoint(BaseViewSet):
model = WorkspaceMemberInvite
serializer_class = WorkSpaceMemberInviteSerializer
@@ -513,7 +631,6 @@ class UserWorkspaceInvitationEndpoint(BaseViewSet):
class UserLastProjectWithWorkspaceEndpoint(BaseAPIView):
def get(self, request):
try:
user = User.objects.get(pk=request.user.id)
last_workspace_id = user.last_workspace_id
@@ -577,7 +694,6 @@ class WorkspaceMemberUserEndpoint(BaseAPIView):
class WorkspaceMemberUserViewsEndpoint(BaseAPIView):
def post(self, request, slug):
try:
workspace_member = WorkspaceMember.objects.get(
workspace__slug=slug, member=request.user
)
@@ -596,3 +712,195 @@ class WorkspaceMemberUserViewsEndpoint(BaseAPIView):
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
class UserActivityGraphEndpoint(BaseAPIView):
def get(self, request, slug):
try:
issue_activities = (
IssueActivity.objects.filter(
actor=request.user,
workspace__slug=slug,
created_at__date__gte=date.today() + relativedelta(months=-6),
)
.annotate(created_date=Cast("created_at", DateField()))
.values("created_date")
.annotate(activity_count=Count("created_date"))
.order_by("created_date")
)
return Response(issue_activities, 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 UserIssueCompletedGraphEndpoint(BaseAPIView):
def get(self, request, slug):
try:
month = request.GET.get("month", 1)
issues = (
Issue.objects.filter(
assignees__in=[request.user],
workspace__slug=slug,
completed_at__month=month,
completed_at__isnull=False,
)
.annotate(completed_week=ExtractWeek("completed_at"))
.annotate(week=F("completed_week") % 4)
.values("week")
.annotate(completed_count=Count("completed_week"))
.order_by("week")
)
return Response(issues, 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 WeekInMonth(Func):
function = "FLOOR"
template = "(((%(expressions)s - 1) / 7) + 1)::INTEGER"
class UserWorkspaceDashboardEndpoint(BaseAPIView):
def get(self, request, slug):
try:
issue_activities = (
IssueActivity.objects.filter(
actor=request.user,
workspace__slug=slug,
created_at__date__gte=date.today() + relativedelta(months=-3),
)
.annotate(created_date=Cast("created_at", DateField()))
.values("created_date")
.annotate(activity_count=Count("created_date"))
.order_by("created_date")
)
month = request.GET.get("month", 1)
completed_issues = (
Issue.objects.filter(
assignees__in=[request.user],
workspace__slug=slug,
completed_at__month=month,
completed_at__isnull=False,
)
.annotate(day_of_month=ExtractDay("completed_at"))
.annotate(week_in_month=WeekInMonth(F("day_of_month")))
.values("week_in_month")
.annotate(completed_count=Count("id"))
.order_by("week_in_month")
)
assigned_issues = Issue.objects.filter(
workspace__slug=slug, assignees__in=[request.user]
).count()
pending_issues_count = Issue.objects.filter(
~Q(state__group__in=["completed", "cancelled"]),
workspace__slug=slug,
assignees__in=[request.user],
).count()
completed_issues_count = Issue.objects.filter(
workspace__slug=slug,
assignees__in=[request.user],
state__group="completed",
).count()
issues_due_week = (
Issue.objects.filter(
workspace__slug=slug,
assignees__in=[request.user],
)
.annotate(target_week=ExtractWeek("target_date"))
.filter(target_week=timezone.now().date().isocalendar()[1])
.count()
)
state_distribution = (
Issue.objects.filter(workspace__slug=slug, assignees__in=[request.user])
.annotate(state_group=F("state__group"))
.values("state_group")
.annotate(state_count=Count("state_group"))
.order_by("state_group")
)
overdue_issues = Issue.objects.filter(
~Q(state__group__in=["completed", "cancelled"]),
workspace__slug=slug,
assignees__in=[request.user],
target_date__lt=timezone.now(),
completed_at__isnull=True,
).values("id", "name", "workspace__slug", "project_id", "target_date")
upcoming_issues = Issue.objects.filter(
~Q(state__group__in=["completed", "cancelled"]),
target_date__gte=timezone.now(),
workspace__slug=slug,
assignees__in=[request.user],
completed_at__isnull=True,
).values("id", "name", "workspace__slug", "project_id", "target_date")
return Response(
{
"issue_activities": issue_activities,
"completed_issues": completed_issues,
"assigned_issues_count": assigned_issues,
"pending_issues_count": pending_issues_count,
"completed_issues_count": completed_issues_count,
"issues_due_week_count": issues_due_week,
"state_distribution": state_distribution,
"overdue_issues": overdue_issues,
"upcoming_issues": upcoming_issues,
},
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 WorkspaceThemeViewSet(BaseViewSet):
permission_classes = [
WorkSpaceAdminPermission,
]
model = WorkspaceTheme
serializer_class = WorkspaceThemeSerializer
def get_queryset(self):
return super().get_queryset().filter(workspace__slug=self.kwargs.get("slug"))
def create(self, request, slug):
try:
workspace = Workspace.objects.get(slug=slug)
serializer = WorkspaceThemeSerializer(data=request.data)
if serializer.is_valid():
serializer.save(workspace=workspace, actor=request.user)
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,
)
@@ -0,0 +1,174 @@
# Python imports
import csv
import io
# Django imports
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils.html import strip_tags
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
from plane.utils.analytics_plot import build_graph_plot
from plane.utils.issue_filters import issue_filters
row_mapping = {
"state__name": "State",
"state__group": "State Group",
"labels__name": "Label",
"assignees__email": "Assignee Name",
"start_date": "Start Date",
"target_date": "Due Date",
"completed_at": "Completed At",
"created_at": "Created At",
"issue_count": "Issue Count",
"priority": "Priority",
"estimate": "Estimate",
}
@shared_task
def analytic_export_task(email, data, slug):
try:
filters = issue_filters(data, "POST")
queryset = Issue.objects.filter(**filters, workspace__slug=slug)
x_axis = data.get("x_axis", False)
y_axis = data.get("y_axis", False)
segment = data.get("segment", False)
distribution = build_graph_plot(
queryset=queryset, x_axis=x_axis, y_axis=y_axis, segment=segment
)
key = "count" if y_axis == "issue_count" else "estimate"
segmented = segment
assignee_details = {}
if x_axis in ["assignees__email"] or segment in ["assignees__email"]:
assignee_details = (
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")
)
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 = []
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]
)
msg.attach(f"{slug}-analytics.csv", csv_buffer.read())
msg.send(fail_silently=False)
except Exception as e:
print(e)
capture_exception(e)
return
@@ -2,23 +2,26 @@
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils.html import strip_tags
from django.conf import settings
# Third party imports
from django_rq import job
from celery import shared_task
from sentry_sdk import capture_exception
# Module imports
from plane.db.models import User
@job("default")
@shared_task
def email_verification(first_name, email, token, current_site):
try:
realtivelink = "/request-email-verification/" + "?token=" + str(token)
abs_url = "http://" + current_site + realtivelink
abs_url = current_site + realtivelink
from_email_string = f"Team Plane <team@mailer.plane.so>"
from_email_string = settings.EMAIL_FROM
subject = f"Verify your Email!"
@@ -2,25 +2,26 @@
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils.html import strip_tags
from django.conf import settings
# Third party imports
from django_rq import job
from celery import shared_task
from sentry_sdk import capture_exception
# Module imports
from plane.db.models import User
@job("default")
@shared_task
def forgot_password(first_name, email, uidb64, token, current_site):
try:
realtivelink = f"/email-verify/?uidb64={uidb64}&token={token}/"
abs_url = "http://" + current_site + realtivelink
realtivelink = f"/reset-password/?uidb64={uidb64}&token={token}"
abs_url = current_site + realtivelink
from_email_string = f"Team Plane <team@mailer.plane.so>"
from_email_string = settings.EMAIL_FROM
subject = f"Verify your Email!"
subject = f"Reset Your Password - Plane"
context = {
"first_name": first_name,
+179
View File
@@ -0,0 +1,179 @@
# Python imports
import json
import requests
import uuid
import jwt
from datetime import datetime
# Django imports
from django.conf import settings
from django.core.serializers.json import DjangoJSONEncoder
from django.contrib.auth.hashers import make_password
# Third Party imports
from celery import shared_task
from sentry_sdk import capture_exception
# Module imports
from plane.api.serializers import ImporterSerializer
from plane.db.models import (
Importer,
WorkspaceMember,
GithubRepositorySync,
GithubRepository,
ProjectMember,
WorkspaceIntegration,
Label,
User,
)
from .workspace_invitation_task import workspace_invitation
from plane.bgtasks.user_welcome_task import send_welcome_slack
@shared_task
def service_importer(service, importer_id):
try:
importer = Importer.objects.get(pk=importer_id)
importer.status = "processing"
importer.save()
users = importer.data.get("users", [])
# Check if we need to import users as well
if len(users):
# For all invited users create the users
new_users = User.objects.bulk_create(
[
User(
email=user.get("email").strip().lower(),
username=uuid.uuid4().hex,
password=make_password(uuid.uuid4().hex),
is_password_autoset=True,
)
for user in users
if user.get("import", False) == "invite"
],
batch_size=10,
ignore_conflicts=True,
)
[
send_welcome_slack.delay(
str(user.id),
True,
f"{user.email} was imported to Plane from {service}",
)
for user in new_users
]
workspace_users = User.objects.filter(
email__in=[
user.get("email").strip().lower()
for user in users
if user.get("import", False) == "invite"
or user.get("import", False) == "map"
]
)
# Add new users to Workspace and project automatically
WorkspaceMember.objects.bulk_create(
[
WorkspaceMember(
member=user,
workspace_id=importer.workspace_id,
created_by=importer.created_by,
)
for user in workspace_users
],
batch_size=100,
ignore_conflicts=True,
)
ProjectMember.objects.bulk_create(
[
ProjectMember(
project_id=importer.project_id,
workspace_id=importer.workspace_id,
member=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)
url = importer.metadata.get("url", False)
config = importer.metadata.get("config", {})
owner = importer.metadata.get("owner", False)
repository_id = importer.metadata.get("repository_id", False)
workspace_integration = WorkspaceIntegration.objects.get(
workspace_id=importer.workspace_id, integration__provider="github"
)
# Delete the old repository object
GithubRepositorySync.objects.filter(project_id=importer.project_id).delete()
GithubRepository.objects.filter(project_id=importer.project_id).delete()
# Create a Label for github
label = Label.objects.filter(
name="GitHub", project_id=importer.project_id
).first()
if label is None:
label = Label.objects.create(
name="GitHub",
project_id=importer.project_id,
description="Label to sync Plane issues with GitHub issues",
color="#003773",
)
# Create repository
repo = GithubRepository.objects.create(
name=name,
url=url,
config=config,
repository_id=repository_id,
owner=owner,
project_id=importer.project_id,
)
# Create repo sync
repo_sync = GithubRepositorySync.objects.create(
repository=repo,
workspace_integration=workspace_integration,
actor=workspace_integration.actor,
credentials=importer.data.get("credentials", {}),
project_id=importer.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=importer.project_id,
)
if settings.PROXY_BASE_URL:
headers = {"Content-Type": "application/json"}
import_data_json = json.dumps(
ImporterSerializer(importer).data,
cls=DjangoJSONEncoder,
)
res = requests.post(
f"{settings.PROXY_BASE_URL}/hooks/workspaces/{str(importer.workspace_id)}/projects/{str(importer.project_id)}/importers/{str(service)}/",
json=import_data_json,
headers=headers,
)
return
except Exception as e:
importer = Importer.objects.get(pk=importer_id)
importer.status = "failed"
importer.save()
capture_exception(e)
return
+505 -48
View File
@@ -1,12 +1,27 @@
# Python imports
import json
import requests
# Django imports
from django.conf import settings
from django.core.serializers.json import DjangoJSONEncoder
# Third Party imports
from django_rq import job
from celery import shared_task
from sentry_sdk import capture_exception
# Module imports
from plane.db.models import User, Issue, Project, Label, IssueActivity, State
from plane.db.models import (
User,
Issue,
Project,
Label,
IssueActivity,
State,
Cycle,
Module,
)
from plane.api.serializers import IssueActivitySerializer
# Track Chnages in name
@@ -44,7 +59,6 @@ def track_parent(
issue_activities,
):
if current_instance.get("parent") != requested_data.get("parent"):
if requested_data.get("parent") == None:
old_parent = Issue.objects.get(pk=current_instance.get("parent"))
issue_activities.append(
@@ -134,7 +148,6 @@ def track_state(
issue_activities,
):
if current_instance.get("state") != requested_data.get("state"):
new_state = State.objects.get(pk=requested_data.get("state", None))
old_state = State.objects.get(pk=current_instance.get("state", None))
@@ -167,7 +180,6 @@ def track_description(
if current_instance.get("description_html") != requested_data.get(
"description_html"
):
issue_activities.append(
IssueActivity(
issue_id=issue_id,
@@ -274,7 +286,6 @@ def track_labels(
):
# Label Addition
if len(requested_data.get("labels_list")) > len(current_instance.get("labels")):
for label in requested_data.get("labels_list"):
if label not in current_instance.get("labels"):
label = Label.objects.get(pk=label)
@@ -296,7 +307,6 @@ def track_labels(
# Label Removal
if len(requested_data.get("labels_list")) < len(current_instance.get("labels")):
for label in current_instance.get("labels"):
if label not in requested_data.get("labels_list"):
label = Label.objects.get(pk=label)
@@ -326,12 +336,10 @@ def track_assignees(
actor,
issue_activities,
):
# Assignee Addition
if len(requested_data.get("assignees_list")) > len(
current_instance.get("assignees")
):
for assignee in requested_data.get("assignees_list"):
if assignee not in current_instance.get("assignees"):
assignee = User.objects.get(pk=assignee)
@@ -354,7 +362,6 @@ def track_assignees(
if len(requested_data.get("assignees_list")) < len(
current_instance.get("assignees")
):
for assignee in current_instance.get("assignees"):
if assignee not in requested_data.get("assignees_list"):
assignee = User.objects.get(pk=assignee)
@@ -386,7 +393,6 @@ def track_blocks(
if len(requested_data.get("blocks_list")) > len(
current_instance.get("blocked_issues")
):
for block in requested_data.get("blocks_list"):
if (
len(
@@ -418,7 +424,6 @@ def track_blocks(
if len(requested_data.get("blocks_list")) < len(
current_instance.get("blocked_issues")
):
for blocked in current_instance.get("blocked_issues"):
if blocked.get("block") not in requested_data.get("blocks_list"):
issue = Issue.objects.get(pk=blocked.get("block"))
@@ -450,7 +455,6 @@ def track_blockings(
if len(requested_data.get("blockers_list")) > len(
current_instance.get("blocker_issues")
):
for block in requested_data.get("blockers_list"):
if (
len(
@@ -482,7 +486,6 @@ def track_blockings(
if len(requested_data.get("blockers_list")) < len(
current_instance.get("blocker_issues")
):
for blocked in current_instance.get("blocker_issues"):
if blocked.get("blocked_by") not in requested_data.get("blockers_list"):
issue = Issue.objects.get(pk=blocked.get("blocked_by"))
@@ -502,51 +505,505 @@ def track_blockings(
)
def create_issue_activity(
requested_data, current_instance, issue_id, project, actor, issue_activities
):
issue_activities.append(
IssueActivity(
issue_id=issue_id,
project=project,
workspace=project.workspace,
comment=f"{actor.email} created the issue",
verb="created",
actor=actor,
)
)
def track_estimate_points(
requested_data, current_instance, issue_id, project, actor, issue_activities
):
if current_instance.get("estimate_point") != requested_data.get("estimate_point"):
if requested_data.get("estimate_point") == None:
issue_activities.append(
IssueActivity(
issue_id=issue_id,
actor=actor,
verb="updated",
old_value=current_instance.get("estimate_point"),
new_value=requested_data.get("estimate_point"),
field="estimate_point",
project=project,
workspace=project.workspace,
comment=f"{actor.email} updated the estimate point to None",
)
)
else:
issue_activities.append(
IssueActivity(
issue_id=issue_id,
actor=actor,
verb="updated",
old_value=current_instance.get("estimate_point"),
new_value=requested_data.get("estimate_point"),
field="estimate_point",
project=project,
workspace=project.workspace,
comment=f"{actor.email} updated the estimate point to {requested_data.get('estimate_point')}",
)
)
def update_issue_activity(
requested_data, current_instance, issue_id, project, actor, issue_activities
):
ISSUE_ACTIVITY_MAPPER = {
"name": track_name,
"parent": track_parent,
"priority": track_priority,
"state": track_state,
"description": track_description,
"target_date": track_target_date,
"start_date": track_start_date,
"labels_list": track_labels,
"assignees_list": track_assignees,
"blocks_list": track_blocks,
"blockers_list": track_blockings,
"estimate_point": track_estimate_points,
}
requested_data = json.loads(requested_data) if requested_data is not None else None
current_instance = (
json.loads(current_instance) if current_instance is not None else None
)
for key in requested_data:
func = ISSUE_ACTIVITY_MAPPER.get(key, None)
if func is not None:
func(
requested_data,
current_instance,
issue_id,
project,
actor,
issue_activities,
)
def delete_issue_activity(
requested_data, current_instance, issue_id, project, actor, issue_activities
):
issue_activities.append(
IssueActivity(
project=project,
workspace=project.workspace,
comment=f"{actor.email} deleted the issue",
verb="deleted",
actor=actor,
field="issue",
)
)
def create_comment_activity(
requested_data, current_instance, issue_id, project, actor, issue_activities
):
requested_data = json.loads(requested_data) if requested_data is not None else None
current_instance = (
json.loads(current_instance) if current_instance is not None else None
)
issue_activities.append(
IssueActivity(
issue_id=issue_id,
project=project,
workspace=project.workspace,
comment=f"{actor.email} created a comment",
verb="created",
actor=actor,
field="comment",
new_value=requested_data.get("comment_html", ""),
new_identifier=requested_data.get("id", None),
issue_comment_id=requested_data.get("id", None),
)
)
def update_comment_activity(
requested_data, current_instance, issue_id, project, actor, issue_activities
):
requested_data = json.loads(requested_data) if requested_data is not None else None
current_instance = (
json.loads(current_instance) if current_instance is not None else None
)
if current_instance.get("comment_html") != requested_data.get("comment_html"):
issue_activities.append(
IssueActivity(
issue_id=issue_id,
project=project,
workspace=project.workspace,
comment=f"{actor.email} updated a comment",
verb="updated",
actor=actor,
field="comment",
old_value=current_instance.get("comment_html", ""),
old_identifier=current_instance.get("id"),
new_value=requested_data.get("comment_html", ""),
new_identifier=current_instance.get("id", None),
issue_comment_id=current_instance.get("id", None),
)
)
def delete_comment_activity(
requested_data, current_instance, issue_id, project, actor, issue_activities
):
issue_activities.append(
IssueActivity(
issue_id=issue_id,
project=project,
workspace=project.workspace,
comment=f"{actor.email} deleted the comment",
verb="deleted",
actor=actor,
field="comment",
)
)
def create_cycle_issue_activity(
requested_data, current_instance, issue_id, project, actor, issue_activities
):
requested_data = json.loads(requested_data) if requested_data is not None else None
current_instance = (
json.loads(current_instance) if current_instance is not None else None
)
# Updated Records:
updated_records = current_instance.get("updated_cycle_issues", [])
created_records = json.loads(current_instance.get("created_cycle_issues", []))
for updated_record in updated_records:
old_cycle = Cycle.objects.filter(
pk=updated_record.get("old_cycle_id", None)
).first()
new_cycle = Cycle.objects.filter(
pk=updated_record.get("new_cycle_id", None)
).first()
issue_activities.append(
IssueActivity(
issue_id=updated_record.get("issue_id"),
actor=actor,
verb="updated",
old_value=old_cycle.name,
new_value=new_cycle.name,
field="cycles",
project=project,
workspace=project.workspace,
comment=f"{actor.email} updated cycle from {old_cycle.name} to {new_cycle.name}",
old_identifier=old_cycle.id,
new_identifier=new_cycle.id,
)
)
for created_record in created_records:
cycle = Cycle.objects.filter(
pk=created_record.get("fields").get("cycle")
).first()
issue_activities.append(
IssueActivity(
issue_id=created_record.get("fields").get("issue"),
actor=actor,
verb="created",
old_value="",
new_value=cycle.name,
field="cycles",
project=project,
workspace=project.workspace,
comment=f"{actor.email} added cycle {cycle.name}",
new_identifier=cycle.id,
)
)
def delete_cycle_issue_activity(
requested_data, current_instance, issue_id, project, actor, issue_activities
):
requested_data = json.loads(requested_data) if requested_data is not None else None
current_instance = (
json.loads(current_instance) if current_instance is not None else None
)
cycle_id = requested_data.get("cycle_id", "")
cycle = Cycle.objects.filter(pk=cycle_id).first()
issues = requested_data.get("issues")
for issue in issues:
issue_activities.append(
IssueActivity(
issue_id=issue,
actor=actor,
verb="deleted",
old_value=cycle.name if cycle is not None else "",
new_value="",
field="cycles",
project=project,
workspace=project.workspace,
comment=f"{actor.email} removed this issue from {cycle.name if cycle is not None else None}",
old_identifier=cycle.id if cycle is not None else None,
)
)
def create_module_issue_activity(
requested_data, current_instance, issue_id, project, actor, issue_activities
):
requested_data = json.loads(requested_data) if requested_data is not None else None
current_instance = (
json.loads(current_instance) if current_instance is not None else None
)
# Updated Records:
updated_records = current_instance.get("updated_module_issues", [])
created_records = json.loads(current_instance.get("created_module_issues", []))
for updated_record in updated_records:
old_module = Module.objects.filter(
pk=updated_record.get("old_module_id", None)
).first()
new_module = Module.objects.filter(
pk=updated_record.get("new_module_id", None)
).first()
issue_activities.append(
IssueActivity(
issue_id=updated_record.get("issue_id"),
actor=actor,
verb="updated",
old_value=old_module.name,
new_value=new_module.name,
field="modules",
project=project,
workspace=project.workspace,
comment=f"{actor.email} updated module from {old_module.name} to {new_module.name}",
old_identifier=old_module.id,
new_identifier=new_module.id,
)
)
for created_record in created_records:
module = Module.objects.filter(
pk=created_record.get("fields").get("module")
).first()
issue_activities.append(
IssueActivity(
issue_id=created_record.get("fields").get("issue"),
actor=actor,
verb="created",
old_value="",
new_value=module.name,
field="modules",
project=project,
workspace=project.workspace,
comment=f"{actor.email} added module {module.name}",
new_identifier=module.id,
)
)
def delete_module_issue_activity(
requested_data, current_instance, issue_id, project, actor, issue_activities
):
requested_data = json.loads(requested_data) if requested_data is not None else None
current_instance = (
json.loads(current_instance) if current_instance is not None else None
)
module_id = requested_data.get("module_id", "")
module = Module.objects.filter(pk=module_id).first()
issues = requested_data.get("issues")
for issue in issues:
issue_activities.append(
IssueActivity(
issue_id=issue,
actor=actor,
verb="deleted",
old_value=module.name if module is not None else "",
new_value="",
field="modules",
project=project,
workspace=project.workspace,
comment=f"{actor.email} removed this issue from {module.name if module is not None else None}",
old_identifier=module.id if module is not None else None,
)
)
def create_link_activity(
requested_data, current_instance, issue_id, project, actor, issue_activities
):
requested_data = json.loads(requested_data) if requested_data is not None else None
current_instance = (
json.loads(current_instance) if current_instance is not None else None
)
issue_activities.append(
IssueActivity(
issue_id=issue_id,
project=project,
workspace=project.workspace,
comment=f"{actor.email} created a link",
verb="created",
actor=actor,
field="link",
new_value=requested_data.get("url", ""),
new_identifier=requested_data.get("id", None),
)
)
def update_link_activity(
requested_data, current_instance, issue_id, project, actor, issue_activities
):
requested_data = json.loads(requested_data) if requested_data is not None else None
current_instance = (
json.loads(current_instance) if current_instance is not None else None
)
if current_instance.get("url") != requested_data.get("url"):
issue_activities.append(
IssueActivity(
issue_id=issue_id,
project=project,
workspace=project.workspace,
comment=f"{actor.email} updated a link",
verb="updated",
actor=actor,
field="link",
old_value=current_instance.get("url", ""),
old_identifier=current_instance.get("id"),
new_value=requested_data.get("url", ""),
new_identifier=current_instance.get("id", None),
)
)
def delete_link_activity(
requested_data, current_instance, issue_id, project, actor, issue_activities
):
issue_activities.append(
IssueActivity(
issue_id=issue_id,
project=project,
workspace=project.workspace,
comment=f"{actor.email} deleted the link",
verb="deleted",
actor=actor,
field="link",
)
)
def create_attachment_activity(
requested_data, current_instance, issue_id, project, actor, issue_activities
):
requested_data = json.loads(requested_data) if requested_data is not None else None
current_instance = (
json.loads(current_instance) if current_instance is not None else None
)
issue_activities.append(
IssueActivity(
issue_id=issue_id,
project=project,
workspace=project.workspace,
comment=f"{actor.email} created an attachment",
verb="created",
actor=actor,
field="attachment",
new_value=current_instance.get("access", ""),
new_identifier=current_instance.get("id", None),
)
)
def delete_attachment_activity(
requested_data, current_instance, issue_id, project, actor, issue_activities
):
issue_activities.append(
IssueActivity(
issue_id=issue_id,
project=project,
workspace=project.workspace,
comment=f"{actor.email} deleted the attachment",
verb="deleted",
actor=actor,
field="attachment",
)
)
# Receive message from room group
@job("default")
def issue_activity(event):
@shared_task
def issue_activity(
type, requested_data, current_instance, issue_id, actor_id, project_id
):
try:
issue_activities = []
requested_data = json.loads(event.get("requested_data"))
current_instance = json.loads(event.get("current_instance"))
issue_id = event.get("issue_id")
actor_id = event.get("actor_id")
project_id = event.get("project_id")
actor = User.objects.get(pk=actor_id)
project = Project.objects.get(pk=project_id)
ISSUE_ACTIVITY_MAPPER = {
"name": track_name,
"parent": track_parent,
"priority": track_priority,
"state": track_state,
"description": track_description,
"target_date": track_target_date,
"start_date": track_start_date,
"labels_list": track_labels,
"assignees_list": track_assignees,
"blocks_list": track_blocks,
"blockers_list": track_blockings,
ACTIVITY_MAPPER = {
"issue.activity.created": create_issue_activity,
"issue.activity.updated": update_issue_activity,
"issue.activity.deleted": delete_issue_activity,
"comment.activity.created": create_comment_activity,
"comment.activity.updated": update_comment_activity,
"comment.activity.deleted": delete_comment_activity,
"cycle.activity.created": create_cycle_issue_activity,
"cycle.activity.deleted": delete_cycle_issue_activity,
"module.activity.created": create_module_issue_activity,
"module.activity.deleted": delete_module_issue_activity,
"link.activity.created": create_link_activity,
"link.activity.updated": update_link_activity,
"link.activity.deleted": delete_link_activity,
"attachment.activity.created": create_attachment_activity,
"attachment.activity.deleted": delete_attachment_activity,
}
for key in requested_data:
func = ISSUE_ACTIVITY_MAPPER.get(key, None)
if func is not None:
func(
requested_data,
current_instance,
issue_id,
project,
actor,
issue_activities,
)
func = ACTIVITY_MAPPER.get(type)
if func is not None:
func(
requested_data,
current_instance,
issue_id,
project,
actor,
issue_activities,
)
# Save all the values to database
_ = IssueActivity.objects.bulk_create(issue_activities)
issue_activities_created = IssueActivity.objects.bulk_create(issue_activities)
# Post the updates to segway for integrations and webhooks
if len(issue_activities_created):
# Don't send activities if the actor is a bot
if settings.PROXY_BASE_URL:
for issue_activity in issue_activities_created:
headers = {"Content-Type": "application/json"}
issue_activity_json = json.dumps(
IssueActivitySerializer(issue_activity).data,
cls=DjangoJSONEncoder,
)
_ = requests.post(
f"{settings.PROXY_BASE_URL}/hooks/workspaces/{str(issue_activity.workspace_id)}/projects/{str(issue_activity.project_id)}/issues/{str(issue_activity.issue_id)}/issue-activity-hooks/",
json=issue_activity_json,
headers=headers,
)
return
except Exception as e:
capture_exception(e)
@@ -2,20 +2,20 @@
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils.html import strip_tags
from django.conf import settings
# Third party imports
from django_rq import job
from celery import shared_task
from sentry_sdk import capture_exception
@job("default")
@shared_task
def magic_link(email, key, token, current_site):
try:
realtivelink = f"/magic-sign-in/?password={token}&key={key}"
abs_url = "http://" + current_site + realtivelink
abs_url = current_site + realtivelink
from_email_string = f"Team Plane <team@mailer.plane.so>"
from_email_string = settings.EMAIL_FROM
subject = f"Login for Plane"
@@ -30,6 +30,5 @@ def magic_link(email, key, token, current_site):
msg.send()
return
except Exception as e:
print(e)
capture_exception(e)
return
@@ -2,29 +2,28 @@
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils.html import strip_tags
from django.conf import settings
# Third party imports
from django_rq import job
from celery import shared_task
from sentry_sdk import capture_exception
# Module imports
from plane.db.models import Project, User, ProjectMemberInvite
@job("default")
@shared_task
def project_invitation(email, project_id, token, current_site):
try:
project = Project.objects.get(pk=project_id)
project_member_invite = ProjectMemberInvite.objects.get(
token=token, email=email
)
relativelink = f"/project-member-invitation/{project_member_invite.id}"
abs_url = "http://" + current_site + relativelink
abs_url = current_site + relativelink
from_email_string = f"Team Plane <team@mailer.plane.so>"
from_email_string = settings.EMAIL_FROM
subject = f"{project.created_by.first_name or project.created_by.email} invited you to join {project.name} on Plane"
@@ -35,7 +34,9 @@ def project_invitation(email, project_id, token, current_site):
"invitation_url": abs_url,
}
html_content = render_to_string("emails/invitations/project_invitation.html", context)
html_content = render_to_string(
"emails/invitations/project_invitation.html", context
)
text_content = strip_tags(html_content)
@@ -49,6 +50,5 @@ def project_invitation(email, project_id, token, current_site):
except (Project.DoesNotExist, ProjectMemberInvite.DoesNotExist) as e:
return
except Exception as e:
print(e)
capture_exception(e)
return
@@ -0,0 +1,33 @@
# Django imports
from django.conf import settings
# Third party imports
from celery import shared_task
from sentry_sdk import capture_exception
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
# Module imports
from plane.db.models import User
@shared_task
def send_welcome_slack(user_id, created, message):
try:
instance = User.objects.get(pk=user_id)
if created and not instance.is_bot:
# Send message on slack as well
if settings.SLACK_BOT_TOKEN:
client = WebClient(token=settings.SLACK_BOT_TOKEN)
try:
_ = client.chat_postMessage(
channel="#trackers",
text=message,
)
except SlackApiError as e:
print(f"Got an error: {e.response['error']}")
return
except Exception as e:
capture_exception(e)
return
@@ -2,31 +2,32 @@
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils.html import strip_tags
from django.conf import settings
# Third party imports
from django_rq import job
from celery import shared_task
from sentry_sdk import capture_exception
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
# Module imports
from plane.db.models import Workspace, User, WorkspaceMemberInvite
@job("default")
@shared_task
def workspace_invitation(email, workspace_id, token, current_site, invitor):
try:
workspace = Workspace.objects.get(pk=workspace_id)
workspace_member_invite = WorkspaceMemberInvite.objects.get(
token=token, email=email
)
realtivelink = (
f"/workspace-member-invitation/{workspace_member_invite.id}?email={email}"
f"/workspace-member-invitation/?invitation_id={workspace_member_invite.id}&email={email}"
)
abs_url = "http://" + current_site + realtivelink
abs_url = current_site + realtivelink
from_email_string = f"Team Plane <team@mailer.plane.so>"
from_email_string = settings.EMAIL_FROM
subject = f"{invitor or email} invited you to join {workspace.name} on Plane"
@@ -49,6 +50,18 @@ def workspace_invitation(email, workspace_id, token, current_site, invitor):
msg = EmailMultiAlternatives(subject, text_content, from_email_string, [email])
msg.attach_alternative(html_content, "text/html")
msg.send()
# Send message on slack as well
if settings.SLACK_BOT_TOKEN:
client = WebClient(token=settings.SLACK_BOT_TOKEN)
try:
_ = client.chat_postMessage(
channel="#trackers",
text=f"{workspace_member_invite.email} has been invited to {workspace.name} as a {workspace_member_invite.role}",
)
except SlackApiError as e:
print(f"Got an error: {e.response['error']}")
return
except (Workspace.DoesNotExist, WorkspaceMemberInvite.DoesNotExist) as e:
return
+17
View File
@@ -0,0 +1,17 @@
import os
from celery import Celery
from plane.settings.redis import redis_instance
# Set the default Django settings module for the 'celery' program.
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "plane.settings.production")
ri = redis_instance()
app = Celery("plane")
# Using a string here means the worker will not have to
# pickle the object when using Windows.
app.config_from_object("django.conf:settings", namespace="CELERY")
# Load task modules from all registered Django app configs.
app.autodiscover_tasks()
@@ -0,0 +1,69 @@
# Generated by Django 3.2.16 on 2023-02-13 19:48
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('db', '0019_auto_20230131_0049'),
]
operations = [
migrations.RenameField(
model_name='label',
old_name='colour',
new_name='color',
),
migrations.AddField(
model_name='apitoken',
name='workspace',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='api_tokens', to='db.workspace'),
),
migrations.AddField(
model_name='issue',
name='completed_at',
field=models.DateTimeField(null=True),
),
migrations.AddField(
model_name='issue',
name='sort_order',
field=models.FloatField(default=65535),
),
migrations.AddField(
model_name='project',
name='cycle_view',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='project',
name='module_view',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='state',
name='default',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='issue',
name='description',
field=models.JSONField(blank=True, default=dict),
),
migrations.AlterField(
model_name='issue',
name='description_html',
field=models.TextField(blank=True, default='<p></p>'),
),
migrations.AlterField(
model_name='issuecomment',
name='comment_html',
field=models.TextField(blank=True, default='<p></p>'),
),
migrations.AlterField(
model_name='issuecomment',
name='comment_json',
field=models.JSONField(blank=True, default=dict),
),
]
@@ -0,0 +1,185 @@
# Generated by Django 3.2.16 on 2023-02-22 19:34
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('db', '0020_auto_20230214_0118'),
]
operations = [
migrations.CreateModel(
name='GithubRepository',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('name', models.CharField(max_length=500)),
('url', models.URLField(null=True)),
('config', models.JSONField(default=dict)),
('repository_id', models.BigIntegerField()),
('owner', models.CharField(max_length=500)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='githubrepository_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_githubrepository', to='db.project')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='githubrepository_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_githubrepository', to='db.workspace')),
],
options={
'verbose_name': 'Repository',
'verbose_name_plural': 'Repositories',
'db_table': 'github_repositories',
'ordering': ('-created_at',),
},
),
migrations.CreateModel(
name='Integration',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('title', models.CharField(max_length=400)),
('provider', models.CharField(max_length=400, unique=True)),
('network', models.PositiveIntegerField(choices=[(1, 'Private'), (2, 'Public')], default=1)),
('description', models.JSONField(default=dict)),
('author', models.CharField(blank=True, max_length=400)),
('webhook_url', models.TextField(blank=True)),
('webhook_secret', models.TextField(blank=True)),
('redirect_url', models.TextField(blank=True)),
('metadata', models.JSONField(default=dict)),
('verified', models.BooleanField(default=False)),
('avatar_url', models.URLField(blank=True, null=True)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='integration_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='integration_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
],
options={
'verbose_name': 'Integration',
'verbose_name_plural': 'Integrations',
'db_table': 'integrations',
'ordering': ('-created_at',),
},
),
migrations.AlterField(
model_name='issueactivity',
name='issue',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='issue_activity', to='db.issue'),
),
migrations.CreateModel(
name='WorkspaceIntegration',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('metadata', models.JSONField(default=dict)),
('config', models.JSONField(default=dict)),
('actor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='integrations', to=settings.AUTH_USER_MODEL)),
('api_token', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='integrations', to='db.apitoken')),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='workspaceintegration_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('integration', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='integrated_workspaces', to='db.integration')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='workspaceintegration_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_integrations', to='db.workspace')),
],
options={
'verbose_name': 'Workspace Integration',
'verbose_name_plural': 'Workspace Integrations',
'db_table': 'workspace_integrations',
'ordering': ('-created_at',),
'unique_together': {('workspace', 'integration')},
},
),
migrations.CreateModel(
name='IssueLink',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('title', models.CharField(max_length=255, null=True)),
('url', models.URLField()),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='issuelink_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('issue', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='issue_link', to='db.issue')),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_issuelink', to='db.project')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='issuelink_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_issuelink', to='db.workspace')),
],
options={
'verbose_name': 'Issue Link',
'verbose_name_plural': 'Issue Links',
'db_table': 'issue_links',
'ordering': ('-created_at',),
},
),
migrations.CreateModel(
name='GithubRepositorySync',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('credentials', models.JSONField(default=dict)),
('actor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_syncs', to=settings.AUTH_USER_MODEL)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='githubrepositorysync_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('label', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='repo_syncs', to='db.label')),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_githubrepositorysync', to='db.project')),
('repository', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='syncs', to='db.githubrepository')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='githubrepositorysync_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_githubrepositorysync', to='db.workspace')),
('workspace_integration', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='github_syncs', to='db.workspaceintegration')),
],
options={
'verbose_name': 'Github Repository Sync',
'verbose_name_plural': 'Github Repository Syncs',
'db_table': 'github_repository_syncs',
'ordering': ('-created_at',),
'unique_together': {('project', 'repository')},
},
),
migrations.CreateModel(
name='GithubIssueSync',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('repo_issue_id', models.BigIntegerField()),
('github_issue_id', models.BigIntegerField()),
('issue_url', models.URLField()),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='githubissuesync_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('issue', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='github_syncs', to='db.issue')),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_githubissuesync', to='db.project')),
('repository_sync', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='issue_syncs', to='db.githubrepositorysync')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='githubissuesync_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_githubissuesync', to='db.workspace')),
],
options={
'verbose_name': 'Github Issue Sync',
'verbose_name_plural': 'Github Issue Syncs',
'db_table': 'github_issue_syncs',
'ordering': ('-created_at',),
'unique_together': {('repository_sync', 'issue')},
},
),
migrations.CreateModel(
name='GithubCommentSync',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('repo_comment_id', models.BigIntegerField()),
('comment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comment_syncs', to='db.issuecomment')),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='githubcommentsync_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('issue_sync', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comment_syncs', to='db.githubissuesync')),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_githubcommentsync', to='db.project')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='githubcommentsync_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_githubcommentsync', to='db.workspace')),
],
options={
'verbose_name': 'Github Comment Sync',
'verbose_name_plural': 'Github Comment Syncs',
'db_table': 'github_comment_syncs',
'ordering': ('-created_at',),
'unique_together': {('issue_sync', 'comment')},
},
),
]
@@ -0,0 +1,101 @@
# Generated by Django 3.2.16 on 2023-03-06 21:34
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('db', '0021_auto_20230223_0104'),
]
operations = [
migrations.RemoveField(
model_name='cycle',
name='status',
),
migrations.RemoveField(
model_name='project',
name='slug',
),
migrations.AddField(
model_name='issuelink',
name='metadata',
field=models.JSONField(default=dict),
),
migrations.AddField(
model_name='modulelink',
name='metadata',
field=models.JSONField(default=dict),
),
migrations.AddField(
model_name='project',
name='cover_image',
field=models.URLField(blank=True, null=True),
),
migrations.CreateModel(
name='ProjectFavorite',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='projectfavorite_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_projectfavorite', to='db.project')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='projectfavorite_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_favorites', to=settings.AUTH_USER_MODEL)),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_projectfavorite', to='db.workspace')),
],
options={
'verbose_name': 'Project Favorite',
'verbose_name_plural': 'Project Favorites',
'db_table': 'project_favorites',
'ordering': ('-created_at',),
'unique_together': {('project', 'user')},
},
),
migrations.CreateModel(
name='ModuleFavorite',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='modulefavorite_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('module', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='module_favorites', to='db.module')),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_modulefavorite', to='db.project')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='modulefavorite_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='module_favorites', to=settings.AUTH_USER_MODEL)),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_modulefavorite', to='db.workspace')),
],
options={
'verbose_name': 'Module Favorite',
'verbose_name_plural': 'Module Favorites',
'db_table': 'module_favorites',
'ordering': ('-created_at',),
'unique_together': {('module', 'user')},
},
),
migrations.CreateModel(
name='CycleFavorite',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='cyclefavorite_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('cycle', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cycle_favorites', to='db.cycle')),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_cyclefavorite', to='db.project')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='cyclefavorite_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cycle_favorites', to=settings.AUTH_USER_MODEL)),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_cyclefavorite', to='db.workspace')),
],
options={
'verbose_name': 'Cycle Favorite',
'verbose_name_plural': 'Cycle Favorites',
'db_table': 'cycle_favorites',
'ordering': ('-created_at',),
'unique_together': {('cycle', 'user')},
},
),
]
@@ -0,0 +1,92 @@
# Generated by Django 3.2.16 on 2023-03-15 19:10
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('db', '0022_auto_20230307_0304'),
]
operations = [
migrations.CreateModel(
name='Importer',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('service', models.CharField(choices=[('github', 'GitHub')], max_length=50)),
('status', models.CharField(choices=[('queued', 'Queued'), ('processing', 'Processing'), ('completed', 'Completed'), ('failed', 'Failed')], default='queued', max_length=50)),
('metadata', models.JSONField(default=dict)),
('config', models.JSONField(default=dict)),
('data', models.JSONField(default=dict)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='importer_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('initiated_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='imports', to=settings.AUTH_USER_MODEL)),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_importer', to='db.project')),
('token', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='importer', to='db.apitoken')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='importer_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_importer', to='db.workspace')),
],
options={
'verbose_name': 'Importer',
'verbose_name_plural': 'Importers',
'db_table': 'importers',
'ordering': ('-created_at',),
},
),
migrations.CreateModel(
name='IssueView',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('name', models.CharField(max_length=255, verbose_name='View Name')),
('description', models.TextField(blank=True, verbose_name='View Description')),
('query', models.JSONField(verbose_name='View Query')),
('access', models.PositiveSmallIntegerField(choices=[(0, 'Private'), (1, 'Public')], default=1)),
('query_data', models.JSONField(default=dict)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='issueview_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_issueview', to='db.project')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='issueview_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_issueview', to='db.workspace')),
],
options={
'verbose_name': 'Issue View',
'verbose_name_plural': 'Issue Views',
'db_table': 'issue_views',
'ordering': ('-created_at',),
},
),
migrations.CreateModel(
name='IssueViewFavorite',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='issueviewfavorite_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_issueviewfavorite', to='db.project')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='issueviewfavorite_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_view_favorites', to=settings.AUTH_USER_MODEL)),
('view', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='view_favorites', to='db.issueview')),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_issueviewfavorite', to='db.workspace')),
],
options={
'verbose_name': 'View Favorite',
'verbose_name_plural': 'View Favorites',
'db_table': 'view_favorites',
'ordering': ('-created_at',),
'unique_together': {('view', 'user')},
},
),
migrations.AlterUniqueTogether(
name='label',
unique_together={('name', 'project')},
),
migrations.DeleteModel(
name='View',
),
]
@@ -0,0 +1,113 @@
# Generated by Django 3.2.16 on 2023-03-21 20:08
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('db', '0023_auto_20230316_0040'),
]
operations = [
migrations.CreateModel(
name='Page',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('name', models.CharField(max_length=255)),
('description', models.JSONField(blank=True, default=dict)),
('description_html', models.TextField(blank=True, default='<p></p>')),
('description_stripped', models.TextField(blank=True, null=True)),
('access', models.PositiveSmallIntegerField(choices=[(0, 'Public'), (1, 'Private')], default=0)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='page_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('owned_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pages', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Page',
'verbose_name_plural': 'Pages',
'db_table': 'pages',
'ordering': ('-created_at',),
},
),
migrations.AddField(
model_name='project',
name='issue_views_view',
field=models.BooleanField(default=True),
),
migrations.AlterField(
model_name='importer',
name='service',
field=models.CharField(choices=[('github', 'GitHub'), ('jira', 'Jira')], max_length=50),
),
migrations.AlterField(
model_name='project',
name='cover_image',
field=models.URLField(blank=True, max_length=800, null=True),
),
migrations.CreateModel(
name='PageBlock',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('name', models.CharField(max_length=255)),
('description', models.JSONField(blank=True, default=dict)),
('description_html', models.TextField(blank=True, default='<p></p>')),
('description_stripped', models.TextField(blank=True, null=True)),
('completed_at', models.DateTimeField(null=True)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='pageblock_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('issue', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='blocks', to='db.issue')),
('page', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='blocks', to='db.page')),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_pageblock', to='db.project')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='pageblock_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_pageblock', to='db.workspace')),
],
options={
'verbose_name': 'Page Block',
'verbose_name_plural': 'Page Blocks',
'db_table': 'page_blocks',
'ordering': ('-created_at',),
},
),
migrations.AddField(
model_name='page',
name='project',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_page', to='db.project'),
),
migrations.AddField(
model_name='page',
name='updated_by',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='page_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By'),
),
migrations.AddField(
model_name='page',
name='workspace',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_page', to='db.workspace'),
),
migrations.CreateModel(
name='PageFavorite',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='pagefavorite_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('page', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='page_favorites', to='db.page')),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_pagefavorite', to='db.project')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='pagefavorite_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='page_favorites', to=settings.AUTH_USER_MODEL)),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_pagefavorite', to='db.workspace')),
],
options={
'verbose_name': 'Page Favorite',
'verbose_name_plural': 'Page Favorites',
'db_table': 'page_favorites',
'ordering': ('-created_at',),
'unique_together': {('page', 'user')},
},
),
]
@@ -0,0 +1,61 @@
# Generated by Django 3.2.18 on 2023-03-30 20:33
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('db', '0024_auto_20230322_0138'),
]
operations = [
migrations.AddField(
model_name='page',
name='color',
field=models.CharField(blank=True, max_length=255),
),
migrations.AddField(
model_name='pageblock',
name='sort_order',
field=models.FloatField(default=65535),
),
migrations.AddField(
model_name='pageblock',
name='sync',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='project',
name='page_view',
field=models.BooleanField(default=True),
),
migrations.CreateModel(
name='PageLabel',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='pagelabel_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('label', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='page_labels', to='db.label')),
('page', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='page_labels', to='db.page')),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_pagelabel', to='db.project')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='pagelabel_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_pagelabel', to='db.workspace')),
],
options={
'verbose_name': 'Page Label',
'verbose_name_plural': 'Page Labels',
'db_table': 'page_labels',
'ordering': ('-created_at',),
},
),
migrations.AddField(
model_name='page',
name='labels',
field=models.ManyToManyField(blank=True, related_name='pages', through='db.PageLabel', to='db.Label'),
),
]
@@ -0,0 +1,19 @@
# Generated by Django 3.2.18 on 2023-04-04 21:50
from django.db import migrations, models
import plane.db.models.project
class Migration(migrations.Migration):
dependencies = [
('db', '0025_auto_20230331_0203'),
]
operations = [
migrations.AlterField(
model_name='projectmember',
name='view_props',
field=models.JSONField(default=plane.db.models.project.get_default_props),
),
]
@@ -0,0 +1,97 @@
# Generated by Django 3.2.18 on 2023-04-08 21:42
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import plane.db.models.issue
import uuid
class Migration(migrations.Migration):
dependencies = [
('db', '0026_alter_projectmember_view_props'),
]
operations = [
migrations.CreateModel(
name='Estimate',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('name', models.CharField(max_length=255)),
('description', models.TextField(blank=True, verbose_name='Estimate Description')),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='estimate_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_estimate', to='db.project')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='estimate_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_estimate', to='db.workspace')),
],
options={
'verbose_name': 'Estimate',
'verbose_name_plural': 'Estimates',
'db_table': 'estimates',
'ordering': ('name',),
'unique_together': {('name', 'project')},
},
),
migrations.RemoveField(
model_name='issue',
name='attachments',
),
migrations.AddField(
model_name='issue',
name='estimate_point',
field=models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(7)]),
),
migrations.CreateModel(
name='IssueAttachment',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('attributes', models.JSONField(default=dict)),
('asset', models.FileField(upload_to=plane.db.models.issue.get_upload_path, validators=[plane.db.models.issue.file_size])),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='issueattachment_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('issue', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='issue_attachment', to='db.issue')),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_issueattachment', to='db.project')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='issueattachment_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_issueattachment', to='db.workspace')),
],
options={
'verbose_name': 'Issue Attachment',
'verbose_name_plural': 'Issue Attachments',
'db_table': 'issue_attachments',
'ordering': ('-created_at',),
},
),
migrations.AddField(
model_name='project',
name='estimate',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='projects', to='db.estimate'),
),
migrations.CreateModel(
name='EstimatePoint',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('key', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(7)])),
('description', models.TextField(blank=True)),
('value', models.CharField(max_length=20)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='estimatepoint_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('estimate', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='points', to='db.estimate')),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_estimatepoint', to='db.project')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='estimatepoint_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_estimatepoint', to='db.workspace')),
],
options={
'verbose_name': 'Estimate Point',
'verbose_name_plural': 'Estimate Points',
'db_table': 'estimate_points',
'ordering': ('value',),
'unique_together': {('value', 'estimate')},
},
),
]
@@ -0,0 +1,48 @@
# Generated by Django 3.2.18 on 2023-04-14 11:33
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('db', '0027_auto_20230409_0312'),
]
operations = [
migrations.AddField(
model_name='user',
name='theme',
field=models.JSONField(default=dict),
),
migrations.AlterField(
model_name='issue',
name='estimate_point',
field=models.IntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(7)]),
),
migrations.CreateModel(
name='WorkspaceTheme',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('name', models.CharField(max_length=300)),
('colors', models.JSONField(default=dict)),
('actor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='themes', to=settings.AUTH_USER_MODEL)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='workspacetheme_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='workspacetheme_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='themes', to='db.workspace')),
],
options={
'verbose_name': 'Workspace Theme',
'verbose_name_plural': 'Workspace Themes',
'db_table': 'workspace_themes',
'ordering': ('-created_at',),
'unique_together': {('workspace', 'name')},
},
),
]
@@ -0,0 +1,58 @@
# Generated by Django 3.2.18 on 2023-05-01 19:56
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('db', '0028_auto_20230414_1703'),
]
operations = [
migrations.AddField(
model_name='cycle',
name='view_props',
field=models.JSONField(default=dict),
),
migrations.AddField(
model_name='importer',
name='imported_data',
field=models.JSONField(null=True),
),
migrations.AddField(
model_name='module',
name='view_props',
field=models.JSONField(default=dict),
),
migrations.CreateModel(
name='SlackProjectSync',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('access_token', models.CharField(max_length=300)),
('scopes', models.TextField()),
('bot_user_id', models.CharField(max_length=50)),
('webhook_url', models.URLField(max_length=1000)),
('data', models.JSONField(default=dict)),
('team_id', models.CharField(max_length=30)),
('team_name', models.CharField(max_length=300)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='slackprojectsync_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_slackprojectsync', to='db.project')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='slackprojectsync_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_slackprojectsync', to='db.workspace')),
('workspace_integration', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='slack_syncs', to='db.workspaceintegration')),
],
options={
'verbose_name': 'Slack Project Sync',
'verbose_name_plural': 'Slack Project Syncs',
'db_table': 'slack_project_syncs',
'ordering': ('-created_at',),
'unique_together': {('team_id', 'project')},
},
),
]
@@ -0,0 +1,17 @@
# Generated by Django 3.2.18 on 2023-05-05 14:17
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('db', '0029_auto_20230502_0126'),
]
operations = [
migrations.AlterUniqueTogether(
name='estimatepoint',
unique_together=set(),
),
]
@@ -0,0 +1,37 @@
# Generated by Django 3.2.18 on 2023-05-12 11:31
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('db', '0030_alter_estimatepoint_unique_together'),
]
operations = [
migrations.CreateModel(
name='AnalyticView',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('name', models.CharField(max_length=255)),
('description', models.TextField(blank=True)),
('query', models.JSONField()),
('query_dict', models.JSONField(default=dict)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='analyticview_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='analyticview_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='analytics', to='db.workspace')),
],
options={
'verbose_name': 'Analytic',
'verbose_name_plural': 'Analytics',
'db_table': 'analytic_views',
'ordering': ('-created_at',),
},
),
]
@@ -0,0 +1,23 @@
# Generated by Django 3.2.19 on 2023-05-20 14:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('db', '0031_analyticview'),
]
operations = [
migrations.RenameField(
model_name='project',
old_name='icon',
new_name='emoji',
),
migrations.AddField(
model_name='project',
name='icon_prop',
field=models.JSONField(null=True),
),
]
+4
View File
@@ -1,3 +1,7 @@
# Python imports
import uuid
# Django imports
from django.db import models
+34 -5
View File
@@ -8,9 +8,17 @@ from .workspace import (
Team,
WorkspaceMemberInvite,
TeamMember,
WorkspaceTheme,
)
from .project import Project, ProjectMember, ProjectBaseModel, ProjectMemberInvite, ProjectIdentifier
from .project import (
Project,
ProjectMember,
ProjectBaseModel,
ProjectMemberInvite,
ProjectIdentifier,
ProjectFavorite,
)
from .issue import (
Issue,
@@ -23,6 +31,9 @@ from .issue import (
IssueAssignee,
Label,
IssueBlocker,
IssueLink,
IssueSequence,
IssueAttachment,
)
from .asset import FileAsset
@@ -31,12 +42,30 @@ from .social_connection import SocialLoginConnection
from .state import State
from .cycle import Cycle, CycleIssue
from .cycle import Cycle, CycleIssue, CycleFavorite
from .shortcut import Shortcut
from .view import View
from .view import IssueView, IssueViewFavorite
from .module import Module, ModuleMember, ModuleIssue, ModuleLink
from .module import Module, ModuleMember, ModuleIssue, ModuleLink, ModuleFavorite
from .api_token import APIToken
from .api_token import APIToken
from .integration import (
WorkspaceIntegration,
Integration,
GithubRepository,
GithubRepositorySync,
GithubIssueSync,
GithubCommentSync,
SlackProjectSync,
)
from .importer import Importer
from .page import Page, PageBlock, PageFavorite, PageLabel
from .estimate import Estimate, EstimatePoint
from .analytic import AnalyticView
+25
View File
@@ -0,0 +1,25 @@
# Django models
from django.db import models
from django.conf import settings
from .base import BaseModel
class AnalyticView(BaseModel):
workspace = models.ForeignKey(
"db.Workspace", related_name="analytics", on_delete=models.CASCADE
)
name = models.CharField(max_length=255)
description = models.TextField(blank=True)
query = models.JSONField()
query_dict = models.JSONField(default=dict)
class Meta:
verbose_name = "Analytic"
verbose_name_plural = "Analytics"
db_table = "analytic_views"
ordering = ("-created_at",)
def __str__(self):
"""Return name of the analytic view"""
return f"{self.name} <{self.workspace.name}>"
+3 -1
View File
@@ -17,7 +17,6 @@ def generate_token():
class APIToken(BaseModel):
token = models.CharField(max_length=255, unique=True, default=generate_token)
label = models.CharField(max_length=255, default=generate_label_token)
user = models.ForeignKey(
@@ -28,6 +27,9 @@ class APIToken(BaseModel):
user_type = models.PositiveSmallIntegerField(
choices=((0, "Human"), (1, "Bot")), default=0
)
workspace = models.ForeignKey(
"db.Workspace", related_name="api_tokens", on_delete=models.CASCADE, null=True
)
class Meta:
verbose_name = "API Token"
+9 -3
View File
@@ -1,20 +1,26 @@
# Python imports
from uuid import uuid4
# Django import
from django.db import models
from django.core.exceptions import ValidationError
from django.conf import settings
# Module import
from . import BaseModel
def get_upload_path(instance, filename):
return f"{instance.workspace.id}/{filename}"
if instance.workspace_id is not None:
return f"{instance.workspace.id}/{uuid4().hex}-{filename}"
return f"user-{uuid4().hex}-{filename}"
def file_size(value):
limit = 5 * 1024 * 1024
if value.size > limit:
if value.size > settings.FILE_SIZE_LIMIT:
raise ValidationError("File too large. Size should not exceed 5 MB.")
class FileAsset(BaseModel):
"""
A file asset.
+27 -11
View File
@@ -7,11 +7,6 @@ from . import ProjectBaseModel
class Cycle(ProjectBaseModel):
STATUS_CHOICES = (
("draft", "Draft"),
("started", "Started"),
("completed", "Completed"),
)
name = models.CharField(max_length=255, verbose_name="Cycle Name")
description = models.TextField(verbose_name="Cycle Description", blank=True)
start_date = models.DateField(verbose_name="Start Date", blank=True, null=True)
@@ -21,12 +16,7 @@ class Cycle(ProjectBaseModel):
on_delete=models.CASCADE,
related_name="owned_by_cycle",
)
status = models.CharField(
max_length=255,
verbose_name="Cycle Status",
choices=STATUS_CHOICES,
default="draft",
)
view_props = models.JSONField(default=dict)
class Meta:
verbose_name = "Cycle"
@@ -59,3 +49,29 @@ class CycleIssue(ProjectBaseModel):
def __str__(self):
return f"{self.cycle}"
class CycleFavorite(ProjectBaseModel):
"""_summary_
CycleFavorite (model): To store all the cycle favorite of the user
"""
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="cycle_favorites",
)
cycle = models.ForeignKey(
"db.Cycle", on_delete=models.CASCADE, related_name="cycle_favorites"
)
class Meta:
unique_together = ["cycle", "user"]
verbose_name = "Cycle Favorite"
verbose_name_plural = "Cycle Favorites"
db_table = "cycle_favorites"
ordering = ("-created_at",)
def __str__(self):
"""Return user and the cycle"""
return f"{self.user.email} <{self.cycle.name}>"
+45
View File
@@ -0,0 +1,45 @@
# Django imports
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
# Module imports
from . import ProjectBaseModel
class Estimate(ProjectBaseModel):
name = models.CharField(max_length=255)
description = models.TextField(verbose_name="Estimate Description", blank=True)
def __str__(self):
"""Return name of the estimate"""
return f"{self.name} <{self.project.name}>"
class Meta:
unique_together = ["name", "project"]
verbose_name = "Estimate"
verbose_name_plural = "Estimates"
db_table = "estimates"
ordering = ("name",)
class EstimatePoint(ProjectBaseModel):
estimate = models.ForeignKey(
"db.Estimate",
on_delete=models.CASCADE,
related_name="points",
)
key = models.IntegerField(
default=0, validators=[MinValueValidator(0), MaxValueValidator(7)]
)
description = models.TextField(blank=True)
value = models.CharField(max_length=20)
def __str__(self):
"""Return name of the estimate"""
return f"{self.estimate.name} <{self.key}> <{self.value}>"
class Meta:
verbose_name = "Estimate Point"
verbose_name_plural = "Estimate Points"
db_table = "estimate_points"
ordering = ("value",)
+46
View File
@@ -0,0 +1,46 @@
# Django imports
from django.db import models
from django.conf import settings
# Module imports
from . import ProjectBaseModel
class Importer(ProjectBaseModel):
service = models.CharField(
max_length=50,
choices=(
("github", "GitHub"),
("jira", "Jira"),
),
)
status = models.CharField(
max_length=50,
choices=(
("queued", "Queued"),
("processing", "Processing"),
("completed", "Completed"),
("failed", "Failed"),
),
default="queued",
)
initiated_by = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="imports"
)
metadata = models.JSONField(default=dict)
config = models.JSONField(default=dict)
data = models.JSONField(default=dict)
token = models.ForeignKey(
"db.APIToken", on_delete=models.CASCADE, related_name="importer"
)
imported_data = models.JSONField(null=True)
class Meta:
verbose_name = "Importer"
verbose_name_plural = "Importers"
db_table = "importers"
ordering = ("-created_at",)
def __str__(self):
"""Return name of the service"""
return f"{self.service} <{self.project.name}>"
@@ -0,0 +1,3 @@
from .base import Integration, WorkspaceIntegration
from .github import GithubRepository, GithubRepositorySync, GithubIssueSync, GithubCommentSync
from .slack import SlackProjectSync
@@ -0,0 +1,68 @@
# Python imports
import uuid
# Django imports
from django.db import models
# Module imports
from plane.db.models import BaseModel
from plane.db.mixins import AuditModel
class Integration(AuditModel):
id = models.UUIDField(
default=uuid.uuid4, unique=True, editable=False, db_index=True, primary_key=True
)
title = models.CharField(max_length=400)
provider = models.CharField(max_length=400, unique=True)
network = models.PositiveIntegerField(
default=1, choices=((1, "Private"), (2, "Public"))
)
description = models.JSONField(default=dict)
author = models.CharField(max_length=400, blank=True)
webhook_url = models.TextField(blank=True)
webhook_secret = models.TextField(blank=True)
redirect_url = models.TextField(blank=True)
metadata = models.JSONField(default=dict)
verified = models.BooleanField(default=False)
avatar_url = models.URLField(blank=True, null=True)
def __str__(self):
"""Return provider of the integration"""
return f"{self.provider}"
class Meta:
verbose_name = "Integration"
verbose_name_plural = "Integrations"
db_table = "integrations"
ordering = ("-created_at",)
class WorkspaceIntegration(BaseModel):
workspace = models.ForeignKey(
"db.Workspace", related_name="workspace_integrations", on_delete=models.CASCADE
)
# Bot user
actor = models.ForeignKey(
"db.User", related_name="integrations", on_delete=models.CASCADE
)
integration = models.ForeignKey(
"db.Integration", related_name="integrated_workspaces", on_delete=models.CASCADE
)
api_token = models.ForeignKey(
"db.APIToken", related_name="integrations", on_delete=models.CASCADE
)
metadata = models.JSONField(default=dict)
config = models.JSONField(default=dict)
def __str__(self):
"""Return name of the integration and workspace"""
return f"{self.workspace.name} <{self.integration.provider}>"
class Meta:
unique_together = ["workspace", "integration"]
verbose_name = "Workspace Integration"
verbose_name_plural = "Workspace Integrations"
db_table = "workspace_integrations"
ordering = ("-created_at",)
@@ -0,0 +1,99 @@
# Python imports
import uuid
# Django imports
from django.db import models
# Module imports
from plane.db.models import ProjectBaseModel
from plane.db.mixins import AuditModel
class GithubRepository(ProjectBaseModel):
name = models.CharField(max_length=500)
url = models.URLField(null=True)
config = models.JSONField(default=dict)
repository_id = models.BigIntegerField()
owner = models.CharField(max_length=500)
def __str__(self):
"""Return the repo name"""
return f"{self.name}"
class Meta:
verbose_name = "Repository"
verbose_name_plural = "Repositories"
db_table = "github_repositories"
ordering = ("-created_at",)
class GithubRepositorySync(ProjectBaseModel):
repository = models.OneToOneField(
"db.GithubRepository", on_delete=models.CASCADE, related_name="syncs"
)
credentials = models.JSONField(default=dict)
# Bot user
actor = models.ForeignKey(
"db.User", related_name="user_syncs", on_delete=models.CASCADE
)
workspace_integration = models.ForeignKey(
"db.WorkspaceIntegration", related_name="github_syncs", on_delete=models.CASCADE
)
label = models.ForeignKey(
"db.Label", on_delete=models.SET_NULL, null=True, related_name="repo_syncs"
)
def __str__(self):
"""Return the repo sync"""
return f"{self.repository.name} <{self.project.name}>"
class Meta:
unique_together = ["project", "repository"]
verbose_name = "Github Repository Sync"
verbose_name_plural = "Github Repository Syncs"
db_table = "github_repository_syncs"
ordering = ("-created_at",)
class GithubIssueSync(ProjectBaseModel):
repo_issue_id = models.BigIntegerField()
github_issue_id = models.BigIntegerField()
issue_url = models.URLField(blank=False)
issue = models.ForeignKey(
"db.Issue", related_name="github_syncs", on_delete=models.CASCADE
)
repository_sync = models.ForeignKey(
"db.GithubRepositorySync", related_name="issue_syncs", on_delete=models.CASCADE
)
def __str__(self):
"""Return the github issue sync"""
return f"{self.repository.name}-{self.project.name}-{self.issue.name}"
class Meta:
unique_together = ["repository_sync", "issue"]
verbose_name = "Github Issue Sync"
verbose_name_plural = "Github Issue Syncs"
db_table = "github_issue_syncs"
ordering = ("-created_at",)
class GithubCommentSync(ProjectBaseModel):
repo_comment_id = models.BigIntegerField()
comment = models.ForeignKey(
"db.IssueComment", related_name="comment_syncs", on_delete=models.CASCADE
)
issue_sync = models.ForeignKey(
"db.GithubIssueSync", related_name="comment_syncs", on_delete=models.CASCADE
)
def __str__(self):
"""Return the github issue sync"""
return f"{self.comment.id}"
class Meta:
unique_together = ["issue_sync", "comment"]
verbose_name = "Github Comment Sync"
verbose_name_plural = "Github Comment Syncs"
db_table = "github_comment_syncs"
ordering = ("-created_at",)
@@ -0,0 +1,32 @@
# Python imports
import uuid
# Django imports
from django.db import models
# Module imports
from plane.db.models import ProjectBaseModel
class SlackProjectSync(ProjectBaseModel):
access_token = models.CharField(max_length=300)
scopes = models.TextField()
bot_user_id = models.CharField(max_length=50)
webhook_url = models.URLField(max_length=1000)
data = models.JSONField(default=dict)
team_id = models.CharField(max_length=30)
team_name = models.CharField(max_length=300)
workspace_integration = models.ForeignKey(
"db.WorkspaceIntegration", related_name="slack_syncs", on_delete=models.CASCADE
)
def __str__(self):
"""Return the repo name"""
return f"{self.project.name}"
class Meta:
unique_together = ["team_id", "project"]
verbose_name = "Slack Project Sync"
verbose_name_plural = "Slack Project Syncs"
db_table = "slack_project_syncs"
ordering = ("-created_at",)
+117 -20
View File
@@ -1,14 +1,21 @@
# Python import
from uuid import uuid4
# Django imports
from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils import timezone
from django.core.validators import MinValueValidator, MaxValueValidator
from django.core.exceptions import ValidationError
# Module imports
from . import ProjectBaseModel
from plane.utils.html_processor import strip_tags
# TODO: Handle identifiers for Bulk Inserts - nk
class Issue(ProjectBaseModel):
PRIORITY_CHOICES = (
@@ -31,9 +38,12 @@ class Issue(ProjectBaseModel):
blank=True,
related_name="state_issue",
)
estimate_point = models.IntegerField(
validators=[MinValueValidator(0), MaxValueValidator(7)], null=True, blank=True
)
name = models.CharField(max_length=255, verbose_name="Issue Name")
description = models.JSONField(blank=True, null=True)
description_html = models.TextField(blank=True, null=True)
description = models.JSONField(blank=True, default=dict)
description_html = models.TextField(blank=True, default="<p></p>")
description_stripped = models.TextField(blank=True, null=True)
priority = models.CharField(
max_length=30,
@@ -52,10 +62,11 @@ class Issue(ProjectBaseModel):
through_fields=("issue", "assignee"),
)
sequence_id = models.IntegerField(default=1, verbose_name="Issue Sequence ID")
attachments = ArrayField(models.URLField(), size=10, blank=True, default=list)
labels = models.ManyToManyField(
"db.Label", blank=True, related_name="labels", through="IssueLabel"
)
sort_order = models.FloatField(default=65535)
completed_at = models.DateTimeField(null=True)
class Meta:
verbose_name = "Issue"
@@ -65,9 +76,48 @@ class Issue(ProjectBaseModel):
def save(self, *args, **kwargs):
# This means that the model isn't saved to the database yet
if self.state is None:
try:
from plane.db.models import State
default_state = State.objects.filter(
project=self.project, default=True
).first()
# if there is no default state assign any random state
if default_state is None:
random_state = State.objects.filter(project=self.project).first()
self.state = random_state
if random_state.group == "started":
self.start_date = timezone.now().date()
else:
if default_state.group == "started":
self.start_date = timezone.now().date()
self.state = default_state
except ImportError:
pass
else:
try:
from plane.db.models import State, PageBlock
# Check if the current issue state and completed state id are same
if self.state.group == "completed":
self.completed_at = timezone.now()
# check if there are any page blocks
PageBlock.objects.filter(issue_id=self.id).filter().update(
completed_at=timezone.now()
)
elif self.state.group == "started":
self.start_date = timezone.now().date()
else:
PageBlock.objects.filter(issue_id=self.id).filter().update(
completed_at=None
)
self.completed_at = None
except ImportError:
pass
if self._state.adding:
# Get the maximum display_id value from the database
last_id = IssueSequence.objects.filter(project=self.project).aggregate(
largest=models.Max("sequence")
)["largest"]
@@ -75,16 +125,16 @@ class Issue(ProjectBaseModel):
# If it isn't none, just use the last ID specified (which should be the greatest) and add one to it
if last_id is not None:
self.sequence_id = last_id + 1
if self.state is None:
try:
from plane.db.models import State
self.state, created = State.objects.get_or_create(
project=self.project, name="Backlog"
)
except ImportError:
pass
largest_sort_order = Issue.objects.filter(
project=self.project, state=self.state
).aggregate(largest=models.Max("sort_order"))["largest"]
if largest_sort_order is not None:
self.sort_order = largest_sort_order + 10000
# If adding it to started state
if self.state.group == "started":
self.start_date = timezone.now().date()
# Strip the html tags using html parser
self.description_stripped = (
None
@@ -137,9 +187,59 @@ class IssueAssignee(ProjectBaseModel):
return f"{self.issue.name} {self.assignee.email}"
class IssueLink(ProjectBaseModel):
title = models.CharField(max_length=255, null=True)
url = models.URLField()
issue = models.ForeignKey(
"db.Issue", on_delete=models.CASCADE, related_name="issue_link"
)
metadata = models.JSONField(default=dict)
class Meta:
verbose_name = "Issue Link"
verbose_name_plural = "Issue Links"
db_table = "issue_links"
ordering = ("-created_at",)
def __str__(self):
return f"{self.issue.name} {self.url}"
def get_upload_path(instance, filename):
return f"{instance.workspace.id}/{uuid4().hex}-{filename}"
def file_size(value):
# File limit check is only for cloud hosted
if value.size > settings.FILE_SIZE_LIMIT:
raise ValidationError("File too large. Size should not exceed 5 MB.")
class IssueAttachment(ProjectBaseModel):
attributes = models.JSONField(default=dict)
asset = models.FileField(
upload_to=get_upload_path,
validators=[
file_size,
],
)
issue = models.ForeignKey(
"db.Issue", on_delete=models.CASCADE, related_name="issue_attachment"
)
class Meta:
verbose_name = "Issue Attachment"
verbose_name_plural = "Issue Attachments"
db_table = "issue_attachments"
ordering = ("-created_at",)
def __str__(self):
return f"{self.issue.name} {self.asset}"
class IssueActivity(ProjectBaseModel):
issue = models.ForeignKey(
Issue, on_delete=models.CASCADE, related_name="issue_activity"
Issue, on_delete=models.SET_NULL, null=True, related_name="issue_activity"
)
verb = models.CharField(max_length=255, verbose_name="Action", default="created")
field = models.CharField(
@@ -196,8 +296,8 @@ class TimelineIssue(ProjectBaseModel):
class IssueComment(ProjectBaseModel):
comment_stripped = models.TextField(verbose_name="Comment", blank=True)
comment_json = models.JSONField(blank=True, null=True)
comment_html = models.TextField(blank=True)
comment_json = models.JSONField(blank=True, default=dict)
comment_html = models.TextField(blank=True, default="<p></p>")
attachments = ArrayField(models.URLField(), size=10, blank=True, default=list)
issue = models.ForeignKey(Issue, on_delete=models.CASCADE)
# System can also create comment
@@ -246,7 +346,6 @@ class IssueProperty(ProjectBaseModel):
class Label(ProjectBaseModel):
parent = models.ForeignKey(
"self",
on_delete=models.CASCADE,
@@ -256,9 +355,10 @@ class Label(ProjectBaseModel):
)
name = models.CharField(max_length=255)
description = models.TextField(blank=True)
colour = models.CharField(max_length=255, blank=True)
color = models.CharField(max_length=255, blank=True)
class Meta:
unique_together = ["name", "project"]
verbose_name = "Label"
verbose_name_plural = "Labels"
db_table = "labels"
@@ -269,7 +369,6 @@ class Label(ProjectBaseModel):
class IssueLabel(ProjectBaseModel):
issue = models.ForeignKey(
"db.Issue", on_delete=models.CASCADE, related_name="label_issue"
)
@@ -288,7 +387,6 @@ class IssueLabel(ProjectBaseModel):
class IssueSequence(ProjectBaseModel):
issue = models.ForeignKey(
Issue, on_delete=models.SET_NULL, related_name="issue_sequence", null=True
)
@@ -305,7 +403,6 @@ class IssueSequence(ProjectBaseModel):
# TODO: Find a better method to save the model
@receiver(post_save, sender=Issue)
def create_issue_sequence(sender, instance, created, **kwargs):
if created:
IssueSequence.objects.create(
issue=instance, sequence=instance.sequence_id, project=instance.project
+32 -7
View File
@@ -7,7 +7,6 @@ from . import ProjectBaseModel
class Module(ProjectBaseModel):
name = models.CharField(max_length=255, verbose_name="Module Name")
description = models.TextField(verbose_name="Module Description", blank=True)
description_text = models.JSONField(
@@ -40,7 +39,7 @@ class Module(ProjectBaseModel):
through="ModuleMember",
through_fields=("module", "member"),
)
view_props = models.JSONField(default=dict)
class Meta:
unique_together = ["name", "project"]
@@ -54,7 +53,6 @@ class Module(ProjectBaseModel):
class ModuleMember(ProjectBaseModel):
module = models.ForeignKey("db.Module", on_delete=models.CASCADE)
member = models.ForeignKey("db.User", on_delete=models.CASCADE)
@@ -70,7 +68,6 @@ class ModuleMember(ProjectBaseModel):
class ModuleIssue(ProjectBaseModel):
module = models.ForeignKey(
"db.Module", on_delete=models.CASCADE, related_name="issue_module"
)
@@ -89,10 +86,12 @@ class ModuleIssue(ProjectBaseModel):
class ModuleLink(ProjectBaseModel):
title = models.CharField(max_length=255, null=True)
url = models.URLField()
module = models.ForeignKey(Module, on_delete=models.CASCADE, related_name="link_module")
module = models.ForeignKey(
Module, on_delete=models.CASCADE, related_name="link_module"
)
metadata = models.JSONField(default=dict)
class Meta:
verbose_name = "Module Link"
@@ -101,4 +100,30 @@ class ModuleLink(ProjectBaseModel):
ordering = ("-created_at",)
def __str__(self):
return f"{self.module.name} {self.url}"
return f"{self.module.name} {self.url}"
class ModuleFavorite(ProjectBaseModel):
"""_summary_
ModuleFavorite (model): To store all the module favorite of the user
"""
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="module_favorites",
)
module = models.ForeignKey(
"db.Module", on_delete=models.CASCADE, related_name="module_favorites"
)
class Meta:
unique_together = ["module", "user"]
verbose_name = "Module Favorite"
verbose_name_plural = "Module Favorites"
db_table = "module_favorites"
ordering = ("-created_at",)
def __str__(self):
"""Return user and the module"""
return f"{self.user.email} <{self.module.name}>"

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