mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
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 commit9d2e0e29e7
, reversing changes made to9595493c42
. * 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 commit1cc18a0915
. * 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
15
.github/workflows/create-sync-pr.yml
vendored
15
.github/workflows/create-sync-pr.yml
vendored
@ -3,7 +3,7 @@ name: Create Sync Action
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- preview
|
||||
- develop # Change this to preview
|
||||
types:
|
||||
- closed
|
||||
env:
|
||||
@ -33,14 +33,23 @@ jobs:
|
||||
sudo apt update
|
||||
sudo apt install gh -y
|
||||
|
||||
- name: Push Changes to Target Repo
|
||||
- name: Create Pull Request
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
run: |
|
||||
TARGET_REPO="${{ secrets.SYNC_TARGET_REPO_NAME }}"
|
||||
TARGET_BRANCH="${{ secrets.SYNC_TARGET_BRANCH_NAME }}"
|
||||
TARGET_BASE_BRANCH="${{ secrets.SYNC_TARGET_BASE_BRANCH_NAME }}"
|
||||
SOURCE_BRANCH="${{ env.SOURCE_BRANCH_NAME }}"
|
||||
|
||||
git checkout $SOURCE_BRANCH
|
||||
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
|
||||
|
@ -97,7 +97,7 @@ class BaseSerializer(serializers.ModelSerializer):
|
||||
exp_serializer = expansion[expand](
|
||||
getattr(instance, expand)
|
||||
)
|
||||
response[expand] = exp_serializer.data
|
||||
response[expand] = exp_serializer.data
|
||||
else:
|
||||
# You might need to handle this case differently
|
||||
response[expand] = getattr(instance, f"{expand}_id", None)
|
||||
|
@ -17,6 +17,7 @@ from .workspace import (
|
||||
WorkspaceThemeSerializer,
|
||||
WorkspaceMemberAdminSerializer,
|
||||
WorkspaceMemberMeSerializer,
|
||||
WorkspaceUserPropertiesSerializer,
|
||||
)
|
||||
from .project import (
|
||||
ProjectSerializer,
|
||||
@ -31,6 +32,7 @@ from .project import (
|
||||
ProjectDeployBoardSerializer,
|
||||
ProjectMemberAdminSerializer,
|
||||
ProjectPublicMemberSerializer,
|
||||
ProjectMemberRoleSerializer,
|
||||
)
|
||||
from .state import StateSerializer, StateLiteSerializer
|
||||
from .view import GlobalViewSerializer, IssueViewSerializer, IssueViewFavoriteSerializer
|
||||
@ -39,6 +41,7 @@ from .cycle import (
|
||||
CycleIssueSerializer,
|
||||
CycleFavoriteSerializer,
|
||||
CycleWriteSerializer,
|
||||
CycleUserPropertiesSerializer,
|
||||
)
|
||||
from .asset import FileAssetSerializer
|
||||
from .issue import (
|
||||
@ -61,6 +64,8 @@ from .issue import (
|
||||
IssueRelationSerializer,
|
||||
RelatedIssueSerializer,
|
||||
IssuePublicSerializer,
|
||||
IssueRelationLiteSerializer,
|
||||
|
||||
)
|
||||
|
||||
from .module import (
|
||||
@ -69,6 +74,7 @@ from .module import (
|
||||
ModuleIssueSerializer,
|
||||
ModuleLinkSerializer,
|
||||
ModuleFavoriteSerializer,
|
||||
ModuleUserPropertiesSerializer,
|
||||
)
|
||||
|
||||
from .api import APITokenSerializer, APITokenReadSerializer
|
||||
|
@ -9,11 +9,12 @@ class DynamicBaseSerializer(BaseSerializer):
|
||||
def __init__(self, *args, **kwargs):
|
||||
# If 'fields' is provided in the arguments, remove it and store it separately.
|
||||
# This is done so as not to pass this custom argument up to the superclass.
|
||||
fields = kwargs.pop("fields", None)
|
||||
fields = kwargs.pop("fields", [])
|
||||
self.expand = kwargs.pop("expand", []) or []
|
||||
fields = self.expand
|
||||
|
||||
# Call the initialization of the superclass.
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# If 'fields' was provided, filter the fields of the serializer accordingly.
|
||||
if fields is not None:
|
||||
self.fields = self._filter_fields(fields)
|
||||
@ -47,12 +48,91 @@ class DynamicBaseSerializer(BaseSerializer):
|
||||
elif isinstance(item, dict):
|
||||
allowed.append(list(item.keys())[0])
|
||||
|
||||
# Convert the current serializer's fields and the allowed fields to sets.
|
||||
existing = set(self.fields)
|
||||
allowed = set(allowed)
|
||||
for field in allowed:
|
||||
if field not in self.fields:
|
||||
from . import (
|
||||
WorkspaceLiteSerializer,
|
||||
ProjectLiteSerializer,
|
||||
UserLiteSerializer,
|
||||
StateLiteSerializer,
|
||||
IssueSerializer,
|
||||
LabelSerializer,
|
||||
CycleIssueSerializer,
|
||||
IssueFlatSerializer,
|
||||
)
|
||||
|
||||
# Remove fields from the serializer that aren't in the 'allowed' list.
|
||||
for field_name in (existing - allowed):
|
||||
self.fields.pop(field_name)
|
||||
# Expansion mapper
|
||||
expansion = {
|
||||
"user": UserLiteSerializer,
|
||||
"workspace": WorkspaceLiteSerializer,
|
||||
"project": ProjectLiteSerializer,
|
||||
"default_assignee": UserLiteSerializer,
|
||||
"project_lead": UserLiteSerializer,
|
||||
"state": StateLiteSerializer,
|
||||
"created_by": UserLiteSerializer,
|
||||
"issue": IssueSerializer,
|
||||
"actor": UserLiteSerializer,
|
||||
"owned_by": UserLiteSerializer,
|
||||
"members": UserLiteSerializer,
|
||||
"assignees": UserLiteSerializer,
|
||||
"labels": LabelSerializer,
|
||||
"issue_cycle": CycleIssueSerializer,
|
||||
"parent": IssueFlatSerializer,
|
||||
}
|
||||
|
||||
self.fields[field] = expansion[field](many=True if field in ["members", "assignees", "labels", "issue_cycle"] else False)
|
||||
|
||||
return self.fields
|
||||
|
||||
|
||||
def to_representation(self, instance):
|
||||
response = super().to_representation(instance)
|
||||
|
||||
# Ensure 'expand' is iterable before processing
|
||||
if self.expand:
|
||||
for expand in self.expand:
|
||||
if expand in self.fields:
|
||||
# Import all the expandable serializers
|
||||
from . import (
|
||||
WorkspaceLiteSerializer,
|
||||
ProjectLiteSerializer,
|
||||
UserLiteSerializer,
|
||||
StateLiteSerializer,
|
||||
IssueSerializer,
|
||||
LabelSerializer,
|
||||
CycleIssueSerializer,
|
||||
)
|
||||
|
||||
# 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 .workspace import WorkspaceLiteSerializer
|
||||
from .project import ProjectLiteSerializer
|
||||
from plane.db.models import Cycle, CycleIssue, CycleFavorite
|
||||
from plane.db.models import Cycle, CycleIssue, CycleFavorite, CycleUserProperties
|
||||
|
||||
|
||||
class CycleWriteSerializer(BaseSerializer):
|
||||
@ -106,3 +106,15 @@ class CycleFavoriteSerializer(BaseSerializer):
|
||||
"project",
|
||||
"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)
|
||||
assignee_details = UserLiteSerializer(read_only=True, source="assignees", many=True)
|
||||
sub_issues_count = serializers.IntegerField(read_only=True)
|
||||
bridge_id = serializers.UUIDField(read_only=True)
|
||||
issue_inbox = InboxIssueLiteSerializer(read_only=True, many=True)
|
||||
|
||||
class Meta:
|
||||
|
@ -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):
|
||||
issue_detail = IssueProjectLiteSerializer(read_only=True, source="related_issue")
|
||||
issue_detail = IssueRelationLiteSerializer(read_only=True, source="related_issue")
|
||||
|
||||
class Meta:
|
||||
model = IssueRelation
|
||||
fields = [
|
||||
"issue_detail",
|
||||
"relation_type",
|
||||
"related_issue",
|
||||
"issue",
|
||||
"id"
|
||||
]
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
@ -296,16 +307,12 @@ class IssueRelationSerializer(BaseSerializer):
|
||||
]
|
||||
|
||||
class RelatedIssueSerializer(BaseSerializer):
|
||||
issue_detail = IssueProjectLiteSerializer(read_only=True, source="issue")
|
||||
issue_detail = IssueRelationLiteSerializer(read_only=True, source="issue")
|
||||
|
||||
class Meta:
|
||||
model = IssueRelation
|
||||
fields = [
|
||||
"issue_detail",
|
||||
"relation_type",
|
||||
"related_issue",
|
||||
"issue",
|
||||
"id"
|
||||
]
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
@ -512,7 +519,6 @@ class IssueStateSerializer(DynamicBaseSerializer):
|
||||
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
||||
assignee_details = UserLiteSerializer(read_only=True, source="assignees", many=True)
|
||||
sub_issues_count = serializers.IntegerField(read_only=True)
|
||||
bridge_id = serializers.UUIDField(read_only=True)
|
||||
attachment_count = serializers.IntegerField(read_only=True)
|
||||
link_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
@ -521,32 +527,58 @@ class IssueStateSerializer(DynamicBaseSerializer):
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class IssueSerializer(BaseSerializer):
|
||||
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
||||
state_detail = StateSerializer(read_only=True, source="state")
|
||||
parent_detail = IssueStateFlatSerializer(read_only=True, source="parent")
|
||||
label_details = LabelSerializer(read_only=True, source="labels", many=True)
|
||||
assignee_details = UserLiteSerializer(read_only=True, source="assignees", many=True)
|
||||
related_issues = IssueRelationSerializer(read_only=True, source="issue_relation", many=True)
|
||||
issue_relations = RelatedIssueSerializer(read_only=True, source="issue_related", many=True)
|
||||
issue_cycle = IssueCycleDetailSerializer(read_only=True)
|
||||
issue_module = IssueModuleDetailSerializer(read_only=True)
|
||||
issue_link = IssueLinkSerializer(read_only=True, many=True)
|
||||
issue_attachment = IssueAttachmentSerializer(read_only=True, many=True)
|
||||
class IssueSerializer(DynamicBaseSerializer):
|
||||
# ids
|
||||
project_id = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||
state_id = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||
parent_id = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||
cycle_id = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||
module_id = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||
|
||||
# Many to many
|
||||
label_ids = serializers.PrimaryKeyRelatedField(read_only=True, many=True, source="labels")
|
||||
assignee_ids = serializers.PrimaryKeyRelatedField(read_only=True, many=True, source="assignees")
|
||||
|
||||
# Count items
|
||||
sub_issues_count = serializers.IntegerField(read_only=True)
|
||||
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:
|
||||
model = Issue
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
"created_by",
|
||||
"updated_by",
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"state_id",
|
||||
"description_html",
|
||||
"sort_order",
|
||||
"completed_at",
|
||||
"estimate_point",
|
||||
"priority",
|
||||
"start_date",
|
||||
"target_date",
|
||||
"sequence_id",
|
||||
"project_id",
|
||||
"parent_id",
|
||||
"cycle_id",
|
||||
"module_id",
|
||||
"label_ids",
|
||||
"assignee_ids",
|
||||
"sub_issues_count",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"created_by",
|
||||
"updated_by",
|
||||
"attachment_count",
|
||||
"link_count",
|
||||
"is_subscribed",
|
||||
"is_draft",
|
||||
"archived_at",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class IssueLiteSerializer(DynamicBaseSerializer):
|
||||
|
@ -2,7 +2,7 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
# Module imports
|
||||
from .base import BaseSerializer
|
||||
from .base import BaseSerializer, DynamicBaseSerializer
|
||||
from .user import UserLiteSerializer
|
||||
from .project import ProjectLiteSerializer
|
||||
from .workspace import WorkspaceLiteSerializer
|
||||
@ -14,6 +14,7 @@ from plane.db.models import (
|
||||
ModuleIssue,
|
||||
ModuleLink,
|
||||
ModuleFavorite,
|
||||
ModuleUserProperties,
|
||||
)
|
||||
|
||||
|
||||
@ -159,7 +160,7 @@ class ModuleLinkSerializer(BaseSerializer):
|
||||
return ModuleLink.objects.create(**validated_data)
|
||||
|
||||
|
||||
class ModuleSerializer(BaseSerializer):
|
||||
class ModuleSerializer(DynamicBaseSerializer):
|
||||
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
||||
lead_detail = UserLiteSerializer(read_only=True, source="lead")
|
||||
members_detail = UserLiteSerializer(read_only=True, many=True, source="members")
|
||||
@ -196,3 +197,14 @@ class ModuleFavoriteSerializer(BaseSerializer):
|
||||
"project",
|
||||
"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
|
||||
fields = "__all__"
|
||||
|
||||
class ProjectMemberRoleSerializer(DynamicBaseSerializer):
|
||||
|
||||
class Meta:
|
||||
model = ProjectMember
|
||||
fields = ("id", "role", "member", "project")
|
||||
|
||||
class ProjectMemberInviteSerializer(BaseSerializer):
|
||||
project = ProjectLiteSerializer(read_only=True)
|
||||
|
@ -2,7 +2,7 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
# Module imports
|
||||
from .base import BaseSerializer
|
||||
from .base import BaseSerializer, DynamicBaseSerializer
|
||||
from .workspace import WorkspaceLiteSerializer
|
||||
from .project import ProjectLiteSerializer
|
||||
from plane.db.models import GlobalView, IssueView, IssueViewFavorite
|
||||
@ -38,7 +38,7 @@ class GlobalViewSerializer(BaseSerializer):
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
class IssueViewSerializer(BaseSerializer):
|
||||
class IssueViewSerializer(DynamicBaseSerializer):
|
||||
is_favorite = serializers.BooleanField(read_only=True)
|
||||
project_detail = ProjectLiteSerializer(source="project", read_only=True)
|
||||
workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
|
||||
@ -80,4 +80,4 @@ class IssueViewFavoriteSerializer(BaseSerializer):
|
||||
"workspace",
|
||||
"project",
|
||||
"user",
|
||||
]
|
||||
]
|
@ -2,7 +2,7 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
# Module imports
|
||||
from .base import BaseSerializer
|
||||
from .base import BaseSerializer, DynamicBaseSerializer
|
||||
from .user import UserLiteSerializer, UserAdminLiteSerializer
|
||||
|
||||
from plane.db.models import (
|
||||
@ -13,10 +13,11 @@ from plane.db.models import (
|
||||
TeamMember,
|
||||
WorkspaceMemberInvite,
|
||||
WorkspaceTheme,
|
||||
WorkspaceUserProperties,
|
||||
)
|
||||
|
||||
|
||||
class WorkSpaceSerializer(BaseSerializer):
|
||||
class WorkSpaceSerializer(DynamicBaseSerializer):
|
||||
owner = UserLiteSerializer(read_only=True)
|
||||
total_members = 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)
|
||||
workspace = WorkspaceLiteSerializer(read_only=True)
|
||||
|
||||
@ -78,7 +79,7 @@ class WorkspaceMemberMeSerializer(BaseSerializer):
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class WorkspaceMemberAdminSerializer(BaseSerializer):
|
||||
class WorkspaceMemberAdminSerializer(DynamicBaseSerializer):
|
||||
member = UserAdminLiteSerializer(read_only=True)
|
||||
workspace = WorkspaceLiteSerializer(read_only=True)
|
||||
|
||||
@ -161,3 +162,13 @@ class WorkspaceThemeSerializer(BaseSerializer):
|
||||
"workspace",
|
||||
"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,
|
||||
CycleFavoriteViewSet,
|
||||
TransferCycleIssueEndpoint,
|
||||
CycleUserPropertiesEndpoint,
|
||||
)
|
||||
|
||||
|
||||
@ -44,7 +45,7 @@ urlpatterns = [
|
||||
name="project-issue-cycle",
|
||||
),
|
||||
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(
|
||||
{
|
||||
"get": "retrieve",
|
||||
@ -84,4 +85,9 @@ urlpatterns = [
|
||||
TransferCycleIssueEndpoint.as_view(),
|
||||
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",
|
||||
),
|
||||
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(
|
||||
{
|
||||
"get": "retrieve",
|
||||
|
@ -235,7 +235,7 @@ urlpatterns = [
|
||||
## End Comment Reactions
|
||||
## IssueProperty
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/issue-display-properties/",
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/user-properties/",
|
||||
IssueUserDisplayPropertyEndpoint.as_view(),
|
||||
name="project-issue-display-properties",
|
||||
),
|
||||
@ -275,16 +275,17 @@ urlpatterns = [
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/issue-relation/",
|
||||
IssueRelationViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
name="issue-relation",
|
||||
),
|
||||
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(
|
||||
{
|
||||
"delete": "destroy",
|
||||
"post": "remove_relation",
|
||||
}
|
||||
),
|
||||
name="issue-relation",
|
||||
|
@ -7,6 +7,7 @@ from plane.app.views import (
|
||||
ModuleLinkViewSet,
|
||||
ModuleFavoriteViewSet,
|
||||
BulkImportModulesEndpoint,
|
||||
ModuleUserPropertiesEndpoint
|
||||
)
|
||||
|
||||
|
||||
@ -44,7 +45,7 @@ urlpatterns = [
|
||||
name="project-module-issues",
|
||||
),
|
||||
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(
|
||||
{
|
||||
"get": "retrieve",
|
||||
@ -101,4 +102,9 @@ urlpatterns = [
|
||||
BulkImportModulesEndpoint.as_view(),
|
||||
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",
|
||||
)
|
||||
]
|
||||
|
@ -5,7 +5,7 @@ from plane.app.views import (
|
||||
IssueViewViewSet,
|
||||
GlobalViewViewSet,
|
||||
GlobalViewIssuesViewSet,
|
||||
IssueViewFavoriteViewSet,
|
||||
IssueViewFavoriteViewSet,
|
||||
)
|
||||
|
||||
|
||||
|
@ -18,6 +18,8 @@ from plane.app.views import (
|
||||
WorkspaceUserProfileEndpoint,
|
||||
WorkspaceUserProfileIssuesEndpoint,
|
||||
WorkspaceLabelsEndpoint,
|
||||
WorkspaceProjectMemberEndpoint,
|
||||
WorkspaceUserPropertiesEndpoint,
|
||||
)
|
||||
|
||||
|
||||
@ -92,6 +94,11 @@ urlpatterns = [
|
||||
WorkSpaceMemberViewSet.as_view({"get": "list"}),
|
||||
name="workspace-member",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/project-members/",
|
||||
WorkspaceProjectMemberEndpoint.as_view(),
|
||||
name="workspace-member-roles",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/members/<uuid:pk>/",
|
||||
WorkSpaceMemberViewSet.as_view(
|
||||
@ -195,4 +202,9 @@ urlpatterns = [
|
||||
WorkspaceLabelsEndpoint.as_view(),
|
||||
name="workspace-labels",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/user-properties/",
|
||||
WorkspaceUserPropertiesEndpoint.as_view(),
|
||||
name="workspace-user-filters",
|
||||
)
|
||||
]
|
||||
|
@ -45,6 +45,8 @@ from .workspace import (
|
||||
WorkspaceUserProfileEndpoint,
|
||||
WorkspaceUserProfileIssuesEndpoint,
|
||||
WorkspaceLabelsEndpoint,
|
||||
WorkspaceProjectMemberEndpoint,
|
||||
WorkspaceUserPropertiesEndpoint,
|
||||
)
|
||||
from .state import StateViewSet
|
||||
from .view import (
|
||||
@ -59,6 +61,7 @@ from .cycle import (
|
||||
CycleDateCheckEndpoint,
|
||||
CycleFavoriteViewSet,
|
||||
TransferCycleIssueEndpoint,
|
||||
CycleUserPropertiesEndpoint,
|
||||
)
|
||||
from .asset import FileAssetEndpoint, UserAssetsEndpoint, FileAssetViewSet
|
||||
from .issue import (
|
||||
@ -103,6 +106,7 @@ from .module import (
|
||||
ModuleIssueViewSet,
|
||||
ModuleLinkViewSet,
|
||||
ModuleFavoriteViewSet,
|
||||
ModuleUserPropertiesEndpoint,
|
||||
)
|
||||
|
||||
from .api import ApiTokenEndpoint
|
||||
|
@ -159,6 +159,21 @@ class BaseViewSet(TimezoneMixin, ModelViewSet, BasePaginator):
|
||||
if resolve(self.request.path_info).url_name == "project":
|
||||
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):
|
||||
permission_classes = [
|
||||
@ -239,3 +254,17 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
|
||||
@property
|
||||
def project_id(self):
|
||||
return self.kwargs.get("project_id", None)
|
||||
|
||||
@property
|
||||
def fields(self):
|
||||
fields = [
|
||||
field for field in self.request.GET.get("fields", "").split(",") if field
|
||||
]
|
||||
return fields if fields else None
|
||||
|
||||
@property
|
||||
def expand(self):
|
||||
expand = [
|
||||
expand for expand in self.request.GET.get("expand", "").split(",") if expand
|
||||
]
|
||||
return expand if expand else None
|
||||
|
@ -14,7 +14,7 @@ from django.db.models import (
|
||||
Case,
|
||||
When,
|
||||
Value,
|
||||
CharField
|
||||
CharField,
|
||||
)
|
||||
from django.core import serializers
|
||||
from django.utils import timezone
|
||||
@ -33,8 +33,9 @@ from plane.app.serializers import (
|
||||
CycleFavoriteSerializer,
|
||||
IssueStateSerializer,
|
||||
CycleWriteSerializer,
|
||||
CycleUserPropertiesSerializer,
|
||||
)
|
||||
from plane.app.permissions import ProjectEntityPermission
|
||||
from plane.app.permissions import ProjectEntityPermission, ProjectLitePermission
|
||||
from plane.db.models import (
|
||||
User,
|
||||
Cycle,
|
||||
@ -44,6 +45,7 @@ from plane.db.models import (
|
||||
IssueLink,
|
||||
IssueAttachment,
|
||||
Label,
|
||||
CycleUserProperties,
|
||||
)
|
||||
from plane.bgtasks.issue_activites_task import issue_activity
|
||||
from plane.utils.grouper import group_results
|
||||
@ -164,23 +166,18 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
|
||||
.annotate(
|
||||
status=Case(
|
||||
When(
|
||||
Q(start_date__lte=timezone.now()) & 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")
|
||||
Q(start_date__lte=timezone.now())
|
||||
& 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(
|
||||
Q(start_date__isnull=True) & Q(end_date__isnull=True),
|
||||
then=Value("DRAFT")
|
||||
then=Value("DRAFT"),
|
||||
),
|
||||
default=Value("DRAFT"),
|
||||
output_field=CharField(),
|
||||
default=Value("DRAFT"),
|
||||
output_field=CharField(),
|
||||
)
|
||||
)
|
||||
.prefetch_related(
|
||||
@ -202,6 +199,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
|
||||
def list(self, request, slug, project_id):
|
||||
queryset = self.get_queryset()
|
||||
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")
|
||||
|
||||
@ -307,44 +305,8 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
|
||||
|
||||
return Response(data, status=status.HTTP_200_OK)
|
||||
|
||||
# Upcoming Cycles
|
||||
if cycle_view == "upcoming":
|
||||
queryset = queryset.filter(start_date__gt=timezone.now())
|
||||
return Response(
|
||||
CycleSerializer(queryset, many=True).data, status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
# 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
|
||||
)
|
||||
cycles = CycleSerializer(queryset, many=True).data
|
||||
return Response(cycles, status=status.HTTP_200_OK)
|
||||
|
||||
def create(self, request, slug, project_id):
|
||||
if (
|
||||
@ -576,7 +538,6 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.annotate(bridge_id=F("issue_cycle__id"))
|
||||
.filter(project_id=project_id)
|
||||
.filter(workspace__slug=slug)
|
||||
.select_related("project")
|
||||
@ -600,12 +561,10 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
|
||||
.values("count")
|
||||
)
|
||||
)
|
||||
|
||||
issues = IssueStateSerializer(
|
||||
serializer = IssueStateSerializer(
|
||||
issues, many=True, fields=fields if fields else None
|
||||
).data
|
||||
issue_dict = {str(issue["id"]): issue for issue in issues}
|
||||
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, cycle_id):
|
||||
issues = request.data.get("issues", [])
|
||||
@ -698,11 +657,13 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
|
||||
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(
|
||||
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(
|
||||
type="cycle.activity.deleted",
|
||||
requested_data=json.dumps(
|
||||
@ -712,7 +673,7 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
|
||||
}
|
||||
),
|
||||
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)),
|
||||
current_instance=None,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
@ -834,3 +795,39 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
||||
)
|
||||
|
||||
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,
|
||||
)
|
||||
.filter(**filters)
|
||||
.annotate(bridge_id=F("issue_inbox__id"))
|
||||
.select_related("workspace", "project", "state", "parent")
|
||||
.prefetch_related("assignees", "labels")
|
||||
.order_by("issue_inbox__snoozed_till", "issue_inbox__status")
|
||||
@ -204,9 +203,9 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
serializer = IssueStateInboxSerializer(issue)
|
||||
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(
|
||||
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
|
||||
project_member = ProjectMember.objects.get(
|
||||
@ -316,19 +315,16 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
InboxIssueSerializer(inbox_issue).data, status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
def retrieve(self, request, slug, project_id, inbox_id, pk):
|
||||
inbox_issue = InboxIssue.objects.get(
|
||||
pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id
|
||||
)
|
||||
def retrieve(self, request, slug, project_id, inbox_id, issue_id):
|
||||
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)
|
||||
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(
|
||||
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
|
||||
project_member = ProjectMember.objects.get(
|
||||
@ -350,7 +346,7 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
if inbox_issue.status in [-2, -1, 0, 2]:
|
||||
# Delete the issue also
|
||||
Issue.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id, pk=inbox_issue.issue_id
|
||||
workspace__slug=slug, project_id=project_id, pk=issue_id
|
||||
).delete()
|
||||
|
||||
inbox_issue.delete()
|
||||
|
@ -52,6 +52,7 @@ from plane.app.serializers import (
|
||||
IssueRelationSerializer,
|
||||
RelatedIssueSerializer,
|
||||
IssuePublicSerializer,
|
||||
IssueRelationLiteSerializer,
|
||||
)
|
||||
from plane.app.permissions import (
|
||||
ProjectEntityPermission,
|
||||
@ -129,22 +130,6 @@ class IssueViewSet(WebhookMixin, BaseViewSet):
|
||||
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(module_id=F("issue_module__module_id"))
|
||||
.annotate(
|
||||
@ -159,7 +144,26 @@ class IssueViewSet(WebhookMixin, BaseViewSet):
|
||||
.annotate(count=Func(F("id"), function="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
|
||||
if order_by_param == "priority" or order_by_param == "-priority":
|
||||
@ -217,9 +221,10 @@ class IssueViewSet(WebhookMixin, BaseViewSet):
|
||||
else:
|
||||
issue_queryset = issue_queryset.order_by(order_by_param)
|
||||
|
||||
issues = IssueLiteSerializer(issue_queryset, many=True, fields=fields if fields else None).data
|
||||
issue_dict = {str(issue["id"]): issue for issue in issues}
|
||||
return Response(issue_dict, status=status.HTTP_200_OK)
|
||||
issues = IssueSerializer(
|
||||
issue_queryset, many=True, fields=self.fields, expand=self.expand
|
||||
).data
|
||||
return Response(issues, status=status.HTTP_200_OK)
|
||||
|
||||
def create(self, request, slug, project_id):
|
||||
project = Project.objects.get(pk=project_id)
|
||||
@ -256,7 +261,10 @@ class IssueViewSet(WebhookMixin, BaseViewSet):
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
).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):
|
||||
issue = Issue.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||
@ -590,16 +598,19 @@ class IssueUserDisplayPropertyEndpoint(BaseAPIView):
|
||||
ProjectLitePermission,
|
||||
]
|
||||
|
||||
def post(self, request, slug, project_id):
|
||||
issue_property, created = IssueProperty.objects.get_or_create(
|
||||
def patch(self, request, slug, project_id):
|
||||
issue_property = IssueProperty.objects.get(
|
||||
user=request.user,
|
||||
project_id=project_id,
|
||||
)
|
||||
|
||||
if not created:
|
||||
issue_property.properties = request.data.get("properties", {})
|
||||
issue_property.save()
|
||||
issue_property.properties = request.data.get("properties", {})
|
||||
issue_property.filters = request.data.get("filters", issue_property.filters)
|
||||
issue_property.display_filters = request.data.get(
|
||||
"display_filters", issue_property.display_filters
|
||||
)
|
||||
issue_property.display_properties = request.data.get(
|
||||
"display_properties", issue_property.display_properties
|
||||
)
|
||||
issue_property.save()
|
||||
serializer = IssuePropertySerializer(issue_property)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
@ -708,6 +719,13 @@ class SubIssuesEndpoint(BaseAPIView):
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
is_subscribed=Exists(
|
||||
IssueSubscriber.objects.filter(
|
||||
subscriber=self.request.user, issue_id=OuterRef("id")
|
||||
)
|
||||
)
|
||||
)
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"issue_reactions",
|
||||
@ -728,7 +746,7 @@ class SubIssuesEndpoint(BaseAPIView):
|
||||
item["state_group"]: item["state_count"] for item in state_distribution
|
||||
}
|
||||
|
||||
serializer = IssueLiteSerializer(
|
||||
serializer = IssueSerializer(
|
||||
sub_issues,
|
||||
many=True,
|
||||
)
|
||||
@ -775,7 +793,7 @@ class SubIssuesEndpoint(BaseAPIView):
|
||||
]
|
||||
|
||||
return Response(
|
||||
IssueFlatSerializer(updated_sub_issues, many=True).data,
|
||||
IssueSerializer(updated_sub_issues, many=True).data,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
@ -1062,9 +1080,10 @@ class IssueArchiveViewSet(BaseViewSet):
|
||||
else issue_queryset.filter(parent__isnull=True)
|
||||
)
|
||||
|
||||
issues = IssueLiteSerializer(issue_queryset, many=True, fields=fields if fields else None).data
|
||||
issue_dict = {str(issue["id"]): issue for issue in issues}
|
||||
return Response(issue_dict, status=status.HTTP_200_OK)
|
||||
issues = IssueLiteSerializer(
|
||||
issue_queryset, many=True, fields=fields if fields else None
|
||||
).data
|
||||
return Response(issues, status=status.HTTP_200_OK)
|
||||
|
||||
def retrieve(self, request, slug, project_id, pk=None):
|
||||
issue = Issue.objects.get(
|
||||
@ -1365,23 +1384,62 @@ class IssueRelationViewSet(BaseViewSet):
|
||||
.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):
|
||||
related_list = request.data.get("related_list", [])
|
||||
relation = request.data.get("relation", None)
|
||||
relation_type = request.data.get("relation_type", None)
|
||||
issues = request.data.get("issues", [])
|
||||
project = Project.objects.get(pk=project_id)
|
||||
|
||||
issue_relation = IssueRelation.objects.bulk_create(
|
||||
[
|
||||
IssueRelation(
|
||||
issue_id=related_issue["issue"],
|
||||
related_issue_id=related_issue["related_issue"],
|
||||
relation_type=related_issue["relation_type"],
|
||||
issue_id=issue if relation_type == "blocking" else issue_id,
|
||||
related_issue_id=issue_id if relation_type == "blocking" else issue,
|
||||
relation_type="blocked_by" if relation_type == "blocking" else relation_type,
|
||||
project_id=project_id,
|
||||
workspace_id=project.workspace_id,
|
||||
created_by=request.user,
|
||||
updated_by=request.user,
|
||||
)
|
||||
for related_issue in related_list
|
||||
for issue in issues
|
||||
],
|
||||
batch_size=10,
|
||||
ignore_conflicts=True,
|
||||
@ -1397,7 +1455,7 @@ class IssueRelationViewSet(BaseViewSet):
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
)
|
||||
|
||||
if relation == "blocking":
|
||||
if relation_type == "blocking":
|
||||
return Response(
|
||||
RelatedIssueSerializer(issue_relation, many=True).data,
|
||||
status=status.HTTP_201_CREATED,
|
||||
@ -1408,10 +1466,18 @@ class IssueRelationViewSet(BaseViewSet):
|
||||
status=status.HTTP_201_CREATED,
|
||||
)
|
||||
|
||||
def destroy(self, request, slug, project_id, issue_id, pk):
|
||||
issue_relation = IssueRelation.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, issue_id=issue_id, pk=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(
|
||||
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(
|
||||
IssueRelationSerializer(issue_relation).data,
|
||||
cls=DjangoJSONEncoder,
|
||||
@ -1419,7 +1485,7 @@ class IssueRelationViewSet(BaseViewSet):
|
||||
issue_relation.delete()
|
||||
issue_activity.delay(
|
||||
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),
|
||||
issue_id=str(issue_id),
|
||||
project_id=str(project_id),
|
||||
@ -1547,9 +1613,10 @@ class IssueDraftViewSet(BaseViewSet):
|
||||
else:
|
||||
issue_queryset = issue_queryset.order_by(order_by_param)
|
||||
|
||||
issues = IssueLiteSerializer(issue_queryset, many=True, fields=fields if fields else None).data
|
||||
issue_dict = {str(issue["id"]): issue for issue in issues}
|
||||
return Response(issue_dict, status=status.HTTP_200_OK)
|
||||
issues = IssueLiteSerializer(
|
||||
issue_queryset, many=True, fields=fields if fields else None
|
||||
).data
|
||||
return Response(issues, status=status.HTTP_200_OK)
|
||||
|
||||
def create(self, request, slug, project_id):
|
||||
project = Project.objects.get(pk=project_id)
|
||||
@ -1626,4 +1693,4 @@ class IssueDraftViewSet(BaseViewSet):
|
||||
current_instance=current_instance,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
@ -21,8 +21,9 @@ from plane.app.serializers import (
|
||||
ModuleLinkSerializer,
|
||||
ModuleFavoriteSerializer,
|
||||
IssueStateSerializer,
|
||||
ModuleUserPropertiesSerializer,
|
||||
)
|
||||
from plane.app.permissions import ProjectEntityPermission
|
||||
from plane.app.permissions import ProjectEntityPermission, ProjectLitePermission
|
||||
from plane.db.models import (
|
||||
Module,
|
||||
ModuleIssue,
|
||||
@ -32,6 +33,7 @@ from plane.db.models import (
|
||||
ModuleFavorite,
|
||||
IssueLink,
|
||||
IssueAttachment,
|
||||
ModuleUserProperties,
|
||||
)
|
||||
from plane.bgtasks.issue_activites_task import issue_activity
|
||||
from plane.utils.grouper import group_results
|
||||
@ -54,7 +56,6 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
|
||||
)
|
||||
|
||||
def get_queryset(self):
|
||||
|
||||
subquery = ModuleFavorite.objects.filter(
|
||||
user=self.request.user,
|
||||
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):
|
||||
@ -153,6 +154,14 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
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):
|
||||
queryset = self.get_queryset().get(pk=pk)
|
||||
|
||||
@ -289,7 +298,6 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet):
|
||||
webhook_event = "module_issue"
|
||||
bulk = True
|
||||
|
||||
|
||||
filterset_fields = [
|
||||
"issue__labels__id",
|
||||
"issue__assignees__id",
|
||||
@ -335,7 +343,6 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet):
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.annotate(bridge_id=F("issue_module__id"))
|
||||
.filter(project_id=project_id)
|
||||
.filter(workspace__slug=slug)
|
||||
.select_related("project")
|
||||
@ -359,9 +366,10 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet):
|
||||
.values("count")
|
||||
)
|
||||
)
|
||||
issues = IssueStateSerializer(issues, many=True, fields=fields if fields else None).data
|
||||
issue_dict = {str(issue["id"]): issue for issue in issues}
|
||||
return Response(issue_dict, status=status.HTTP_200_OK)
|
||||
serializer = IssueStateSerializer(
|
||||
issues, many=True, fields=fields if fields else None
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def create(self, request, slug, project_id, module_id):
|
||||
issues = request.data.get("issues", [])
|
||||
@ -444,20 +452,23 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet):
|
||||
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(
|
||||
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(
|
||||
type="module.activity.deleted",
|
||||
requested_data=json.dumps(
|
||||
{
|
||||
"module_id": str(module_id),
|
||||
"issues": [str(module_issue.issue_id)],
|
||||
"issues": [str(issue_id)],
|
||||
}
|
||||
),
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(module_issue.issue_id),
|
||||
issue_id=str(issue_id),
|
||||
project_id=str(project_id),
|
||||
current_instance=None,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
@ -521,4 +532,42 @@ class ModuleFavoriteViewSet(BaseViewSet):
|
||||
module_id=module_id,
|
||||
)
|
||||
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):
|
||||
queryset = self.get_queryset().filter(archived_at__isnull=True)
|
||||
return Response(
|
||||
PageSerializer(queryset, many=True).data, status=status.HTTP_200_OK
|
||||
)
|
||||
pages = PageSerializer(queryset, many=True).data
|
||||
return Response(pages, status=status.HTTP_200_OK)
|
||||
|
||||
def archive(self, request, slug, project_id, page_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,
|
||||
).filter(archived_at__isnull=False)
|
||||
|
||||
return Response(
|
||||
PageSerializer(pages, many=True).data, status=status.HTTP_200_OK
|
||||
)
|
||||
pages = PageSerializer(pages, many=True).data
|
||||
return Response(pages, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
def destroy(self, request, slug, project_id, pk):
|
||||
page = Page.objects.get(pk=pk, workspace__slug=slug, project_id=project_id)
|
||||
|
@ -36,6 +36,7 @@ from plane.app.serializers import (
|
||||
ProjectFavoriteSerializer,
|
||||
ProjectDeployBoardSerializer,
|
||||
ProjectMemberAdminSerializer,
|
||||
ProjectMemberRoleSerializer,
|
||||
)
|
||||
|
||||
from plane.app.permissions import (
|
||||
@ -180,12 +181,9 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
|
||||
projects, many=True
|
||||
).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):
|
||||
try:
|
||||
@ -713,13 +711,7 @@ class ProjectMemberViewSet(BaseViewSet):
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
def list(self, request, slug, project_id):
|
||||
project_member = ProjectMember.objects.get(
|
||||
member=request.user,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
is_active=True,
|
||||
)
|
||||
|
||||
# Get the list of project members for the project
|
||||
project_members = ProjectMember.objects.filter(
|
||||
project_id=project_id,
|
||||
workspace__slug=slug,
|
||||
@ -727,10 +719,7 @@ class ProjectMemberViewSet(BaseViewSet):
|
||||
is_active=True,
|
||||
).select_related("project", "member", "workspace")
|
||||
|
||||
if project_member.role > 10:
|
||||
serializer = ProjectMemberAdminSerializer(project_members, many=True)
|
||||
else:
|
||||
serializer = ProjectMemberSerializer(project_members, many=True)
|
||||
serializer = ProjectMemberRoleSerializer(project_members, fields=("id", "member", "role"), many=True)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def partial_update(self, request, slug, project_id, pk):
|
||||
@ -1010,18 +999,11 @@ class ProjectPublicCoverImagesEndpoint(BaseAPIView):
|
||||
|
||||
def get(self, request):
|
||||
files = []
|
||||
s3_client_params = {
|
||||
"service_name": "s3",
|
||||
"aws_access_key_id": settings.AWS_ACCESS_KEY_ID,
|
||||
"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)
|
||||
|
||||
s3 = boto3.client(
|
||||
"s3",
|
||||
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
|
||||
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
|
||||
)
|
||||
params = {
|
||||
"Bucket": settings.AWS_STORAGE_BUCKET_NAME,
|
||||
"Prefix": "static/project-cover/",
|
||||
@ -1034,19 +1016,9 @@ class ProjectPublicCoverImagesEndpoint(BaseAPIView):
|
||||
if not content["Key"].endswith(
|
||||
"/"
|
||||
): # 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(
|
||||
f"https://{settings.AWS_STORAGE_BUCKET_NAME}.s3.{settings.AWS_REGION}.amazonaws.com/{content['Key']}"
|
||||
)
|
||||
files.append(
|
||||
f"https://{settings.AWS_STORAGE_BUCKET_NAME}.s3.{settings.AWS_REGION}.amazonaws.com/{content['Key']}"
|
||||
)
|
||||
|
||||
return Response(files, status=status.HTTP_200_OK)
|
||||
|
||||
|
@ -27,7 +27,12 @@ from plane.app.serializers import (
|
||||
IssueLiteSerializer,
|
||||
IssueViewFavoriteSerializer,
|
||||
)
|
||||
from plane.app.permissions import WorkspaceEntityPermission, ProjectEntityPermission
|
||||
from plane.app.permissions import (
|
||||
WorkspaceEntityPermission,
|
||||
ProjectEntityPermission,
|
||||
WorkspaceViewerPermission,
|
||||
ProjectLitePermission,
|
||||
)
|
||||
from plane.db.models import (
|
||||
Workspace,
|
||||
GlobalView,
|
||||
@ -43,8 +48,8 @@ from plane.utils.grouper import group_results
|
||||
|
||||
|
||||
class GlobalViewViewSet(BaseViewSet):
|
||||
serializer_class = GlobalViewSerializer
|
||||
model = GlobalView
|
||||
serializer_class = IssueViewSerializer
|
||||
model = IssueView
|
||||
permission_classes = [
|
||||
WorkspaceEntityPermission,
|
||||
]
|
||||
@ -58,6 +63,7 @@ class GlobalViewViewSet(BaseViewSet):
|
||||
super()
|
||||
.get_queryset()
|
||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
.filter(project__isnull=True)
|
||||
.select_related("workspace")
|
||||
.order_by(self.request.GET.get("order_by", "-created_at"))
|
||||
.distinct()
|
||||
@ -179,12 +185,10 @@ class GlobalViewIssuesViewSet(BaseViewSet):
|
||||
else:
|
||||
issue_queryset = issue_queryset.order_by(order_by_param)
|
||||
|
||||
issues = IssueLiteSerializer(issue_queryset, many=True, fields=fields if fields else None).data
|
||||
issue_dict = {str(issue["id"]): issue for issue in issues}
|
||||
return Response(
|
||||
issue_dict,
|
||||
status=status.HTTP_200_OK,
|
||||
serializer = IssueLiteSerializer(
|
||||
issue_queryset, many=True, fields=fields if fields else None
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class IssueViewViewSet(BaseViewSet):
|
||||
@ -217,6 +221,14 @@ class IssueViewViewSet(BaseViewSet):
|
||||
.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):
|
||||
serializer_class = IssueViewFavoriteSerializer
|
||||
@ -246,4 +258,4 @@ class IssueViewFavoriteViewSet(BaseViewSet):
|
||||
view_id=view_id,
|
||||
)
|
||||
view_favourite.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
@ -44,6 +44,8 @@ from plane.app.serializers import (
|
||||
IssueLiteSerializer,
|
||||
WorkspaceMemberAdminSerializer,
|
||||
WorkspaceMemberMeSerializer,
|
||||
ProjectMemberRoleSerializer,
|
||||
WorkspaceUserPropertiesSerializer,
|
||||
)
|
||||
from plane.app.views.base import BaseAPIView
|
||||
from . import BaseViewSet
|
||||
@ -64,6 +66,7 @@ from plane.db.models import (
|
||||
WorkspaceMember,
|
||||
CycleIssue,
|
||||
IssueReaction,
|
||||
WorkspaceUserProperties
|
||||
)
|
||||
from plane.app.permissions import (
|
||||
WorkSpaceBasePermission,
|
||||
@ -71,11 +74,13 @@ from plane.app.permissions import (
|
||||
WorkspaceEntityPermission,
|
||||
WorkspaceViewerPermission,
|
||||
WorkspaceUserPermission,
|
||||
ProjectLitePermission,
|
||||
)
|
||||
from plane.bgtasks.workspace_invitation_task import workspace_invitation
|
||||
from plane.utils.issue_filters import issue_filters
|
||||
from plane.bgtasks.event_tracking_task import workspace_invite_event
|
||||
|
||||
|
||||
class WorkSpaceViewSet(BaseViewSet):
|
||||
model = Workspace
|
||||
serializer_class = WorkSpaceSerializer
|
||||
@ -173,6 +178,7 @@ class UserWorkSpacesEndpoint(BaseAPIView):
|
||||
]
|
||||
|
||||
def get(self, request):
|
||||
fields = [field for field in request.GET.get("fields", "").split(",") if field]
|
||||
member_count = (
|
||||
WorkspaceMember.objects.filter(
|
||||
workspace=OuterRef("id"),
|
||||
@ -208,9 +214,12 @@ class UserWorkSpacesEndpoint(BaseAPIView):
|
||||
)
|
||||
.distinct()
|
||||
)
|
||||
|
||||
serializer = WorkSpaceSerializer(self.filter_queryset(workspace), many=True)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
workspaces = WorkSpaceSerializer(
|
||||
self.filter_queryset(workspace),
|
||||
fields=fields if fields else None,
|
||||
many=True,
|
||||
).data
|
||||
return Response(workspaces, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class WorkSpaceAvailabilityCheckEndpoint(BaseAPIView):
|
||||
@ -407,7 +416,7 @@ class WorkspaceJoinEndpoint(BaseAPIView):
|
||||
|
||||
# Delete the invitation
|
||||
workspace_invite.delete()
|
||||
|
||||
|
||||
# Send event
|
||||
workspace_invite_event.delay(
|
||||
user=user.id if user is not None else None,
|
||||
@ -537,10 +546,15 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
||||
workspace_members = self.get_queryset()
|
||||
|
||||
if workspace_member.role > 10:
|
||||
serializer = WorkspaceMemberAdminSerializer(workspace_members, many=True)
|
||||
serializer = WorkspaceMemberAdminSerializer(
|
||||
workspace_members,
|
||||
fields=("id", "member", "role"),
|
||||
many=True,
|
||||
)
|
||||
else:
|
||||
serializer = WorkSpaceMemberSerializer(
|
||||
workspace_members,
|
||||
fields=("id", "member", "role"),
|
||||
many=True,
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
@ -705,6 +719,43 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
||||
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):
|
||||
serializer_class = TeamSerializer
|
||||
model = Team
|
||||
@ -1334,8 +1385,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
|
||||
issues = IssueLiteSerializer(
|
||||
issue_queryset, many=True, fields=fields if fields else None
|
||||
).data
|
||||
issue_dict = {str(issue["id"]): issue for issue in issues}
|
||||
return Response(issue_dict, status=status.HTTP_200_OK)
|
||||
return Response(issues, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class WorkspaceLabelsEndpoint(BaseAPIView):
|
||||
@ -1349,3 +1399,30 @@ class WorkspaceLabelsEndpoint(BaseAPIView):
|
||||
project__project_projectmember__member=request.user,
|
||||
).values("parent", "name", "color", "id", "project_id", "workspace__slug")
|
||||
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,
|
||||
):
|
||||
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
|
||||
new_parent = Issue.objects.filter(pk=requested_data.get("parent")).first() if requested_data.get("parent") is not None else None
|
||||
old_parent = (
|
||||
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(
|
||||
IssueActivity(
|
||||
@ -714,7 +722,9 @@ def create_cycle_issue_activity(
|
||||
cycle = Cycle.objects.filter(
|
||||
pk=created_record.get("fields").get("cycle")
|
||||
).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:
|
||||
issue.updated_at = timezone.now()
|
||||
issue.save(update_fields=["updated_at"])
|
||||
@ -830,7 +840,9 @@ def create_module_issue_activity(
|
||||
module = Module.objects.filter(
|
||||
pk=created_record.get("fields").get("module")
|
||||
).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:
|
||||
issue.updated_at = timezone.now()
|
||||
issue.save(update_fields=["updated_at"])
|
||||
@ -1276,40 +1288,42 @@ def create_issue_relation_activity(
|
||||
current_instance = (
|
||||
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:
|
||||
for issue_relation in requested_data.get("related_list"):
|
||||
if issue_relation.get("relation_type") == "blocked_by":
|
||||
relation_type = "blocking"
|
||||
else:
|
||||
relation_type = issue_relation.get("relation_type")
|
||||
issue = Issue.objects.get(pk=issue_relation.get("issue"))
|
||||
if current_instance is None and requested_data.get("issues") is not None:
|
||||
for related_issue in requested_data.get("issues"):
|
||||
issue = Issue.objects.get(pk=related_issue)
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_id=issue_relation.get("related_issue"),
|
||||
issue_id=issue_id,
|
||||
actor_id=actor_id,
|
||||
verb="created",
|
||||
old_value="",
|
||||
new_value=f"{issue.project.identifier}-{issue.sequence_id}",
|
||||
field=relation_type,
|
||||
field=requested_data.get("relation_type"),
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"added {relation_type} relation",
|
||||
old_identifier=issue_relation.get("issue"),
|
||||
comment=f"added {requested_data.get('relation_type')} relation",
|
||||
old_identifier=related_issue,
|
||||
)
|
||||
)
|
||||
issue = Issue.objects.get(pk=issue_relation.get("related_issue"))
|
||||
issue = Issue.objects.get(pk=issue_id)
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_id=issue_relation.get("issue"),
|
||||
issue_id=related_issue,
|
||||
actor_id=actor_id,
|
||||
verb="created",
|
||||
old_value="",
|
||||
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,
|
||||
workspace_id=workspace_id,
|
||||
comment=f'added {issue_relation.get("relation_type")} relation',
|
||||
old_identifier=issue_relation.get("related_issue"),
|
||||
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_id,
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
@ -1329,44 +1343,44 @@ def delete_issue_relation_activity(
|
||||
current_instance = (
|
||||
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:
|
||||
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(
|
||||
IssueActivity(
|
||||
issue_id=current_instance.get("related_issue"),
|
||||
actor_id=actor_id,
|
||||
verb="deleted",
|
||||
old_value=f"{issue.project.identifier}-{issue.sequence_id}",
|
||||
new_value="",
|
||||
field=relation_type,
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"deleted {relation_type} relation",
|
||||
old_identifier=current_instance.get("issue"),
|
||||
epoch=epoch,
|
||||
)
|
||||
issue = Issue.objects.get(pk=requested_data.get("related_issue"))
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_id=issue_id,
|
||||
actor_id=actor_id,
|
||||
verb="deleted",
|
||||
old_value=f"{issue.project.identifier}-{issue.sequence_id}",
|
||||
new_value="",
|
||||
field=requested_data.get("relation_type"),
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"deleted {requested_data.get('relation_type')} relation",
|
||||
old_identifier=requested_data.get("related_issue"),
|
||||
epoch=epoch,
|
||||
)
|
||||
issue = Issue.objects.get(pk=current_instance.get("related_issue"))
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_id=current_instance.get("issue"),
|
||||
actor_id=actor_id,
|
||||
verb="deleted",
|
||||
old_value=f"{issue.project.identifier}-{issue.sequence_id}",
|
||||
new_value="",
|
||||
field=f'{current_instance.get("relation_type")}',
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f'deleted {current_instance.get("relation_type")} relation',
|
||||
old_identifier=current_instance.get("related_issue"),
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
issue = Issue.objects.get(pk=issue_id)
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_id=requested_data.get("related_issue"),
|
||||
actor_id=actor_id,
|
||||
verb="deleted",
|
||||
old_value=f"{issue.project.identifier}-{issue.sequence_id}",
|
||||
new_value="",
|
||||
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,
|
||||
workspace_id=workspace_id,
|
||||
comment=f'deleted {requested_data.get("relation_type")} relation',
|
||||
old_identifier=requested_data.get("related_issue"),
|
||||
epoch=epoch,
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
def create_draft_issue_activity(
|
||||
requested_data,
|
||||
|
@ -291,6 +291,9 @@ def notifications(type, issue_id, project_id, actor_id, subscriber, issue_activi
|
||||
sender = "in_app:issue_activities:assigned"
|
||||
|
||||
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")
|
||||
if issue_comment is not None:
|
||||
issue_comment = IssueComment.objects.get(
|
||||
@ -341,7 +344,7 @@ def notifications(type, issue_id, project_id, actor_id, subscriber, issue_activi
|
||||
.order_by("-created_at")
|
||||
.first()
|
||||
)
|
||||
|
||||
|
||||
actor = User.objects.get(pk=actor_id)
|
||||
|
||||
for mention_id in comment_mentions:
|
||||
|
@ -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,
|
||||
TeamMember,
|
||||
WorkspaceTheme,
|
||||
WorkspaceUserProperties,
|
||||
WorkspaceBaseModel,
|
||||
)
|
||||
|
||||
from .project import (
|
||||
@ -48,11 +50,11 @@ from .social_connection import SocialLoginConnection
|
||||
|
||||
from .state import State
|
||||
|
||||
from .cycle import Cycle, CycleIssue, CycleFavorite
|
||||
from .cycle import Cycle, CycleIssue, CycleFavorite, CycleUserProperties
|
||||
|
||||
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
|
||||
|
||||
|
@ -6,6 +6,47 @@ from django.conf import settings
|
||||
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):
|
||||
name = models.CharField(max_length=255, verbose_name="Cycle Name")
|
||||
description = models.TextField(verbose_name="Cycle Description", blank=True)
|
||||
@ -89,3 +130,28 @@ class CycleFavorite(ProjectBaseModel):
|
||||
def __str__(self):
|
||||
"""Return user and the cycle"""
|
||||
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
|
||||
class IssueManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
@ -394,7 +436,9 @@ class IssueProperty(ProjectBaseModel):
|
||||
on_delete=models.CASCADE,
|
||||
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:
|
||||
verbose_name = "Issue Property"
|
||||
|
@ -6,6 +6,47 @@ from django.conf import settings
|
||||
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):
|
||||
name = models.CharField(max_length=255, verbose_name="Module Name")
|
||||
description = models.TextField(verbose_name="Module Description", blank=True)
|
||||
@ -141,3 +182,28 @@ class ModuleFavorite(ProjectBaseModel):
|
||||
def __str__(self):
|
||||
"""Return user and the module"""
|
||||
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
|
||||
|
||||
# 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):
|
||||
workspace = models.ForeignKey(
|
||||
"db.Workspace", on_delete=models.CASCADE, related_name="global_views"
|
||||
@ -40,14 +81,17 @@ class GlobalView(BaseModel):
|
||||
return f"{self.name} <{self.workspace.name}>"
|
||||
|
||||
|
||||
class IssueView(ProjectBaseModel):
|
||||
class IssueView(WorkspaceBaseModel):
|
||||
name = models.CharField(max_length=255, verbose_name="View Name")
|
||||
description = models.TextField(verbose_name="View Description", blank=True)
|
||||
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(
|
||||
default=1, choices=((0, "Private"), (1, "Public"))
|
||||
)
|
||||
query_data = models.JSONField(default=dict)
|
||||
sort_order = models.FloatField(default=65535)
|
||||
|
||||
class Meta:
|
||||
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():
|
||||
return {
|
||||
@ -103,6 +148,22 @@ class Workspace(BaseModel):
|
||||
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):
|
||||
workspace = models.ForeignKey(
|
||||
"db.Workspace", on_delete=models.CASCADE, related_name="workspace_member"
|
||||
@ -218,3 +279,28 @@ class WorkspaceTheme(BaseModel):
|
||||
verbose_name_plural = "Workspace Themes"
|
||||
db_table = "workspace_themes"
|
||||
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
|
||||
dj-database-url==2.1.0
|
||||
posthog==3.0.2
|
||||
cryptography==41.0.6
|
||||
cryptography==41.0.5
|
||||
lxml==4.9.3
|
||||
boto3==1.28.40
|
||||
|
||||
|
@ -39,7 +39,7 @@ function download(){
|
||||
echo ""
|
||||
echo "Latest version is now available for you to use"
|
||||
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 ""
|
||||
|
||||
}
|
||||
|
@ -10,7 +10,8 @@
|
||||
"packages/eslint-config-custom",
|
||||
"packages/tailwind-config-custom",
|
||||
"packages/tsconfig",
|
||||
"packages/ui"
|
||||
"packages/ui",
|
||||
"packages/types"
|
||||
],
|
||||
"scripts": {
|
||||
"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 {
|
||||
response: string;
|
@ -1,6 +1,4 @@
|
||||
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
|
||||
getLayout?: (page: ReactElement) => ReactNode;
|
||||
};
|
||||
|
||||
|
||||
export interface IAppConfig {
|
||||
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";
|
||||
|
||||
@ -68,7 +68,7 @@ export type TLabelsDistribution = {
|
||||
|
||||
export interface CycleIssueResponse {
|
||||
id: string;
|
||||
issue_detail: IIssue;
|
||||
issue_detail: TIssue;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
created_by: string;
|
||||
@ -82,7 +82,7 @@ export interface CycleIssueResponse {
|
||||
|
||||
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 = {
|
||||
start_date: string;
|
@ -1,24 +1,24 @@
|
||||
export interface IEstimate {
|
||||
id: string;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
name: string;
|
||||
description: string;
|
||||
created_by: string;
|
||||
updated_by: string;
|
||||
points: IEstimatePoint[];
|
||||
description: string;
|
||||
id: string;
|
||||
name: string;
|
||||
project: string;
|
||||
project_detail: IProject;
|
||||
updated_at: Date;
|
||||
updated_by: string;
|
||||
points: IEstimatePoint[];
|
||||
workspace: string;
|
||||
workspace_detail: IWorkspace;
|
||||
}
|
||||
|
||||
export interface IEstimatePoint {
|
||||
id: string;
|
||||
created_at: string;
|
||||
created_by: string;
|
||||
description: string;
|
||||
estimate: string;
|
||||
id: string;
|
||||
key: number;
|
||||
project: string;
|
||||
updated_at: string;
|
@ -1,9 +1,9 @@
|
||||
export * from "./github-importer";
|
||||
export * from "./jira-importer";
|
||||
|
||||
import { IProjectLite } from "types/projects";
|
||||
import { IProjectLite } from "../projects";
|
||||
// types
|
||||
import { IUserLite } from "types/users";
|
||||
import { IUserLite } from "../users";
|
||||
|
||||
export interface IImporterService {
|
||||
created_at: string;
|
@ -1,7 +1,7 @@
|
||||
import { IIssue } from "./issues";
|
||||
import { TIssue } from "./issues";
|
||||
import type { IProjectLite } from "./projects";
|
||||
|
||||
export interface IInboxIssue extends IIssue {
|
||||
export interface IInboxIssue extends TIssue {
|
||||
issue_inbox: {
|
||||
duplicate_to: string | null;
|
||||
id: string;
|
@ -21,6 +21,11 @@ export * from "./reaction";
|
||||
export * from "./view-props";
|
||||
export * from "./workspace-views";
|
||||
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> = {
|
||||
[Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object
|
@ -1,3 +1,4 @@
|
||||
import { ReactElement } from "react";
|
||||
import { KeyedMutator } from "swr";
|
||||
import type {
|
||||
IState,
|
||||
@ -10,7 +11,8 @@ import type {
|
||||
IStateLite,
|
||||
Properties,
|
||||
IIssueDisplayFilterOptions,
|
||||
} from "types";
|
||||
IIssueReaction,
|
||||
} from "@plane/types";
|
||||
|
||||
export interface IIssueCycle {
|
||||
id: string;
|
||||
@ -83,6 +85,7 @@ export interface IIssue {
|
||||
attachment_count: number;
|
||||
attachments: any[];
|
||||
issue_relations: IssueRelation[];
|
||||
issue_reactions: IIssueReaction[];
|
||||
related_issues: IssueRelation[];
|
||||
bridge_id?: string | null;
|
||||
completed_at: Date;
|
||||
@ -138,7 +141,7 @@ export interface ISubIssuesState {
|
||||
|
||||
export interface ISubIssueResponse {
|
||||
state_distribution: ISubIssuesState;
|
||||
sub_issues: IIssue[];
|
||||
sub_issues: TIssue[];
|
||||
}
|
||||
|
||||
export interface BlockeIssueDetail {
|
||||
@ -240,13 +243,13 @@ export interface IIssueAttachment {
|
||||
}
|
||||
|
||||
export interface IIssueViewProps {
|
||||
groupedIssues: { [key: string]: IIssue[] } | undefined;
|
||||
groupedIssues: { [key: string]: TIssue[] } | undefined;
|
||||
displayFilters: IIssueDisplayFilterOptions | undefined;
|
||||
isEmpty: boolean;
|
||||
mutateIssues: KeyedMutator<
|
||||
| IIssue[]
|
||||
| TIssue[]
|
||||
| {
|
||||
[key: string]: IIssue[];
|
||||
[key: string]: TIssue[];
|
||||
}
|
||||
>;
|
||||
params: any;
|
||||
@ -254,3 +257,88 @@ export interface IIssueViewProps {
|
||||
}
|
||||
|
||||
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 {
|
||||
IUser,
|
||||
IUserLite,
|
||||
IIssue,
|
||||
TIssue,
|
||||
IProject,
|
||||
IWorkspace,
|
||||
IWorkspaceLite,
|
||||
IProjectLite,
|
||||
IIssueFilterOptions,
|
||||
ILinkDetails,
|
||||
} from "types";
|
||||
} from "@plane/types";
|
||||
|
||||
export type TModuleStatus = "backlog" | "planned" | "in-progress" | "paused" | "completed" | "cancelled";
|
||||
|
||||
@ -58,7 +58,7 @@ export interface ModuleIssueResponse {
|
||||
created_by: string;
|
||||
id: string;
|
||||
issue: string;
|
||||
issue_detail: IIssue;
|
||||
issue_detail: TIssue;
|
||||
module: string;
|
||||
module_detail: IModule;
|
||||
project: string;
|
||||
@ -75,4 +75,4 @@ export type ModuleLink = {
|
||||
|
||||
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
|
||||
import { IIssue, IIssueLabel, IWorkspaceLite, IProjectLite } from "types";
|
||||
import { TIssue, IIssueLabel, IWorkspaceLite, IProjectLite } from "@plane/types";
|
||||
|
||||
export interface IPage {
|
||||
access: number;
|
||||
@ -27,15 +27,11 @@ export interface IPage {
|
||||
}
|
||||
|
||||
export interface IRecentPages {
|
||||
today: IPage[];
|
||||
yesterday: IPage[];
|
||||
this_week: IPage[];
|
||||
older: IPage[];
|
||||
[key: string]: IPage[];
|
||||
}
|
||||
|
||||
export interface RecentPagesResponse {
|
||||
[key: string]: IPage[];
|
||||
today: string[];
|
||||
yesterday: string[];
|
||||
this_week: string[];
|
||||
older: string[];
|
||||
[key: string]: string[];
|
||||
}
|
||||
|
||||
export interface IPageBlock {
|
||||
@ -47,7 +43,7 @@ export interface IPageBlock {
|
||||
description_stripped: any;
|
||||
id: string;
|
||||
issue: string | null;
|
||||
issue_detail: IIssue | null;
|
||||
issue_detail: TIssue | null;
|
||||
name: string;
|
||||
page: string;
|
||||
project: string;
|
@ -1,6 +1,5 @@
|
||||
import type { IUserLite, IWorkspace, IWorkspaceLite, IUserMemberLite, TStateGroups, IProjectViewProps } from ".";
|
||||
|
||||
export type TUserProjectRole = 5 | 10 | 15 | 20;
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
import type { IUser, IUserLite, IWorkspace, IWorkspaceLite, TStateGroups } from ".";
|
||||
|
||||
export interface IProject {
|
||||
archive_in: number;
|
||||
@ -34,13 +33,10 @@ export interface IProject {
|
||||
is_deployed: boolean;
|
||||
is_favorite: boolean;
|
||||
is_member: boolean;
|
||||
member_role: TUserProjectRole | null;
|
||||
member_role: EUserProjectRoles | null;
|
||||
members: IProjectMemberLite[];
|
||||
issue_views_view: boolean;
|
||||
module_view: boolean;
|
||||
name: string;
|
||||
network: number;
|
||||
page_view: boolean;
|
||||
project_lead: IUserLite | string | null;
|
||||
sort_order: number | null;
|
||||
total_cycles: number;
|
||||
@ -64,6 +60,10 @@ type ProjectPreferences = {
|
||||
};
|
||||
};
|
||||
|
||||
export interface IProjectMap {
|
||||
[id: string]: IProject;
|
||||
}
|
||||
|
||||
export interface IProjectMemberLite {
|
||||
id: string;
|
||||
member__avatar: string;
|
||||
@ -77,7 +77,7 @@ export interface IProjectMember {
|
||||
project: IProjectLite;
|
||||
workspace: IWorkspaceLite;
|
||||
comment: string;
|
||||
role: TUserProjectRole;
|
||||
role: EUserProjectRoles;
|
||||
|
||||
preferences: ProjectPreferences;
|
||||
|
||||
@ -90,27 +90,14 @@ export interface IProjectMember {
|
||||
updated_by: string;
|
||||
}
|
||||
|
||||
export interface IProjectMemberInvitation {
|
||||
export interface IProjectMembership {
|
||||
id: string;
|
||||
|
||||
project: IProject;
|
||||
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;
|
||||
member: string;
|
||||
role: EUserProjectRoles;
|
||||
}
|
||||
|
||||
export interface IProjectBulkAddFormData {
|
||||
members: { role: TUserProjectRole; member_id: string }[];
|
||||
members: { role: EUserProjectRoles; member_id: string }[];
|
||||
}
|
||||
|
||||
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";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
import { IIssueActivity, IIssueLite, TStateGroups } from ".";
|
||||
|
||||
export interface IUser {
|
||||
@ -61,11 +62,10 @@ export interface IUserTheme {
|
||||
|
||||
export interface IUserLite {
|
||||
avatar: string;
|
||||
created_at: Date;
|
||||
display_name: string;
|
||||
email?: string;
|
||||
first_name: string;
|
||||
readonly id: string;
|
||||
id: string;
|
||||
is_bot: boolean;
|
||||
last_name: string;
|
||||
}
|
||||
@ -163,7 +163,7 @@ export interface IUserProfileProjectSegregation {
|
||||
}
|
||||
|
||||
export interface IUserProjectsRole {
|
||||
[project_id: string]: number;
|
||||
[projectId: string]: EUserProjectRoles;
|
||||
}
|
||||
|
||||
// export interface ICurrentUser {
|
@ -108,6 +108,18 @@ export interface IIssueDisplayProperties {
|
||||
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 {
|
||||
assignees?: 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 {
|
||||
id: string;
|
||||
@ -10,6 +10,9 @@ export interface IProjectView {
|
||||
updated_by: string;
|
||||
name: string;
|
||||
description: string;
|
||||
filters: IIssueFilterOptions;
|
||||
display_filters: IIssueDisplayFilterOptions;
|
||||
display_properties: IIssueDisplayProperties;
|
||||
query: IIssueFilterOptions;
|
||||
query_data: IIssueFilterOptions;
|
||||
project: string;
|
@ -1,4 +1,9 @@
|
||||
import { IWorkspaceViewProps } from "./view-props";
|
||||
import {
|
||||
IWorkspaceViewProps,
|
||||
IIssueDisplayFilterOptions,
|
||||
IIssueDisplayProperties,
|
||||
IIssueFilterOptions,
|
||||
} from "./view-props";
|
||||
|
||||
export interface IWorkspaceView {
|
||||
id: string;
|
||||
@ -10,6 +15,9 @@ export interface IWorkspaceView {
|
||||
updated_by: string;
|
||||
name: string;
|
||||
description: string;
|
||||
filters: IIssueIIFilterOptions;
|
||||
display_filters: IIssueDisplayFilterOptions;
|
||||
display_properties: IIssueDisplayProperties;
|
||||
query: any;
|
||||
query_data: IWorkspaceViewProps;
|
||||
project: string;
|
@ -1,6 +1,5 @@
|
||||
import type { IProjectMember, IUser, IUserLite, IWorkspaceViewProps } from "types";
|
||||
|
||||
export type TUserWorkspaceRole = 5 | 10 | 15 | 20;
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
import type { IProjectMember, IUser, IUserLite, IWorkspaceViewProps } from "@plane/types";
|
||||
|
||||
export interface IWorkspace {
|
||||
readonly id: string;
|
||||
@ -27,18 +26,23 @@ export interface IWorkspaceLite {
|
||||
|
||||
export interface IWorkspaceMemberInvitation {
|
||||
accepted: boolean;
|
||||
readonly id: string;
|
||||
email: string;
|
||||
token: string;
|
||||
id: string;
|
||||
message: string;
|
||||
responded_at: Date;
|
||||
role: TUserWorkspaceRole;
|
||||
created_by_detail: IUser;
|
||||
workspace: IWorkspace;
|
||||
role: EUserWorkspaceRoles;
|
||||
token: string;
|
||||
workspace: string;
|
||||
workspace_detail: {
|
||||
id: string;
|
||||
logo: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IWorkspaceBulkInviteFormData {
|
||||
emails: { email: string; role: TUserWorkspaceRole }[];
|
||||
emails: { email: string; role: EUserWorkspaceRoles }[];
|
||||
}
|
||||
|
||||
export type Properties = {
|
||||
@ -58,15 +62,9 @@ export type Properties = {
|
||||
};
|
||||
|
||||
export interface IWorkspaceMember {
|
||||
company_role: string | null;
|
||||
created_at: Date;
|
||||
created_by: string;
|
||||
id: string;
|
||||
member: IUserLite;
|
||||
role: TUserWorkspaceRole;
|
||||
updated_at: Date;
|
||||
updated_by: string;
|
||||
workspace: IWorkspaceLite;
|
||||
role: EUserWorkspaceRoles;
|
||||
}
|
||||
|
||||
export interface IWorkspaceMemberMe {
|
||||
@ -76,7 +74,7 @@ export interface IWorkspaceMemberMe {
|
||||
default_props: IWorkspaceViewProps;
|
||||
id: string;
|
||||
member: string;
|
||||
role: TUserWorkspaceRole;
|
||||
role: EUserWorkspaceRoles;
|
||||
updated_at: Date;
|
||||
updated_by: string;
|
||||
view_props: IWorkspaceViewProps;
|
@ -1,13 +1,16 @@
|
||||
import * as React from "react";
|
||||
|
||||
// icons
|
||||
import { AlertCircle, Ban, SignalHigh, SignalLow, SignalMedium } from "lucide-react";
|
||||
|
||||
// types
|
||||
import { IPriorityIcon } from "./type";
|
||||
type TIssuePriorities = "urgent" | "high" | "medium" | "low" | "none";
|
||||
|
||||
export const PriorityIcon: React.FC<IPriorityIcon> = ({ priority, className = "", transparentBg = false }) => {
|
||||
if (!className || className === "") className = "h-4 w-4";
|
||||
interface IPriorityIcon {
|
||||
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
|
||||
const lowercasePriority = priority?.toLowerCase();
|
||||
@ -16,31 +19,17 @@ export const PriorityIcon: React.FC<IPriorityIcon> = ({ priority, className = ""
|
||||
const getPriorityIcon = (): React.ReactNode => {
|
||||
switch (lowercasePriority) {
|
||||
case "urgent":
|
||||
return <AlertCircle className={`text-red-500 ${transparentBg ? "" : "p-0.5"} ${className}`} />;
|
||||
return <AlertCircle size={size} className={`text-red-500 ${className}`} />;
|
||||
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":
|
||||
return <SignalMedium className={`text-yellow-500 ${transparentBg ? "" : "ml-1.5"} ${className}`} />;
|
||||
return <SignalMedium size={size} strokeWidth={3} className={`text-yellow-500 ${className}`} />;
|
||||
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:
|
||||
return <Ban className={`text-custom-text-200 ${transparentBg ? "" : "p-0.5"} ${className}`} />;
|
||||
return <Ban size={size} className={`text-custom-text-200 ${className}`} />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
return <>{getPriorityIcon()}</>;
|
||||
};
|
||||
|
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> {
|
||||
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 { Trash2 } from "lucide-react";
|
||||
import { mutate } from "swr";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
// hooks
|
||||
@ -22,9 +22,7 @@ export const DeactivateAccountModal: React.FC<Props> = (props) => {
|
||||
// states
|
||||
const [isDeactivating, setIsDeactivating] = useState(false);
|
||||
|
||||
const {
|
||||
user: { deactivateAccount },
|
||||
} = useMobxStore();
|
||||
const { deactivateAccount } = useUser();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
|
@ -10,7 +10,7 @@ import { Button, Input } from "@plane/ui";
|
||||
// helpers
|
||||
import { checkEmailValidity } from "helpers/string.helper";
|
||||
// types
|
||||
import { IEmailCheckData } from "types/auth";
|
||||
import { IEmailCheckData } from "@plane/types";
|
||||
// constants
|
||||
import { ESignInSteps } from "components/account";
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// services
|
||||
import { AuthService } from "services/auth.service";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// components
|
||||
import { GitHubSignInButton, GoogleSignInButton } from "components/account";
|
||||
@ -21,8 +20,8 @@ export const OAuthOptions: React.FC<Props> = observer((props) => {
|
||||
const { setToastAlert } = useToast();
|
||||
// mobx store
|
||||
const {
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
|
||||
const handleGoogleSignIn = async ({ clientId, credential }: any) => {
|
||||
try {
|
||||
|
@ -11,7 +11,7 @@ import { Button, Input } from "@plane/ui";
|
||||
// helpers
|
||||
import { checkEmailValidity } from "helpers/string.helper";
|
||||
// types
|
||||
import { IPasswordSignInData } from "types/auth";
|
||||
import { IPasswordSignInData } from "@plane/types";
|
||||
// constants
|
||||
import { ESignInSteps } from "components/account";
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useSignInRedirection from "hooks/use-sign-in-redirection";
|
||||
// components
|
||||
import { LatestFeatureBlock } from "components/common";
|
||||
@ -38,8 +37,8 @@ export const SignInRoot = observer(() => {
|
||||
const { handleRedirection } = useSignInRedirection();
|
||||
// mobx store
|
||||
const {
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
|
||||
const isOAuthEnabled = envConfig && (envConfig.google_client_id || envConfig.github_client_id);
|
||||
|
||||
|
@ -11,7 +11,7 @@ import { Button, Input } from "@plane/ui";
|
||||
// helpers
|
||||
import { checkEmailValidity } from "helpers/string.helper";
|
||||
// types
|
||||
import { IPasswordSignInData } from "types/auth";
|
||||
import { IPasswordSignInData } from "@plane/types";
|
||||
|
||||
type Props = {
|
||||
email: string;
|
||||
|
@ -9,7 +9,7 @@ import { Button, Input } from "@plane/ui";
|
||||
// helpers
|
||||
import { checkEmailValidity } from "helpers/string.helper";
|
||||
// types
|
||||
import { IEmailCheckData } from "types/auth";
|
||||
import { IEmailCheckData } from "@plane/types";
|
||||
|
||||
type Props = {
|
||||
email: string;
|
||||
|
@ -13,7 +13,7 @@ import { Button, Input } from "@plane/ui";
|
||||
// helpers
|
||||
import { checkEmailValidity } from "helpers/string.helper";
|
||||
// types
|
||||
import { IEmailCheckData, IMagicSignInData } from "types/auth";
|
||||
import { IEmailCheckData, IMagicSignInData } from "@plane/types";
|
||||
// constants
|
||||
import { ESignInSteps } from "components/account";
|
||||
|
||||
@ -233,8 +233,8 @@ export const UniqueCodeForm: React.FC<Props> = (props) => {
|
||||
{resendTimerCode > 0
|
||||
? `Request new code in ${resendTimerCode}s`
|
||||
: isRequestingNewCode
|
||||
? "Requesting new code"
|
||||
: "Request new code"}
|
||||
? "Requesting new code"
|
||||
: "Request new code"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -7,7 +7,7 @@ import { AnalyticsService } from "services/analytics.service";
|
||||
// components
|
||||
import { CustomAnalyticsSelectBar, CustomAnalyticsMainContent, CustomAnalyticsSidebar } from "components/analytics";
|
||||
// types
|
||||
import { IAnalyticsParams } from "types";
|
||||
import { IAnalyticsParams } from "@plane/types";
|
||||
// fetch-keys
|
||||
import { ANALYTICS } from "constants/fetch-keys";
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { BarTooltipProps } from "@nivo/bar";
|
||||
import { DATE_KEYS } from "constants/analytics";
|
||||
import { renderMonthAndYear } from "helpers/analytics.helper";
|
||||
// types
|
||||
import { IAnalyticsParams, IAnalyticsResponse } from "types";
|
||||
import { IAnalyticsParams, IAnalyticsResponse } from "@plane/types";
|
||||
|
||||
type Props = {
|
||||
datum: BarTooltipProps<any>;
|
||||
@ -60,8 +60,8 @@ export const CustomTooltip: React.FC<Props> = ({ datum, analytics, params }) =>
|
||||
? "capitalize"
|
||||
: ""
|
||||
: params.x_axis === "priority" || params.x_axis === "state__group"
|
||||
? "capitalize"
|
||||
: ""
|
||||
? "capitalize"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{params.segment === "assignees__id" ? renderAssigneeName(tooltipValue.toString()) : tooltipValue}:
|
||||
|
@ -9,7 +9,7 @@ import { BarGraph } from "components/ui";
|
||||
import { findStringWithMostCharacters } from "helpers/array.helper";
|
||||
import { generateBarColor, generateDisplayName } from "helpers/analytics.helper";
|
||||
// types
|
||||
import { IAnalyticsParams, IAnalyticsResponse } from "types";
|
||||
import { IAnalyticsParams, IAnalyticsResponse } from "@plane/types";
|
||||
|
||||
type Props = {
|
||||
analytics: IAnalyticsResponse;
|
||||
@ -101,8 +101,8 @@ export const AnalyticsGraph: React.FC<Props> = ({ analytics, barGraphData, param
|
||||
? generateDisplayName(datum.value, analytics, params, "x_axis")[0].toUpperCase()
|
||||
: "?"
|
||||
: datum.value && datum.value !== "None"
|
||||
? `${datum.value}`.toUpperCase()[0]
|
||||
: "?"}
|
||||
? `${datum.value}`.toUpperCase()[0]
|
||||
: "?"}
|
||||
</text>
|
||||
</g>
|
||||
</Tooltip>
|
||||
|
@ -8,7 +8,7 @@ import { Button, Loader } from "@plane/ui";
|
||||
// helpers
|
||||
import { convertResponseToBarGraphData } from "helpers/analytics.helper";
|
||||
// types
|
||||
import { IAnalyticsParams, IAnalyticsResponse } from "types";
|
||||
import { IAnalyticsParams, IAnalyticsResponse } from "@plane/types";
|
||||
// fetch-keys
|
||||
import { ANALYTICS } from "constants/fetch-keys";
|
||||
|
||||
|
@ -1,13 +1,11 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Control, Controller, UseFormSetValue } from "react-hook-form";
|
||||
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useProject } from "hooks/store";
|
||||
// components
|
||||
import { SelectProject, SelectSegment, SelectXAxis, SelectYAxis } from "components/analytics";
|
||||
// types
|
||||
import { IAnalyticsParams } from "types";
|
||||
import { IAnalyticsParams } from "@plane/types";
|
||||
|
||||
type Props = {
|
||||
control: Control<IAnalyticsParams, any>;
|
||||
@ -20,12 +18,7 @@ type Props = {
|
||||
export const CustomAnalyticsSelectBar: React.FC<Props> = observer((props) => {
|
||||
const { control, setValue, params, fullScreen, isProjectLevel } = props;
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { project: projectStore } = useMobxStore();
|
||||
|
||||
const projectsList = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : null;
|
||||
const { workspaceProjectIds: workspaceProjectIds } = useProject();
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -40,7 +33,11 @@ export const CustomAnalyticsSelectBar: React.FC<Props> = observer((props) => {
|
||||
name="project"
|
||||
control={control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<SelectProject value={value ?? undefined} onChange={onChange} projects={projectsList ?? undefined} />
|
||||
<SelectProject
|
||||
value={value ?? undefined}
|
||||
onChange={onChange}
|
||||
projectIds={workspaceProjectIds ?? undefined}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,25 +1,33 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useProject } from "hooks/store";
|
||||
// ui
|
||||
import { CustomSearchSelect } from "@plane/ui";
|
||||
// types
|
||||
import { IProject } from "types";
|
||||
|
||||
type Props = {
|
||||
value: string[] | undefined;
|
||||
onChange: (val: string[] | null) => void;
|
||||
projects: IProject[] | undefined;
|
||||
projectIds: string[] | undefined;
|
||||
};
|
||||
|
||||
export const SelectProject: React.FC<Props> = ({ value, onChange, projects }) => {
|
||||
const options = projects?.map((project) => ({
|
||||
value: project.id,
|
||||
query: project.name + project.identifier,
|
||||
content: (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[0.65rem] text-custom-text-200">{project.identifier}</span>
|
||||
{project.name}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
export const SelectProject: React.FC<Props> = observer((props) => {
|
||||
const { value, onChange, projectIds } = props;
|
||||
const { getProjectById } = useProject();
|
||||
|
||||
const options = projectIds?.map((projectId) => {
|
||||
const projectDetails = getProjectById(projectId);
|
||||
|
||||
return {
|
||||
value: projectDetails?.id,
|
||||
query: `${projectDetails?.name} ${projectDetails?.identifier}`,
|
||||
content: (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[0.65rem] text-custom-text-200">{projectDetails?.identifier}</span>
|
||||
{projectDetails?.name}
|
||||
</div>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<CustomSearchSelect
|
||||
@ -28,9 +36,9 @@ export const SelectProject: React.FC<Props> = ({ value, onChange, projects }) =>
|
||||
options={options}
|
||||
label={
|
||||
value && value.length > 0
|
||||
? projects
|
||||
?.filter((p) => value.includes(p.id))
|
||||
.map((p) => p.identifier)
|
||||
? projectIds
|
||||
?.filter((p) => value.includes(p))
|
||||
.map((p) => getProjectById(p)?.name)
|
||||
.join(", ")
|
||||
: "All projects"
|
||||
}
|
||||
@ -38,4 +46,4 @@ export const SelectProject: React.FC<Props> = ({ value, onChange, projects }) =>
|
||||
multiple
|
||||
/>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -3,7 +3,7 @@ import { useRouter } from "next/router";
|
||||
// ui
|
||||
import { CustomSelect } from "@plane/ui";
|
||||
// types
|
||||
import { IAnalyticsParams, TXAxisValues } from "types";
|
||||
import { IAnalyticsParams, TXAxisValues } from "@plane/types";
|
||||
// constants
|
||||
import { ANALYTICS_X_AXIS_VALUES } from "constants/analytics";
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { useRouter } from "next/router";
|
||||
// ui
|
||||
import { CustomSelect } from "@plane/ui";
|
||||
// types
|
||||
import { IAnalyticsParams, TXAxisValues } from "types";
|
||||
import { IAnalyticsParams, TXAxisValues } from "@plane/types";
|
||||
// constants
|
||||
import { ANALYTICS_X_AXIS_VALUES } from "constants/analytics";
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
// ui
|
||||
import { CustomSelect } from "@plane/ui";
|
||||
// types
|
||||
import { TYAxisValues } from "types";
|
||||
import { TYAxisValues } from "@plane/types";
|
||||
// constants
|
||||
import { ANALYTICS_Y_AXIS_VALUES } from "constants/analytics";
|
||||
|
||||
|
@ -1,65 +1,74 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useProject } from "hooks/store";
|
||||
// icons
|
||||
import { Contrast, LayoutGrid, Users } from "lucide-react";
|
||||
// helpers
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
// types
|
||||
import { IProject } from "types";
|
||||
|
||||
type Props = {
|
||||
projects: IProject[];
|
||||
projectIds: string[];
|
||||
};
|
||||
|
||||
export const CustomAnalyticsSidebarProjectsList: React.FC<Props> = (props) => {
|
||||
const { projects } = props;
|
||||
export const CustomAnalyticsSidebarProjectsList: React.FC<Props> = observer((props) => {
|
||||
const { projectIds } = props;
|
||||
|
||||
const { getProjectById } = useProject();
|
||||
|
||||
return (
|
||||
<div className="hidden h-full overflow-hidden md:flex md:flex-col">
|
||||
<h4 className="font-medium">Selected Projects</h4>
|
||||
<div className="mt-4 h-full space-y-6 overflow-y-auto">
|
||||
{projects.map((project) => (
|
||||
<div key={project.id} className="w-full">
|
||||
<div className="flex items-center gap-1 text-sm">
|
||||
{project.emoji ? (
|
||||
<span className="grid h-6 w-6 flex-shrink-0 place-items-center">{renderEmoji(project.emoji)}</span>
|
||||
) : project.icon_prop ? (
|
||||
<div className="grid h-6 w-6 flex-shrink-0 place-items-center">{renderEmoji(project.icon_prop)}</div>
|
||||
) : (
|
||||
<span className="mr-1 grid h-6 w-6 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
||||
{project?.name.charAt(0)}
|
||||
</span>
|
||||
)}
|
||||
<h5 className="flex items-center gap-1">
|
||||
<p className="break-words">{truncateText(project.name, 20)}</p>
|
||||
<span className="ml-1 text-xs text-custom-text-200">({project.identifier})</span>
|
||||
</h5>
|
||||
</div>
|
||||
<div className="mt-4 w-full space-y-3 pl-2">
|
||||
<div className="flex items-center justify-between gap-2 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<Users className="text-custom-text-200" size={14} strokeWidth={2} />
|
||||
<h6>Total members</h6>
|
||||
</div>
|
||||
<span className="text-custom-text-200">{project.total_members}</span>
|
||||
{projectIds.map((projectId) => {
|
||||
const project = getProjectById(projectId);
|
||||
|
||||
if (!project) return;
|
||||
|
||||
return (
|
||||
<div key={projectId} className="w-full">
|
||||
<div className="flex items-center gap-1 text-sm">
|
||||
{project.emoji ? (
|
||||
<span className="grid h-6 w-6 flex-shrink-0 place-items-center">{renderEmoji(project.emoji)}</span>
|
||||
) : project.icon_prop ? (
|
||||
<div className="grid h-6 w-6 flex-shrink-0 place-items-center">{renderEmoji(project.icon_prop)}</div>
|
||||
) : (
|
||||
<span className="mr-1 grid h-6 w-6 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
||||
{project?.name.charAt(0)}
|
||||
</span>
|
||||
)}
|
||||
<h5 className="flex items-center gap-1">
|
||||
<p className="break-words">{truncateText(project.name, 20)}</p>
|
||||
<span className="ml-1 text-xs text-custom-text-200">({project.identifier})</span>
|
||||
</h5>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-2 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<Contrast className="text-custom-text-200" size={14} strokeWidth={2} />
|
||||
<h6>Total cycles</h6>
|
||||
<div className="mt-4 w-full space-y-3 pl-2">
|
||||
<div className="flex items-center justify-between gap-2 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<Users className="text-custom-text-200" size={14} strokeWidth={2} />
|
||||
<h6>Total members</h6>
|
||||
</div>
|
||||
<span className="text-custom-text-200">{project.total_members}</span>
|
||||
</div>
|
||||
<span className="text-custom-text-200">{project.total_cycles}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-2 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<LayoutGrid className="text-custom-text-200" size={14} strokeWidth={2} />
|
||||
<h6>Total modules</h6>
|
||||
<div className="flex items-center justify-between gap-2 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<Contrast className="text-custom-text-200" size={14} strokeWidth={2} />
|
||||
<h6>Total cycles</h6>
|
||||
</div>
|
||||
<span className="text-custom-text-200">{project.total_cycles}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-2 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<LayoutGrid className="text-custom-text-200" size={14} strokeWidth={2} />
|
||||
<h6>Total modules</h6>
|
||||
</div>
|
||||
<span className="text-custom-text-200">{project.total_modules}</span>
|
||||
</div>
|
||||
<span className="text-custom-text-200">{project.total_modules}</span>
|
||||
</div>
|
||||
</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