forked from github/plane
refactor: MobX store structure (#3228)
* query params from router as computed * chore: setup workspace store and sub-stores * chore: update router query store * chore: update store types * fix: pages store changes * change observables and retain object reference * fix build errors * chore: changed the structure of workspace, project, cycle, module and pages * fix: pages fixes * fix: merge conflicts resolved * chore: fixed workspace list * chore: update workspace store accroding to the new response * fix: adding page details to store * fix: adding new contexts and providers * dev: issues store and filters in new store * dev: optimised the issue fetching in issue base store * chore: project views id mapped * update lodash set to directly run inside runInaction since it mutates the object * fix: context changes * code refactor kanban for better mainatinability * optimize Kanban for performance * chore: implemented hooks for all the created stores * chore: removed bridge id * css change and refactor * chore: update cycle store structure * chore: implement the new label root store * chore: removed object structure * chore: implement project view hook * Kanban new store implementation for project issues * fix project root for kanban * feat: workspace and project members endpoint (#3092) * fix: merge conflicts resolved * issue properties optimization * chore: user stores * chore: create new store context and update hooks * chore: setup inbox store and implement router store * chore: initialize and implement project estimate store * chore: initialize global view store * kanban and list view optimization * chore: use new cycle and module store. (#3172) * chore: use new cycle and module store. * chore: minor improvements. * Revert "chore: merge develop" This reverts commit 9d2e0e29e7370b55b48fc2fee4fd126093a6cc48, reversing changes made to 9595493c42be3ea0ddd17b23a0b124555075c062. * chore: implement useGlobalView hook * refactor: projects & inbox store instances (#3179) * refactor: projects & inbox store instances * fix: formatting * fix: action usage * chore: implement useProjectState hook. (#3185) * dev: issue, cycle store optimiation * fix build for code * dev: removed dummy variables * dev: issue store * fix: adding todos * chore: removing legacy store * dev: issues store types and typos * chore: cycle module user properties * fix legacy store deletion issues * chore: change POST to PATCH * fix issues rendering for project root * chore: removed workspace details in workpsaceinvite * chore: created models for display properties * chore: setup member store and implement it everywhere * refactor: module store (#3202) * refactor: cycle store (#3192) * refator: cycle store * some more improvements. * chore: implement useLabel hook. (#3190) * refactor: inbox & project related stores. (#3193) * refactor: inbox -> filter, issues, inoxes & project -> publish, projects store * refactor: workspace-project-id name * fix kanban dropdown overlapping issue * fix kanban layout minor re rendering * chore: implement useMember store everywhere * chore: create and implement editor mention store * chore: removed the issue view user property * chore: created at id changed * dev: segway intgegration (#3132) * feat: implemented rabbitmq * dev: initialize segway with queue setup * dev: import refactors * dev: create communication with the segway server * dev: create new workers * dev: create celery node queue for consuming messages from django * dev: node to celery connection * dev: setup segway and django connection * dev: refactor the structure and add database integration to the app * dev: add external id and source added --------- Co-authored-by: NarayanBavisetti <narayan3119@gmail.com> * dev: github importer (#3205) * dev: initiate github import * dev: github importer all issues import * dev: github comments and links for the imported issues * dev: update controller to use logger and spread the resultData in getAllEntities * dev: removed console log * dev: update code structure and sync functions * dev: updated retry logic when exception * dev: add imported data as well * dev: update logger and repo fetch * dev: update jira integration to new structure * dev: update migrations * dev: update the reason field * chore: workspace object id removed * chore: view's creation fixed * refactor: mobx store improvements. (#3213) * fix: state and label errors * chore: remove legacy code * fix: branch build fix (#3214) * branch build fix for release-* in case of space,backend,proxy * fixes * chore: update store names and types * fix - file size limit not work on plane.settings.production (#3160) * fix - file size limit not work on plane.settings.production * fix - file size limit not work on plane.settings.production * fix - file size limit not work on plane.settings.production, move to common.py --------- Co-authored-by: luanduongtel4vn <hoangluan@tel4vn.com> Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com> * style: instance admin email settings ui & ux update. (#3186) * refactor: use-user-auth hook (#3215) * refactor: use-user-auth hook * fix: user store currentUserLoader * refactor: project-view & application related stores (#3207) * refactor: project-view & application related stores * rename: projectViews -> projectViewIds * fix: project-view favourite state in store * chore: remove unnecessary hooks and contexts (#3217) * chore: update issue assignee property component * chore: bug fixes & improvement (#3218) * chore: draft issue validation added to prevent saving empty or whitespace title * chore: resolve scrolling issue in page empty state * chore: kanban layout quick add issue improvement * fix: bugs & improvements (#3189) * fix: workspace invitation modal form values reset * fix: profile sidebar avatar letter * [refactor] Editor code refactoring (#3194) * removed relative imports from editor core * Update issue widget file paths and imports to use kebab case instead of camel case, to align with coding conventions and improve consistency. * Update Tiptap core and extensions versions to 2.1.13 and Tiptap React version to 2.1.13. Update Tiptap table imports to use the new location in package @tiptap/pm/tables. Update AlertLabel component to use the new type definition for LucideIcon. * updated lock file * removed default exports from editor/core * fixed injecting css into the core package itself * seperated css code to have single source of origin wrt to the package * removed default imports from document editor * all instances using index as key while mapping fixed * Update Lite Text Editor package.json to remove @plane/editor-types as a dependency. Update Lite Text Editor index.ts to update the import of IMentionSuggestion and IMentionHighlight from @plane/editor-types to @plane/editor-core. Update Lite Text Editor ui/index.tsx to update the import of UploadImage, DeleteImage, IMentionSuggestion, and RestoreImage from @plane/editor-types to @plane/editor-core. Update Lite Text Editor ui/menus/fixed-menu/index.tsx to update the import of UploadImage from @plane/editor-types to @plane/editor-core. Update turbo.json to remove @plane/editor-types#build as a dependency for @plane/lite-text-editor#build, @plane/rich-text-editor#build, and @plane/document-editor#build. * Remove deprecated import and adjust tippy.js usage in the slash-commands.tsx file of the editor extensions package. * Update dependencies in `rich-text-editor/package.json`, remove `@plane/editor-types` and add `@plane/editor-core` in `rich-text-editor/src/index.ts`, and update imports in `rich-text-editor/src/ui/extensions/index.tsx` and `rich-text-editor/src/ui/index.tsx` to use `@plane/editor-core` instead of `@plane/editor-types`. * Update package.json dependencies and add new types for image deletion, upload, restore, mention highlight, mention suggestion, and slash command item. * Update import statements in various files to use the new package "@plane/editor-core" instead of "@plane/editor-types". * fixed document editor to follow conventions * Refactor imports in the Rich Text Editor package to use relative paths instead of absolute paths. - Updated imports in `index.ts`, `ui/index.tsx`, and `ui/menus/bubble-menu/index.tsx` to use relative paths. - Updated `tsconfig.json` to include the `baseUrl` compiler option and adjust the `include` and `exclude` paths. * Refactor Lite Text Editor code to use relative import paths instead of absolute import paths. * Added LucideIconType to the exports in index.ts for use in other files. Created a new file lucide-icon.ts which contains the type LucideIconType. Updated the icon type in HeadingOneItem in menu-items/index.tsx to use LucideIconType. Updated the Icon type in AlertLabel in alert-label.tsx to use LucideIconType. Updated the Icon type in VerticalDropdownItemProps in vertical-dropdown-menu.tsx to use LucideIconType. Updated the Icon type in BubbleMenuItem in fixed-menu/index.tsx to use LucideIconType. Deleted the file tooltip.tsx since it is no longer used. Updated the Icon type in BubbleMenuItem in bubble-menu/index.tsx to use LucideIconType. * ♻️ refactor: simplify rendering logic in slash-commands.tsx The rendering logic in the file "slash-commands.tsx" has been simplified. Previously, the code used inline positioning for the popup, but it has now been removed. Instead of appending the popup to the document body, it is now appended to the element with the ID "tiptap-container". The "flip" option has also been removed. These changes have improved the readability and maintainability of the code. * fixed build errors caused due to core's internal imports * regression: fixed pages not saving issue and not duplicating with proper content issue * build: Update @tiptap dependencies Updated the @tiptap dependencies in the package.json files of `document-editor`, `extensions`, and `rich-text-editor` packages to version 2.1.13. * 🚑 fix: Correct appendTo selector in slash-commands.tsx Update the `appendTo` function call in `slash-commands.tsx` to use the correct selector `#editor-container` instead of `#tiptap-container`. This ensures that the component is appended to the appropriate container in the editor extension. Note: The commit message assumes that the change is a fix for an issue or error. If it's not a fix, please provide more context so that an appropriate commit type can be determined. * style: email placeholder changed across the platform (#3206) * style: email placeholder changed across the platform * fix: placeholder text * dev: updated new filter endpoints and restructured issue and issue filters store * implement issues and replace useMobxStore * remove all store legacy references * dev: updated the orderby and subgroupby filters data * dev:added projectId in issue filters for consistency * fix more build errors * dev: updated profile issues * dev: removed store legacy * dev: active cycle issues in the cycle issue store * fix additional build errors and memoize issueActions in each layout component * change store enums * remove all useMobxStore references * fix more build errors * dev: reverted workspace invitation * fix: build errors and warnings * fix: optimistic update for instant operations (#3221) * fix: update functions failed case * fix: typo * chore: revert back to optimistic update approach for all `update related actions` (#3219) * fix: merge conflicts resolved * chore: update memberMap logic in components * add assignees to kanban groups and properties * dev: migration fixes * final bit of optimization on list view * change all TODOs that are to be done before this release to FIXME * change base Kanban TODOs that are to be done before this release to FIXME * dev: add fields and expand for app serializers * dev: issue detail store * dev: update issue serializer to return object ids * fix: Instance key added in settings and converted issues list api to arry instead of dict * fix: removing segway files * dev: control expand through query parameters * revert: github importer * Revert "dev: segway intgegration (#3132)" This reverts commit 1cc18a09156d1790d114061dbac8c901e0f2754c. * dev: remove migrations for segway * dev: issue structure change and created workspacebasemodel * dev: issue detail serializer * fix: changed workspace dict * dev: updated new issue structure * chore: build fix * dev: issue detail store refactor * dev: created list endpoint for issue-relation * dev: added issue attachments in issue detail store * dev: added issue activity computed * fix: build error * chore: peek overview modal context added * chore: build error fix * dev: added sub_issues in issue details store * dev: added complete issue serializer for sub issues * dev: resolved type errors in issue root store * dev: changed the issue relation structure * chore: new global dropdowns * chore: build error fix * chore: cycle and module selection if disabled * dev: removed unnecessary code from the workspace root * chore: build error fix * chore: issue relation remove endpoint * fix: build error * dev: typos and implemented issue relation store * fix: yarn lock updated * style: update the UI of all the dropdowns * fix: state store fixes * fix: key issue * fix: state store console logs removed * refactor: member dropdowns * fix: moving types to packages * fix: dropdown arrow positioning * dev: removed logs * style: label dropdown * chore: restrict description notifications * chore: description changes * chore: update spreadsheet layout dropdowns * fix: build errors * chore: duplicate key change * fix: ui bugs * chore: relation activity change * chore: comment activity changes * chore: blocking issue removal * chore: added project_id for relation * chore: issue relation store and component * chore: issue redirection issue in the issue realtion in detail page * chore: created activity changed * chore: issue links new store implementation on the issue detail * chore: issue relation deletion acitivity changed * chore: issue attachments new store implementation on the issue detail * chore: workspace level issues * fix: build errors --------- Co-authored-by: rahulramesha <rahulramesham@gmail.com> Co-authored-by: gurusainath <gurusainath007@gmail.com> Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com> Co-authored-by: NarayanBavisetti <narayan3119@gmail.com> Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com> Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com> Co-authored-by: Lakhan Baheti <94619783+1akhanBaheti@users.noreply.github.com> Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com> Co-authored-by: Manish Gupta <59428681+mguptahub@users.noreply.github.com> Co-authored-by: Hoang Luan <luandnh98@gmail.com> Co-authored-by: luanduongtel4vn <hoangluan@tel4vn.com> Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Co-authored-by: M. Palanikannan <73993394+Palanikannan1437@users.noreply.github.com> Co-authored-by: pablohashescobar <nikhilschacko@gmail.com> Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so>
This commit is contained in:
parent
1539340113
commit
804b7d8663
13
.github/workflows/create-sync-pr.yml
vendored
13
.github/workflows/create-sync-pr.yml
vendored
@ -3,7 +3,7 @@ name: Create Sync Action
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- preview
|
- develop # Change this to preview
|
||||||
types:
|
types:
|
||||||
- closed
|
- closed
|
||||||
env:
|
env:
|
||||||
@ -33,14 +33,23 @@ jobs:
|
|||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install gh -y
|
sudo apt install gh -y
|
||||||
|
|
||||||
- name: Push Changes to Target Repo
|
- name: Create Pull Request
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
GH_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
TARGET_REPO="${{ secrets.SYNC_TARGET_REPO_NAME }}"
|
TARGET_REPO="${{ secrets.SYNC_TARGET_REPO_NAME }}"
|
||||||
TARGET_BRANCH="${{ secrets.SYNC_TARGET_BRANCH_NAME }}"
|
TARGET_BRANCH="${{ secrets.SYNC_TARGET_BRANCH_NAME }}"
|
||||||
|
TARGET_BASE_BRANCH="${{ secrets.SYNC_TARGET_BASE_BRANCH_NAME }}"
|
||||||
SOURCE_BRANCH="${{ env.SOURCE_BRANCH_NAME }}"
|
SOURCE_BRANCH="${{ env.SOURCE_BRANCH_NAME }}"
|
||||||
|
|
||||||
git checkout $SOURCE_BRANCH
|
git checkout $SOURCE_BRANCH
|
||||||
git remote add target-origin "https://$GH_TOKEN@github.com/$TARGET_REPO.git"
|
git remote add target-origin "https://$GH_TOKEN@github.com/$TARGET_REPO.git"
|
||||||
git push target-origin $SOURCE_BRANCH:$TARGET_BRANCH
|
git push target-origin $SOURCE_BRANCH:$TARGET_BRANCH
|
||||||
|
|
||||||
|
PR_TITLE=${{secrets.SYNC_PR_TITLE}}
|
||||||
|
|
||||||
|
gh pr create \
|
||||||
|
--base $TARGET_BASE_BRANCH \
|
||||||
|
--head $TARGET_BRANCH \
|
||||||
|
--title "$PR_TITLE" \
|
||||||
|
--repo $TARGET_REPO
|
||||||
|
@ -17,6 +17,7 @@ from .workspace import (
|
|||||||
WorkspaceThemeSerializer,
|
WorkspaceThemeSerializer,
|
||||||
WorkspaceMemberAdminSerializer,
|
WorkspaceMemberAdminSerializer,
|
||||||
WorkspaceMemberMeSerializer,
|
WorkspaceMemberMeSerializer,
|
||||||
|
WorkspaceUserPropertiesSerializer,
|
||||||
)
|
)
|
||||||
from .project import (
|
from .project import (
|
||||||
ProjectSerializer,
|
ProjectSerializer,
|
||||||
@ -31,6 +32,7 @@ from .project import (
|
|||||||
ProjectDeployBoardSerializer,
|
ProjectDeployBoardSerializer,
|
||||||
ProjectMemberAdminSerializer,
|
ProjectMemberAdminSerializer,
|
||||||
ProjectPublicMemberSerializer,
|
ProjectPublicMemberSerializer,
|
||||||
|
ProjectMemberRoleSerializer,
|
||||||
)
|
)
|
||||||
from .state import StateSerializer, StateLiteSerializer
|
from .state import StateSerializer, StateLiteSerializer
|
||||||
from .view import GlobalViewSerializer, IssueViewSerializer, IssueViewFavoriteSerializer
|
from .view import GlobalViewSerializer, IssueViewSerializer, IssueViewFavoriteSerializer
|
||||||
@ -39,6 +41,7 @@ from .cycle import (
|
|||||||
CycleIssueSerializer,
|
CycleIssueSerializer,
|
||||||
CycleFavoriteSerializer,
|
CycleFavoriteSerializer,
|
||||||
CycleWriteSerializer,
|
CycleWriteSerializer,
|
||||||
|
CycleUserPropertiesSerializer,
|
||||||
)
|
)
|
||||||
from .asset import FileAssetSerializer
|
from .asset import FileAssetSerializer
|
||||||
from .issue import (
|
from .issue import (
|
||||||
@ -61,6 +64,8 @@ from .issue import (
|
|||||||
IssueRelationSerializer,
|
IssueRelationSerializer,
|
||||||
RelatedIssueSerializer,
|
RelatedIssueSerializer,
|
||||||
IssuePublicSerializer,
|
IssuePublicSerializer,
|
||||||
|
IssueRelationLiteSerializer,
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from .module import (
|
from .module import (
|
||||||
@ -69,6 +74,7 @@ from .module import (
|
|||||||
ModuleIssueSerializer,
|
ModuleIssueSerializer,
|
||||||
ModuleLinkSerializer,
|
ModuleLinkSerializer,
|
||||||
ModuleFavoriteSerializer,
|
ModuleFavoriteSerializer,
|
||||||
|
ModuleUserPropertiesSerializer,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .api import APITokenSerializer, APITokenReadSerializer
|
from .api import APITokenSerializer, APITokenReadSerializer
|
||||||
|
@ -9,11 +9,12 @@ class DynamicBaseSerializer(BaseSerializer):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
# If 'fields' is provided in the arguments, remove it and store it separately.
|
# If 'fields' is provided in the arguments, remove it and store it separately.
|
||||||
# This is done so as not to pass this custom argument up to the superclass.
|
# This is done so as not to pass this custom argument up to the superclass.
|
||||||
fields = kwargs.pop("fields", None)
|
fields = kwargs.pop("fields", [])
|
||||||
|
self.expand = kwargs.pop("expand", []) or []
|
||||||
|
fields = self.expand
|
||||||
|
|
||||||
# Call the initialization of the superclass.
|
# Call the initialization of the superclass.
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
# If 'fields' was provided, filter the fields of the serializer accordingly.
|
# If 'fields' was provided, filter the fields of the serializer accordingly.
|
||||||
if fields is not None:
|
if fields is not None:
|
||||||
self.fields = self._filter_fields(fields)
|
self.fields = self._filter_fields(fields)
|
||||||
@ -47,12 +48,91 @@ class DynamicBaseSerializer(BaseSerializer):
|
|||||||
elif isinstance(item, dict):
|
elif isinstance(item, dict):
|
||||||
allowed.append(list(item.keys())[0])
|
allowed.append(list(item.keys())[0])
|
||||||
|
|
||||||
# Convert the current serializer's fields and the allowed fields to sets.
|
for field in allowed:
|
||||||
existing = set(self.fields)
|
if field not in self.fields:
|
||||||
allowed = set(allowed)
|
from . import (
|
||||||
|
WorkspaceLiteSerializer,
|
||||||
|
ProjectLiteSerializer,
|
||||||
|
UserLiteSerializer,
|
||||||
|
StateLiteSerializer,
|
||||||
|
IssueSerializer,
|
||||||
|
LabelSerializer,
|
||||||
|
CycleIssueSerializer,
|
||||||
|
IssueFlatSerializer,
|
||||||
|
)
|
||||||
|
|
||||||
# Remove fields from the serializer that aren't in the 'allowed' list.
|
# Expansion mapper
|
||||||
for field_name in (existing - allowed):
|
expansion = {
|
||||||
self.fields.pop(field_name)
|
"user": UserLiteSerializer,
|
||||||
|
"workspace": WorkspaceLiteSerializer,
|
||||||
|
"project": ProjectLiteSerializer,
|
||||||
|
"default_assignee": UserLiteSerializer,
|
||||||
|
"project_lead": UserLiteSerializer,
|
||||||
|
"state": StateLiteSerializer,
|
||||||
|
"created_by": UserLiteSerializer,
|
||||||
|
"issue": IssueSerializer,
|
||||||
|
"actor": UserLiteSerializer,
|
||||||
|
"owned_by": UserLiteSerializer,
|
||||||
|
"members": UserLiteSerializer,
|
||||||
|
"assignees": UserLiteSerializer,
|
||||||
|
"labels": LabelSerializer,
|
||||||
|
"issue_cycle": CycleIssueSerializer,
|
||||||
|
"parent": IssueFlatSerializer,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.fields[field] = expansion[field](many=True if field in ["members", "assignees", "labels", "issue_cycle"] else False)
|
||||||
|
|
||||||
return self.fields
|
return self.fields
|
||||||
|
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
response = super().to_representation(instance)
|
||||||
|
|
||||||
|
# Ensure 'expand' is iterable before processing
|
||||||
|
if self.expand:
|
||||||
|
for expand in self.expand:
|
||||||
|
if expand in self.fields:
|
||||||
|
# Import all the expandable serializers
|
||||||
|
from . import (
|
||||||
|
WorkspaceLiteSerializer,
|
||||||
|
ProjectLiteSerializer,
|
||||||
|
UserLiteSerializer,
|
||||||
|
StateLiteSerializer,
|
||||||
|
IssueSerializer,
|
||||||
|
LabelSerializer,
|
||||||
|
CycleIssueSerializer,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Expansion mapper
|
||||||
|
expansion = {
|
||||||
|
"user": UserLiteSerializer,
|
||||||
|
"workspace": WorkspaceLiteSerializer,
|
||||||
|
"project": ProjectLiteSerializer,
|
||||||
|
"default_assignee": UserLiteSerializer,
|
||||||
|
"project_lead": UserLiteSerializer,
|
||||||
|
"state": StateLiteSerializer,
|
||||||
|
"created_by": UserLiteSerializer,
|
||||||
|
"issue": IssueSerializer,
|
||||||
|
"actor": UserLiteSerializer,
|
||||||
|
"owned_by": UserLiteSerializer,
|
||||||
|
"members": UserLiteSerializer,
|
||||||
|
"assignees": UserLiteSerializer,
|
||||||
|
"labels": LabelSerializer,
|
||||||
|
"issue_cycle": CycleIssueSerializer,
|
||||||
|
}
|
||||||
|
# Check if field in expansion then expand the field
|
||||||
|
if expand in expansion:
|
||||||
|
if isinstance(response.get(expand), list):
|
||||||
|
exp_serializer = expansion[expand](
|
||||||
|
getattr(instance, expand), many=True
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
exp_serializer = expansion[expand](
|
||||||
|
getattr(instance, expand)
|
||||||
|
)
|
||||||
|
response[expand] = exp_serializer.data
|
||||||
|
else:
|
||||||
|
# You might need to handle this case differently
|
||||||
|
response[expand] = getattr(instance, f"{expand}_id", None)
|
||||||
|
|
||||||
|
return response
|
||||||
|
@ -7,7 +7,7 @@ from .user import UserLiteSerializer
|
|||||||
from .issue import IssueStateSerializer
|
from .issue import IssueStateSerializer
|
||||||
from .workspace import WorkspaceLiteSerializer
|
from .workspace import WorkspaceLiteSerializer
|
||||||
from .project import ProjectLiteSerializer
|
from .project import ProjectLiteSerializer
|
||||||
from plane.db.models import Cycle, CycleIssue, CycleFavorite
|
from plane.db.models import Cycle, CycleIssue, CycleFavorite, CycleUserProperties
|
||||||
|
|
||||||
|
|
||||||
class CycleWriteSerializer(BaseSerializer):
|
class CycleWriteSerializer(BaseSerializer):
|
||||||
@ -106,3 +106,15 @@ class CycleFavoriteSerializer(BaseSerializer):
|
|||||||
"project",
|
"project",
|
||||||
"user",
|
"user",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class CycleUserPropertiesSerializer(BaseSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = CycleUserProperties
|
||||||
|
fields = "__all__"
|
||||||
|
read_only_fields = [
|
||||||
|
"workspace",
|
||||||
|
"project",
|
||||||
|
"cycle"
|
||||||
|
"user",
|
||||||
|
]
|
@ -49,7 +49,6 @@ class IssueStateInboxSerializer(BaseSerializer):
|
|||||||
label_details = LabelLiteSerializer(read_only=True, source="labels", many=True)
|
label_details = LabelLiteSerializer(read_only=True, source="labels", many=True)
|
||||||
assignee_details = UserLiteSerializer(read_only=True, source="assignees", many=True)
|
assignee_details = UserLiteSerializer(read_only=True, source="assignees", many=True)
|
||||||
sub_issues_count = serializers.IntegerField(read_only=True)
|
sub_issues_count = serializers.IntegerField(read_only=True)
|
||||||
bridge_id = serializers.UUIDField(read_only=True)
|
|
||||||
issue_inbox = InboxIssueLiteSerializer(read_only=True, many=True)
|
issue_inbox = InboxIssueLiteSerializer(read_only=True, many=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -278,17 +278,28 @@ class IssueLabelSerializer(BaseSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class IssueRelationLiteSerializer(DynamicBaseSerializer):
|
||||||
|
project_id = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||||
|
class Meta:
|
||||||
|
model = Issue
|
||||||
|
fields = [
|
||||||
|
"id",
|
||||||
|
"project_id",
|
||||||
|
"sequence_id",
|
||||||
|
]
|
||||||
|
read_only_fields = [
|
||||||
|
"workspace",
|
||||||
|
"project",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class IssueRelationSerializer(BaseSerializer):
|
class IssueRelationSerializer(BaseSerializer):
|
||||||
issue_detail = IssueProjectLiteSerializer(read_only=True, source="related_issue")
|
issue_detail = IssueRelationLiteSerializer(read_only=True, source="related_issue")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IssueRelation
|
model = IssueRelation
|
||||||
fields = [
|
fields = [
|
||||||
"issue_detail",
|
"issue_detail",
|
||||||
"relation_type",
|
|
||||||
"related_issue",
|
|
||||||
"issue",
|
|
||||||
"id"
|
|
||||||
]
|
]
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
"workspace",
|
"workspace",
|
||||||
@ -296,16 +307,12 @@ class IssueRelationSerializer(BaseSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
class RelatedIssueSerializer(BaseSerializer):
|
class RelatedIssueSerializer(BaseSerializer):
|
||||||
issue_detail = IssueProjectLiteSerializer(read_only=True, source="issue")
|
issue_detail = IssueRelationLiteSerializer(read_only=True, source="issue")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IssueRelation
|
model = IssueRelation
|
||||||
fields = [
|
fields = [
|
||||||
"issue_detail",
|
"issue_detail",
|
||||||
"relation_type",
|
|
||||||
"related_issue",
|
|
||||||
"issue",
|
|
||||||
"id"
|
|
||||||
]
|
]
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
"workspace",
|
"workspace",
|
||||||
@ -512,7 +519,6 @@ class IssueStateSerializer(DynamicBaseSerializer):
|
|||||||
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
||||||
assignee_details = UserLiteSerializer(read_only=True, source="assignees", many=True)
|
assignee_details = UserLiteSerializer(read_only=True, source="assignees", many=True)
|
||||||
sub_issues_count = serializers.IntegerField(read_only=True)
|
sub_issues_count = serializers.IntegerField(read_only=True)
|
||||||
bridge_id = serializers.UUIDField(read_only=True)
|
|
||||||
attachment_count = serializers.IntegerField(read_only=True)
|
attachment_count = serializers.IntegerField(read_only=True)
|
||||||
link_count = serializers.IntegerField(read_only=True)
|
link_count = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
@ -521,32 +527,58 @@ class IssueStateSerializer(DynamicBaseSerializer):
|
|||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
class IssueSerializer(BaseSerializer):
|
class IssueSerializer(DynamicBaseSerializer):
|
||||||
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
# ids
|
||||||
state_detail = StateSerializer(read_only=True, source="state")
|
project_id = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||||
parent_detail = IssueStateFlatSerializer(read_only=True, source="parent")
|
state_id = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||||
label_details = LabelSerializer(read_only=True, source="labels", many=True)
|
parent_id = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||||
assignee_details = UserLiteSerializer(read_only=True, source="assignees", many=True)
|
cycle_id = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||||
related_issues = IssueRelationSerializer(read_only=True, source="issue_relation", many=True)
|
module_id = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||||
issue_relations = RelatedIssueSerializer(read_only=True, source="issue_related", many=True)
|
|
||||||
issue_cycle = IssueCycleDetailSerializer(read_only=True)
|
# Many to many
|
||||||
issue_module = IssueModuleDetailSerializer(read_only=True)
|
label_ids = serializers.PrimaryKeyRelatedField(read_only=True, many=True, source="labels")
|
||||||
issue_link = IssueLinkSerializer(read_only=True, many=True)
|
assignee_ids = serializers.PrimaryKeyRelatedField(read_only=True, many=True, source="assignees")
|
||||||
issue_attachment = IssueAttachmentSerializer(read_only=True, many=True)
|
|
||||||
|
# Count items
|
||||||
sub_issues_count = serializers.IntegerField(read_only=True)
|
sub_issues_count = serializers.IntegerField(read_only=True)
|
||||||
issue_reactions = IssueReactionSerializer(read_only=True, many=True)
|
attachment_count = serializers.IntegerField(read_only=True)
|
||||||
|
link_count = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
|
# is
|
||||||
|
is_subscribed = serializers.BooleanField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Issue
|
model = Issue
|
||||||
fields = "__all__"
|
fields = [
|
||||||
read_only_fields = [
|
"id",
|
||||||
"workspace",
|
"name",
|
||||||
"project",
|
"state_id",
|
||||||
"created_by",
|
"description_html",
|
||||||
"updated_by",
|
"sort_order",
|
||||||
|
"completed_at",
|
||||||
|
"estimate_point",
|
||||||
|
"priority",
|
||||||
|
"start_date",
|
||||||
|
"target_date",
|
||||||
|
"sequence_id",
|
||||||
|
"project_id",
|
||||||
|
"parent_id",
|
||||||
|
"cycle_id",
|
||||||
|
"module_id",
|
||||||
|
"label_ids",
|
||||||
|
"assignee_ids",
|
||||||
|
"sub_issues_count",
|
||||||
"created_at",
|
"created_at",
|
||||||
"updated_at",
|
"updated_at",
|
||||||
|
"created_by",
|
||||||
|
"updated_by",
|
||||||
|
"attachment_count",
|
||||||
|
"link_count",
|
||||||
|
"is_subscribed",
|
||||||
|
"is_draft",
|
||||||
|
"archived_at",
|
||||||
]
|
]
|
||||||
|
read_only_fields = fields
|
||||||
|
|
||||||
|
|
||||||
class IssueLiteSerializer(DynamicBaseSerializer):
|
class IssueLiteSerializer(DynamicBaseSerializer):
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from .base import BaseSerializer
|
from .base import BaseSerializer, DynamicBaseSerializer
|
||||||
from .user import UserLiteSerializer
|
from .user import UserLiteSerializer
|
||||||
from .project import ProjectLiteSerializer
|
from .project import ProjectLiteSerializer
|
||||||
from .workspace import WorkspaceLiteSerializer
|
from .workspace import WorkspaceLiteSerializer
|
||||||
@ -14,6 +14,7 @@ from plane.db.models import (
|
|||||||
ModuleIssue,
|
ModuleIssue,
|
||||||
ModuleLink,
|
ModuleLink,
|
||||||
ModuleFavorite,
|
ModuleFavorite,
|
||||||
|
ModuleUserProperties,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -159,7 +160,7 @@ class ModuleLinkSerializer(BaseSerializer):
|
|||||||
return ModuleLink.objects.create(**validated_data)
|
return ModuleLink.objects.create(**validated_data)
|
||||||
|
|
||||||
|
|
||||||
class ModuleSerializer(BaseSerializer):
|
class ModuleSerializer(DynamicBaseSerializer):
|
||||||
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
||||||
lead_detail = UserLiteSerializer(read_only=True, source="lead")
|
lead_detail = UserLiteSerializer(read_only=True, source="lead")
|
||||||
members_detail = UserLiteSerializer(read_only=True, many=True, source="members")
|
members_detail = UserLiteSerializer(read_only=True, many=True, source="members")
|
||||||
@ -196,3 +197,14 @@ class ModuleFavoriteSerializer(BaseSerializer):
|
|||||||
"project",
|
"project",
|
||||||
"user",
|
"user",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
class ModuleUserPropertiesSerializer(BaseSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = ModuleUserProperties
|
||||||
|
fields = "__all__"
|
||||||
|
read_only_fields = [
|
||||||
|
"workspace",
|
||||||
|
"project",
|
||||||
|
"module",
|
||||||
|
"user"
|
||||||
|
]
|
@ -159,6 +159,11 @@ class ProjectMemberAdminSerializer(BaseSerializer):
|
|||||||
model = ProjectMember
|
model = ProjectMember
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
|
||||||
|
class ProjectMemberRoleSerializer(DynamicBaseSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ProjectMember
|
||||||
|
fields = ("id", "role", "member", "project")
|
||||||
|
|
||||||
class ProjectMemberInviteSerializer(BaseSerializer):
|
class ProjectMemberInviteSerializer(BaseSerializer):
|
||||||
project = ProjectLiteSerializer(read_only=True)
|
project = ProjectLiteSerializer(read_only=True)
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from .base import BaseSerializer
|
from .base import BaseSerializer, DynamicBaseSerializer
|
||||||
from .workspace import WorkspaceLiteSerializer
|
from .workspace import WorkspaceLiteSerializer
|
||||||
from .project import ProjectLiteSerializer
|
from .project import ProjectLiteSerializer
|
||||||
from plane.db.models import GlobalView, IssueView, IssueViewFavorite
|
from plane.db.models import GlobalView, IssueView, IssueViewFavorite
|
||||||
@ -38,7 +38,7 @@ class GlobalViewSerializer(BaseSerializer):
|
|||||||
return super().update(instance, validated_data)
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
|
|
||||||
class IssueViewSerializer(BaseSerializer):
|
class IssueViewSerializer(DynamicBaseSerializer):
|
||||||
is_favorite = serializers.BooleanField(read_only=True)
|
is_favorite = serializers.BooleanField(read_only=True)
|
||||||
project_detail = ProjectLiteSerializer(source="project", read_only=True)
|
project_detail = ProjectLiteSerializer(source="project", read_only=True)
|
||||||
workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
|
workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from .base import BaseSerializer
|
from .base import BaseSerializer, DynamicBaseSerializer
|
||||||
from .user import UserLiteSerializer, UserAdminLiteSerializer
|
from .user import UserLiteSerializer, UserAdminLiteSerializer
|
||||||
|
|
||||||
from plane.db.models import (
|
from plane.db.models import (
|
||||||
@ -13,10 +13,11 @@ from plane.db.models import (
|
|||||||
TeamMember,
|
TeamMember,
|
||||||
WorkspaceMemberInvite,
|
WorkspaceMemberInvite,
|
||||||
WorkspaceTheme,
|
WorkspaceTheme,
|
||||||
|
WorkspaceUserProperties,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class WorkSpaceSerializer(BaseSerializer):
|
class WorkSpaceSerializer(DynamicBaseSerializer):
|
||||||
owner = UserLiteSerializer(read_only=True)
|
owner = UserLiteSerializer(read_only=True)
|
||||||
total_members = serializers.IntegerField(read_only=True)
|
total_members = serializers.IntegerField(read_only=True)
|
||||||
total_issues = serializers.IntegerField(read_only=True)
|
total_issues = serializers.IntegerField(read_only=True)
|
||||||
@ -62,7 +63,7 @@ class WorkspaceLiteSerializer(BaseSerializer):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
class WorkSpaceMemberSerializer(BaseSerializer):
|
class WorkSpaceMemberSerializer(DynamicBaseSerializer):
|
||||||
member = UserLiteSerializer(read_only=True)
|
member = UserLiteSerializer(read_only=True)
|
||||||
workspace = WorkspaceLiteSerializer(read_only=True)
|
workspace = WorkspaceLiteSerializer(read_only=True)
|
||||||
|
|
||||||
@ -78,7 +79,7 @@ class WorkspaceMemberMeSerializer(BaseSerializer):
|
|||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
class WorkspaceMemberAdminSerializer(BaseSerializer):
|
class WorkspaceMemberAdminSerializer(DynamicBaseSerializer):
|
||||||
member = UserAdminLiteSerializer(read_only=True)
|
member = UserAdminLiteSerializer(read_only=True)
|
||||||
workspace = WorkspaceLiteSerializer(read_only=True)
|
workspace = WorkspaceLiteSerializer(read_only=True)
|
||||||
|
|
||||||
@ -161,3 +162,13 @@ class WorkspaceThemeSerializer(BaseSerializer):
|
|||||||
"workspace",
|
"workspace",
|
||||||
"actor",
|
"actor",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class WorkspaceUserPropertiesSerializer(BaseSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = WorkspaceUserProperties
|
||||||
|
fields = "__all__"
|
||||||
|
read_only_fields = [
|
||||||
|
"workspace",
|
||||||
|
"user",
|
||||||
|
]
|
@ -7,6 +7,7 @@ from plane.app.views import (
|
|||||||
CycleDateCheckEndpoint,
|
CycleDateCheckEndpoint,
|
||||||
CycleFavoriteViewSet,
|
CycleFavoriteViewSet,
|
||||||
TransferCycleIssueEndpoint,
|
TransferCycleIssueEndpoint,
|
||||||
|
CycleUserPropertiesEndpoint,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -44,7 +45,7 @@ urlpatterns = [
|
|||||||
name="project-issue-cycle",
|
name="project-issue-cycle",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/<uuid:cycle_id>/cycle-issues/<uuid:pk>/",
|
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/<uuid:cycle_id>/cycle-issues/<uuid:issue_id>/",
|
||||||
CycleIssueViewSet.as_view(
|
CycleIssueViewSet.as_view(
|
||||||
{
|
{
|
||||||
"get": "retrieve",
|
"get": "retrieve",
|
||||||
@ -84,4 +85,9 @@ urlpatterns = [
|
|||||||
TransferCycleIssueEndpoint.as_view(),
|
TransferCycleIssueEndpoint.as_view(),
|
||||||
name="transfer-issues",
|
name="transfer-issues",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/<uuid:cycle_id>/user-properties/",
|
||||||
|
CycleUserPropertiesEndpoint.as_view(),
|
||||||
|
name="cycle-user-filters",
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
@ -40,7 +40,7 @@ urlpatterns = [
|
|||||||
name="inbox-issue",
|
name="inbox-issue",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"workspaces/<str:slug>/projects/<uuid:project_id>/inboxes/<uuid:inbox_id>/inbox-issues/<uuid:pk>/",
|
"workspaces/<str:slug>/projects/<uuid:project_id>/inboxes/<uuid:inbox_id>/inbox-issues/<uuid:issue_id>/",
|
||||||
InboxIssueViewSet.as_view(
|
InboxIssueViewSet.as_view(
|
||||||
{
|
{
|
||||||
"get": "retrieve",
|
"get": "retrieve",
|
||||||
|
@ -235,7 +235,7 @@ urlpatterns = [
|
|||||||
## End Comment Reactions
|
## End Comment Reactions
|
||||||
## IssueProperty
|
## IssueProperty
|
||||||
path(
|
path(
|
||||||
"workspaces/<str:slug>/projects/<uuid:project_id>/issue-display-properties/",
|
"workspaces/<str:slug>/projects/<uuid:project_id>/user-properties/",
|
||||||
IssueUserDisplayPropertyEndpoint.as_view(),
|
IssueUserDisplayPropertyEndpoint.as_view(),
|
||||||
name="project-issue-display-properties",
|
name="project-issue-display-properties",
|
||||||
),
|
),
|
||||||
@ -275,16 +275,17 @@ urlpatterns = [
|
|||||||
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-relation/",
|
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-relation/",
|
||||||
IssueRelationViewSet.as_view(
|
IssueRelationViewSet.as_view(
|
||||||
{
|
{
|
||||||
|
"get": "list",
|
||||||
"post": "create",
|
"post": "create",
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
name="issue-relation",
|
name="issue-relation",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-relation/<uuid:pk>/",
|
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/remove-relation/",
|
||||||
IssueRelationViewSet.as_view(
|
IssueRelationViewSet.as_view(
|
||||||
{
|
{
|
||||||
"delete": "destroy",
|
"post": "remove_relation",
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
name="issue-relation",
|
name="issue-relation",
|
||||||
|
@ -7,6 +7,7 @@ from plane.app.views import (
|
|||||||
ModuleLinkViewSet,
|
ModuleLinkViewSet,
|
||||||
ModuleFavoriteViewSet,
|
ModuleFavoriteViewSet,
|
||||||
BulkImportModulesEndpoint,
|
BulkImportModulesEndpoint,
|
||||||
|
ModuleUserPropertiesEndpoint
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -44,7 +45,7 @@ urlpatterns = [
|
|||||||
name="project-module-issues",
|
name="project-module-issues",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:module_id>/module-issues/<uuid:pk>/",
|
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:module_id>/module-issues/<uuid:issue_id>/",
|
||||||
ModuleIssueViewSet.as_view(
|
ModuleIssueViewSet.as_view(
|
||||||
{
|
{
|
||||||
"get": "retrieve",
|
"get": "retrieve",
|
||||||
@ -101,4 +102,9 @@ urlpatterns = [
|
|||||||
BulkImportModulesEndpoint.as_view(),
|
BulkImportModulesEndpoint.as_view(),
|
||||||
name="bulk-modules-create",
|
name="bulk-modules-create",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:module_id>/user-properties/",
|
||||||
|
ModuleUserPropertiesEndpoint.as_view(),
|
||||||
|
name="cycle-user-filters",
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
@ -18,6 +18,8 @@ from plane.app.views import (
|
|||||||
WorkspaceUserProfileEndpoint,
|
WorkspaceUserProfileEndpoint,
|
||||||
WorkspaceUserProfileIssuesEndpoint,
|
WorkspaceUserProfileIssuesEndpoint,
|
||||||
WorkspaceLabelsEndpoint,
|
WorkspaceLabelsEndpoint,
|
||||||
|
WorkspaceProjectMemberEndpoint,
|
||||||
|
WorkspaceUserPropertiesEndpoint,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -92,6 +94,11 @@ urlpatterns = [
|
|||||||
WorkSpaceMemberViewSet.as_view({"get": "list"}),
|
WorkSpaceMemberViewSet.as_view({"get": "list"}),
|
||||||
name="workspace-member",
|
name="workspace-member",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"workspaces/<str:slug>/project-members/",
|
||||||
|
WorkspaceProjectMemberEndpoint.as_view(),
|
||||||
|
name="workspace-member-roles",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"workspaces/<str:slug>/members/<uuid:pk>/",
|
"workspaces/<str:slug>/members/<uuid:pk>/",
|
||||||
WorkSpaceMemberViewSet.as_view(
|
WorkSpaceMemberViewSet.as_view(
|
||||||
@ -195,4 +202,9 @@ urlpatterns = [
|
|||||||
WorkspaceLabelsEndpoint.as_view(),
|
WorkspaceLabelsEndpoint.as_view(),
|
||||||
name="workspace-labels",
|
name="workspace-labels",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"workspaces/<str:slug>/user-properties/",
|
||||||
|
WorkspaceUserPropertiesEndpoint.as_view(),
|
||||||
|
name="workspace-user-filters",
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
@ -45,6 +45,8 @@ from .workspace import (
|
|||||||
WorkspaceUserProfileEndpoint,
|
WorkspaceUserProfileEndpoint,
|
||||||
WorkspaceUserProfileIssuesEndpoint,
|
WorkspaceUserProfileIssuesEndpoint,
|
||||||
WorkspaceLabelsEndpoint,
|
WorkspaceLabelsEndpoint,
|
||||||
|
WorkspaceProjectMemberEndpoint,
|
||||||
|
WorkspaceUserPropertiesEndpoint,
|
||||||
)
|
)
|
||||||
from .state import StateViewSet
|
from .state import StateViewSet
|
||||||
from .view import (
|
from .view import (
|
||||||
@ -59,6 +61,7 @@ from .cycle import (
|
|||||||
CycleDateCheckEndpoint,
|
CycleDateCheckEndpoint,
|
||||||
CycleFavoriteViewSet,
|
CycleFavoriteViewSet,
|
||||||
TransferCycleIssueEndpoint,
|
TransferCycleIssueEndpoint,
|
||||||
|
CycleUserPropertiesEndpoint,
|
||||||
)
|
)
|
||||||
from .asset import FileAssetEndpoint, UserAssetsEndpoint, FileAssetViewSet
|
from .asset import FileAssetEndpoint, UserAssetsEndpoint, FileAssetViewSet
|
||||||
from .issue import (
|
from .issue import (
|
||||||
@ -103,6 +106,7 @@ from .module import (
|
|||||||
ModuleIssueViewSet,
|
ModuleIssueViewSet,
|
||||||
ModuleLinkViewSet,
|
ModuleLinkViewSet,
|
||||||
ModuleFavoriteViewSet,
|
ModuleFavoriteViewSet,
|
||||||
|
ModuleUserPropertiesEndpoint,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .api import ApiTokenEndpoint
|
from .api import ApiTokenEndpoint
|
||||||
|
@ -159,6 +159,21 @@ class BaseViewSet(TimezoneMixin, ModelViewSet, BasePaginator):
|
|||||||
if resolve(self.request.path_info).url_name == "project":
|
if resolve(self.request.path_info).url_name == "project":
|
||||||
return self.kwargs.get("pk", None)
|
return self.kwargs.get("pk", None)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fields(self):
|
||||||
|
fields = [
|
||||||
|
field for field in self.request.GET.get("fields", "").split(",") if field
|
||||||
|
]
|
||||||
|
return fields if fields else None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def expand(self):
|
||||||
|
expand = [
|
||||||
|
expand for expand in self.request.GET.get("expand", "").split(",") if expand
|
||||||
|
]
|
||||||
|
return expand if expand else None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
|
class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
|
||||||
permission_classes = [
|
permission_classes = [
|
||||||
@ -239,3 +254,17 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
|
|||||||
@property
|
@property
|
||||||
def project_id(self):
|
def project_id(self):
|
||||||
return self.kwargs.get("project_id", None)
|
return self.kwargs.get("project_id", None)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fields(self):
|
||||||
|
fields = [
|
||||||
|
field for field in self.request.GET.get("fields", "").split(",") if field
|
||||||
|
]
|
||||||
|
return fields if fields else None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def expand(self):
|
||||||
|
expand = [
|
||||||
|
expand for expand in self.request.GET.get("expand", "").split(",") if expand
|
||||||
|
]
|
||||||
|
return expand if expand else None
|
||||||
|
@ -14,7 +14,7 @@ from django.db.models import (
|
|||||||
Case,
|
Case,
|
||||||
When,
|
When,
|
||||||
Value,
|
Value,
|
||||||
CharField
|
CharField,
|
||||||
)
|
)
|
||||||
from django.core import serializers
|
from django.core import serializers
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
@ -33,8 +33,9 @@ from plane.app.serializers import (
|
|||||||
CycleFavoriteSerializer,
|
CycleFavoriteSerializer,
|
||||||
IssueStateSerializer,
|
IssueStateSerializer,
|
||||||
CycleWriteSerializer,
|
CycleWriteSerializer,
|
||||||
|
CycleUserPropertiesSerializer,
|
||||||
)
|
)
|
||||||
from plane.app.permissions import ProjectEntityPermission
|
from plane.app.permissions import ProjectEntityPermission, ProjectLitePermission
|
||||||
from plane.db.models import (
|
from plane.db.models import (
|
||||||
User,
|
User,
|
||||||
Cycle,
|
Cycle,
|
||||||
@ -44,6 +45,7 @@ from plane.db.models import (
|
|||||||
IssueLink,
|
IssueLink,
|
||||||
IssueAttachment,
|
IssueAttachment,
|
||||||
Label,
|
Label,
|
||||||
|
CycleUserProperties,
|
||||||
)
|
)
|
||||||
from plane.bgtasks.issue_activites_task import issue_activity
|
from plane.bgtasks.issue_activites_task import issue_activity
|
||||||
from plane.utils.grouper import group_results
|
from plane.utils.grouper import group_results
|
||||||
@ -164,20 +166,15 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
|
|||||||
.annotate(
|
.annotate(
|
||||||
status=Case(
|
status=Case(
|
||||||
When(
|
When(
|
||||||
Q(start_date__lte=timezone.now()) & Q(end_date__gte=timezone.now()),
|
Q(start_date__lte=timezone.now())
|
||||||
then=Value("CURRENT")
|
& Q(end_date__gte=timezone.now()),
|
||||||
),
|
then=Value("CURRENT"),
|
||||||
When(
|
|
||||||
start_date__gt=timezone.now(),
|
|
||||||
then=Value("UPCOMING")
|
|
||||||
),
|
|
||||||
When(
|
|
||||||
end_date__lt=timezone.now(),
|
|
||||||
then=Value("COMPLETED")
|
|
||||||
),
|
),
|
||||||
|
When(start_date__gt=timezone.now(), then=Value("UPCOMING")),
|
||||||
|
When(end_date__lt=timezone.now(), then=Value("COMPLETED")),
|
||||||
When(
|
When(
|
||||||
Q(start_date__isnull=True) & Q(end_date__isnull=True),
|
Q(start_date__isnull=True) & Q(end_date__isnull=True),
|
||||||
then=Value("DRAFT")
|
then=Value("DRAFT"),
|
||||||
),
|
),
|
||||||
default=Value("DRAFT"),
|
default=Value("DRAFT"),
|
||||||
output_field=CharField(),
|
output_field=CharField(),
|
||||||
@ -202,6 +199,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
|
|||||||
def list(self, request, slug, project_id):
|
def list(self, request, slug, project_id):
|
||||||
queryset = self.get_queryset()
|
queryset = self.get_queryset()
|
||||||
cycle_view = request.GET.get("cycle_view", "all")
|
cycle_view = request.GET.get("cycle_view", "all")
|
||||||
|
fields = [field for field in request.GET.get("fields", "").split(",") if field]
|
||||||
|
|
||||||
queryset = queryset.order_by("-is_favorite", "-created_at")
|
queryset = queryset.order_by("-is_favorite", "-created_at")
|
||||||
|
|
||||||
@ -307,44 +305,8 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
|
|||||||
|
|
||||||
return Response(data, status=status.HTTP_200_OK)
|
return Response(data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
# Upcoming Cycles
|
cycles = CycleSerializer(queryset, many=True).data
|
||||||
if cycle_view == "upcoming":
|
return Response(cycles, status=status.HTTP_200_OK)
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
# If no matching view is found return all cycles
|
|
||||||
return Response(
|
|
||||||
CycleSerializer(queryset, many=True).data, status=status.HTTP_200_OK
|
|
||||||
)
|
|
||||||
|
|
||||||
def create(self, request, slug, project_id):
|
def create(self, request, slug, project_id):
|
||||||
if (
|
if (
|
||||||
@ -576,7 +538,6 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
.annotate(count=Func(F("id"), function="Count"))
|
.annotate(count=Func(F("id"), function="Count"))
|
||||||
.values("count")
|
.values("count")
|
||||||
)
|
)
|
||||||
.annotate(bridge_id=F("issue_cycle__id"))
|
|
||||||
.filter(project_id=project_id)
|
.filter(project_id=project_id)
|
||||||
.filter(workspace__slug=slug)
|
.filter(workspace__slug=slug)
|
||||||
.select_related("project")
|
.select_related("project")
|
||||||
@ -600,12 +561,10 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
.values("count")
|
.values("count")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
serializer = IssueStateSerializer(
|
||||||
issues = IssueStateSerializer(
|
|
||||||
issues, many=True, fields=fields if fields else None
|
issues, many=True, fields=fields if fields else None
|
||||||
).data
|
)
|
||||||
issue_dict = {str(issue["id"]): issue for issue in issues}
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
return Response(issue_dict, status=status.HTTP_200_OK)
|
|
||||||
|
|
||||||
def create(self, request, slug, project_id, cycle_id):
|
def create(self, request, slug, project_id, cycle_id):
|
||||||
issues = request.data.get("issues", [])
|
issues = request.data.get("issues", [])
|
||||||
@ -698,11 +657,13 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
)
|
)
|
||||||
|
|
||||||
def destroy(self, request, slug, project_id, cycle_id, pk):
|
def destroy(self, request, slug, project_id, cycle_id, issue_id):
|
||||||
cycle_issue = CycleIssue.objects.get(
|
cycle_issue = CycleIssue.objects.get(
|
||||||
pk=pk, workspace__slug=slug, project_id=project_id, cycle_id=cycle_id
|
issue_id=issue_id,
|
||||||
|
workspace__slug=slug,
|
||||||
|
project_id=project_id,
|
||||||
|
cycle_id=cycle_id,
|
||||||
)
|
)
|
||||||
issue_id = cycle_issue.issue_id
|
|
||||||
issue_activity.delay(
|
issue_activity.delay(
|
||||||
type="cycle.activity.deleted",
|
type="cycle.activity.deleted",
|
||||||
requested_data=json.dumps(
|
requested_data=json.dumps(
|
||||||
@ -712,7 +673,7 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
actor_id=str(self.request.user.id),
|
actor_id=str(self.request.user.id),
|
||||||
issue_id=str(cycle_issue.issue_id),
|
issue_id=str(issue_id),
|
||||||
project_id=str(self.kwargs.get("project_id", None)),
|
project_id=str(self.kwargs.get("project_id", None)),
|
||||||
current_instance=None,
|
current_instance=None,
|
||||||
epoch=int(timezone.now().timestamp()),
|
epoch=int(timezone.now().timestamp()),
|
||||||
@ -834,3 +795,39 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return Response({"message": "Success"}, status=status.HTTP_200_OK)
|
return Response({"message": "Success"}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class CycleUserPropertiesEndpoint(BaseAPIView):
|
||||||
|
permission_classes = [
|
||||||
|
ProjectLitePermission,
|
||||||
|
]
|
||||||
|
|
||||||
|
def patch(self, request, slug, project_id, cycle_id):
|
||||||
|
cycle_properties = CycleUserProperties.objects.get(
|
||||||
|
user=request.user,
|
||||||
|
cycle_id=cycle_id,
|
||||||
|
project_id=project_id,
|
||||||
|
workspace__slug=slug,
|
||||||
|
)
|
||||||
|
|
||||||
|
cycle_properties.filters = request.data.get("filters", cycle_properties.filters)
|
||||||
|
cycle_properties.display_filters = request.data.get(
|
||||||
|
"display_filters", cycle_properties.display_filters
|
||||||
|
)
|
||||||
|
cycle_properties.display_properties = request.data.get(
|
||||||
|
"display_properties", cycle_properties.display_properties
|
||||||
|
)
|
||||||
|
cycle_properties.save()
|
||||||
|
|
||||||
|
serializer = CycleUserPropertiesSerializer(cycle_properties)
|
||||||
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
def get(self, request, slug, project_id, cycle_id):
|
||||||
|
cycle_properties, _ = CycleUserProperties.objects.get_or_create(
|
||||||
|
user=request.user,
|
||||||
|
project_id=project_id,
|
||||||
|
cycle_id=cycle_id,
|
||||||
|
workspace__slug=slug,
|
||||||
|
)
|
||||||
|
serializer = CycleUserPropertiesSerializer(cycle_properties)
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
@ -107,7 +107,6 @@ class InboxIssueViewSet(BaseViewSet):
|
|||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
)
|
)
|
||||||
.filter(**filters)
|
.filter(**filters)
|
||||||
.annotate(bridge_id=F("issue_inbox__id"))
|
|
||||||
.select_related("workspace", "project", "state", "parent")
|
.select_related("workspace", "project", "state", "parent")
|
||||||
.prefetch_related("assignees", "labels")
|
.prefetch_related("assignees", "labels")
|
||||||
.order_by("issue_inbox__snoozed_till", "issue_inbox__status")
|
.order_by("issue_inbox__snoozed_till", "issue_inbox__status")
|
||||||
@ -204,9 +203,9 @@ class InboxIssueViewSet(BaseViewSet):
|
|||||||
serializer = IssueStateInboxSerializer(issue)
|
serializer = IssueStateInboxSerializer(issue)
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
def partial_update(self, request, slug, project_id, inbox_id, pk):
|
def partial_update(self, request, slug, project_id, inbox_id, issue_id):
|
||||||
inbox_issue = InboxIssue.objects.get(
|
inbox_issue = InboxIssue.objects.get(
|
||||||
pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id
|
issue_id=issue_id, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id
|
||||||
)
|
)
|
||||||
# Get the project member
|
# Get the project member
|
||||||
project_member = ProjectMember.objects.get(
|
project_member = ProjectMember.objects.get(
|
||||||
@ -316,19 +315,16 @@ class InboxIssueViewSet(BaseViewSet):
|
|||||||
InboxIssueSerializer(inbox_issue).data, status=status.HTTP_200_OK
|
InboxIssueSerializer(inbox_issue).data, status=status.HTTP_200_OK
|
||||||
)
|
)
|
||||||
|
|
||||||
def retrieve(self, request, slug, project_id, inbox_id, pk):
|
def retrieve(self, request, slug, project_id, inbox_id, issue_id):
|
||||||
inbox_issue = InboxIssue.objects.get(
|
|
||||||
pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id
|
|
||||||
)
|
|
||||||
issue = Issue.objects.get(
|
issue = Issue.objects.get(
|
||||||
pk=inbox_issue.issue_id, workspace__slug=slug, project_id=project_id
|
pk=issue_id, workspace__slug=slug, project_id=project_id
|
||||||
)
|
)
|
||||||
serializer = IssueStateInboxSerializer(issue)
|
serializer = IssueStateInboxSerializer(issue)
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
def destroy(self, request, slug, project_id, inbox_id, pk):
|
def destroy(self, request, slug, project_id, inbox_id, issue_id):
|
||||||
inbox_issue = InboxIssue.objects.get(
|
inbox_issue = InboxIssue.objects.get(
|
||||||
pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id
|
issue_id=issue_id, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id
|
||||||
)
|
)
|
||||||
# Get the project member
|
# Get the project member
|
||||||
project_member = ProjectMember.objects.get(
|
project_member = ProjectMember.objects.get(
|
||||||
@ -350,7 +346,7 @@ class InboxIssueViewSet(BaseViewSet):
|
|||||||
if inbox_issue.status in [-2, -1, 0, 2]:
|
if inbox_issue.status in [-2, -1, 0, 2]:
|
||||||
# Delete the issue also
|
# Delete the issue also
|
||||||
Issue.objects.filter(
|
Issue.objects.filter(
|
||||||
workspace__slug=slug, project_id=project_id, pk=inbox_issue.issue_id
|
workspace__slug=slug, project_id=project_id, pk=issue_id
|
||||||
).delete()
|
).delete()
|
||||||
|
|
||||||
inbox_issue.delete()
|
inbox_issue.delete()
|
||||||
|
@ -52,6 +52,7 @@ from plane.app.serializers import (
|
|||||||
IssueRelationSerializer,
|
IssueRelationSerializer,
|
||||||
RelatedIssueSerializer,
|
RelatedIssueSerializer,
|
||||||
IssuePublicSerializer,
|
IssuePublicSerializer,
|
||||||
|
IssueRelationLiteSerializer,
|
||||||
)
|
)
|
||||||
from plane.app.permissions import (
|
from plane.app.permissions import (
|
||||||
ProjectEntityPermission,
|
ProjectEntityPermission,
|
||||||
@ -129,22 +130,6 @@ class IssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
queryset=IssueReaction.objects.select_related("actor"),
|
queryset=IssueReaction.objects.select_related("actor"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
).distinct()
|
|
||||||
|
|
||||||
@method_decorator(gzip_page)
|
|
||||||
def list(self, request, slug, project_id):
|
|
||||||
fields = [field for field in request.GET.get("fields", "").split(",") if field]
|
|
||||||
filters = issue_filters(request.query_params, "GET")
|
|
||||||
|
|
||||||
# Custom ordering for priority and state
|
|
||||||
priority_order = ["urgent", "high", "medium", "low", "none"]
|
|
||||||
state_order = ["backlog", "unstarted", "started", "completed", "cancelled"]
|
|
||||||
|
|
||||||
order_by_param = request.GET.get("order_by", "-created_at")
|
|
||||||
|
|
||||||
issue_queryset = (
|
|
||||||
self.get_queryset()
|
|
||||||
.filter(**filters)
|
|
||||||
.annotate(cycle_id=F("issue_cycle__cycle_id"))
|
.annotate(cycle_id=F("issue_cycle__cycle_id"))
|
||||||
.annotate(module_id=F("issue_module__module_id"))
|
.annotate(module_id=F("issue_module__module_id"))
|
||||||
.annotate(
|
.annotate(
|
||||||
@ -159,7 +144,26 @@ class IssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
.annotate(count=Func(F("id"), function="Count"))
|
.annotate(count=Func(F("id"), function="Count"))
|
||||||
.values("count")
|
.values("count")
|
||||||
)
|
)
|
||||||
|
.annotate(
|
||||||
|
is_subscribed=Exists(
|
||||||
|
IssueSubscriber.objects.filter(
|
||||||
|
subscriber=self.request.user, issue_id=OuterRef("id")
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
@method_decorator(gzip_page)
|
||||||
|
def list(self, request, slug, project_id):
|
||||||
|
filters = issue_filters(request.query_params, "GET")
|
||||||
|
|
||||||
|
# Custom ordering for priority and state
|
||||||
|
priority_order = ["urgent", "high", "medium", "low", "none"]
|
||||||
|
state_order = ["backlog", "unstarted", "started", "completed", "cancelled"]
|
||||||
|
|
||||||
|
order_by_param = request.GET.get("order_by", "-created_at")
|
||||||
|
|
||||||
|
issue_queryset = self.get_queryset().filter(**filters)
|
||||||
|
|
||||||
# Priority Ordering
|
# Priority Ordering
|
||||||
if order_by_param == "priority" or order_by_param == "-priority":
|
if order_by_param == "priority" or order_by_param == "-priority":
|
||||||
@ -217,9 +221,10 @@ class IssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
else:
|
else:
|
||||||
issue_queryset = issue_queryset.order_by(order_by_param)
|
issue_queryset = issue_queryset.order_by(order_by_param)
|
||||||
|
|
||||||
issues = IssueLiteSerializer(issue_queryset, many=True, fields=fields if fields else None).data
|
issues = IssueSerializer(
|
||||||
issue_dict = {str(issue["id"]): issue for issue in issues}
|
issue_queryset, many=True, fields=self.fields, expand=self.expand
|
||||||
return Response(issue_dict, status=status.HTTP_200_OK)
|
).data
|
||||||
|
return Response(issues, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
def create(self, request, slug, project_id):
|
def create(self, request, slug, project_id):
|
||||||
project = Project.objects.get(pk=project_id)
|
project = Project.objects.get(pk=project_id)
|
||||||
@ -256,7 +261,10 @@ class IssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
.annotate(count=Func(F("id"), function="Count"))
|
.annotate(count=Func(F("id"), function="Count"))
|
||||||
.values("count")
|
.values("count")
|
||||||
).get(workspace__slug=slug, project_id=project_id, pk=pk)
|
).get(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||||
return Response(IssueSerializer(issue).data, status=status.HTTP_200_OK)
|
return Response(
|
||||||
|
IssueSerializer(issue, fields=self.fields, expand=self.expand).data,
|
||||||
|
status=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
|
|
||||||
def partial_update(self, request, slug, project_id, pk=None):
|
def partial_update(self, request, slug, project_id, pk=None):
|
||||||
issue = Issue.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
|
issue = Issue.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||||
@ -590,16 +598,19 @@ class IssueUserDisplayPropertyEndpoint(BaseAPIView):
|
|||||||
ProjectLitePermission,
|
ProjectLitePermission,
|
||||||
]
|
]
|
||||||
|
|
||||||
def post(self, request, slug, project_id):
|
def patch(self, request, slug, project_id):
|
||||||
issue_property, created = IssueProperty.objects.get_or_create(
|
issue_property = IssueProperty.objects.get(
|
||||||
user=request.user,
|
user=request.user,
|
||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not created:
|
issue_property.filters = request.data.get("filters", issue_property.filters)
|
||||||
issue_property.properties = request.data.get("properties", {})
|
issue_property.display_filters = request.data.get(
|
||||||
issue_property.save()
|
"display_filters", issue_property.display_filters
|
||||||
issue_property.properties = request.data.get("properties", {})
|
)
|
||||||
|
issue_property.display_properties = request.data.get(
|
||||||
|
"display_properties", issue_property.display_properties
|
||||||
|
)
|
||||||
issue_property.save()
|
issue_property.save()
|
||||||
serializer = IssuePropertySerializer(issue_property)
|
serializer = IssuePropertySerializer(issue_property)
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
@ -708,6 +719,13 @@ class SubIssuesEndpoint(BaseAPIView):
|
|||||||
.annotate(count=Func(F("id"), function="Count"))
|
.annotate(count=Func(F("id"), function="Count"))
|
||||||
.values("count")
|
.values("count")
|
||||||
)
|
)
|
||||||
|
.annotate(
|
||||||
|
is_subscribed=Exists(
|
||||||
|
IssueSubscriber.objects.filter(
|
||||||
|
subscriber=self.request.user, issue_id=OuterRef("id")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
.prefetch_related(
|
.prefetch_related(
|
||||||
Prefetch(
|
Prefetch(
|
||||||
"issue_reactions",
|
"issue_reactions",
|
||||||
@ -728,7 +746,7 @@ class SubIssuesEndpoint(BaseAPIView):
|
|||||||
item["state_group"]: item["state_count"] for item in state_distribution
|
item["state_group"]: item["state_count"] for item in state_distribution
|
||||||
}
|
}
|
||||||
|
|
||||||
serializer = IssueLiteSerializer(
|
serializer = IssueSerializer(
|
||||||
sub_issues,
|
sub_issues,
|
||||||
many=True,
|
many=True,
|
||||||
)
|
)
|
||||||
@ -775,7 +793,7 @@ class SubIssuesEndpoint(BaseAPIView):
|
|||||||
]
|
]
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
IssueFlatSerializer(updated_sub_issues, many=True).data,
|
IssueSerializer(updated_sub_issues, many=True).data,
|
||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1062,9 +1080,10 @@ class IssueArchiveViewSet(BaseViewSet):
|
|||||||
else issue_queryset.filter(parent__isnull=True)
|
else issue_queryset.filter(parent__isnull=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
issues = IssueLiteSerializer(issue_queryset, many=True, fields=fields if fields else None).data
|
issues = IssueLiteSerializer(
|
||||||
issue_dict = {str(issue["id"]): issue for issue in issues}
|
issue_queryset, many=True, fields=fields if fields else None
|
||||||
return Response(issue_dict, status=status.HTTP_200_OK)
|
).data
|
||||||
|
return Response(issues, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
def retrieve(self, request, slug, project_id, pk=None):
|
def retrieve(self, request, slug, project_id, pk=None):
|
||||||
issue = Issue.objects.get(
|
issue = Issue.objects.get(
|
||||||
@ -1365,23 +1384,62 @@ class IssueRelationViewSet(BaseViewSet):
|
|||||||
.distinct()
|
.distinct()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def list(self, request, slug, project_id, issue_id):
|
||||||
|
issue_relations = (
|
||||||
|
IssueRelation.objects.filter(Q(issue_id=issue_id) | Q(related_issue=issue_id))
|
||||||
|
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||||
|
.select_related("project")
|
||||||
|
.select_related("workspace")
|
||||||
|
.select_related("issue")
|
||||||
|
.order_by("-created_at")
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
|
||||||
|
blocking_issues = issue_relations.filter(relation_type="blocked_by", related_issue_id=issue_id)
|
||||||
|
blocked_by_issues = issue_relations.filter(relation_type="blocked_by", issue_id=issue_id)
|
||||||
|
duplicate_issues = issue_relations.filter(issue_id=issue_id, relation_type="duplicate")
|
||||||
|
duplicate_issues_related = issue_relations.filter(related_issue_id=issue_id, relation_type="duplicate")
|
||||||
|
relates_to_issues = issue_relations.filter(issue_id=issue_id, relation_type="relates_to")
|
||||||
|
relates_to_issues_related = issue_relations.filter(related_issue_id=issue_id, relation_type="relates_to")
|
||||||
|
|
||||||
|
blocked_by_issues_serialized = IssueRelationSerializer(blocked_by_issues, many=True).data
|
||||||
|
duplicate_issues_serialized = IssueRelationSerializer(duplicate_issues, many=True).data
|
||||||
|
relates_to_issues_serialized = IssueRelationSerializer(relates_to_issues, many=True).data
|
||||||
|
|
||||||
|
# revere relation for blocked by issues
|
||||||
|
blocking_issues_serialized = RelatedIssueSerializer(blocking_issues, many=True).data
|
||||||
|
# reverse relation for duplicate issues
|
||||||
|
duplicate_issues_related_serialized = RelatedIssueSerializer(duplicate_issues_related, many=True).data
|
||||||
|
# reverse relation for related issues
|
||||||
|
relates_to_issues_related_serialized = RelatedIssueSerializer(relates_to_issues_related, many=True).data
|
||||||
|
|
||||||
|
response_data = {
|
||||||
|
'blocking': blocking_issues_serialized,
|
||||||
|
'blocked_by': blocked_by_issues_serialized,
|
||||||
|
'duplicate': duplicate_issues_serialized + duplicate_issues_related_serialized,
|
||||||
|
'relates_to': relates_to_issues_serialized + relates_to_issues_related_serialized,
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response(response_data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
def create(self, request, slug, project_id, issue_id):
|
def create(self, request, slug, project_id, issue_id):
|
||||||
related_list = request.data.get("related_list", [])
|
relation_type = request.data.get("relation_type", None)
|
||||||
relation = request.data.get("relation", None)
|
issues = request.data.get("issues", [])
|
||||||
project = Project.objects.get(pk=project_id)
|
project = Project.objects.get(pk=project_id)
|
||||||
|
|
||||||
issue_relation = IssueRelation.objects.bulk_create(
|
issue_relation = IssueRelation.objects.bulk_create(
|
||||||
[
|
[
|
||||||
IssueRelation(
|
IssueRelation(
|
||||||
issue_id=related_issue["issue"],
|
issue_id=issue if relation_type == "blocking" else issue_id,
|
||||||
related_issue_id=related_issue["related_issue"],
|
related_issue_id=issue_id if relation_type == "blocking" else issue,
|
||||||
relation_type=related_issue["relation_type"],
|
relation_type="blocked_by" if relation_type == "blocking" else relation_type,
|
||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
workspace_id=project.workspace_id,
|
workspace_id=project.workspace_id,
|
||||||
created_by=request.user,
|
created_by=request.user,
|
||||||
updated_by=request.user,
|
updated_by=request.user,
|
||||||
)
|
)
|
||||||
for related_issue in related_list
|
for issue in issues
|
||||||
],
|
],
|
||||||
batch_size=10,
|
batch_size=10,
|
||||||
ignore_conflicts=True,
|
ignore_conflicts=True,
|
||||||
@ -1397,7 +1455,7 @@ class IssueRelationViewSet(BaseViewSet):
|
|||||||
epoch=int(timezone.now().timestamp()),
|
epoch=int(timezone.now().timestamp()),
|
||||||
)
|
)
|
||||||
|
|
||||||
if relation == "blocking":
|
if relation_type == "blocking":
|
||||||
return Response(
|
return Response(
|
||||||
RelatedIssueSerializer(issue_relation, many=True).data,
|
RelatedIssueSerializer(issue_relation, many=True).data,
|
||||||
status=status.HTTP_201_CREATED,
|
status=status.HTTP_201_CREATED,
|
||||||
@ -1408,9 +1466,17 @@ class IssueRelationViewSet(BaseViewSet):
|
|||||||
status=status.HTTP_201_CREATED,
|
status=status.HTTP_201_CREATED,
|
||||||
)
|
)
|
||||||
|
|
||||||
def destroy(self, request, slug, project_id, issue_id, pk):
|
def remove_relation(self, request, slug, project_id, issue_id):
|
||||||
|
relation_type = request.data.get("relation_type", None)
|
||||||
|
related_issue = request.data.get("related_issue", None)
|
||||||
|
|
||||||
|
if relation_type == "blocking":
|
||||||
issue_relation = IssueRelation.objects.get(
|
issue_relation = IssueRelation.objects.get(
|
||||||
workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=pk
|
workspace__slug=slug, project_id=project_id, issue_id=related_issue, related_issue_id=issue_id
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
issue_relation = IssueRelation.objects.get(
|
||||||
|
workspace__slug=slug, project_id=project_id, issue_id=issue_id, related_issue_id=related_issue
|
||||||
)
|
)
|
||||||
current_instance = json.dumps(
|
current_instance = json.dumps(
|
||||||
IssueRelationSerializer(issue_relation).data,
|
IssueRelationSerializer(issue_relation).data,
|
||||||
@ -1419,7 +1485,7 @@ class IssueRelationViewSet(BaseViewSet):
|
|||||||
issue_relation.delete()
|
issue_relation.delete()
|
||||||
issue_activity.delay(
|
issue_activity.delay(
|
||||||
type="issue_relation.activity.deleted",
|
type="issue_relation.activity.deleted",
|
||||||
requested_data=json.dumps({"related_list": None}),
|
requested_data=json.dumps(request.data, cls=DjangoJSONEncoder),
|
||||||
actor_id=str(request.user.id),
|
actor_id=str(request.user.id),
|
||||||
issue_id=str(issue_id),
|
issue_id=str(issue_id),
|
||||||
project_id=str(project_id),
|
project_id=str(project_id),
|
||||||
@ -1547,9 +1613,10 @@ class IssueDraftViewSet(BaseViewSet):
|
|||||||
else:
|
else:
|
||||||
issue_queryset = issue_queryset.order_by(order_by_param)
|
issue_queryset = issue_queryset.order_by(order_by_param)
|
||||||
|
|
||||||
issues = IssueLiteSerializer(issue_queryset, many=True, fields=fields if fields else None).data
|
issues = IssueLiteSerializer(
|
||||||
issue_dict = {str(issue["id"]): issue for issue in issues}
|
issue_queryset, many=True, fields=fields if fields else None
|
||||||
return Response(issue_dict, status=status.HTTP_200_OK)
|
).data
|
||||||
|
return Response(issues, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
def create(self, request, slug, project_id):
|
def create(self, request, slug, project_id):
|
||||||
project = Project.objects.get(pk=project_id)
|
project = Project.objects.get(pk=project_id)
|
||||||
|
@ -21,8 +21,9 @@ from plane.app.serializers import (
|
|||||||
ModuleLinkSerializer,
|
ModuleLinkSerializer,
|
||||||
ModuleFavoriteSerializer,
|
ModuleFavoriteSerializer,
|
||||||
IssueStateSerializer,
|
IssueStateSerializer,
|
||||||
|
ModuleUserPropertiesSerializer,
|
||||||
)
|
)
|
||||||
from plane.app.permissions import ProjectEntityPermission
|
from plane.app.permissions import ProjectEntityPermission, ProjectLitePermission
|
||||||
from plane.db.models import (
|
from plane.db.models import (
|
||||||
Module,
|
Module,
|
||||||
ModuleIssue,
|
ModuleIssue,
|
||||||
@ -32,6 +33,7 @@ from plane.db.models import (
|
|||||||
ModuleFavorite,
|
ModuleFavorite,
|
||||||
IssueLink,
|
IssueLink,
|
||||||
IssueAttachment,
|
IssueAttachment,
|
||||||
|
ModuleUserProperties,
|
||||||
)
|
)
|
||||||
from plane.bgtasks.issue_activites_task import issue_activity
|
from plane.bgtasks.issue_activites_task import issue_activity
|
||||||
from plane.utils.grouper import group_results
|
from plane.utils.grouper import group_results
|
||||||
@ -54,7 +56,6 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
|
||||||
subquery = ModuleFavorite.objects.filter(
|
subquery = ModuleFavorite.objects.filter(
|
||||||
user=self.request.user,
|
user=self.request.user,
|
||||||
module_id=OuterRef("pk"),
|
module_id=OuterRef("pk"),
|
||||||
@ -136,7 +137,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.order_by("-is_favorite","-created_at")
|
.order_by("-is_favorite", "-created_at")
|
||||||
)
|
)
|
||||||
|
|
||||||
def create(self, request, slug, project_id):
|
def create(self, request, slug, project_id):
|
||||||
@ -153,6 +154,14 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
|
|||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
def list(self, request, slug, project_id):
|
||||||
|
queryset = self.get_queryset()
|
||||||
|
fields = [field for field in request.GET.get("fields", "").split(",") if field]
|
||||||
|
modules = ModuleSerializer(
|
||||||
|
queryset, many=True, fields=fields if fields else None
|
||||||
|
).data
|
||||||
|
return Response(modules, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
def retrieve(self, request, slug, project_id, pk):
|
def retrieve(self, request, slug, project_id, pk):
|
||||||
queryset = self.get_queryset().get(pk=pk)
|
queryset = self.get_queryset().get(pk=pk)
|
||||||
|
|
||||||
@ -289,7 +298,6 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
webhook_event = "module_issue"
|
webhook_event = "module_issue"
|
||||||
bulk = True
|
bulk = True
|
||||||
|
|
||||||
|
|
||||||
filterset_fields = [
|
filterset_fields = [
|
||||||
"issue__labels__id",
|
"issue__labels__id",
|
||||||
"issue__assignees__id",
|
"issue__assignees__id",
|
||||||
@ -335,7 +343,6 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
.annotate(count=Func(F("id"), function="Count"))
|
.annotate(count=Func(F("id"), function="Count"))
|
||||||
.values("count")
|
.values("count")
|
||||||
)
|
)
|
||||||
.annotate(bridge_id=F("issue_module__id"))
|
|
||||||
.filter(project_id=project_id)
|
.filter(project_id=project_id)
|
||||||
.filter(workspace__slug=slug)
|
.filter(workspace__slug=slug)
|
||||||
.select_related("project")
|
.select_related("project")
|
||||||
@ -359,9 +366,10 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
.values("count")
|
.values("count")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
issues = IssueStateSerializer(issues, many=True, fields=fields if fields else None).data
|
serializer = IssueStateSerializer(
|
||||||
issue_dict = {str(issue["id"]): issue for issue in issues}
|
issues, many=True, fields=fields if fields else None
|
||||||
return Response(issue_dict, status=status.HTTP_200_OK)
|
)
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
def create(self, request, slug, project_id, module_id):
|
def create(self, request, slug, project_id, module_id):
|
||||||
issues = request.data.get("issues", [])
|
issues = request.data.get("issues", [])
|
||||||
@ -444,20 +452,23 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
)
|
)
|
||||||
|
|
||||||
def destroy(self, request, slug, project_id, module_id, pk):
|
def destroy(self, request, slug, project_id, module_id, issue_id):
|
||||||
module_issue = ModuleIssue.objects.get(
|
module_issue = ModuleIssue.objects.get(
|
||||||
workspace__slug=slug, project_id=project_id, module_id=module_id, pk=pk
|
workspace__slug=slug,
|
||||||
|
project_id=project_id,
|
||||||
|
module_id=module_id,
|
||||||
|
issue_id=issue_id,
|
||||||
)
|
)
|
||||||
issue_activity.delay(
|
issue_activity.delay(
|
||||||
type="module.activity.deleted",
|
type="module.activity.deleted",
|
||||||
requested_data=json.dumps(
|
requested_data=json.dumps(
|
||||||
{
|
{
|
||||||
"module_id": str(module_id),
|
"module_id": str(module_id),
|
||||||
"issues": [str(module_issue.issue_id)],
|
"issues": [str(issue_id)],
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
actor_id=str(request.user.id),
|
actor_id=str(request.user.id),
|
||||||
issue_id=str(module_issue.issue_id),
|
issue_id=str(issue_id),
|
||||||
project_id=str(project_id),
|
project_id=str(project_id),
|
||||||
current_instance=None,
|
current_instance=None,
|
||||||
epoch=int(timezone.now().timestamp()),
|
epoch=int(timezone.now().timestamp()),
|
||||||
@ -522,3 +533,41 @@ class ModuleFavoriteViewSet(BaseViewSet):
|
|||||||
)
|
)
|
||||||
module_favorite.delete()
|
module_favorite.delete()
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleUserPropertiesEndpoint(BaseAPIView):
|
||||||
|
permission_classes = [
|
||||||
|
ProjectLitePermission,
|
||||||
|
]
|
||||||
|
|
||||||
|
def patch(self, request, slug, project_id, module_id):
|
||||||
|
module_properties = ModuleUserProperties.objects.get(
|
||||||
|
user=request.user,
|
||||||
|
module_id=module_id,
|
||||||
|
project_id=project_id,
|
||||||
|
workspace__slug=slug,
|
||||||
|
)
|
||||||
|
|
||||||
|
module_properties.filters = request.data.get(
|
||||||
|
"filters", module_properties.filters
|
||||||
|
)
|
||||||
|
module_properties.display_filters = request.data.get(
|
||||||
|
"display_filters", module_properties.display_filters
|
||||||
|
)
|
||||||
|
module_properties.display_properties = request.data.get(
|
||||||
|
"display_properties", module_properties.display_properties
|
||||||
|
)
|
||||||
|
module_properties.save()
|
||||||
|
|
||||||
|
serializer = ModuleUserPropertiesSerializer(module_properties)
|
||||||
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
def get(self, request, slug, project_id, module_id):
|
||||||
|
module_properties, _ = ModuleUserProperties.objects.get_or_create(
|
||||||
|
user=request.user,
|
||||||
|
project_id=project_id,
|
||||||
|
module_id=module_id,
|
||||||
|
workspace__slug=slug,
|
||||||
|
)
|
||||||
|
serializer = ModuleUserPropertiesSerializer(module_properties)
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
@ -157,9 +157,8 @@ class PageViewSet(BaseViewSet):
|
|||||||
|
|
||||||
def list(self, request, slug, project_id):
|
def list(self, request, slug, project_id):
|
||||||
queryset = self.get_queryset().filter(archived_at__isnull=True)
|
queryset = self.get_queryset().filter(archived_at__isnull=True)
|
||||||
return Response(
|
pages = PageSerializer(queryset, many=True).data
|
||||||
PageSerializer(queryset, many=True).data, status=status.HTTP_200_OK
|
return Response(pages, status=status.HTTP_200_OK)
|
||||||
)
|
|
||||||
|
|
||||||
def archive(self, request, slug, project_id, page_id):
|
def archive(self, request, slug, project_id, page_id):
|
||||||
page = Page.objects.get(pk=page_id, workspace__slug=slug, project_id=project_id)
|
page = Page.objects.get(pk=page_id, workspace__slug=slug, project_id=project_id)
|
||||||
@ -210,9 +209,9 @@ class PageViewSet(BaseViewSet):
|
|||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
).filter(archived_at__isnull=False)
|
).filter(archived_at__isnull=False)
|
||||||
|
|
||||||
return Response(
|
pages = PageSerializer(pages, many=True).data
|
||||||
PageSerializer(pages, many=True).data, status=status.HTTP_200_OK
|
return Response(pages, status=status.HTTP_200_OK)
|
||||||
)
|
|
||||||
|
|
||||||
def destroy(self, request, slug, project_id, pk):
|
def destroy(self, request, slug, project_id, pk):
|
||||||
page = Page.objects.get(pk=pk, workspace__slug=slug, project_id=project_id)
|
page = Page.objects.get(pk=pk, workspace__slug=slug, project_id=project_id)
|
||||||
|
@ -36,6 +36,7 @@ from plane.app.serializers import (
|
|||||||
ProjectFavoriteSerializer,
|
ProjectFavoriteSerializer,
|
||||||
ProjectDeployBoardSerializer,
|
ProjectDeployBoardSerializer,
|
||||||
ProjectMemberAdminSerializer,
|
ProjectMemberAdminSerializer,
|
||||||
|
ProjectMemberRoleSerializer,
|
||||||
)
|
)
|
||||||
|
|
||||||
from plane.app.permissions import (
|
from plane.app.permissions import (
|
||||||
@ -180,12 +181,9 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
|
|||||||
projects, many=True
|
projects, many=True
|
||||||
).data,
|
).data,
|
||||||
)
|
)
|
||||||
|
projects = ProjectListSerializer(projects, many=True, fields=fields if fields else None).data
|
||||||
|
return Response(projects, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
return Response(
|
|
||||||
ProjectListSerializer(
|
|
||||||
projects, many=True, fields=fields if fields else None
|
|
||||||
).data
|
|
||||||
)
|
|
||||||
|
|
||||||
def create(self, request, slug):
|
def create(self, request, slug):
|
||||||
try:
|
try:
|
||||||
@ -713,13 +711,7 @@ class ProjectMemberViewSet(BaseViewSet):
|
|||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
def list(self, request, slug, project_id):
|
def list(self, request, slug, project_id):
|
||||||
project_member = ProjectMember.objects.get(
|
# Get the list of project members for the project
|
||||||
member=request.user,
|
|
||||||
workspace__slug=slug,
|
|
||||||
project_id=project_id,
|
|
||||||
is_active=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
project_members = ProjectMember.objects.filter(
|
project_members = ProjectMember.objects.filter(
|
||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
@ -727,10 +719,7 @@ class ProjectMemberViewSet(BaseViewSet):
|
|||||||
is_active=True,
|
is_active=True,
|
||||||
).select_related("project", "member", "workspace")
|
).select_related("project", "member", "workspace")
|
||||||
|
|
||||||
if project_member.role > 10:
|
serializer = ProjectMemberRoleSerializer(project_members, fields=("id", "member", "role"), many=True)
|
||||||
serializer = ProjectMemberAdminSerializer(project_members, many=True)
|
|
||||||
else:
|
|
||||||
serializer = ProjectMemberSerializer(project_members, many=True)
|
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
def partial_update(self, request, slug, project_id, pk):
|
def partial_update(self, request, slug, project_id, pk):
|
||||||
@ -1010,18 +999,11 @@ class ProjectPublicCoverImagesEndpoint(BaseAPIView):
|
|||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
files = []
|
files = []
|
||||||
s3_client_params = {
|
s3 = boto3.client(
|
||||||
"service_name": "s3",
|
"s3",
|
||||||
"aws_access_key_id": settings.AWS_ACCESS_KEY_ID,
|
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
|
||||||
"aws_secret_access_key": settings.AWS_SECRET_ACCESS_KEY,
|
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
|
||||||
}
|
)
|
||||||
|
|
||||||
# Use AWS_S3_ENDPOINT_URL if it is present in the settings
|
|
||||||
if hasattr(settings, "AWS_S3_ENDPOINT_URL") and settings.AWS_S3_ENDPOINT_URL:
|
|
||||||
s3_client_params["endpoint_url"] = settings.AWS_S3_ENDPOINT_URL
|
|
||||||
|
|
||||||
s3 = boto3.client(**s3_client_params)
|
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
"Bucket": settings.AWS_STORAGE_BUCKET_NAME,
|
"Bucket": settings.AWS_STORAGE_BUCKET_NAME,
|
||||||
"Prefix": "static/project-cover/",
|
"Prefix": "static/project-cover/",
|
||||||
@ -1034,16 +1016,6 @@ class ProjectPublicCoverImagesEndpoint(BaseAPIView):
|
|||||||
if not content["Key"].endswith(
|
if not content["Key"].endswith(
|
||||||
"/"
|
"/"
|
||||||
): # This line ensures we're only getting files, not "sub-folders"
|
): # This line ensures we're only getting files, not "sub-folders"
|
||||||
if (
|
|
||||||
hasattr(settings, "AWS_S3_CUSTOM_DOMAIN")
|
|
||||||
and settings.AWS_S3_CUSTOM_DOMAIN
|
|
||||||
and hasattr(settings, "AWS_S3_URL_PROTOCOL")
|
|
||||||
and settings.AWS_S3_URL_PROTOCOL
|
|
||||||
):
|
|
||||||
files.append(
|
|
||||||
f"{settings.AWS_S3_URL_PROTOCOL}//{settings.AWS_S3_CUSTOM_DOMAIN}/{content['Key']}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
files.append(
|
files.append(
|
||||||
f"https://{settings.AWS_STORAGE_BUCKET_NAME}.s3.{settings.AWS_REGION}.amazonaws.com/{content['Key']}"
|
f"https://{settings.AWS_STORAGE_BUCKET_NAME}.s3.{settings.AWS_REGION}.amazonaws.com/{content['Key']}"
|
||||||
)
|
)
|
||||||
|
@ -27,7 +27,12 @@ from plane.app.serializers import (
|
|||||||
IssueLiteSerializer,
|
IssueLiteSerializer,
|
||||||
IssueViewFavoriteSerializer,
|
IssueViewFavoriteSerializer,
|
||||||
)
|
)
|
||||||
from plane.app.permissions import WorkspaceEntityPermission, ProjectEntityPermission
|
from plane.app.permissions import (
|
||||||
|
WorkspaceEntityPermission,
|
||||||
|
ProjectEntityPermission,
|
||||||
|
WorkspaceViewerPermission,
|
||||||
|
ProjectLitePermission,
|
||||||
|
)
|
||||||
from plane.db.models import (
|
from plane.db.models import (
|
||||||
Workspace,
|
Workspace,
|
||||||
GlobalView,
|
GlobalView,
|
||||||
@ -43,8 +48,8 @@ from plane.utils.grouper import group_results
|
|||||||
|
|
||||||
|
|
||||||
class GlobalViewViewSet(BaseViewSet):
|
class GlobalViewViewSet(BaseViewSet):
|
||||||
serializer_class = GlobalViewSerializer
|
serializer_class = IssueViewSerializer
|
||||||
model = GlobalView
|
model = IssueView
|
||||||
permission_classes = [
|
permission_classes = [
|
||||||
WorkspaceEntityPermission,
|
WorkspaceEntityPermission,
|
||||||
]
|
]
|
||||||
@ -58,6 +63,7 @@ class GlobalViewViewSet(BaseViewSet):
|
|||||||
super()
|
super()
|
||||||
.get_queryset()
|
.get_queryset()
|
||||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||||
|
.filter(project__isnull=True)
|
||||||
.select_related("workspace")
|
.select_related("workspace")
|
||||||
.order_by(self.request.GET.get("order_by", "-created_at"))
|
.order_by(self.request.GET.get("order_by", "-created_at"))
|
||||||
.distinct()
|
.distinct()
|
||||||
@ -179,12 +185,10 @@ class GlobalViewIssuesViewSet(BaseViewSet):
|
|||||||
else:
|
else:
|
||||||
issue_queryset = issue_queryset.order_by(order_by_param)
|
issue_queryset = issue_queryset.order_by(order_by_param)
|
||||||
|
|
||||||
issues = IssueLiteSerializer(issue_queryset, many=True, fields=fields if fields else None).data
|
serializer = IssueLiteSerializer(
|
||||||
issue_dict = {str(issue["id"]): issue for issue in issues}
|
issue_queryset, many=True, fields=fields if fields else None
|
||||||
return Response(
|
|
||||||
issue_dict,
|
|
||||||
status=status.HTTP_200_OK,
|
|
||||||
)
|
)
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class IssueViewViewSet(BaseViewSet):
|
class IssueViewViewSet(BaseViewSet):
|
||||||
@ -217,6 +221,14 @@ class IssueViewViewSet(BaseViewSet):
|
|||||||
.distinct()
|
.distinct()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def list(self, request, slug, project_id):
|
||||||
|
queryset = self.get_queryset()
|
||||||
|
fields = [field for field in request.GET.get("fields", "").split(",") if field]
|
||||||
|
views = IssueViewSerializer(
|
||||||
|
queryset, many=True, fields=fields if fields else None
|
||||||
|
).data
|
||||||
|
return Response(views, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class IssueViewFavoriteViewSet(BaseViewSet):
|
class IssueViewFavoriteViewSet(BaseViewSet):
|
||||||
serializer_class = IssueViewFavoriteSerializer
|
serializer_class = IssueViewFavoriteSerializer
|
||||||
|
@ -44,6 +44,8 @@ from plane.app.serializers import (
|
|||||||
IssueLiteSerializer,
|
IssueLiteSerializer,
|
||||||
WorkspaceMemberAdminSerializer,
|
WorkspaceMemberAdminSerializer,
|
||||||
WorkspaceMemberMeSerializer,
|
WorkspaceMemberMeSerializer,
|
||||||
|
ProjectMemberRoleSerializer,
|
||||||
|
WorkspaceUserPropertiesSerializer,
|
||||||
)
|
)
|
||||||
from plane.app.views.base import BaseAPIView
|
from plane.app.views.base import BaseAPIView
|
||||||
from . import BaseViewSet
|
from . import BaseViewSet
|
||||||
@ -64,6 +66,7 @@ from plane.db.models import (
|
|||||||
WorkspaceMember,
|
WorkspaceMember,
|
||||||
CycleIssue,
|
CycleIssue,
|
||||||
IssueReaction,
|
IssueReaction,
|
||||||
|
WorkspaceUserProperties
|
||||||
)
|
)
|
||||||
from plane.app.permissions import (
|
from plane.app.permissions import (
|
||||||
WorkSpaceBasePermission,
|
WorkSpaceBasePermission,
|
||||||
@ -71,11 +74,13 @@ from plane.app.permissions import (
|
|||||||
WorkspaceEntityPermission,
|
WorkspaceEntityPermission,
|
||||||
WorkspaceViewerPermission,
|
WorkspaceViewerPermission,
|
||||||
WorkspaceUserPermission,
|
WorkspaceUserPermission,
|
||||||
|
ProjectLitePermission,
|
||||||
)
|
)
|
||||||
from plane.bgtasks.workspace_invitation_task import workspace_invitation
|
from plane.bgtasks.workspace_invitation_task import workspace_invitation
|
||||||
from plane.utils.issue_filters import issue_filters
|
from plane.utils.issue_filters import issue_filters
|
||||||
from plane.bgtasks.event_tracking_task import workspace_invite_event
|
from plane.bgtasks.event_tracking_task import workspace_invite_event
|
||||||
|
|
||||||
|
|
||||||
class WorkSpaceViewSet(BaseViewSet):
|
class WorkSpaceViewSet(BaseViewSet):
|
||||||
model = Workspace
|
model = Workspace
|
||||||
serializer_class = WorkSpaceSerializer
|
serializer_class = WorkSpaceSerializer
|
||||||
@ -173,6 +178,7 @@ class UserWorkSpacesEndpoint(BaseAPIView):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
fields = [field for field in request.GET.get("fields", "").split(",") if field]
|
||||||
member_count = (
|
member_count = (
|
||||||
WorkspaceMember.objects.filter(
|
WorkspaceMember.objects.filter(
|
||||||
workspace=OuterRef("id"),
|
workspace=OuterRef("id"),
|
||||||
@ -208,9 +214,12 @@ class UserWorkSpacesEndpoint(BaseAPIView):
|
|||||||
)
|
)
|
||||||
.distinct()
|
.distinct()
|
||||||
)
|
)
|
||||||
|
workspaces = WorkSpaceSerializer(
|
||||||
serializer = WorkSpaceSerializer(self.filter_queryset(workspace), many=True)
|
self.filter_queryset(workspace),
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
fields=fields if fields else None,
|
||||||
|
many=True,
|
||||||
|
).data
|
||||||
|
return Response(workspaces, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class WorkSpaceAvailabilityCheckEndpoint(BaseAPIView):
|
class WorkSpaceAvailabilityCheckEndpoint(BaseAPIView):
|
||||||
@ -537,10 +546,15 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
|||||||
workspace_members = self.get_queryset()
|
workspace_members = self.get_queryset()
|
||||||
|
|
||||||
if workspace_member.role > 10:
|
if workspace_member.role > 10:
|
||||||
serializer = WorkspaceMemberAdminSerializer(workspace_members, many=True)
|
serializer = WorkspaceMemberAdminSerializer(
|
||||||
|
workspace_members,
|
||||||
|
fields=("id", "member", "role"),
|
||||||
|
many=True,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
serializer = WorkSpaceMemberSerializer(
|
serializer = WorkSpaceMemberSerializer(
|
||||||
workspace_members,
|
workspace_members,
|
||||||
|
fields=("id", "member", "role"),
|
||||||
many=True,
|
many=True,
|
||||||
)
|
)
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
@ -705,6 +719,43 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
|||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkspaceProjectMemberEndpoint(BaseAPIView):
|
||||||
|
serializer_class = ProjectMemberRoleSerializer
|
||||||
|
model = ProjectMember
|
||||||
|
|
||||||
|
permission_classes = [
|
||||||
|
WorkspaceEntityPermission,
|
||||||
|
]
|
||||||
|
|
||||||
|
def get(self, request, slug):
|
||||||
|
# Fetch all project IDs where the user is involved
|
||||||
|
project_ids = ProjectMember.objects.filter(
|
||||||
|
member=request.user,
|
||||||
|
member__is_bot=False,
|
||||||
|
is_active=True,
|
||||||
|
).values_list('project_id', flat=True).distinct()
|
||||||
|
|
||||||
|
# Get all the project members in which the user is involved
|
||||||
|
project_members = ProjectMember.objects.filter(
|
||||||
|
workspace__slug=slug,
|
||||||
|
member__is_bot=False,
|
||||||
|
project_id__in=project_ids,
|
||||||
|
is_active=True,
|
||||||
|
).select_related("project", "member", "workspace")
|
||||||
|
project_members = ProjectMemberRoleSerializer(project_members, many=True).data
|
||||||
|
|
||||||
|
project_members_dict = dict()
|
||||||
|
|
||||||
|
# Construct a dictionary with project_id as key and project_members as value
|
||||||
|
for project_member in project_members:
|
||||||
|
project_id = project_member.pop("project")
|
||||||
|
if str(project_id) not in project_members_dict:
|
||||||
|
project_members_dict[str(project_id)] = []
|
||||||
|
project_members_dict[str(project_id)].append(project_member)
|
||||||
|
|
||||||
|
return Response(project_members_dict, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class TeamMemberViewSet(BaseViewSet):
|
class TeamMemberViewSet(BaseViewSet):
|
||||||
serializer_class = TeamSerializer
|
serializer_class = TeamSerializer
|
||||||
model = Team
|
model = Team
|
||||||
@ -1334,8 +1385,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
|
|||||||
issues = IssueLiteSerializer(
|
issues = IssueLiteSerializer(
|
||||||
issue_queryset, many=True, fields=fields if fields else None
|
issue_queryset, many=True, fields=fields if fields else None
|
||||||
).data
|
).data
|
||||||
issue_dict = {str(issue["id"]): issue for issue in issues}
|
return Response(issues, status=status.HTTP_200_OK)
|
||||||
return Response(issue_dict, status=status.HTTP_200_OK)
|
|
||||||
|
|
||||||
|
|
||||||
class WorkspaceLabelsEndpoint(BaseAPIView):
|
class WorkspaceLabelsEndpoint(BaseAPIView):
|
||||||
@ -1349,3 +1399,30 @@ class WorkspaceLabelsEndpoint(BaseAPIView):
|
|||||||
project__project_projectmember__member=request.user,
|
project__project_projectmember__member=request.user,
|
||||||
).values("parent", "name", "color", "id", "project_id", "workspace__slug")
|
).values("parent", "name", "color", "id", "project_id", "workspace__slug")
|
||||||
return Response(labels, status=status.HTTP_200_OK)
|
return Response(labels, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkspaceUserPropertiesEndpoint(BaseAPIView):
|
||||||
|
permission_classes = [
|
||||||
|
WorkspaceViewerPermission,
|
||||||
|
]
|
||||||
|
|
||||||
|
def patch(self, request, slug):
|
||||||
|
workspace_properties = WorkspaceUserProperties.objects.get(
|
||||||
|
user=request.user,
|
||||||
|
workspace__slug=slug,
|
||||||
|
)
|
||||||
|
|
||||||
|
workspace_properties.filters = request.data.get("filters", workspace_properties.filters)
|
||||||
|
workspace_properties.display_filters = request.data.get("display_filters", workspace_properties.display_filters)
|
||||||
|
workspace_properties.display_properties = request.data.get("display_properties", workspace_properties.display_properties)
|
||||||
|
workspace_properties.save()
|
||||||
|
|
||||||
|
serializer = WorkspaceUserPropertiesSerializer(workspace_properties)
|
||||||
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
def get(self, request, slug):
|
||||||
|
workspace_properties, _ = WorkspaceUserProperties.objects.get_or_create(
|
||||||
|
user=request.user, workspace__slug=slug
|
||||||
|
)
|
||||||
|
serializer = WorkspaceUserPropertiesSerializer(workspace_properties)
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
@ -112,8 +112,16 @@ def track_parent(
|
|||||||
epoch,
|
epoch,
|
||||||
):
|
):
|
||||||
if current_instance.get("parent") != requested_data.get("parent"):
|
if current_instance.get("parent") != requested_data.get("parent"):
|
||||||
old_parent = Issue.objects.filter(pk=current_instance.get("parent")).first() if current_instance.get("parent") is not None else None
|
old_parent = (
|
||||||
new_parent = Issue.objects.filter(pk=requested_data.get("parent")).first() if requested_data.get("parent") is not None else None
|
Issue.objects.filter(pk=current_instance.get("parent")).first()
|
||||||
|
if current_instance.get("parent") is not None
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
new_parent = (
|
||||||
|
Issue.objects.filter(pk=requested_data.get("parent")).first()
|
||||||
|
if requested_data.get("parent") is not None
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
issue_activities.append(
|
issue_activities.append(
|
||||||
IssueActivity(
|
IssueActivity(
|
||||||
@ -714,7 +722,9 @@ def create_cycle_issue_activity(
|
|||||||
cycle = Cycle.objects.filter(
|
cycle = Cycle.objects.filter(
|
||||||
pk=created_record.get("fields").get("cycle")
|
pk=created_record.get("fields").get("cycle")
|
||||||
).first()
|
).first()
|
||||||
issue = Issue.objects.filter(pk=created_record.get("fields").get("issue")).first()
|
issue = Issue.objects.filter(
|
||||||
|
pk=created_record.get("fields").get("issue")
|
||||||
|
).first()
|
||||||
if issue:
|
if issue:
|
||||||
issue.updated_at = timezone.now()
|
issue.updated_at = timezone.now()
|
||||||
issue.save(update_fields=["updated_at"])
|
issue.save(update_fields=["updated_at"])
|
||||||
@ -830,7 +840,9 @@ def create_module_issue_activity(
|
|||||||
module = Module.objects.filter(
|
module = Module.objects.filter(
|
||||||
pk=created_record.get("fields").get("module")
|
pk=created_record.get("fields").get("module")
|
||||||
).first()
|
).first()
|
||||||
issue = Issue.objects.filter(pk=created_record.get("fields").get("issue")).first()
|
issue = Issue.objects.filter(
|
||||||
|
pk=created_record.get("fields").get("issue")
|
||||||
|
).first()
|
||||||
if issue:
|
if issue:
|
||||||
issue.updated_at = timezone.now()
|
issue.updated_at = timezone.now()
|
||||||
issue.save(update_fields=["updated_at"])
|
issue.save(update_fields=["updated_at"])
|
||||||
@ -1276,40 +1288,42 @@ def create_issue_relation_activity(
|
|||||||
current_instance = (
|
current_instance = (
|
||||||
json.loads(current_instance) if current_instance is not None else None
|
json.loads(current_instance) if current_instance is not None else None
|
||||||
)
|
)
|
||||||
if current_instance is None and requested_data.get("related_list") is not None:
|
if current_instance is None and requested_data.get("issues") is not None:
|
||||||
for issue_relation in requested_data.get("related_list"):
|
for related_issue in requested_data.get("issues"):
|
||||||
if issue_relation.get("relation_type") == "blocked_by":
|
issue = Issue.objects.get(pk=related_issue)
|
||||||
relation_type = "blocking"
|
|
||||||
else:
|
|
||||||
relation_type = issue_relation.get("relation_type")
|
|
||||||
issue = Issue.objects.get(pk=issue_relation.get("issue"))
|
|
||||||
issue_activities.append(
|
issue_activities.append(
|
||||||
IssueActivity(
|
IssueActivity(
|
||||||
issue_id=issue_relation.get("related_issue"),
|
issue_id=issue_id,
|
||||||
actor_id=actor_id,
|
actor_id=actor_id,
|
||||||
verb="created",
|
verb="created",
|
||||||
old_value="",
|
old_value="",
|
||||||
new_value=f"{issue.project.identifier}-{issue.sequence_id}",
|
new_value=f"{issue.project.identifier}-{issue.sequence_id}",
|
||||||
field=relation_type,
|
field=requested_data.get("relation_type"),
|
||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
workspace_id=workspace_id,
|
workspace_id=workspace_id,
|
||||||
comment=f"added {relation_type} relation",
|
comment=f"added {requested_data.get('relation_type')} relation",
|
||||||
old_identifier=issue_relation.get("issue"),
|
old_identifier=related_issue,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
issue = Issue.objects.get(pk=issue_relation.get("related_issue"))
|
issue = Issue.objects.get(pk=issue_id)
|
||||||
issue_activities.append(
|
issue_activities.append(
|
||||||
IssueActivity(
|
IssueActivity(
|
||||||
issue_id=issue_relation.get("issue"),
|
issue_id=related_issue,
|
||||||
actor_id=actor_id,
|
actor_id=actor_id,
|
||||||
verb="created",
|
verb="created",
|
||||||
old_value="",
|
old_value="",
|
||||||
new_value=f"{issue.project.identifier}-{issue.sequence_id}",
|
new_value=f"{issue.project.identifier}-{issue.sequence_id}",
|
||||||
field=f'{issue_relation.get("relation_type")}',
|
field="blocking"
|
||||||
|
if requested_data.get("relation_type") == "blocked_by"
|
||||||
|
else (
|
||||||
|
"blocked_by"
|
||||||
|
if requested_data.get("relation_type") == "blocking"
|
||||||
|
else requested_data.get("relation_type")
|
||||||
|
),
|
||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
workspace_id=workspace_id,
|
workspace_id=workspace_id,
|
||||||
comment=f'added {issue_relation.get("relation_type")} relation',
|
comment=f'added {"blocking" if requested_data.get("relation_type") == "blocked_by" else ("blocked_by" if requested_data.get("relation_type") == "blocking" else requested_data.get("relation_type")),} relation',
|
||||||
old_identifier=issue_relation.get("related_issue"),
|
old_identifier=issue_id,
|
||||||
epoch=epoch,
|
epoch=epoch,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -1329,45 +1343,45 @@ def delete_issue_relation_activity(
|
|||||||
current_instance = (
|
current_instance = (
|
||||||
json.loads(current_instance) if current_instance is not None else None
|
json.loads(current_instance) if current_instance is not None else None
|
||||||
)
|
)
|
||||||
if current_instance is not None and requested_data.get("related_list") is None:
|
issue = Issue.objects.get(pk=requested_data.get("related_issue"))
|
||||||
if current_instance.get("relation_type") == "blocked_by":
|
|
||||||
relation_type = "blocking"
|
|
||||||
else:
|
|
||||||
relation_type = current_instance.get("relation_type")
|
|
||||||
issue = Issue.objects.get(pk=current_instance.get("issue"))
|
|
||||||
issue_activities.append(
|
issue_activities.append(
|
||||||
IssueActivity(
|
IssueActivity(
|
||||||
issue_id=current_instance.get("related_issue"),
|
issue_id=issue_id,
|
||||||
actor_id=actor_id,
|
actor_id=actor_id,
|
||||||
verb="deleted",
|
verb="deleted",
|
||||||
old_value=f"{issue.project.identifier}-{issue.sequence_id}",
|
old_value=f"{issue.project.identifier}-{issue.sequence_id}",
|
||||||
new_value="",
|
new_value="",
|
||||||
field=relation_type,
|
field=requested_data.get("relation_type"),
|
||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
workspace_id=workspace_id,
|
workspace_id=workspace_id,
|
||||||
comment=f"deleted {relation_type} relation",
|
comment=f"deleted {requested_data.get('relation_type')} relation",
|
||||||
old_identifier=current_instance.get("issue"),
|
old_identifier=requested_data.get("related_issue"),
|
||||||
epoch=epoch,
|
epoch=epoch,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
issue = Issue.objects.get(pk=current_instance.get("related_issue"))
|
issue = Issue.objects.get(pk=issue_id)
|
||||||
issue_activities.append(
|
issue_activities.append(
|
||||||
IssueActivity(
|
IssueActivity(
|
||||||
issue_id=current_instance.get("issue"),
|
issue_id=requested_data.get("related_issue"),
|
||||||
actor_id=actor_id,
|
actor_id=actor_id,
|
||||||
verb="deleted",
|
verb="deleted",
|
||||||
old_value=f"{issue.project.identifier}-{issue.sequence_id}",
|
old_value=f"{issue.project.identifier}-{issue.sequence_id}",
|
||||||
new_value="",
|
new_value="",
|
||||||
field=f'{current_instance.get("relation_type")}',
|
field="blocking"
|
||||||
|
if requested_data.get("relation_type") == "blocked_by"
|
||||||
|
else (
|
||||||
|
"blocked_by"
|
||||||
|
if requested_data.get("relation_type") == "blocking"
|
||||||
|
else requested_data.get("relation_type")
|
||||||
|
),
|
||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
workspace_id=workspace_id,
|
workspace_id=workspace_id,
|
||||||
comment=f'deleted {current_instance.get("relation_type")} relation',
|
comment=f'deleted {requested_data.get("relation_type")} relation',
|
||||||
old_identifier=current_instance.get("related_issue"),
|
old_identifier=requested_data.get("related_issue"),
|
||||||
epoch=epoch,
|
epoch=epoch,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_draft_issue_activity(
|
def create_draft_issue_activity(
|
||||||
requested_data,
|
requested_data,
|
||||||
current_instance,
|
current_instance,
|
||||||
|
@ -291,6 +291,9 @@ def notifications(type, issue_id, project_id, actor_id, subscriber, issue_activi
|
|||||||
sender = "in_app:issue_activities:assigned"
|
sender = "in_app:issue_activities:assigned"
|
||||||
|
|
||||||
for issue_activity in issue_activities_created:
|
for issue_activity in issue_activities_created:
|
||||||
|
# Do not send notification for description update
|
||||||
|
if issue_activity.get("field") == "description":
|
||||||
|
continue;
|
||||||
issue_comment = issue_activity.get("issue_comment")
|
issue_comment = issue_activity.get("issue_comment")
|
||||||
if issue_comment is not None:
|
if issue_comment is not None:
|
||||||
issue_comment = IssueComment.objects.get(
|
issue_comment = IssueComment.objects.get(
|
||||||
|
@ -0,0 +1,136 @@
|
|||||||
|
# Generated by Django 4.2.7 on 2023-12-20 11:14
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import plane.db.models.cycle
|
||||||
|
import plane.db.models.issue
|
||||||
|
import plane.db.models.module
|
||||||
|
import plane.db.models.view
|
||||||
|
import plane.db.models.workspace
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('db', '0050_user_use_case_alter_workspace_organization_size'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='issueview',
|
||||||
|
old_name='query_data',
|
||||||
|
new_name='filters',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='issueproperty',
|
||||||
|
old_name='properties',
|
||||||
|
new_name='display_properties',
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='issueproperty',
|
||||||
|
name='display_properties',
|
||||||
|
field=models.JSONField(default=plane.db.models.issue.get_default_display_properties),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='issueproperty',
|
||||||
|
name='display_filters',
|
||||||
|
field=models.JSONField(default=plane.db.models.issue.get_default_display_filters),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='issueproperty',
|
||||||
|
name='filters',
|
||||||
|
field=models.JSONField(default=plane.db.models.issue.get_default_filters),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='issueview',
|
||||||
|
name='display_filters',
|
||||||
|
field=models.JSONField(default=plane.db.models.view.get_default_display_filters),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='issueview',
|
||||||
|
name='display_properties',
|
||||||
|
field=models.JSONField(default=plane.db.models.view.get_default_display_properties),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='issueview',
|
||||||
|
name='sort_order',
|
||||||
|
field=models.FloatField(default=65535),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='issueview',
|
||||||
|
name='project',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='project_%(class)s', to='db.project'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='WorkspaceUserProperties',
|
||||||
|
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)),
|
||||||
|
('filters', models.JSONField(default=plane.db.models.workspace.get_default_filters)),
|
||||||
|
('display_filters', models.JSONField(default=plane.db.models.workspace.get_default_display_filters)),
|
||||||
|
('display_properties', models.JSONField(default=plane.db.models.workspace.get_default_display_properties)),
|
||||||
|
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_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='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_user_properties', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_user_properties', to='db.workspace')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Workspace User Property',
|
||||||
|
'verbose_name_plural': 'Workspace User Property',
|
||||||
|
'db_table': 'Workspace_user_properties',
|
||||||
|
'ordering': ('-created_at',),
|
||||||
|
'unique_together': {('workspace', 'user')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ModuleUserProperties',
|
||||||
|
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)),
|
||||||
|
('filters', models.JSONField(default=plane.db.models.module.get_default_filters)),
|
||||||
|
('display_filters', models.JSONField(default=plane.db.models.module.get_default_display_filters)),
|
||||||
|
('display_properties', models.JSONField(default=plane.db.models.module.get_default_display_properties)),
|
||||||
|
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
|
||||||
|
('module', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='module_user_properties', to='db.module')),
|
||||||
|
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_%(class)s', to='db.project')),
|
||||||
|
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_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_user_properties', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_%(class)s', to='db.workspace')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Module User Property',
|
||||||
|
'verbose_name_plural': 'Module User Property',
|
||||||
|
'db_table': 'module_user_properties',
|
||||||
|
'ordering': ('-created_at',),
|
||||||
|
'unique_together': {('module', 'user')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CycleUserProperties',
|
||||||
|
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)),
|
||||||
|
('filters', models.JSONField(default=plane.db.models.cycle.get_default_filters)),
|
||||||
|
('display_filters', models.JSONField(default=plane.db.models.cycle.get_default_display_filters)),
|
||||||
|
('display_properties', models.JSONField(default=plane.db.models.cycle.get_default_display_properties)),
|
||||||
|
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
|
||||||
|
('cycle', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cycle_user_properties', to='db.cycle')),
|
||||||
|
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_%(class)s', to='db.project')),
|
||||||
|
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_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_user_properties', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_%(class)s', to='db.workspace')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Cycle User Property',
|
||||||
|
'verbose_name_plural': 'Cycle User Properties',
|
||||||
|
'db_table': 'cycle_user_properties',
|
||||||
|
'ordering': ('-created_at',),
|
||||||
|
'unique_together': {('cycle', 'user')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
65
apiserver/plane/db/migrations/0052_auto_20231220_1141.py
Normal file
65
apiserver/plane/db/migrations/0052_auto_20231220_1141.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# Generated by Django 4.2.7 on 2023-12-19 19:11
|
||||||
|
from plane.db.models import WorkspaceUserProperties, ProjectMember, IssueView
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def workspace_user_properties(apps, schema_editor):
|
||||||
|
WorkspaceMember = apps.get_model("db", "WorkspaceMember")
|
||||||
|
updated_workspace_user_properties = []
|
||||||
|
for workspace_members in WorkspaceMember.objects.all():
|
||||||
|
updated_workspace_user_properties.append(
|
||||||
|
WorkspaceUserProperties(
|
||||||
|
user_id=workspace_members.member_id,
|
||||||
|
display_filters=workspace_members.view_props.get("display_filters"),
|
||||||
|
display_properties=workspace_members.view_props.get("display_properties"),
|
||||||
|
workspace_id=workspace_members.workspace_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
WorkspaceUserProperties.objects.bulk_create(updated_workspace_user_properties, batch_size=2000)
|
||||||
|
|
||||||
|
|
||||||
|
def project_user_properties(apps, schema_editor):
|
||||||
|
IssueProperty = apps.get_model("db", "IssueProperty")
|
||||||
|
updated_issue_user_properties = []
|
||||||
|
for issue_property in IssueProperty.objects.all():
|
||||||
|
project_member = ProjectMember.objects.filter(project_id=issue_property.project_id, member_id=issue_property.user_id).first()
|
||||||
|
if project_member:
|
||||||
|
issue_property.filters = project_member.view_props.get("filters")
|
||||||
|
issue_property.display_filters = project_member.view_props.get("display_filters")
|
||||||
|
updated_issue_user_properties.append(issue_property)
|
||||||
|
|
||||||
|
IssueProperty.objects.bulk_update(updated_issue_user_properties, ["filters", "display_filters"], batch_size=2000)
|
||||||
|
|
||||||
|
|
||||||
|
def issue_view(apps, schema_editor):
|
||||||
|
GlobalView = apps.get_model("db", "GlobalView")
|
||||||
|
updated_issue_views = []
|
||||||
|
|
||||||
|
for global_view in GlobalView.objects.all():
|
||||||
|
updated_issue_views.append(
|
||||||
|
IssueView(
|
||||||
|
workspace_id=global_view.workspace_id,
|
||||||
|
name=global_view.name,
|
||||||
|
description=global_view.description,
|
||||||
|
query=global_view.query,
|
||||||
|
access=global_view.access,
|
||||||
|
filters=global_view.query_data.get("filters", {}),
|
||||||
|
sort_order=global_view.sort_order,
|
||||||
|
created_by_id=global_view.created_by_id,
|
||||||
|
updated_by_id=global_view.updated_by_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
IssueView.objects.bulk_create(updated_issue_views, batch_size=100)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('db', '0051_remove_issueproperty_properties_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(workspace_user_properties),
|
||||||
|
migrations.RunPython(project_user_properties),
|
||||||
|
migrations.RunPython(issue_view),
|
||||||
|
]
|
@ -9,6 +9,8 @@ from .workspace import (
|
|||||||
WorkspaceMemberInvite,
|
WorkspaceMemberInvite,
|
||||||
TeamMember,
|
TeamMember,
|
||||||
WorkspaceTheme,
|
WorkspaceTheme,
|
||||||
|
WorkspaceUserProperties,
|
||||||
|
WorkspaceBaseModel,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .project import (
|
from .project import (
|
||||||
@ -48,11 +50,11 @@ from .social_connection import SocialLoginConnection
|
|||||||
|
|
||||||
from .state import State
|
from .state import State
|
||||||
|
|
||||||
from .cycle import Cycle, CycleIssue, CycleFavorite
|
from .cycle import Cycle, CycleIssue, CycleFavorite, CycleUserProperties
|
||||||
|
|
||||||
from .view import GlobalView, IssueView, IssueViewFavorite
|
from .view import GlobalView, IssueView, IssueViewFavorite
|
||||||
|
|
||||||
from .module import Module, ModuleMember, ModuleIssue, ModuleLink, ModuleFavorite
|
from .module import Module, ModuleMember, ModuleIssue, ModuleLink, ModuleFavorite, ModuleUserProperties
|
||||||
|
|
||||||
from .api import APIToken, APIActivityLog
|
from .api import APIToken, APIActivityLog
|
||||||
|
|
||||||
|
@ -6,6 +6,47 @@ from django.conf import settings
|
|||||||
from . import ProjectBaseModel
|
from . import ProjectBaseModel
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_filters():
|
||||||
|
return {
|
||||||
|
"priority": None,
|
||||||
|
"state": None,
|
||||||
|
"state_group": None,
|
||||||
|
"assignees": None,
|
||||||
|
"created_by": None,
|
||||||
|
"labels": None,
|
||||||
|
"start_date": None,
|
||||||
|
"target_date": None,
|
||||||
|
"subscriber": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_default_display_filters():
|
||||||
|
return {
|
||||||
|
"group_by": None,
|
||||||
|
"order_by": "-created_at",
|
||||||
|
"type": None,
|
||||||
|
"sub_issue": True,
|
||||||
|
"show_empty_groups": True,
|
||||||
|
"layout": "list",
|
||||||
|
"calendar_date_range": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_default_display_properties():
|
||||||
|
return {
|
||||||
|
"assignee": True,
|
||||||
|
"attachment_count": True,
|
||||||
|
"created_on": True,
|
||||||
|
"due_date": True,
|
||||||
|
"estimate": True,
|
||||||
|
"key": True,
|
||||||
|
"labels": True,
|
||||||
|
"link": True,
|
||||||
|
"priority": True,
|
||||||
|
"start_date": True,
|
||||||
|
"state": True,
|
||||||
|
"sub_issue_count": True,
|
||||||
|
"updated_on": True,
|
||||||
|
}
|
||||||
|
|
||||||
class Cycle(ProjectBaseModel):
|
class Cycle(ProjectBaseModel):
|
||||||
name = models.CharField(max_length=255, verbose_name="Cycle Name")
|
name = models.CharField(max_length=255, verbose_name="Cycle Name")
|
||||||
description = models.TextField(verbose_name="Cycle Description", blank=True)
|
description = models.TextField(verbose_name="Cycle Description", blank=True)
|
||||||
@ -89,3 +130,28 @@ class CycleFavorite(ProjectBaseModel):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Return user and the cycle"""
|
"""Return user and the cycle"""
|
||||||
return f"{self.user.email} <{self.cycle.name}>"
|
return f"{self.user.email} <{self.cycle.name}>"
|
||||||
|
|
||||||
|
|
||||||
|
class CycleUserProperties(ProjectBaseModel):
|
||||||
|
cycle = models.ForeignKey(
|
||||||
|
"db.Cycle", on_delete=models.CASCADE, related_name="cycle_user_properties"
|
||||||
|
)
|
||||||
|
user = models.ForeignKey(
|
||||||
|
settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="cycle_user_properties",
|
||||||
|
)
|
||||||
|
filters = models.JSONField(default=get_default_filters)
|
||||||
|
display_filters = models.JSONField(default=get_default_display_filters)
|
||||||
|
display_properties = models.JSONField(default=get_default_display_properties)
|
||||||
|
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ["cycle", "user"]
|
||||||
|
verbose_name = "Cycle User Property"
|
||||||
|
verbose_name_plural = "Cycle User Properties"
|
||||||
|
db_table = "cycle_user_properties"
|
||||||
|
ordering = ("-created_at",)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.cycle.name} {self.user.email}"
|
@ -33,6 +33,48 @@ def get_default_properties():
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_filters():
|
||||||
|
return {
|
||||||
|
"priority": None,
|
||||||
|
"state": None,
|
||||||
|
"state_group": None,
|
||||||
|
"assignees": None,
|
||||||
|
"created_by": None,
|
||||||
|
"labels": None,
|
||||||
|
"start_date": None,
|
||||||
|
"target_date": None,
|
||||||
|
"subscriber": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_default_display_filters():
|
||||||
|
return {
|
||||||
|
"group_by": None,
|
||||||
|
"order_by": "-created_at",
|
||||||
|
"type": None,
|
||||||
|
"sub_issue": True,
|
||||||
|
"show_empty_groups": True,
|
||||||
|
"layout": "list",
|
||||||
|
"calendar_date_range": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_default_display_properties():
|
||||||
|
return {
|
||||||
|
"assignee": True,
|
||||||
|
"attachment_count": True,
|
||||||
|
"created_on": True,
|
||||||
|
"due_date": True,
|
||||||
|
"estimate": True,
|
||||||
|
"key": True,
|
||||||
|
"labels": True,
|
||||||
|
"link": True,
|
||||||
|
"priority": True,
|
||||||
|
"start_date": True,
|
||||||
|
"state": True,
|
||||||
|
"sub_issue_count": True,
|
||||||
|
"updated_on": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# TODO: Handle identifiers for Bulk Inserts - nk
|
# TODO: Handle identifiers for Bulk Inserts - nk
|
||||||
class IssueManager(models.Manager):
|
class IssueManager(models.Manager):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
@ -394,7 +436,9 @@ class IssueProperty(ProjectBaseModel):
|
|||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name="issue_property_user",
|
related_name="issue_property_user",
|
||||||
)
|
)
|
||||||
properties = models.JSONField(default=get_default_properties)
|
filters = models.JSONField(default=get_default_filters)
|
||||||
|
display_filters = models.JSONField(default=get_default_display_filters)
|
||||||
|
display_properties = models.JSONField(default=get_default_display_properties)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Issue Property"
|
verbose_name = "Issue Property"
|
||||||
|
@ -6,6 +6,47 @@ from django.conf import settings
|
|||||||
from . import ProjectBaseModel
|
from . import ProjectBaseModel
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_filters():
|
||||||
|
return {
|
||||||
|
"priority": None,
|
||||||
|
"state": None,
|
||||||
|
"state_group": None,
|
||||||
|
"assignees": None,
|
||||||
|
"created_by": None,
|
||||||
|
"labels": None,
|
||||||
|
"start_date": None,
|
||||||
|
"target_date": None,
|
||||||
|
"subscriber": None,
|
||||||
|
},
|
||||||
|
|
||||||
|
def get_default_display_filters():
|
||||||
|
return {
|
||||||
|
"group_by": None,
|
||||||
|
"order_by": "-created_at",
|
||||||
|
"type": None,
|
||||||
|
"sub_issue": True,
|
||||||
|
"show_empty_groups": True,
|
||||||
|
"layout": "list",
|
||||||
|
"calendar_date_range": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_default_display_properties():
|
||||||
|
return {
|
||||||
|
"assignee": True,
|
||||||
|
"attachment_count": True,
|
||||||
|
"created_on": True,
|
||||||
|
"due_date": True,
|
||||||
|
"estimate": True,
|
||||||
|
"key": True,
|
||||||
|
"labels": True,
|
||||||
|
"link": True,
|
||||||
|
"priority": True,
|
||||||
|
"start_date": True,
|
||||||
|
"state": True,
|
||||||
|
"sub_issue_count": True,
|
||||||
|
"updated_on": True,
|
||||||
|
}
|
||||||
|
|
||||||
class Module(ProjectBaseModel):
|
class Module(ProjectBaseModel):
|
||||||
name = models.CharField(max_length=255, verbose_name="Module Name")
|
name = models.CharField(max_length=255, verbose_name="Module Name")
|
||||||
description = models.TextField(verbose_name="Module Description", blank=True)
|
description = models.TextField(verbose_name="Module Description", blank=True)
|
||||||
@ -141,3 +182,28 @@ class ModuleFavorite(ProjectBaseModel):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Return user and the module"""
|
"""Return user and the module"""
|
||||||
return f"{self.user.email} <{self.module.name}>"
|
return f"{self.user.email} <{self.module.name}>"
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleUserProperties(ProjectBaseModel):
|
||||||
|
module = models.ForeignKey(
|
||||||
|
"db.Module", on_delete=models.CASCADE, related_name="module_user_properties"
|
||||||
|
)
|
||||||
|
user = models.ForeignKey(
|
||||||
|
settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="module_user_properties",
|
||||||
|
)
|
||||||
|
filters = models.JSONField(default=get_default_filters)
|
||||||
|
display_filters = models.JSONField(default=get_default_display_filters)
|
||||||
|
display_properties = models.JSONField(default=get_default_display_properties)
|
||||||
|
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ["module", "user"]
|
||||||
|
verbose_name = "Module User Property"
|
||||||
|
verbose_name_plural = "Module User Property"
|
||||||
|
db_table = "module_user_properties"
|
||||||
|
ordering = ("-created_at",)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.module.name} {self.user.email}"
|
@ -3,9 +3,50 @@ from django.db import models
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
# Module import
|
# Module import
|
||||||
from . import ProjectBaseModel, BaseModel
|
from . import ProjectBaseModel, BaseModel, WorkspaceBaseModel
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_filters():
|
||||||
|
return {
|
||||||
|
"priority": None,
|
||||||
|
"state": None,
|
||||||
|
"state_group": None,
|
||||||
|
"assignees": None,
|
||||||
|
"created_by": None,
|
||||||
|
"labels": None,
|
||||||
|
"start_date": None,
|
||||||
|
"target_date": None,
|
||||||
|
"subscriber": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_default_display_filters():
|
||||||
|
return {
|
||||||
|
"group_by": None,
|
||||||
|
"order_by": "-created_at",
|
||||||
|
"type": None,
|
||||||
|
"sub_issue": True,
|
||||||
|
"show_empty_groups": True,
|
||||||
|
"layout": "list",
|
||||||
|
"calendar_date_range": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_default_display_properties():
|
||||||
|
return {
|
||||||
|
"assignee": True,
|
||||||
|
"attachment_count": True,
|
||||||
|
"created_on": True,
|
||||||
|
"due_date": True,
|
||||||
|
"estimate": True,
|
||||||
|
"key": True,
|
||||||
|
"labels": True,
|
||||||
|
"link": True,
|
||||||
|
"priority": True,
|
||||||
|
"start_date": True,
|
||||||
|
"state": True,
|
||||||
|
"sub_issue_count": True,
|
||||||
|
"updated_on": True,
|
||||||
|
}
|
||||||
|
|
||||||
class GlobalView(BaseModel):
|
class GlobalView(BaseModel):
|
||||||
workspace = models.ForeignKey(
|
workspace = models.ForeignKey(
|
||||||
"db.Workspace", on_delete=models.CASCADE, related_name="global_views"
|
"db.Workspace", on_delete=models.CASCADE, related_name="global_views"
|
||||||
@ -40,14 +81,17 @@ class GlobalView(BaseModel):
|
|||||||
return f"{self.name} <{self.workspace.name}>"
|
return f"{self.name} <{self.workspace.name}>"
|
||||||
|
|
||||||
|
|
||||||
class IssueView(ProjectBaseModel):
|
class IssueView(WorkspaceBaseModel):
|
||||||
name = models.CharField(max_length=255, verbose_name="View Name")
|
name = models.CharField(max_length=255, verbose_name="View Name")
|
||||||
description = models.TextField(verbose_name="View Description", blank=True)
|
description = models.TextField(verbose_name="View Description", blank=True)
|
||||||
query = models.JSONField(verbose_name="View Query")
|
query = models.JSONField(verbose_name="View Query")
|
||||||
|
filters = models.JSONField(default=dict)
|
||||||
|
display_filters = models.JSONField(default=get_default_display_filters)
|
||||||
|
display_properties = models.JSONField(default=get_default_display_properties)
|
||||||
access = models.PositiveSmallIntegerField(
|
access = models.PositiveSmallIntegerField(
|
||||||
default=1, choices=((0, "Private"), (1, "Public"))
|
default=1, choices=((0, "Private"), (1, "Public"))
|
||||||
)
|
)
|
||||||
query_data = models.JSONField(default=dict)
|
sort_order = models.FloatField(default=65535)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Issue View"
|
verbose_name = "Issue View"
|
||||||
|
@ -54,6 +54,51 @@ def get_default_props():
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def get_default_filters():
|
||||||
|
return {
|
||||||
|
"priority": None,
|
||||||
|
"state": None,
|
||||||
|
"state_group": None,
|
||||||
|
"assignees": None,
|
||||||
|
"created_by": None,
|
||||||
|
"labels": None,
|
||||||
|
"start_date": None,
|
||||||
|
"target_date": None,
|
||||||
|
"subscriber": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_default_display_filters():
|
||||||
|
return {
|
||||||
|
"display_filters": {
|
||||||
|
"group_by": None,
|
||||||
|
"order_by": "-created_at",
|
||||||
|
"type": None,
|
||||||
|
"sub_issue": True,
|
||||||
|
"show_empty_groups": True,
|
||||||
|
"layout": "list",
|
||||||
|
"calendar_date_range": "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_default_display_properties():
|
||||||
|
return {
|
||||||
|
"display_properties": {
|
||||||
|
"assignee": True,
|
||||||
|
"attachment_count": True,
|
||||||
|
"created_on": True,
|
||||||
|
"due_date": True,
|
||||||
|
"estimate": True,
|
||||||
|
"key": True,
|
||||||
|
"labels": True,
|
||||||
|
"link": True,
|
||||||
|
"priority": True,
|
||||||
|
"start_date": True,
|
||||||
|
"state": True,
|
||||||
|
"sub_issue_count": True,
|
||||||
|
"updated_on": True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_issue_props():
|
def get_issue_props():
|
||||||
return {
|
return {
|
||||||
@ -103,6 +148,22 @@ class Workspace(BaseModel):
|
|||||||
ordering = ("-created_at",)
|
ordering = ("-created_at",)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkspaceBaseModel(BaseModel):
|
||||||
|
workspace = models.ForeignKey(
|
||||||
|
"db.Workspace", models.CASCADE, related_name="workspace_%(class)s"
|
||||||
|
)
|
||||||
|
project = models.ForeignKey(
|
||||||
|
"db.Project", models.CASCADE, related_name="project_%(class)s", null=True
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if self.project:
|
||||||
|
self.workspace = self.project.workspace
|
||||||
|
super(WorkspaceBaseModel, self).save(*args, **kwargs)
|
||||||
|
|
||||||
class WorkspaceMember(BaseModel):
|
class WorkspaceMember(BaseModel):
|
||||||
workspace = models.ForeignKey(
|
workspace = models.ForeignKey(
|
||||||
"db.Workspace", on_delete=models.CASCADE, related_name="workspace_member"
|
"db.Workspace", on_delete=models.CASCADE, related_name="workspace_member"
|
||||||
@ -218,3 +279,28 @@ class WorkspaceTheme(BaseModel):
|
|||||||
verbose_name_plural = "Workspace Themes"
|
verbose_name_plural = "Workspace Themes"
|
||||||
db_table = "workspace_themes"
|
db_table = "workspace_themes"
|
||||||
ordering = ("-created_at",)
|
ordering = ("-created_at",)
|
||||||
|
|
||||||
|
|
||||||
|
class WorkspaceUserProperties(BaseModel):
|
||||||
|
workspace = models.ForeignKey(
|
||||||
|
"db.Workspace", on_delete=models.CASCADE, related_name="workspace_user_properties"
|
||||||
|
)
|
||||||
|
user = models.ForeignKey(
|
||||||
|
settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="workspace_user_properties",
|
||||||
|
)
|
||||||
|
filters = models.JSONField(default=get_default_filters)
|
||||||
|
display_filters = models.JSONField(default=get_default_display_filters)
|
||||||
|
display_properties = models.JSONField(default=get_default_display_properties)
|
||||||
|
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ["workspace", "user"]
|
||||||
|
verbose_name = "Workspace User Property"
|
||||||
|
verbose_name_plural = "Workspace User Property"
|
||||||
|
db_table = "Workspace_user_properties"
|
||||||
|
ordering = ("-created_at",)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.workspace.name} {self.user.email}"
|
@ -30,7 +30,7 @@ openpyxl==3.1.2
|
|||||||
beautifulsoup4==4.12.2
|
beautifulsoup4==4.12.2
|
||||||
dj-database-url==2.1.0
|
dj-database-url==2.1.0
|
||||||
posthog==3.0.2
|
posthog==3.0.2
|
||||||
cryptography==41.0.6
|
cryptography==41.0.5
|
||||||
lxml==4.9.3
|
lxml==4.9.3
|
||||||
boto3==1.28.40
|
boto3==1.28.40
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ function download(){
|
|||||||
echo ""
|
echo ""
|
||||||
echo "Latest version is now available for you to use"
|
echo "Latest version is now available for you to use"
|
||||||
echo ""
|
echo ""
|
||||||
echo "In case of Upgrade, your new setting file is available as 'variables-upgrade.env'. Please compare and set the required values in '.env 'file."
|
echo "In case of Upgrade, your new setting file is availabe as 'variables-upgrade.env'. Please compare and set the required values in '.env 'file."
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,8 @@
|
|||||||
"packages/eslint-config-custom",
|
"packages/eslint-config-custom",
|
||||||
"packages/tailwind-config-custom",
|
"packages/tailwind-config-custom",
|
||||||
"packages/tsconfig",
|
"packages/tsconfig",
|
||||||
"packages/ui"
|
"packages/ui",
|
||||||
|
"packages/types"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "turbo run build",
|
"build": "turbo run build",
|
||||||
|
7
packages/types/package.json
Normal file
7
packages/types/package.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "@plane/types",
|
||||||
|
"version": "0.14.0",
|
||||||
|
"private": true,
|
||||||
|
"main": "./src/index.d.ts"
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
import { IProjectLite, IWorkspaceLite } from "types";
|
import { IProjectLite, IWorkspaceLite } from "@plane/types";
|
||||||
|
|
||||||
export interface IGptResponse {
|
export interface IGptResponse {
|
||||||
response: string;
|
response: string;
|
@ -1,6 +1,4 @@
|
|||||||
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
|
|
||||||
getLayout?: (page: ReactElement) => ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface IAppConfig {
|
export interface IAppConfig {
|
||||||
email_password_login: boolean;
|
email_password_login: boolean;
|
@ -1,4 +1,4 @@
|
|||||||
import type { IUser, IIssue, IProjectLite, IWorkspaceLite, IIssueFilterOptions, IUserLite } from "types";
|
import type { IUser, TIssue, IProjectLite, IWorkspaceLite, IIssueFilterOptions, IUserLite } from "@plane/types";
|
||||||
|
|
||||||
export type TCycleView = "all" | "active" | "upcoming" | "completed" | "draft";
|
export type TCycleView = "all" | "active" | "upcoming" | "completed" | "draft";
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ export type TLabelsDistribution = {
|
|||||||
|
|
||||||
export interface CycleIssueResponse {
|
export interface CycleIssueResponse {
|
||||||
id: string;
|
id: string;
|
||||||
issue_detail: IIssue;
|
issue_detail: TIssue;
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
updated_at: Date;
|
updated_at: Date;
|
||||||
created_by: string;
|
created_by: string;
|
||||||
@ -82,7 +82,7 @@ export interface CycleIssueResponse {
|
|||||||
|
|
||||||
export type SelectCycleType = (ICycle & { actionType: "edit" | "delete" | "create-issue" }) | undefined;
|
export type SelectCycleType = (ICycle & { actionType: "edit" | "delete" | "create-issue" }) | undefined;
|
||||||
|
|
||||||
export type SelectIssue = (IIssue & { actionType: "edit" | "delete" | "create" }) | null;
|
export type SelectIssue = (TIssue & { actionType: "edit" | "delete" | "create" }) | null;
|
||||||
|
|
||||||
export type CycleDateCheckData = {
|
export type CycleDateCheckData = {
|
||||||
start_date: string;
|
start_date: string;
|
@ -1,24 +1,24 @@
|
|||||||
export interface IEstimate {
|
export interface IEstimate {
|
||||||
id: string;
|
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
updated_at: Date;
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
created_by: string;
|
created_by: string;
|
||||||
updated_by: string;
|
description: string;
|
||||||
points: IEstimatePoint[];
|
id: string;
|
||||||
|
name: string;
|
||||||
project: string;
|
project: string;
|
||||||
project_detail: IProject;
|
project_detail: IProject;
|
||||||
|
updated_at: Date;
|
||||||
|
updated_by: string;
|
||||||
|
points: IEstimatePoint[];
|
||||||
workspace: string;
|
workspace: string;
|
||||||
workspace_detail: IWorkspace;
|
workspace_detail: IWorkspace;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IEstimatePoint {
|
export interface IEstimatePoint {
|
||||||
id: string;
|
|
||||||
created_at: string;
|
created_at: string;
|
||||||
created_by: string;
|
created_by: string;
|
||||||
description: string;
|
description: string;
|
||||||
estimate: string;
|
estimate: string;
|
||||||
|
id: string;
|
||||||
key: number;
|
key: number;
|
||||||
project: string;
|
project: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
@ -1,9 +1,9 @@
|
|||||||
export * from "./github-importer";
|
export * from "./github-importer";
|
||||||
export * from "./jira-importer";
|
export * from "./jira-importer";
|
||||||
|
|
||||||
import { IProjectLite } from "types/projects";
|
import { IProjectLite } from "../projects";
|
||||||
// types
|
// types
|
||||||
import { IUserLite } from "types/users";
|
import { IUserLite } from "../users";
|
||||||
|
|
||||||
export interface IImporterService {
|
export interface IImporterService {
|
||||||
created_at: string;
|
created_at: string;
|
@ -1,7 +1,7 @@
|
|||||||
import { IIssue } from "./issues";
|
import { TIssue } from "./issues";
|
||||||
import type { IProjectLite } from "./projects";
|
import type { IProjectLite } from "./projects";
|
||||||
|
|
||||||
export interface IInboxIssue extends IIssue {
|
export interface IInboxIssue extends TIssue {
|
||||||
issue_inbox: {
|
issue_inbox: {
|
||||||
duplicate_to: string | null;
|
duplicate_to: string | null;
|
||||||
id: string;
|
id: string;
|
@ -21,6 +21,11 @@ export * from "./reaction";
|
|||||||
export * from "./view-props";
|
export * from "./view-props";
|
||||||
export * from "./workspace-views";
|
export * from "./workspace-views";
|
||||||
export * from "./webhook";
|
export * from "./webhook";
|
||||||
|
export * from "./issues/base"; // TODO: Remove this after development and the refactor/mobx-store-issue branch is stable
|
||||||
|
export * from "./auth";
|
||||||
|
export * from "./api_token";
|
||||||
|
export * from "./instance";
|
||||||
|
export * from "./app";
|
||||||
|
|
||||||
export type NestedKeyOf<ObjectType extends object> = {
|
export type NestedKeyOf<ObjectType extends object> = {
|
||||||
[Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object
|
[Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object
|
@ -1,3 +1,4 @@
|
|||||||
|
import { ReactElement } from "react";
|
||||||
import { KeyedMutator } from "swr";
|
import { KeyedMutator } from "swr";
|
||||||
import type {
|
import type {
|
||||||
IState,
|
IState,
|
||||||
@ -10,7 +11,8 @@ import type {
|
|||||||
IStateLite,
|
IStateLite,
|
||||||
Properties,
|
Properties,
|
||||||
IIssueDisplayFilterOptions,
|
IIssueDisplayFilterOptions,
|
||||||
} from "types";
|
IIssueReaction,
|
||||||
|
} from "@plane/types";
|
||||||
|
|
||||||
export interface IIssueCycle {
|
export interface IIssueCycle {
|
||||||
id: string;
|
id: string;
|
||||||
@ -83,6 +85,7 @@ export interface IIssue {
|
|||||||
attachment_count: number;
|
attachment_count: number;
|
||||||
attachments: any[];
|
attachments: any[];
|
||||||
issue_relations: IssueRelation[];
|
issue_relations: IssueRelation[];
|
||||||
|
issue_reactions: IIssueReaction[];
|
||||||
related_issues: IssueRelation[];
|
related_issues: IssueRelation[];
|
||||||
bridge_id?: string | null;
|
bridge_id?: string | null;
|
||||||
completed_at: Date;
|
completed_at: Date;
|
||||||
@ -138,7 +141,7 @@ export interface ISubIssuesState {
|
|||||||
|
|
||||||
export interface ISubIssueResponse {
|
export interface ISubIssueResponse {
|
||||||
state_distribution: ISubIssuesState;
|
state_distribution: ISubIssuesState;
|
||||||
sub_issues: IIssue[];
|
sub_issues: TIssue[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BlockeIssueDetail {
|
export interface BlockeIssueDetail {
|
||||||
@ -240,13 +243,13 @@ export interface IIssueAttachment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IIssueViewProps {
|
export interface IIssueViewProps {
|
||||||
groupedIssues: { [key: string]: IIssue[] } | undefined;
|
groupedIssues: { [key: string]: TIssue[] } | undefined;
|
||||||
displayFilters: IIssueDisplayFilterOptions | undefined;
|
displayFilters: IIssueDisplayFilterOptions | undefined;
|
||||||
isEmpty: boolean;
|
isEmpty: boolean;
|
||||||
mutateIssues: KeyedMutator<
|
mutateIssues: KeyedMutator<
|
||||||
| IIssue[]
|
| TIssue[]
|
||||||
| {
|
| {
|
||||||
[key: string]: IIssue[];
|
[key: string]: TIssue[];
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
params: any;
|
params: any;
|
||||||
@ -254,3 +257,88 @@ export interface IIssueViewProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type TIssuePriorities = "urgent" | "high" | "medium" | "low" | "none";
|
export type TIssuePriorities = "urgent" | "high" | "medium" | "low" | "none";
|
||||||
|
|
||||||
|
export interface ViewFlags {
|
||||||
|
enableQuickAdd: boolean;
|
||||||
|
enableIssueCreation: boolean;
|
||||||
|
enableInlineEditing: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GroupByColumnTypes =
|
||||||
|
| "project"
|
||||||
|
| "state"
|
||||||
|
| "state_detail.group"
|
||||||
|
| "priority"
|
||||||
|
| "labels"
|
||||||
|
| "assignees"
|
||||||
|
| "created_by";
|
||||||
|
|
||||||
|
export interface IGroupByColumn {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
Icon: ReactElement | undefined;
|
||||||
|
payload: Partial<TIssue>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IIssueMap {
|
||||||
|
[key: string]: TIssue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// new issue structure types
|
||||||
|
export type TIssue = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
state_id: string;
|
||||||
|
description_html: string;
|
||||||
|
sort_order: number;
|
||||||
|
completed_at: string | null;
|
||||||
|
estimate_point: number | null;
|
||||||
|
priority: TIssuePriorities;
|
||||||
|
start_date: string | null;
|
||||||
|
target_date: string | null;
|
||||||
|
sequence_id: number;
|
||||||
|
project_id: string;
|
||||||
|
parent_id: string | null;
|
||||||
|
cycle_id: string | null;
|
||||||
|
module_id: string | null;
|
||||||
|
label_ids: string[];
|
||||||
|
assignee_ids: string[];
|
||||||
|
sub_issues_count: number;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
created_by: string;
|
||||||
|
updated_by: string;
|
||||||
|
attachment_count: number;
|
||||||
|
link_count: number;
|
||||||
|
is_subscribed: boolean;
|
||||||
|
archived_at: boolean;
|
||||||
|
is_draft: boolean;
|
||||||
|
// tempId is used for optimistic updates. It is not a part of the API response.
|
||||||
|
tempId?: string;
|
||||||
|
// issue details
|
||||||
|
related_issues: any;
|
||||||
|
issue_reactions: any;
|
||||||
|
issue_relations: any;
|
||||||
|
issue_cycle: any;
|
||||||
|
issue_module: any;
|
||||||
|
parent_detail: any;
|
||||||
|
issue_link: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIssueMap = {
|
||||||
|
[issue_id: string]: TIssue;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TLoader = "init-loader" | "mutation" | undefined;
|
||||||
|
|
||||||
|
export type TGroupedIssues = {
|
||||||
|
[group_id: string]: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TSubGroupedIssues = {
|
||||||
|
[sub_grouped_id: string]: {
|
||||||
|
[group_id: string]: string[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TUnGroupedIssues = string[];
|
23
packages/types/src/issues/base.d.ts
vendored
Normal file
23
packages/types/src/issues/base.d.ts
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// issues
|
||||||
|
export * from "./issue";
|
||||||
|
export * from "./issue_reaction";
|
||||||
|
export * from "./issue_link";
|
||||||
|
export * from "./issue_attachment";
|
||||||
|
export * from "./issue_relation";
|
||||||
|
export * from "./issue_activity";
|
||||||
|
export * from "./issue_comment_reaction";
|
||||||
|
export * from "./issue_sub_issues";
|
||||||
|
|
||||||
|
export type TLoader = "init-loader" | "mutation" | undefined;
|
||||||
|
|
||||||
|
export type TGroupedIssues = {
|
||||||
|
[group_id: string]: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TSubGroupedIssues = {
|
||||||
|
[sub_grouped_id: string]: {
|
||||||
|
[group_id: string]: string[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TUnGroupedIssues = string[];
|
36
packages/types/src/issues/issue.d.ts
vendored
Normal file
36
packages/types/src/issues/issue.d.ts
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// new issue structure types
|
||||||
|
export type TIssue = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
state_id: string;
|
||||||
|
description_html: string;
|
||||||
|
sort_order: number;
|
||||||
|
completed_at: string | null;
|
||||||
|
estimate_point: number | null;
|
||||||
|
priority: TIssuePriorities;
|
||||||
|
start_date: string;
|
||||||
|
target_date: string;
|
||||||
|
sequence_id: number;
|
||||||
|
project_id: string;
|
||||||
|
parent_id: string | null;
|
||||||
|
cycle_id: string | null;
|
||||||
|
module_id: string | null;
|
||||||
|
label_ids: string[];
|
||||||
|
assignee_ids: string[];
|
||||||
|
sub_issues_count: number;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
created_by: string;
|
||||||
|
updated_by: string;
|
||||||
|
attachment_count: number;
|
||||||
|
link_count: number;
|
||||||
|
is_subscribed: boolean;
|
||||||
|
archived_at: boolean;
|
||||||
|
is_draft: boolean;
|
||||||
|
// tempId is used for optimistic updates. It is not a part of the API response.
|
||||||
|
tempId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIssueMap = {
|
||||||
|
[issue_id: string]: TIssue;
|
||||||
|
};
|
41
packages/types/src/issues/issue_activity.d.ts
vendored
Normal file
41
packages/types/src/issues/issue_activity.d.ts
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
export type TIssueActivity = {
|
||||||
|
access?: "EXTERNAL" | "INTERNAL";
|
||||||
|
actor: string;
|
||||||
|
actor_detail: IUserLite;
|
||||||
|
attachments: any[];
|
||||||
|
comment?: string;
|
||||||
|
comment_html?: string;
|
||||||
|
comment_stripped?: string;
|
||||||
|
created_at: Date;
|
||||||
|
created_by: string;
|
||||||
|
field: string | null;
|
||||||
|
id: string;
|
||||||
|
issue: string | null;
|
||||||
|
issue_comment?: string | null;
|
||||||
|
issue_detail: {
|
||||||
|
description_html: string;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
priority: string | null;
|
||||||
|
sequence_id: string;
|
||||||
|
} | null;
|
||||||
|
new_identifier: string | null;
|
||||||
|
new_value: string | null;
|
||||||
|
old_identifier: string | null;
|
||||||
|
old_value: string | null;
|
||||||
|
project: string;
|
||||||
|
project_detail: IProjectLite;
|
||||||
|
updated_at: Date;
|
||||||
|
updated_by: string;
|
||||||
|
verb: string;
|
||||||
|
workspace: string;
|
||||||
|
workspace_detail?: IWorkspaceLite;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIssueActivityMap = {
|
||||||
|
[issue_id: string]: TIssueActivity;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIssueActivityIdMap = {
|
||||||
|
[issue_id: string]: string[];
|
||||||
|
};
|
23
packages/types/src/issues/issue_attachment.d.ts
vendored
Normal file
23
packages/types/src/issues/issue_attachment.d.ts
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
export type TIssueAttachment = {
|
||||||
|
id: string;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
attributes: {
|
||||||
|
name: string;
|
||||||
|
size: number;
|
||||||
|
};
|
||||||
|
asset: string;
|
||||||
|
created_by: string;
|
||||||
|
updated_by: string;
|
||||||
|
project: string;
|
||||||
|
workspace: string;
|
||||||
|
issue: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIssueAttachmentMap = {
|
||||||
|
[issue_id: string]: TIssueAttachment;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIssueAttachmentIdMap = {
|
||||||
|
[issue_id: string]: string[];
|
||||||
|
};
|
20
packages/types/src/issues/issue_comment_reaction.d.ts
vendored
Normal file
20
packages/types/src/issues/issue_comment_reaction.d.ts
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export type TIssueCommentReaction = {
|
||||||
|
id: string;
|
||||||
|
created_at: Date;
|
||||||
|
updated_at: Date;
|
||||||
|
reaction: string;
|
||||||
|
created_by: string;
|
||||||
|
updated_by: string;
|
||||||
|
project: string;
|
||||||
|
workspace: string;
|
||||||
|
actor: string;
|
||||||
|
comment: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIssueCommentReactionMap = {
|
||||||
|
[issue_id: string]: TIssueCommentReaction;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIssueCommentReactionIdMap = {
|
||||||
|
[issue_id: string]: string[];
|
||||||
|
};
|
20
packages/types/src/issues/issue_link.d.ts
vendored
Normal file
20
packages/types/src/issues/issue_link.d.ts
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export type TIssueLinkEditableFields = {
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIssueLink = TIssueLinkEditableFields & {
|
||||||
|
created_at: Date;
|
||||||
|
created_by: string;
|
||||||
|
created_by_detail: IUserLite;
|
||||||
|
id: string;
|
||||||
|
metadata: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIssueLinkMap = {
|
||||||
|
[issue_id: string]: TIssueLink;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIssueLinkIdMap = {
|
||||||
|
[issue_id: string]: string[];
|
||||||
|
};
|
21
packages/types/src/issues/issue_reaction.d.ts
vendored
Normal file
21
packages/types/src/issues/issue_reaction.d.ts
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
export type TIssueReaction = {
|
||||||
|
actor: string;
|
||||||
|
actor_detail: IUserLite;
|
||||||
|
created_at: Date;
|
||||||
|
created_by: string;
|
||||||
|
id: string;
|
||||||
|
issue: string;
|
||||||
|
project: string;
|
||||||
|
reaction: string;
|
||||||
|
updated_at: Date;
|
||||||
|
updated_by: string;
|
||||||
|
workspace: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIssueReactionMap = {
|
||||||
|
[issue_id: string]: TIssueReaction;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIssueReactionIdMap = {
|
||||||
|
[issue_id: string]: string[];
|
||||||
|
};
|
20
packages/types/src/issues/issue_relation.d.ts
vendored
Normal file
20
packages/types/src/issues/issue_relation.d.ts
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { TIssue } from "./issues";
|
||||||
|
|
||||||
|
export type TIssueRelationTypes =
|
||||||
|
| "blocking"
|
||||||
|
| "blocked_by"
|
||||||
|
| "duplicate"
|
||||||
|
| "relates_to";
|
||||||
|
|
||||||
|
export type TIssueRelationObject = { issue_detail: TIssue };
|
||||||
|
|
||||||
|
export type TIssueRelation = Record<
|
||||||
|
TIssueRelationTypes,
|
||||||
|
TIssueRelationObject[]
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type TIssueRelationMap = {
|
||||||
|
[issue_id: string]: Record<TIssueRelationTypes, string[]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIssueRelationIdMap = Record<TIssueRelationTypes, string[]>;
|
22
packages/types/src/issues/issue_sub_issues.d.ts
vendored
Normal file
22
packages/types/src/issues/issue_sub_issues.d.ts
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { TIssue } from "./issue";
|
||||||
|
|
||||||
|
export type TSubIssuesStateDistribution = {
|
||||||
|
backlog: number;
|
||||||
|
unstarted: number;
|
||||||
|
started: number;
|
||||||
|
completed: number;
|
||||||
|
cancelled: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIssueSubIssues = {
|
||||||
|
state_distribution: TSubIssuesStateDistribution;
|
||||||
|
sub_issues: TIssue[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIssueSubIssuesStateDistributionMap = {
|
||||||
|
[issue_id: string]: TSubIssuesStateDistribution;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIssueSubIssuesIdMap = {
|
||||||
|
[issue_id: string]: string[];
|
||||||
|
};
|
0
packages/types/src/issues/issue_subscription.d.ts
vendored
Normal file
0
packages/types/src/issues/issue_subscription.d.ts
vendored
Normal file
@ -1,14 +1,14 @@
|
|||||||
import type {
|
import type {
|
||||||
IUser,
|
IUser,
|
||||||
IUserLite,
|
IUserLite,
|
||||||
IIssue,
|
TIssue,
|
||||||
IProject,
|
IProject,
|
||||||
IWorkspace,
|
IWorkspace,
|
||||||
IWorkspaceLite,
|
IWorkspaceLite,
|
||||||
IProjectLite,
|
IProjectLite,
|
||||||
IIssueFilterOptions,
|
IIssueFilterOptions,
|
||||||
ILinkDetails,
|
ILinkDetails,
|
||||||
} from "types";
|
} from "@plane/types";
|
||||||
|
|
||||||
export type TModuleStatus = "backlog" | "planned" | "in-progress" | "paused" | "completed" | "cancelled";
|
export type TModuleStatus = "backlog" | "planned" | "in-progress" | "paused" | "completed" | "cancelled";
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ export interface ModuleIssueResponse {
|
|||||||
created_by: string;
|
created_by: string;
|
||||||
id: string;
|
id: string;
|
||||||
issue: string;
|
issue: string;
|
||||||
issue_detail: IIssue;
|
issue_detail: TIssue;
|
||||||
module: string;
|
module: string;
|
||||||
module_detail: IModule;
|
module_detail: IModule;
|
||||||
project: string;
|
project: string;
|
||||||
@ -75,4 +75,4 @@ export type ModuleLink = {
|
|||||||
|
|
||||||
export type SelectModuleType = (IModule & { actionType: "edit" | "delete" | "create-issue" }) | undefined;
|
export type SelectModuleType = (IModule & { actionType: "edit" | "delete" | "create-issue" }) | undefined;
|
||||||
|
|
||||||
export type SelectIssue = (IIssue & { actionType: "edit" | "delete" | "create" }) | undefined;
|
export type SelectIssue = (TIssue & { actionType: "edit" | "delete" | "create" }) | undefined;
|
@ -1,5 +1,5 @@
|
|||||||
// types
|
// types
|
||||||
import { IIssue, IIssueLabel, IWorkspaceLite, IProjectLite } from "types";
|
import { TIssue, IIssueLabel, IWorkspaceLite, IProjectLite } from "@plane/types";
|
||||||
|
|
||||||
export interface IPage {
|
export interface IPage {
|
||||||
access: number;
|
access: number;
|
||||||
@ -27,15 +27,11 @@ export interface IPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IRecentPages {
|
export interface IRecentPages {
|
||||||
today: IPage[];
|
today: string[];
|
||||||
yesterday: IPage[];
|
yesterday: string[];
|
||||||
this_week: IPage[];
|
this_week: string[];
|
||||||
older: IPage[];
|
older: string[];
|
||||||
[key: string]: IPage[];
|
[key: string]: string[];
|
||||||
}
|
|
||||||
|
|
||||||
export interface RecentPagesResponse {
|
|
||||||
[key: string]: IPage[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPageBlock {
|
export interface IPageBlock {
|
||||||
@ -47,7 +43,7 @@ export interface IPageBlock {
|
|||||||
description_stripped: any;
|
description_stripped: any;
|
||||||
id: string;
|
id: string;
|
||||||
issue: string | null;
|
issue: string | null;
|
||||||
issue_detail: IIssue | null;
|
issue_detail: TIssue | null;
|
||||||
name: string;
|
name: string;
|
||||||
page: string;
|
page: string;
|
||||||
project: string;
|
project: string;
|
@ -1,6 +1,5 @@
|
|||||||
import type { IUserLite, IWorkspace, IWorkspaceLite, IUserMemberLite, TStateGroups, IProjectViewProps } from ".";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
|
import type { IUser, IUserLite, IWorkspace, IWorkspaceLite, TStateGroups } from ".";
|
||||||
export type TUserProjectRole = 5 | 10 | 15 | 20;
|
|
||||||
|
|
||||||
export interface IProject {
|
export interface IProject {
|
||||||
archive_in: number;
|
archive_in: number;
|
||||||
@ -34,13 +33,10 @@ export interface IProject {
|
|||||||
is_deployed: boolean;
|
is_deployed: boolean;
|
||||||
is_favorite: boolean;
|
is_favorite: boolean;
|
||||||
is_member: boolean;
|
is_member: boolean;
|
||||||
member_role: TUserProjectRole | null;
|
member_role: EUserProjectRoles | null;
|
||||||
members: IProjectMemberLite[];
|
members: IProjectMemberLite[];
|
||||||
issue_views_view: boolean;
|
|
||||||
module_view: boolean;
|
|
||||||
name: string;
|
name: string;
|
||||||
network: number;
|
network: number;
|
||||||
page_view: boolean;
|
|
||||||
project_lead: IUserLite | string | null;
|
project_lead: IUserLite | string | null;
|
||||||
sort_order: number | null;
|
sort_order: number | null;
|
||||||
total_cycles: number;
|
total_cycles: number;
|
||||||
@ -64,6 +60,10 @@ type ProjectPreferences = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface IProjectMap {
|
||||||
|
[id: string]: IProject;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IProjectMemberLite {
|
export interface IProjectMemberLite {
|
||||||
id: string;
|
id: string;
|
||||||
member__avatar: string;
|
member__avatar: string;
|
||||||
@ -77,7 +77,7 @@ export interface IProjectMember {
|
|||||||
project: IProjectLite;
|
project: IProjectLite;
|
||||||
workspace: IWorkspaceLite;
|
workspace: IWorkspaceLite;
|
||||||
comment: string;
|
comment: string;
|
||||||
role: TUserProjectRole;
|
role: EUserProjectRoles;
|
||||||
|
|
||||||
preferences: ProjectPreferences;
|
preferences: ProjectPreferences;
|
||||||
|
|
||||||
@ -90,27 +90,14 @@ export interface IProjectMember {
|
|||||||
updated_by: string;
|
updated_by: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IProjectMemberInvitation {
|
export interface IProjectMembership {
|
||||||
id: string;
|
id: string;
|
||||||
|
member: string;
|
||||||
project: IProject;
|
role: EUserProjectRoles;
|
||||||
workspace: IWorkspace;
|
|
||||||
|
|
||||||
email: string;
|
|
||||||
accepted: boolean;
|
|
||||||
token: string;
|
|
||||||
message: string;
|
|
||||||
responded_at: Date;
|
|
||||||
role: TUserProjectRole;
|
|
||||||
|
|
||||||
created_at: Date;
|
|
||||||
updated_at: Date;
|
|
||||||
created_by: string;
|
|
||||||
updated_by: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IProjectBulkAddFormData {
|
export interface IProjectBulkAddFormData {
|
||||||
members: { role: TUserProjectRole; member_id: string }[];
|
members: { role: EUserProjectRoles; member_id: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IGithubRepository {
|
export interface IGithubRepository {
|
@ -1,4 +1,4 @@
|
|||||||
import { IProject, IProjectLite, IWorkspaceLite } from "types";
|
import { IProject, IProjectLite, IWorkspaceLite } from "@plane/types";
|
||||||
|
|
||||||
export type TStateGroups = "backlog" | "unstarted" | "started" | "completed" | "cancelled";
|
export type TStateGroups = "backlog" | "unstarted" | "started" | "completed" | "cancelled";
|
||||||
|
|
@ -1,3 +1,4 @@
|
|||||||
|
import { EUserProjectRoles } from "constants/project";
|
||||||
import { IIssueActivity, IIssueLite, TStateGroups } from ".";
|
import { IIssueActivity, IIssueLite, TStateGroups } from ".";
|
||||||
|
|
||||||
export interface IUser {
|
export interface IUser {
|
||||||
@ -61,11 +62,10 @@ export interface IUserTheme {
|
|||||||
|
|
||||||
export interface IUserLite {
|
export interface IUserLite {
|
||||||
avatar: string;
|
avatar: string;
|
||||||
created_at: Date;
|
|
||||||
display_name: string;
|
display_name: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
first_name: string;
|
first_name: string;
|
||||||
readonly id: string;
|
id: string;
|
||||||
is_bot: boolean;
|
is_bot: boolean;
|
||||||
last_name: string;
|
last_name: string;
|
||||||
}
|
}
|
||||||
@ -163,7 +163,7 @@ export interface IUserProfileProjectSegregation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IUserProjectsRole {
|
export interface IUserProjectsRole {
|
||||||
[project_id: string]: number;
|
[projectId: string]: EUserProjectRoles;
|
||||||
}
|
}
|
||||||
|
|
||||||
// export interface ICurrentUser {
|
// export interface ICurrentUser {
|
@ -108,6 +108,18 @@ export interface IIssueDisplayProperties {
|
|||||||
updated_on?: boolean;
|
updated_on?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IIssueFilters {
|
||||||
|
filters: IIssueFilterOptions | undefined;
|
||||||
|
displayFilters: IIssueDisplayFilterOptions | undefined;
|
||||||
|
displayProperties: IIssueDisplayProperties | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IIssueFiltersResponse {
|
||||||
|
filters: IIssueFilterOptions;
|
||||||
|
display_filters: IIssueDisplayFilterOptions;
|
||||||
|
display_properties: IIssueDisplayProperties;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IWorkspaceIssueFilterOptions {
|
export interface IWorkspaceIssueFilterOptions {
|
||||||
assignees?: string[] | null;
|
assignees?: string[] | null;
|
||||||
created_by?: string[] | null;
|
created_by?: string[] | null;
|
@ -1,4 +1,4 @@
|
|||||||
import { IIssueFilterOptions } from "./view-props";
|
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "./view-props";
|
||||||
|
|
||||||
export interface IProjectView {
|
export interface IProjectView {
|
||||||
id: string;
|
id: string;
|
||||||
@ -10,6 +10,9 @@ export interface IProjectView {
|
|||||||
updated_by: string;
|
updated_by: string;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
filters: IIssueFilterOptions;
|
||||||
|
display_filters: IIssueDisplayFilterOptions;
|
||||||
|
display_properties: IIssueDisplayProperties;
|
||||||
query: IIssueFilterOptions;
|
query: IIssueFilterOptions;
|
||||||
query_data: IIssueFilterOptions;
|
query_data: IIssueFilterOptions;
|
||||||
project: string;
|
project: string;
|
@ -1,4 +1,9 @@
|
|||||||
import { IWorkspaceViewProps } from "./view-props";
|
import {
|
||||||
|
IWorkspaceViewProps,
|
||||||
|
IIssueDisplayFilterOptions,
|
||||||
|
IIssueDisplayProperties,
|
||||||
|
IIssueFilterOptions,
|
||||||
|
} from "./view-props";
|
||||||
|
|
||||||
export interface IWorkspaceView {
|
export interface IWorkspaceView {
|
||||||
id: string;
|
id: string;
|
||||||
@ -10,6 +15,9 @@ export interface IWorkspaceView {
|
|||||||
updated_by: string;
|
updated_by: string;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
filters: IIssueIIFilterOptions;
|
||||||
|
display_filters: IIssueDisplayFilterOptions;
|
||||||
|
display_properties: IIssueDisplayProperties;
|
||||||
query: any;
|
query: any;
|
||||||
query_data: IWorkspaceViewProps;
|
query_data: IWorkspaceViewProps;
|
||||||
project: string;
|
project: string;
|
@ -1,6 +1,5 @@
|
|||||||
import type { IProjectMember, IUser, IUserLite, IWorkspaceViewProps } from "types";
|
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||||
|
import type { IProjectMember, IUser, IUserLite, IWorkspaceViewProps } from "@plane/types";
|
||||||
export type TUserWorkspaceRole = 5 | 10 | 15 | 20;
|
|
||||||
|
|
||||||
export interface IWorkspace {
|
export interface IWorkspace {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
@ -27,18 +26,23 @@ export interface IWorkspaceLite {
|
|||||||
|
|
||||||
export interface IWorkspaceMemberInvitation {
|
export interface IWorkspaceMemberInvitation {
|
||||||
accepted: boolean;
|
accepted: boolean;
|
||||||
readonly id: string;
|
|
||||||
email: string;
|
email: string;
|
||||||
token: string;
|
id: string;
|
||||||
message: string;
|
message: string;
|
||||||
responded_at: Date;
|
responded_at: Date;
|
||||||
role: TUserWorkspaceRole;
|
role: EUserWorkspaceRoles;
|
||||||
created_by_detail: IUser;
|
token: string;
|
||||||
workspace: IWorkspace;
|
workspace: string;
|
||||||
|
workspace_detail: {
|
||||||
|
id: string;
|
||||||
|
logo: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IWorkspaceBulkInviteFormData {
|
export interface IWorkspaceBulkInviteFormData {
|
||||||
emails: { email: string; role: TUserWorkspaceRole }[];
|
emails: { email: string; role: EUserWorkspaceRoles }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Properties = {
|
export type Properties = {
|
||||||
@ -58,15 +62,9 @@ export type Properties = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface IWorkspaceMember {
|
export interface IWorkspaceMember {
|
||||||
company_role: string | null;
|
|
||||||
created_at: Date;
|
|
||||||
created_by: string;
|
|
||||||
id: string;
|
id: string;
|
||||||
member: IUserLite;
|
member: IUserLite;
|
||||||
role: TUserWorkspaceRole;
|
role: EUserWorkspaceRoles;
|
||||||
updated_at: Date;
|
|
||||||
updated_by: string;
|
|
||||||
workspace: IWorkspaceLite;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IWorkspaceMemberMe {
|
export interface IWorkspaceMemberMe {
|
||||||
@ -76,7 +74,7 @@ export interface IWorkspaceMemberMe {
|
|||||||
default_props: IWorkspaceViewProps;
|
default_props: IWorkspaceViewProps;
|
||||||
id: string;
|
id: string;
|
||||||
member: string;
|
member: string;
|
||||||
role: TUserWorkspaceRole;
|
role: EUserWorkspaceRoles;
|
||||||
updated_at: Date;
|
updated_at: Date;
|
||||||
updated_by: string;
|
updated_by: string;
|
||||||
view_props: IWorkspaceViewProps;
|
view_props: IWorkspaceViewProps;
|
@ -1,13 +1,16 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
// icons
|
|
||||||
import { AlertCircle, Ban, SignalHigh, SignalLow, SignalMedium } from "lucide-react";
|
import { AlertCircle, Ban, SignalHigh, SignalLow, SignalMedium } from "lucide-react";
|
||||||
|
|
||||||
// types
|
type TIssuePriorities = "urgent" | "high" | "medium" | "low" | "none";
|
||||||
import { IPriorityIcon } from "./type";
|
|
||||||
|
|
||||||
export const PriorityIcon: React.FC<IPriorityIcon> = ({ priority, className = "", transparentBg = false }) => {
|
interface IPriorityIcon {
|
||||||
if (!className || className === "") className = "h-4 w-4";
|
className?: string;
|
||||||
|
priority: TIssuePriorities;
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PriorityIcon: React.FC<IPriorityIcon> = (props) => {
|
||||||
|
const { priority, className = "", size = 14 } = props;
|
||||||
|
|
||||||
// Convert to lowercase for string comparison
|
// Convert to lowercase for string comparison
|
||||||
const lowercasePriority = priority?.toLowerCase();
|
const lowercasePriority = priority?.toLowerCase();
|
||||||
@ -16,31 +19,17 @@ export const PriorityIcon: React.FC<IPriorityIcon> = ({ priority, className = ""
|
|||||||
const getPriorityIcon = (): React.ReactNode => {
|
const getPriorityIcon = (): React.ReactNode => {
|
||||||
switch (lowercasePriority) {
|
switch (lowercasePriority) {
|
||||||
case "urgent":
|
case "urgent":
|
||||||
return <AlertCircle className={`text-red-500 ${transparentBg ? "" : "p-0.5"} ${className}`} />;
|
return <AlertCircle size={size} className={`text-red-500 ${className}`} />;
|
||||||
case "high":
|
case "high":
|
||||||
return <SignalHigh className={`text-orange-500 ${transparentBg ? "" : "pl-1"} ${className}`} />;
|
return <SignalHigh size={size} strokeWidth={3} className={`text-orange-500 ${className}`} />;
|
||||||
case "medium":
|
case "medium":
|
||||||
return <SignalMedium className={`text-yellow-500 ${transparentBg ? "" : "ml-1.5"} ${className}`} />;
|
return <SignalMedium size={size} strokeWidth={3} className={`text-yellow-500 ${className}`} />;
|
||||||
case "low":
|
case "low":
|
||||||
return <SignalLow className={`text-green-500 ${transparentBg ? "" : "ml-2"} ${className}`} />;
|
return <SignalLow size={size} strokeWidth={3} className={`text-custom-primary-100 ${className}`} />;
|
||||||
default:
|
default:
|
||||||
return <Ban className={`text-custom-text-200 ${transparentBg ? "" : "p-0.5"} ${className}`} />;
|
return <Ban size={size} className={`text-custom-text-200 ${className}`} />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return <>{getPriorityIcon()}</>;
|
||||||
<>
|
|
||||||
{transparentBg ? (
|
|
||||||
getPriorityIcon()
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
className={`grid h-5 w-5 place-items-center items-center rounded border ${
|
|
||||||
lowercasePriority === "urgent" ? "border-red-500/20 bg-red-500/20" : "border-custom-border-200"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{getPriorityIcon()}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
8
packages/ui/src/icons/type.d.ts
vendored
8
packages/ui/src/icons/type.d.ts
vendored
@ -1,11 +1,3 @@
|
|||||||
export interface ISvgIcons extends React.SVGAttributes<SVGElement> {
|
export interface ISvgIcons extends React.SVGAttributes<SVGElement> {
|
||||||
className?: string | undefined;
|
className?: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TIssuePriorities = "urgent" | "high" | "medium" | "low" | "none";
|
|
||||||
|
|
||||||
export interface IPriorityIcon {
|
|
||||||
priority: TIssuePriorities | null;
|
|
||||||
className?: string;
|
|
||||||
transparentBg?: boolean | false;
|
|
||||||
}
|
|
||||||
|
@ -4,8 +4,8 @@ import { useTheme } from "next-themes";
|
|||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { Trash2 } from "lucide-react";
|
import { Trash2 } from "lucide-react";
|
||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
// mobx store
|
// hooks
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useUser } from "hooks/store";
|
||||||
// ui
|
// ui
|
||||||
import { Button } from "@plane/ui";
|
import { Button } from "@plane/ui";
|
||||||
// hooks
|
// hooks
|
||||||
@ -22,9 +22,7 @@ export const DeactivateAccountModal: React.FC<Props> = (props) => {
|
|||||||
// states
|
// states
|
||||||
const [isDeactivating, setIsDeactivating] = useState(false);
|
const [isDeactivating, setIsDeactivating] = useState(false);
|
||||||
|
|
||||||
const {
|
const { deactivateAccount } = useUser();
|
||||||
user: { deactivateAccount },
|
|
||||||
} = useMobxStore();
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import { Button, Input } from "@plane/ui";
|
|||||||
// helpers
|
// helpers
|
||||||
import { checkEmailValidity } from "helpers/string.helper";
|
import { checkEmailValidity } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import { IEmailCheckData } from "types/auth";
|
import { IEmailCheckData } from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
import { ESignInSteps } from "components/account";
|
import { ESignInSteps } from "components/account";
|
||||||
|
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// mobx store
|
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
|
||||||
// services
|
// services
|
||||||
import { AuthService } from "services/auth.service";
|
import { AuthService } from "services/auth.service";
|
||||||
// hooks
|
// hooks
|
||||||
|
import { useApplication } from "hooks/store";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// components
|
// components
|
||||||
import { GitHubSignInButton, GoogleSignInButton } from "components/account";
|
import { GitHubSignInButton, GoogleSignInButton } from "components/account";
|
||||||
@ -21,8 +20,8 @@ export const OAuthOptions: React.FC<Props> = observer((props) => {
|
|||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
// mobx store
|
// mobx store
|
||||||
const {
|
const {
|
||||||
appConfig: { envConfig },
|
config: { envConfig },
|
||||||
} = useMobxStore();
|
} = useApplication();
|
||||||
|
|
||||||
const handleGoogleSignIn = async ({ clientId, credential }: any) => {
|
const handleGoogleSignIn = async ({ clientId, credential }: any) => {
|
||||||
try {
|
try {
|
||||||
|
@ -11,7 +11,7 @@ import { Button, Input } from "@plane/ui";
|
|||||||
// helpers
|
// helpers
|
||||||
import { checkEmailValidity } from "helpers/string.helper";
|
import { checkEmailValidity } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import { IPasswordSignInData } from "types/auth";
|
import { IPasswordSignInData } from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
import { ESignInSteps } from "components/account";
|
import { ESignInSteps } from "components/account";
|
||||||
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// mobx store
|
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
|
||||||
// hooks
|
// hooks
|
||||||
|
import { useApplication } from "hooks/store";
|
||||||
import useSignInRedirection from "hooks/use-sign-in-redirection";
|
import useSignInRedirection from "hooks/use-sign-in-redirection";
|
||||||
// components
|
// components
|
||||||
import { LatestFeatureBlock } from "components/common";
|
import { LatestFeatureBlock } from "components/common";
|
||||||
@ -38,8 +37,8 @@ export const SignInRoot = observer(() => {
|
|||||||
const { handleRedirection } = useSignInRedirection();
|
const { handleRedirection } = useSignInRedirection();
|
||||||
// mobx store
|
// mobx store
|
||||||
const {
|
const {
|
||||||
appConfig: { envConfig },
|
config: { envConfig },
|
||||||
} = useMobxStore();
|
} = useApplication();
|
||||||
|
|
||||||
const isOAuthEnabled = envConfig && (envConfig.google_client_id || envConfig.github_client_id);
|
const isOAuthEnabled = envConfig && (envConfig.google_client_id || envConfig.github_client_id);
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import { Button, Input } from "@plane/ui";
|
|||||||
// helpers
|
// helpers
|
||||||
import { checkEmailValidity } from "helpers/string.helper";
|
import { checkEmailValidity } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import { IPasswordSignInData } from "types/auth";
|
import { IPasswordSignInData } from "@plane/types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
email: string;
|
email: string;
|
||||||
|
@ -9,7 +9,7 @@ import { Button, Input } from "@plane/ui";
|
|||||||
// helpers
|
// helpers
|
||||||
import { checkEmailValidity } from "helpers/string.helper";
|
import { checkEmailValidity } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import { IEmailCheckData } from "types/auth";
|
import { IEmailCheckData } from "@plane/types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
email: string;
|
email: string;
|
||||||
|
@ -13,7 +13,7 @@ import { Button, Input } from "@plane/ui";
|
|||||||
// helpers
|
// helpers
|
||||||
import { checkEmailValidity } from "helpers/string.helper";
|
import { checkEmailValidity } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import { IEmailCheckData, IMagicSignInData } from "types/auth";
|
import { IEmailCheckData, IMagicSignInData } from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
import { ESignInSteps } from "components/account";
|
import { ESignInSteps } from "components/account";
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import { AnalyticsService } from "services/analytics.service";
|
|||||||
// components
|
// components
|
||||||
import { CustomAnalyticsSelectBar, CustomAnalyticsMainContent, CustomAnalyticsSidebar } from "components/analytics";
|
import { CustomAnalyticsSelectBar, CustomAnalyticsMainContent, CustomAnalyticsSidebar } from "components/analytics";
|
||||||
// types
|
// types
|
||||||
import { IAnalyticsParams } from "types";
|
import { IAnalyticsParams } from "@plane/types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { ANALYTICS } from "constants/fetch-keys";
|
import { ANALYTICS } from "constants/fetch-keys";
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import { BarTooltipProps } from "@nivo/bar";
|
|||||||
import { DATE_KEYS } from "constants/analytics";
|
import { DATE_KEYS } from "constants/analytics";
|
||||||
import { renderMonthAndYear } from "helpers/analytics.helper";
|
import { renderMonthAndYear } from "helpers/analytics.helper";
|
||||||
// types
|
// types
|
||||||
import { IAnalyticsParams, IAnalyticsResponse } from "types";
|
import { IAnalyticsParams, IAnalyticsResponse } from "@plane/types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
datum: BarTooltipProps<any>;
|
datum: BarTooltipProps<any>;
|
||||||
|
@ -9,7 +9,7 @@ import { BarGraph } from "components/ui";
|
|||||||
import { findStringWithMostCharacters } from "helpers/array.helper";
|
import { findStringWithMostCharacters } from "helpers/array.helper";
|
||||||
import { generateBarColor, generateDisplayName } from "helpers/analytics.helper";
|
import { generateBarColor, generateDisplayName } from "helpers/analytics.helper";
|
||||||
// types
|
// types
|
||||||
import { IAnalyticsParams, IAnalyticsResponse } from "types";
|
import { IAnalyticsParams, IAnalyticsResponse } from "@plane/types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
analytics: IAnalyticsResponse;
|
analytics: IAnalyticsResponse;
|
||||||
|
@ -8,7 +8,7 @@ import { Button, Loader } from "@plane/ui";
|
|||||||
// helpers
|
// helpers
|
||||||
import { convertResponseToBarGraphData } from "helpers/analytics.helper";
|
import { convertResponseToBarGraphData } from "helpers/analytics.helper";
|
||||||
// types
|
// types
|
||||||
import { IAnalyticsParams, IAnalyticsResponse } from "types";
|
import { IAnalyticsParams, IAnalyticsResponse } from "@plane/types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { ANALYTICS } from "constants/fetch-keys";
|
import { ANALYTICS } from "constants/fetch-keys";
|
||||||
|
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import { useRouter } from "next/router";
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Control, Controller, UseFormSetValue } from "react-hook-form";
|
import { Control, Controller, UseFormSetValue } from "react-hook-form";
|
||||||
|
// hooks
|
||||||
// mobx store
|
import { useProject } from "hooks/store";
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
|
||||||
// components
|
// components
|
||||||
import { SelectProject, SelectSegment, SelectXAxis, SelectYAxis } from "components/analytics";
|
import { SelectProject, SelectSegment, SelectXAxis, SelectYAxis } from "components/analytics";
|
||||||
// types
|
// types
|
||||||
import { IAnalyticsParams } from "types";
|
import { IAnalyticsParams } from "@plane/types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
control: Control<IAnalyticsParams, any>;
|
control: Control<IAnalyticsParams, any>;
|
||||||
@ -20,12 +18,7 @@ type Props = {
|
|||||||
export const CustomAnalyticsSelectBar: React.FC<Props> = observer((props) => {
|
export const CustomAnalyticsSelectBar: React.FC<Props> = observer((props) => {
|
||||||
const { control, setValue, params, fullScreen, isProjectLevel } = props;
|
const { control, setValue, params, fullScreen, isProjectLevel } = props;
|
||||||
|
|
||||||
const router = useRouter();
|
const { workspaceProjectIds: workspaceProjectIds } = useProject();
|
||||||
const { workspaceSlug } = router.query;
|
|
||||||
|
|
||||||
const { project: projectStore } = useMobxStore();
|
|
||||||
|
|
||||||
const projectsList = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -40,7 +33,11 @@ export const CustomAnalyticsSelectBar: React.FC<Props> = observer((props) => {
|
|||||||
name="project"
|
name="project"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value, onChange } }) => (
|
||||||
<SelectProject value={value ?? undefined} onChange={onChange} projects={projectsList ?? undefined} />
|
<SelectProject
|
||||||
|
value={value ?? undefined}
|
||||||
|
onChange={onChange}
|
||||||
|
projectIds={workspaceProjectIds ?? undefined}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,25 +1,33 @@
|
|||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
// hooks
|
||||||
|
import { useProject } from "hooks/store";
|
||||||
// ui
|
// ui
|
||||||
import { CustomSearchSelect } from "@plane/ui";
|
import { CustomSearchSelect } from "@plane/ui";
|
||||||
// types
|
|
||||||
import { IProject } from "types";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
value: string[] | undefined;
|
value: string[] | undefined;
|
||||||
onChange: (val: string[] | null) => void;
|
onChange: (val: string[] | null) => void;
|
||||||
projects: IProject[] | undefined;
|
projectIds: string[] | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SelectProject: React.FC<Props> = ({ value, onChange, projects }) => {
|
export const SelectProject: React.FC<Props> = observer((props) => {
|
||||||
const options = projects?.map((project) => ({
|
const { value, onChange, projectIds } = props;
|
||||||
value: project.id,
|
const { getProjectById } = useProject();
|
||||||
query: project.name + project.identifier,
|
|
||||||
|
const options = projectIds?.map((projectId) => {
|
||||||
|
const projectDetails = getProjectById(projectId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
value: projectDetails?.id,
|
||||||
|
query: `${projectDetails?.name} ${projectDetails?.identifier}`,
|
||||||
content: (
|
content: (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-[0.65rem] text-custom-text-200">{project.identifier}</span>
|
<span className="text-[0.65rem] text-custom-text-200">{projectDetails?.identifier}</span>
|
||||||
{project.name}
|
{projectDetails?.name}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
}));
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomSearchSelect
|
<CustomSearchSelect
|
||||||
@ -28,9 +36,9 @@ export const SelectProject: React.FC<Props> = ({ value, onChange, projects }) =>
|
|||||||
options={options}
|
options={options}
|
||||||
label={
|
label={
|
||||||
value && value.length > 0
|
value && value.length > 0
|
||||||
? projects
|
? projectIds
|
||||||
?.filter((p) => value.includes(p.id))
|
?.filter((p) => value.includes(p))
|
||||||
.map((p) => p.identifier)
|
.map((p) => getProjectById(p)?.name)
|
||||||
.join(", ")
|
.join(", ")
|
||||||
: "All projects"
|
: "All projects"
|
||||||
}
|
}
|
||||||
@ -38,4 +46,4 @@ export const SelectProject: React.FC<Props> = ({ value, onChange, projects }) =>
|
|||||||
multiple
|
multiple
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -3,7 +3,7 @@ import { useRouter } from "next/router";
|
|||||||
// ui
|
// ui
|
||||||
import { CustomSelect } from "@plane/ui";
|
import { CustomSelect } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
import { IAnalyticsParams, TXAxisValues } from "types";
|
import { IAnalyticsParams, TXAxisValues } from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
import { ANALYTICS_X_AXIS_VALUES } from "constants/analytics";
|
import { ANALYTICS_X_AXIS_VALUES } from "constants/analytics";
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import { useRouter } from "next/router";
|
|||||||
// ui
|
// ui
|
||||||
import { CustomSelect } from "@plane/ui";
|
import { CustomSelect } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
import { IAnalyticsParams, TXAxisValues } from "types";
|
import { IAnalyticsParams, TXAxisValues } from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
import { ANALYTICS_X_AXIS_VALUES } from "constants/analytics";
|
import { ANALYTICS_X_AXIS_VALUES } from "constants/analytics";
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// ui
|
// ui
|
||||||
import { CustomSelect } from "@plane/ui";
|
import { CustomSelect } from "@plane/ui";
|
||||||
// types
|
// types
|
||||||
import { TYAxisValues } from "types";
|
import { TYAxisValues } from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
import { ANALYTICS_Y_AXIS_VALUES } from "constants/analytics";
|
import { ANALYTICS_Y_AXIS_VALUES } from "constants/analytics";
|
||||||
|
|
||||||
|
@ -1,24 +1,32 @@
|
|||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
// hooks
|
||||||
|
import { useProject } from "hooks/store";
|
||||||
// icons
|
// icons
|
||||||
import { Contrast, LayoutGrid, Users } from "lucide-react";
|
import { Contrast, LayoutGrid, Users } from "lucide-react";
|
||||||
// helpers
|
// helpers
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
import { renderEmoji } from "helpers/emoji.helper";
|
||||||
import { truncateText } from "helpers/string.helper";
|
import { truncateText } from "helpers/string.helper";
|
||||||
// types
|
|
||||||
import { IProject } from "types";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
projects: IProject[];
|
projectIds: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CustomAnalyticsSidebarProjectsList: React.FC<Props> = (props) => {
|
export const CustomAnalyticsSidebarProjectsList: React.FC<Props> = observer((props) => {
|
||||||
const { projects } = props;
|
const { projectIds } = props;
|
||||||
|
|
||||||
|
const { getProjectById } = useProject();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="hidden h-full overflow-hidden md:flex md:flex-col">
|
<div className="hidden h-full overflow-hidden md:flex md:flex-col">
|
||||||
<h4 className="font-medium">Selected Projects</h4>
|
<h4 className="font-medium">Selected Projects</h4>
|
||||||
<div className="mt-4 h-full space-y-6 overflow-y-auto">
|
<div className="mt-4 h-full space-y-6 overflow-y-auto">
|
||||||
{projects.map((project) => (
|
{projectIds.map((projectId) => {
|
||||||
<div key={project.id} className="w-full">
|
const project = getProjectById(projectId);
|
||||||
|
|
||||||
|
if (!project) return;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={projectId} className="w-full">
|
||||||
<div className="flex items-center gap-1 text-sm">
|
<div className="flex items-center gap-1 text-sm">
|
||||||
{project.emoji ? (
|
{project.emoji ? (
|
||||||
<span className="grid h-6 w-6 flex-shrink-0 place-items-center">{renderEmoji(project.emoji)}</span>
|
<span className="grid h-6 w-6 flex-shrink-0 place-items-center">{renderEmoji(project.emoji)}</span>
|
||||||
@ -58,8 +66,9 @@ export const CustomAnalyticsSidebarProjectsList: React.FC<Props> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// mobx store
|
// hooks
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useCycle, useModule, useProject } from "hooks/store";
|
||||||
// helpers
|
// helpers
|
||||||
import { renderEmoji } from "helpers/emoji.helper";
|
import { renderEmoji } from "helpers/emoji.helper";
|
||||||
import { renderFormattedDate } from "helpers/date-time.helper";
|
import { renderFormattedDate } from "helpers/date-time.helper";
|
||||||
@ -10,16 +10,15 @@ import { NETWORK_CHOICES } from "constants/project";
|
|||||||
|
|
||||||
export const CustomAnalyticsSidebarHeader = observer(() => {
|
export const CustomAnalyticsSidebarHeader = observer(() => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
const { projectId, cycleId, moduleId } = router.query;
|
||||||
|
|
||||||
const { cycle: cycleStore, module: moduleStore, project: projectStore } = useMobxStore();
|
const { getProjectById } = useProject();
|
||||||
|
const { getCycleById } = useCycle();
|
||||||
|
const { getModuleById } = useModule();
|
||||||
|
|
||||||
const cycleDetails = cycleId ? cycleStore.getCycleById(cycleId.toString()) : undefined;
|
const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined;
|
||||||
const moduleDetails = moduleId ? moduleStore.getModuleById(moduleId.toString()) : undefined;
|
const moduleDetails = moduleId ? getModuleById(moduleId.toString()) : undefined;
|
||||||
const projectDetails =
|
const projectDetails = projectId ? getProjectById(projectId.toString()) : undefined;
|
||||||
workspaceSlug && projectId
|
|
||||||
? projectStore.getProjectById(workspaceSlug.toString(), projectId.toString())
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -5,8 +5,8 @@ import { mutate } from "swr";
|
|||||||
// services
|
// services
|
||||||
import { AnalyticsService } from "services/analytics.service";
|
import { AnalyticsService } from "services/analytics.service";
|
||||||
// hooks
|
// hooks
|
||||||
|
import { useCycle, useModule, useProject, useUser } from "hooks/store";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
|
||||||
// components
|
// components
|
||||||
import { CustomAnalyticsSidebarHeader, CustomAnalyticsSidebarProjectsList } from "components/analytics";
|
import { CustomAnalyticsSidebarHeader, CustomAnalyticsSidebarProjectsList } from "components/analytics";
|
||||||
// ui
|
// ui
|
||||||
@ -16,7 +16,7 @@ import { CalendarDays, Download, RefreshCw } from "lucide-react";
|
|||||||
// helpers
|
// helpers
|
||||||
import { renderFormattedDate } from "helpers/date-time.helper";
|
import { renderFormattedDate } from "helpers/date-time.helper";
|
||||||
// types
|
// types
|
||||||
import { IAnalyticsParams, IAnalyticsResponse, IExportAnalyticsFormData, IWorkspace } from "types";
|
import { IAnalyticsParams, IAnalyticsResponse, IExportAnalyticsFormData, IWorkspace } from "@plane/types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { ANALYTICS } from "constants/fetch-keys";
|
import { ANALYTICS } from "constants/fetch-keys";
|
||||||
|
|
||||||
@ -29,25 +29,23 @@ type Props = {
|
|||||||
|
|
||||||
const analyticsService = new AnalyticsService();
|
const analyticsService = new AnalyticsService();
|
||||||
|
|
||||||
export const CustomAnalyticsSidebar: React.FC<Props> = observer(
|
export const CustomAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
||||||
({ analytics, params, fullScreen, isProjectLevel = false }) => {
|
const { analytics, params, fullScreen, isProjectLevel = false } = props;
|
||||||
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
||||||
|
// toast alert
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
// store hooks
|
||||||
|
const { currentUser } = useUser();
|
||||||
|
const { workspaceProjectIds, getProjectById } = useProject();
|
||||||
|
const { fetchCycleDetails, getCycleById } = useCycle();
|
||||||
|
const { fetchModuleDetails, getModuleById } = useModule();
|
||||||
|
|
||||||
const { user: userStore, project: projectStore, cycle: cycleStore, module: moduleStore } = useMobxStore();
|
const projectDetails = projectId ? getProjectById(projectId.toString()) ?? undefined : undefined;
|
||||||
|
|
||||||
const user = userStore.currentUser;
|
|
||||||
|
|
||||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
|
|
||||||
const projectDetails =
|
|
||||||
workspaceSlug && projectId
|
|
||||||
? projectStore.getProjectById(workspaceSlug.toString(), projectId.toString()) ?? undefined
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const trackExportAnalytics = () => {
|
const trackExportAnalytics = () => {
|
||||||
if (!user) return;
|
if (!currentUser) return;
|
||||||
|
|
||||||
const eventPayload: any = {
|
const eventPayload: any = {
|
||||||
workspaceSlug: workspaceSlug?.toString(),
|
workspaceSlug: workspaceSlug?.toString(),
|
||||||
@ -121,24 +119,24 @@ export const CustomAnalyticsSidebar: React.FC<Props> = observer(
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const cycleDetails = cycleId ? cycleStore.getCycleById(cycleId.toString()) : undefined;
|
const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined;
|
||||||
const moduleDetails = moduleId ? moduleStore.getModuleById(moduleId.toString()) : undefined;
|
const moduleDetails = moduleId ? getModuleById(moduleId.toString()) : undefined;
|
||||||
|
|
||||||
// fetch cycle details
|
// fetch cycle details
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!workspaceSlug || !projectId || !cycleId || cycleDetails) return;
|
if (!workspaceSlug || !projectId || !cycleId || cycleDetails) return;
|
||||||
|
|
||||||
cycleStore.fetchCycleWithId(workspaceSlug.toString(), projectId.toString(), cycleId.toString());
|
fetchCycleDetails(workspaceSlug.toString(), projectId.toString(), cycleId.toString());
|
||||||
}, [cycleId, cycleDetails, cycleStore, projectId, workspaceSlug]);
|
}, [cycleId, cycleDetails, fetchCycleDetails, projectId, workspaceSlug]);
|
||||||
|
|
||||||
// fetch module details
|
// fetch module details
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!workspaceSlug || !projectId || !moduleId || moduleDetails) return;
|
if (!workspaceSlug || !projectId || !moduleId || moduleDetails) return;
|
||||||
|
|
||||||
moduleStore.fetchModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString());
|
fetchModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString());
|
||||||
}, [moduleId, moduleDetails, moduleStore, projectId, workspaceSlug]);
|
}, [moduleId, moduleDetails, fetchModuleDetails, projectId, workspaceSlug]);
|
||||||
|
|
||||||
const selectedProjects = params.project && params.project.length > 0 ? params.project : projects?.map((p) => p.id);
|
const selectedProjects = params.project && params.project.length > 0 ? params.project : workspaceProjectIds;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -170,9 +168,7 @@ export const CustomAnalyticsSidebar: React.FC<Props> = observer(
|
|||||||
{fullScreen ? (
|
{fullScreen ? (
|
||||||
<>
|
<>
|
||||||
{!isProjectLevel && selectedProjects && selectedProjects.length > 0 && (
|
{!isProjectLevel && selectedProjects && selectedProjects.length > 0 && (
|
||||||
<CustomAnalyticsSidebarProjectsList
|
<CustomAnalyticsSidebarProjectsList projectIds={selectedProjects} />
|
||||||
projects={projects?.filter((p) => selectedProjects.includes(p.id)) ?? []}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
<CustomAnalyticsSidebarHeader />
|
<CustomAnalyticsSidebarHeader />
|
||||||
</>
|
</>
|
||||||
@ -196,5 +192,4 @@ export const CustomAnalyticsSidebar: React.FC<Props> = observer(
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user