diff --git a/.env.example b/.env.example index 1d95c56a0..9fe0f47d9 100644 --- a/.env.example +++ b/.env.example @@ -23,6 +23,8 @@ NEXT_PUBLIC_SLACK_CLIENT_ID="" NEXT_PUBLIC_PLAUSIBLE_DOMAIN="" # public boards deploy url NEXT_PUBLIC_DEPLOY_URL="" +# plane deploy using nginx +NEXT_PUBLIC_DEPLOY_WITH_NGINX=1 # Backend # Debug value for api server use it as 0 for production use diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index b10689843..1d4a16eb6 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -92,6 +92,7 @@ from plane.api.views import ( IssueReactionViewSet, IssueRelationViewSet, CommentReactionViewSet, + IssueDraftViewSet, ## End Issues # States StateViewSet, @@ -1031,6 +1032,27 @@ urlpatterns = [ name="issue-relation", ), ## End Issue Relation + ## Issue Drafts + path( + "workspaces//projects//issue-drafts/", + IssueDraftViewSet.as_view( + { + "get": "list", + } + ), + name="project-issue-draft", + ), + path( + "workspaces//projects//issue-drafts//", + IssueDraftViewSet.as_view( + { + "get": "retrieve", + "delete": "destroy", + } + ), + name="project-issue-draft", + ), + ## End Issue Drafts ## File Assets path( "workspaces//file-assets/", diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index 8e5f279a8..265ed9c90 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -89,6 +89,7 @@ from .issue import ( IssueRelationViewSet, IssueRetrievePublicEndpoint, ProjectIssuesPublicEndpoint, + IssueDraftViewSet, ) from .auth_extended import ( diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index 918d7a55e..2e5551f41 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -181,7 +181,7 @@ class IssueViewSet(BaseViewSet): filters = issue_filters(request.query_params, "GET") # Custom ordering for priority and state - priority_order = ["urgent", "high", "medium", "low", None] + priority_order = ["urgent", "high", "medium", "low", "none"] state_order = ["backlog", "unstarted", "started", "completed", "cancelled"] order_by_param = request.GET.get("order_by", "-created_at") @@ -334,7 +334,7 @@ class UserWorkSpaceIssues(BaseAPIView): try: filters = issue_filters(request.query_params, "GET") # Custom ordering for priority and state - priority_order = ["urgent", "high", "medium", "low", None] + priority_order = ["urgent", "high", "medium", "low", "none"] state_order = ["backlog", "unstarted", "started", "completed", "cancelled"] order_by_param = request.GET.get("order_by", "-created_at") @@ -1071,7 +1071,7 @@ class IssueArchiveViewSet(BaseViewSet): show_sub_issues = request.GET.get("show_sub_issues", "true") # Custom ordering for priority and state - priority_order = ["urgent", "high", "medium", "low", None] + priority_order = ["urgent", "high", "medium", "low", "none"] state_order = ["backlog", "unstarted", "started", "completed", "cancelled"] order_by_param = request.GET.get("order_by", "-created_at") @@ -2173,7 +2173,7 @@ class ProjectIssuesPublicEndpoint(BaseAPIView): filters = issue_filters(request.query_params, "GET") # Custom ordering for priority and state - priority_order = ["urgent", "high", "medium", "low", None] + priority_order = ["urgent", "high", "medium", "low", "none"] state_order = ["backlog", "unstarted", "started", "completed", "cancelled"] order_by_param = request.GET.get("order_by", "-created_at") @@ -2335,3 +2335,157 @@ class ProjectIssuesPublicEndpoint(BaseAPIView): {"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST, ) + + +class IssueDraftViewSet(BaseViewSet): + permission_classes = [ + ProjectEntityPermission, + ] + serializer_class = IssueFlatSerializer + model = Issue + + def get_queryset(self): + return ( + Issue.objects.annotate( + sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id")) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) + .filter(project_id=self.kwargs.get("project_id")) + .filter(workspace__slug=self.kwargs.get("slug")) + .filter(is_draft=True) + .select_related("project") + .select_related("workspace") + .select_related("state") + .select_related("parent") + .prefetch_related("assignees") + .prefetch_related("labels") + .prefetch_related( + Prefetch( + "issue_reactions", + queryset=IssueReaction.objects.select_related("actor"), + ) + ) + ) + + @method_decorator(gzip_page) + def list(self, request, slug, project_id): + try: + 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( + link_count=IssueLink.objects.filter(issue=OuterRef("id")) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) + .annotate( + attachment_count=IssueAttachment.objects.filter( + issue=OuterRef("id") + ) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) + ) + + # Priority Ordering + if order_by_param == "priority" or order_by_param == "-priority": + priority_order = ( + priority_order + if order_by_param == "priority" + else priority_order[::-1] + ) + issue_queryset = issue_queryset.annotate( + priority_order=Case( + *[ + When(priority=p, then=Value(i)) + for i, p in enumerate(priority_order) + ], + output_field=CharField(), + ) + ).order_by("priority_order") + + # State Ordering + elif order_by_param in [ + "state__name", + "state__group", + "-state__name", + "-state__group", + ]: + state_order = ( + state_order + if order_by_param in ["state__name", "state__group"] + else state_order[::-1] + ) + issue_queryset = issue_queryset.annotate( + state_order=Case( + *[ + When(state__group=state_group, then=Value(i)) + for i, state_group in enumerate(state_order) + ], + default=Value(len(state_order)), + output_field=CharField(), + ) + ).order_by("state_order") + # assignee and label ordering + elif order_by_param in [ + "labels__name", + "-labels__name", + "assignees__first_name", + "-assignees__first_name", + ]: + issue_queryset = issue_queryset.annotate( + max_values=Max( + order_by_param[1::] + if order_by_param.startswith("-") + else order_by_param + ) + ).order_by( + "-max_values" if order_by_param.startswith("-") else "max_values" + ) + else: + issue_queryset = issue_queryset.order_by(order_by_param) + + issues = IssueLiteSerializer(issue_queryset, many=True).data + + ## Grouping the results + group_by = request.GET.get("group_by", False) + if group_by: + return Response( + group_results(issues, group_by), status=status.HTTP_200_OK + ) + + return Response(issues, status=status.HTTP_200_OK) + + except Exception as e: + capture_exception(e) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + + def retrieve(self, request, slug, project_id, pk=None): + try: + issue = Issue.objects.get( + workspace__slug=slug, project_id=project_id, pk=pk, is_draft=True + ) + return Response(IssueSerializer(issue).data, status=status.HTTP_200_OK) + except Issue.DoesNotExist: + return Response( + {"error": "Issue Does not exist"}, status=status.HTTP_404_NOT_FOUND + ) + diff --git a/apiserver/plane/api/views/workspace.py b/apiserver/plane/api/views/workspace.py index 2ec3f324a..2d1ee8132 100644 --- a/apiserver/plane/api/views/workspace.py +++ b/apiserver/plane/api/views/workspace.py @@ -1072,7 +1072,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView): .order_by("state_group") ) - priority_order = ["urgent", "high", "medium", "low", None] + priority_order = ["urgent", "high", "medium", "low", "none"] priority_distribution = ( Issue.issue_objects.filter( diff --git a/apiserver/plane/db/migrations/0043_auto_20230911_1655.py b/apiserver/plane/db/migrations/0043_auto_20230911_1655.py new file mode 100644 index 000000000..027e60cbf --- /dev/null +++ b/apiserver/plane/db/migrations/0043_auto_20230911_1655.py @@ -0,0 +1,137 @@ +# Generated by Django 4.2.3 on 2023-09-11 16:55 + +from django.db import migrations + +def workspace_member_props(old_props): + new_props = { + "filters": { + "priority": old_props.get("filters", {}).get("priority", None), + "state": old_props.get("filters", {}).get("state", None), + "state_group": old_props.get("filters", {}).get("state_group", None), + "assignees": old_props.get("filters", {}).get("assignees", None), + "created_by": old_props.get("filters", {}).get("created_by", None), + "labels": old_props.get("filters", {}).get("labels", None), + "start_date": old_props.get("filters", {}).get("start_date", None), + "target_date": old_props.get("filters", {}).get("target_date", None), + "subscriber": old_props.get("filters", {}).get("subscriber", None), + }, + "display_filters": { + "group_by": old_props.get("groupByProperty", None), + "order_by": old_props.get("orderBy", "-created_at"), + "type": old_props.get("filters", {}).get("type", None), + "sub_issue": old_props.get("showSubIssues", True), + "show_empty_groups": old_props.get("showEmptyGroups", True), + "layout": old_props.get("issueView", "list"), + "calendar_date_range": old_props.get("calendarDateRange", ""), + }, + "display_properties": { + "assignee": old_props.get("properties", {}).get("assignee",None), + "attachment_count": old_props.get("properties", {}).get("attachment_count", None), + "created_on": old_props.get("properties", {}).get("created_on", None), + "due_date": old_props.get("properties", {}).get("due_date", None), + "estimate": old_props.get("properties", {}).get("estimate", None), + "key": old_props.get("properties", {}).get("key", None), + "labels": old_props.get("properties", {}).get("labels", None), + "link": old_props.get("properties", {}).get("link", None), + "priority": old_props.get("properties", {}).get("priority", None), + "start_date": old_props.get("properties", {}).get("start_date", None), + "state": old_props.get("properties", {}).get("state", None), + "sub_issue_count": old_props.get("properties", {}).get("sub_issue_count", None), + "updated_on": old_props.get("properties", {}).get("updated_on", None), + }, + } + return new_props + + +def project_member_props(old_props): + new_props = { + "filters": { + "priority": old_props.get("filters", {}).get("priority", None), + "state": old_props.get("filters", {}).get("state", None), + "state_group": old_props.get("filters", {}).get("state_group", None), + "assignees": old_props.get("filters", {}).get("assignees", None), + "created_by": old_props.get("filters", {}).get("created_by", None), + "labels": old_props.get("filters", {}).get("labels", None), + "start_date": old_props.get("filters", {}).get("start_date", None), + "target_date": old_props.get("filters", {}).get("target_date", None), + "subscriber": old_props.get("filters", {}).get("subscriber", None), + }, + "display_filters": { + "group_by": old_props.get("groupByProperty", None), + "order_by": old_props.get("orderBy", "-created_at"), + "type": old_props.get("filters", {}).get("type", None), + "sub_issue": old_props.get("showSubIssues", True), + "show_empty_groups": old_props.get("showEmptyGroups", True), + "layout": old_props.get("issueView", "list"), + "calendar_date_range": old_props.get("calendarDateRange", ""), + }, + } + return new_props + + +def cycle_module_props(old_props): + new_props = { + "filters": { + "priority": old_props.get("filters", {}).get("priority", None), + "state": old_props.get("filters", {}).get("state", None), + "state_group": old_props.get("filters", {}).get("state_group", None), + "assignees": old_props.get("filters", {}).get("assignees", None), + "created_by": old_props.get("filters", {}).get("created_by", None), + "labels": old_props.get("filters", {}).get("labels", None), + "start_date": old_props.get("filters", {}).get("start_date", None), + "target_date": old_props.get("filters", {}).get("target_date", None), + "subscriber": old_props.get("filters", {}).get("subscriber", None), + }, + } + return new_props + + +def update_workspace_member_view_props(apps, schema_editor): + WorkspaceMemberModel = apps.get_model("db", "WorkspaceMember") + updated_workspace_member = [] + for obj in WorkspaceMemberModel.objects.all(): + obj.view_props = workspace_member_props(obj.view_props) + obj.default_props = workspace_member_props(obj.default_props) + updated_workspace_member.append(obj) + WorkspaceMemberModel.objects.bulk_update(updated_workspace_member, ["view_props", "default_props"], batch_size=100) + +def update_project_member_view_props(apps, schema_editor): + ProjectMemberModel = apps.get_model("db", "ProjectMember") + updated_project_member = [] + for obj in ProjectMemberModel.objects.all(): + obj.view_props = project_member_props(obj.view_props) + obj.default_props = project_member_props(obj.default_props) + updated_project_member.append(obj) + ProjectMemberModel.objects.bulk_update(updated_project_member, ["view_props", "default_props"], batch_size=100) + +def update_cycle_props(apps, schema_editor): + CycleModel = apps.get_model("db", "Cycle") + updated_cycle = [] + for obj in CycleModel.objects.all(): + if "filter" in obj.view_props: + obj.view_props = cycle_module_props(obj.view_props) + updated_cycle.append(obj) + CycleModel.objects.bulk_update(updated_cycle, ["view_props"], batch_size=100) + +def update_module_props(apps, schema_editor): + ModuleModel = apps.get_model("db", "Module") + updated_module = [] + for obj in ModuleModel.objects.all(): + if "filter" in obj.view_props: + obj.view_props = cycle_module_props(obj.view_props) + updated_module.append(obj) + ModuleModel.objects.bulk_update(updated_module, ["view_props"], batch_size=100) + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0042_alter_analyticview_created_by_and_more'), + ] + + operations = [ + migrations.RunPython(update_workspace_member_view_props), + migrations.RunPython(update_project_member_view_props), + migrations.RunPython(update_cycle_props), + migrations.RunPython(update_module_props), + ] diff --git a/apiserver/plane/db/migrations/0043_auto_20230912_1733.py b/apiserver/plane/db/migrations/0043_auto_20230912_1733.py new file mode 100644 index 000000000..e0602dc93 --- /dev/null +++ b/apiserver/plane/db/migrations/0043_auto_20230912_1733.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2.3 on 2023-09-12 17:33 + +from django.db import migrations + + +def update_issue_priority_choice(apps, schema_editor): + IssueModel = apps.get_model("db", "Issue") + updated_issues = [] + for obj in IssueModel.objects.all(): + if obj.priority is None: + obj.priority = "none" + updated_issues.append(obj) + IssueModel.objects.bulk_update(updated_issues, ["priority"], batch_size=100) + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0042_alter_analyticview_created_by_and_more'), + ] + + operations = [ + migrations.RunPython(update_issue_priority_choice), + ] diff --git a/apiserver/plane/db/migrations/0043_issue_is_draft_alter_analyticview_created_by_and_more.py b/apiserver/plane/db/migrations/0043_issue_is_draft_alter_analyticview_created_by_and_more.py new file mode 100644 index 000000000..7589f3d95 --- /dev/null +++ b/apiserver/plane/db/migrations/0043_issue_is_draft_alter_analyticview_created_by_and_more.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.3 on 2023-09-12 12:33 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0042_alter_analyticview_created_by_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='issue', + name='is_draft', + field=models.BooleanField(default=False), + ), + ] diff --git a/apiserver/plane/db/models/issue.py b/apiserver/plane/db/models/issue.py index aa48614ee..65f1bc965 100644 --- a/apiserver/plane/db/models/issue.py +++ b/apiserver/plane/db/models/issue.py @@ -29,6 +29,7 @@ class IssueManager(models.Manager): | models.Q(issue_inbox__isnull=True) ) .exclude(archived_at__isnull=False) + .exclude(is_draft=True) ) @@ -38,6 +39,7 @@ class Issue(ProjectBaseModel): ("high", "High"), ("medium", "Medium"), ("low", "Low"), + ("none", "None") ) parent = models.ForeignKey( "self", @@ -64,8 +66,7 @@ class Issue(ProjectBaseModel): max_length=30, choices=PRIORITY_CHOICES, verbose_name="Issue Priority", - null=True, - blank=True, + default="none", ) start_date = models.DateField(null=True, blank=True) target_date = models.DateField(null=True, blank=True) @@ -83,6 +84,7 @@ class Issue(ProjectBaseModel): sort_order = models.FloatField(default=65535) completed_at = models.DateTimeField(null=True) archived_at = models.DateField(null=True) + is_draft = models.BooleanField(default=False) objects = models.Manager() issue_objects = IssueManager() diff --git a/apiserver/plane/utils/issue_filters.py b/apiserver/plane/utils/issue_filters.py index 34e1e8203..226d909cd 100644 --- a/apiserver/plane/utils/issue_filters.py +++ b/apiserver/plane/utils/issue_filters.py @@ -1,6 +1,7 @@ from django.utils.timezone import make_aware from django.utils.dateparse import parse_datetime + def filter_state(params, filter, method): if method == "GET": states = params.get("state").split(",") @@ -23,7 +24,6 @@ def filter_state_group(params, filter, method): return filter - def filter_estimate_point(params, filter, method): if method == "GET": estimate_points = params.get("estimate_point").split(",") @@ -39,25 +39,10 @@ def filter_priority(params, filter, method): if method == "GET": priorities = params.get("priority").split(",") if len(priorities) and "" not in priorities: - if len(priorities) == 1 and "null" in priorities: - filter["priority__isnull"] = True - elif len(priorities) > 1 and "null" in priorities: - filter["priority__isnull"] = True - filter["priority__in"] = [p for p in priorities if p != "null"] - else: - filter["priority__in"] = [p for p in priorities if p != "null"] - + filter["priority__in"] = priorities else: if params.get("priority", None) and len(params.get("priority")): - priorities = params.get("priority") - if len(priorities) == 1 and "null" in priorities: - filter["priority__isnull"] = True - elif len(priorities) > 1 and "null" in priorities: - filter["priority__isnull"] = True - filter["priority__in"] = [p for p in priorities if p != "null"] - else: - filter["priority__in"] = [p for p in priorities if p != "null"] - + filter["priority__in"] = params.get("priority") return filter @@ -229,7 +214,6 @@ def filter_issue_state_type(params, filter, method): return filter - def filter_project(params, filter, method): if method == "GET": projects = params.get("project").split(",") @@ -329,7 +313,7 @@ def issue_filters(query_params, method): "module": filter_module, "inbox_status": filter_inbox_status, "sub_issue": filter_sub_issue_toggle, - "subscriber": filter_subscribed_issues, + "subscriber": filter_subscribed_issues, "start_target_date": filter_start_target_date_issues, } diff --git a/packages/eslint-config-custom/index.js b/packages/eslint-config-custom/index.js index d31a76406..82be65376 100644 --- a/packages/eslint-config-custom/index.js +++ b/packages/eslint-config-custom/index.js @@ -16,5 +16,7 @@ module.exports = { "no-duplicate-imports": "error", "arrow-body-style": ["error", "as-needed"], "react/self-closing-comp": ["error", { component: true, html: true }], + "@next/next/no-img-element": "off", + "@typescript-eslint/no-unused-vars": ["warn"], }, }; diff --git a/replace-env-vars.sh b/replace-env-vars.sh index afdc1492e..949ffd7d7 100644 --- a/replace-env-vars.sh +++ b/replace-env-vars.sh @@ -12,4 +12,4 @@ fi # Only perform action if $FROM and $TO are different. echo "Replacing all statically built instances of $FROM with this string $TO ." -grep -R -la "${FROM}" apps/$DIRECTORY/.next | xargs -I{} sed -i "s|$FROM|$TO|g" "{}" +grep -R -la "${FROM}" $DIRECTORY/.next | xargs -I{} sed -i "s|$FROM|$TO|g" "{}" diff --git a/space/.eslintrc.js b/space/.eslintrc.js index 38e6a5f4c..c8df60750 100644 --- a/space/.eslintrc.js +++ b/space/.eslintrc.js @@ -1,7 +1,4 @@ module.exports = { root: true, extends: ["custom"], - rules: { - "@next/next/no-img-element": "off", - }, }; diff --git a/space/additional.d.ts b/space/additional.d.ts new file mode 100644 index 000000000..f400344c6 --- /dev/null +++ b/space/additional.d.ts @@ -0,0 +1,2 @@ +// additional.d.ts +/// diff --git a/space/components/accounts/email-password-form.tsx b/space/components/accounts/email-password-form.tsx index 23742eefe..b00740a15 100644 --- a/space/components/accounts/email-password-form.tsx +++ b/space/components/accounts/email-password-form.tsx @@ -1,9 +1,6 @@ import React, { useState } from "react"; - import { useRouter } from "next/router"; import Link from "next/link"; - -// react hook form import { useForm } from "react-hook-form"; // components import { EmailResetPasswordForm } from "./email-reset-password-form"; diff --git a/space/components/accounts/sign-in.tsx b/space/components/accounts/sign-in.tsx index b21ef167d..ed55f7697 100644 --- a/space/components/accounts/sign-in.tsx +++ b/space/components/accounts/sign-in.tsx @@ -13,7 +13,7 @@ import useToast from "hooks/use-toast"; // components import { EmailPasswordForm, GithubLoginButton, GoogleLoginButton, EmailCodeForm } from "components/accounts"; // images -import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png"; +const imagePrefix = process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX ? "/spaces/" : ""; export const SignInView = observer(() => { const { user: userStore } = useMobxStore(); @@ -112,7 +112,7 @@ export const SignInView = observer(() => {
- Plane Logo + Plane Logo
diff --git a/space/next.config.js b/space/next.config.js index 392a4cab9..bd3749f10 100644 --- a/space/next.config.js +++ b/space/next.config.js @@ -12,7 +12,10 @@ const nextConfig = { }; if (parseInt(process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX || "0")) { - const nextConfigWithNginx = withImages({ basePath: "/spaces", ...nextConfig }); + const nextConfigWithNginx = withImages({ + basePath: "/spaces", + ...nextConfig, + }); module.exports = nextConfigWithNginx; } else { module.exports = nextConfig; diff --git a/space/package.json b/space/package.json index 768abb8ff..f2bb39df6 100644 --- a/space/package.json +++ b/space/package.json @@ -69,6 +69,7 @@ "@types/react": "18.0.28", "@types/react-dom": "18.0.11", "@types/uuid": "^9.0.1", + "@typescript-eslint/eslint-plugin": "^5.48.2", "autoprefixer": "^10.4.13", "eslint": "8.34.0", "eslint-config-custom": "*", diff --git a/space/pages/onboarding/index.tsx b/space/pages/onboarding/index.tsx index 2f08ee648..e7ed35222 100644 --- a/space/pages/onboarding/index.tsx +++ b/space/pages/onboarding/index.tsx @@ -1,24 +1,17 @@ import React, { useEffect } from "react"; -import Image from "next/image"; -// assets -import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png"; // mobx import { observer } from "mobx-react-lite"; import { useMobxStore } from "lib/mobx/store-provider"; -// services -import authenticationService from "services/authentication.service"; -// hooks -import useToast from "hooks/use-toast"; // components import { OnBoardingForm } from "components/accounts/onboarding-form"; +const imagePrefix = process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX ? "/spaces/" : ""; + const OnBoardingPage = () => { const { user: userStore } = useMobxStore(); const user = userStore?.currentUser; - const { setToastAlert } = useToast(); - useEffect(() => { const user = userStore?.currentUser; @@ -34,7 +27,7 @@ const OnBoardingPage = () => {
- Plane logo + Plane logo
diff --git a/space/tsconfig.json b/space/tsconfig.json index 63c95575d..3047edb7c 100644 --- a/space/tsconfig.json +++ b/space/tsconfig.json @@ -1,6 +1,6 @@ { "extends": "tsconfig/nextjs.json", - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "additional.d.ts"], "exclude": ["node_modules"], "compilerOptions": { "baseUrl": ".", diff --git a/web/.eslintrc.js b/web/.eslintrc.js index 38e6a5f4c..c8df60750 100644 --- a/web/.eslintrc.js +++ b/web/.eslintrc.js @@ -1,7 +1,4 @@ module.exports = { root: true, extends: ["custom"], - rules: { - "@next/next/no-img-element": "off", - }, }; diff --git a/web/components/core/filters/issues-view-filter.tsx b/web/components/core/filters/issues-view-filter.tsx index 1266bd5a3..6354625dc 100644 --- a/web/components/core/filters/issues-view-filter.tsx +++ b/web/components/core/filters/issues-view-filter.tsx @@ -58,16 +58,8 @@ export const IssuesFilterView: React.FC = () => { const isArchivedIssues = router.pathname.includes("archived-issues"); const { - issueView, - setIssueView, - groupByProperty, - setGroupByProperty, - orderBy, - setOrderBy, - showEmptyGroups, - showSubIssues, - setShowSubIssues, - setShowEmptyGroups, + displayFilters, + setDisplayFilters, filters, setFilters, resetFilterToDefault, @@ -96,11 +88,11 @@ export const IssuesFilterView: React.FC = () => {
- {issueView !== "gantt_chart" && ( + {displayFilters.layout !== "gantt_chart" && (

Display Properties

@@ -310,7 +315,7 @@ export const IssuesFilterView: React.FC = () => { if (key === "estimate" && !isEstimateActive) return null; if ( - issueView === "spreadsheet" && + displayFilters.layout === "spreadsheet" && (key === "attachment_count" || key === "link" || key === "sub_issue_count") @@ -318,7 +323,7 @@ export const IssuesFilterView: React.FC = () => { return null; if ( - issueView !== "spreadsheet" && + displayFilters.layout !== "spreadsheet" && (key === "created_on" || key === "updated_on") ) return null; diff --git a/web/components/core/modals/bulk-delete-issues-modal.tsx b/web/components/core/modals/bulk-delete-issues-modal.tsx index 59e7c8a84..375e155a4 100644 --- a/web/components/core/modals/bulk-delete-issues-modal.tsx +++ b/web/components/core/modals/bulk-delete-issues-modal.tsx @@ -58,7 +58,7 @@ export const BulkDeleteIssuesModal: React.FC = ({ isOpen, setIsOpen, user ); const { setToastAlert } = useToast(); - const { issueView, params } = useIssuesView(); + const { displayFilters, params } = useIssuesView(); const { params: calendarParams } = useCalendarIssuesView(); const { order_by, group_by, ...viewGanttParams } = params; @@ -126,8 +126,8 @@ export const BulkDeleteIssuesModal: React.FC = ({ isOpen, setIsOpen, user message: "Issues deleted successfully!", }); - if (issueView === "calendar") mutate(calendarFetchKey); - else if (issueView === "gantt_chart") mutate(ganttFetchKey); + if (displayFilters.layout === "calendar") mutate(calendarFetchKey); + else if (displayFilters.layout === "gantt_chart") mutate(ganttFetchKey); else { if (cycleId) { mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), params)); diff --git a/web/components/core/views/all-views.tsx b/web/components/core/views/all-views.tsx index eb54ccb2a..3b95ed863 100644 --- a/web/components/core/views/all-views.tsx +++ b/web/components/core/views/all-views.tsx @@ -80,7 +80,7 @@ export const AllViews: React.FC = ({ const { user } = useUser(); const { memberRole } = useProjectMyMembership(); - const { groupedIssues, isEmpty, issueView } = viewProps; + const { groupedIssues, isEmpty, displayFilters } = viewProps; const { data: stateGroups } = useSWR( workspaceSlug && projectId ? STATES_LIST(projectId as string) : null, @@ -117,11 +117,11 @@ export const AllViews: React.FC = ({ {groupedIssues ? ( !isEmpty || - issueView === "kanban" || - issueView === "calendar" || - issueView === "gantt_chart" ? ( + displayFilters?.layout === "kanban" || + displayFilters?.layout === "calendar" || + displayFilters?.layout === "gantt_chart" ? ( <> - {issueView === "list" ? ( + {displayFilters?.layout === "list" ? ( = ({ userAuth={memberRole} viewProps={viewProps} /> - ) : issueView === "kanban" ? ( + ) : displayFilters?.layout === "kanban" ? ( = ({ userAuth={memberRole} viewProps={viewProps} /> - ) : issueView === "calendar" ? ( + ) : displayFilters?.layout === "calendar" ? ( = ({ user={user} userAuth={memberRole} /> - ) : issueView === "spreadsheet" ? ( + ) : displayFilters?.layout === "spreadsheet" ? ( = ({ userAuth={memberRole} /> ) : ( - issueView === "gantt_chart" && + displayFilters?.layout === "gantt_chart" && )} ) : router.pathname.includes("archived-issues") ? ( diff --git a/web/components/core/views/board-view/all-boards.tsx b/web/components/core/views/board-view/all-boards.tsx index ea0f64ace..0d5a4534e 100644 --- a/web/components/core/views/board-view/all-boards.tsx +++ b/web/components/core/views/board-view/all-boards.tsx @@ -36,7 +36,7 @@ export const AllBoards: React.FC = ({ userAuth, viewProps, }) => { - const { groupByProperty: selectedGroup, groupedIssues, showEmptyGroups } = viewProps; + const { displayFilters, groupedIssues } = viewProps; return ( <> @@ -44,9 +44,12 @@ export const AllBoards: React.FC = ({
{Object.keys(groupedIssues).map((singleGroup, index) => { const currentState = - selectedGroup === "state" ? states?.find((s) => s.id === singleGroup) : null; + displayFilters?.group_by === "state" + ? states?.find((s) => s.id === singleGroup) + : null; - if (!showEmptyGroups && groupedIssues[singleGroup].length === 0) return null; + if (!displayFilters?.show_empty_groups && groupedIssues[singleGroup].length === 0) + return null; return ( = ({ /> ); })} - {!showEmptyGroups && ( + {!displayFilters?.show_empty_groups && (

Hidden groups

{Object.keys(groupedIssues).map((singleGroup, index) => { const currentState = - selectedGroup === "state" ? states?.find((s) => s.id === singleGroup) : null; + displayFilters?.group_by === "state" + ? states?.find((s) => s.id === singleGroup) + : null; if (groupedIssues[singleGroup].length === 0) return ( @@ -91,7 +96,7 @@ export const AllBoards: React.FC = ({ /> )}

- {selectedGroup === "state" + {displayFilters?.group_by === "state" ? addSpaceIfCamelCase(currentState?.name ?? "") : addSpaceIfCamelCase(singleGroup)}

diff --git a/web/components/core/views/board-view/board-header.tsx b/web/components/core/views/board-view/board-header.tsx index 144bdb826..1cbfdc81a 100644 --- a/web/components/core/views/board-view/board-header.tsx +++ b/web/components/core/views/board-view/board-header.tsx @@ -48,22 +48,28 @@ export const BoardHeader: React.FC = ({ const router = useRouter(); const { workspaceSlug, projectId } = router.query; - const { groupedIssues, groupByProperty: selectedGroup } = viewProps; + const { displayFilters, groupedIssues } = viewProps; + + console.log("dF", displayFilters); const { data: issueLabels } = useSWR( - workspaceSlug && projectId && selectedGroup === "labels" + workspaceSlug && projectId && displayFilters?.group_by === "labels" ? PROJECT_ISSUE_LABELS(projectId.toString()) : null, - workspaceSlug && projectId && selectedGroup === "labels" + workspaceSlug && projectId && displayFilters?.group_by === "labels" ? () => issuesService.getIssueLabels(workspaceSlug.toString(), projectId.toString()) : null ); const { data: members } = useSWR( - workspaceSlug && projectId && (selectedGroup === "created_by" || selectedGroup === "assignees") + workspaceSlug && + projectId && + (displayFilters?.group_by === "created_by" || displayFilters?.group_by === "assignees") ? PROJECT_MEMBERS(projectId.toString()) : null, - workspaceSlug && projectId && (selectedGroup === "created_by" || selectedGroup === "assignees") + workspaceSlug && + projectId && + (displayFilters?.group_by === "created_by" || displayFilters?.group_by === "assignees") ? () => projectService.projectMembers(workspaceSlug.toString(), projectId.toString()) : null ); @@ -73,7 +79,7 @@ export const BoardHeader: React.FC = ({ const getGroupTitle = () => { let title = addSpaceIfCamelCase(groupTitle); - switch (selectedGroup) { + switch (displayFilters?.group_by) { case "state": title = addSpaceIfCamelCase(currentState?.name ?? ""); break; @@ -97,7 +103,7 @@ export const BoardHeader: React.FC = ({ const getGroupIcon = () => { let icon; - switch (selectedGroup) { + switch (displayFilters?.group_by) { case "state": icon = currentState && ( = ({ {getGroupIcon()}

= ({ )} - {!disableAddIssue && !disableUserActions && selectedGroup !== "created_by" && ( + {!disableAddIssue && !disableUserActions && displayFilters?.group_by !== "created_by" && (