From 1ce8e988830f0bce4810c8ad283805daffbb192c Mon Sep 17 00:00:00 2001 From: NarayanBavisetti Date: Tue, 12 Dec 2023 19:36:54 +0530 Subject: [PATCH 01/14] chore: instance script --- .../plane/app/management/commands/__init__.py | 0 .../app/management/commands/cloud_instance.py | 72 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 apiserver/plane/app/management/commands/__init__.py create mode 100644 apiserver/plane/app/management/commands/cloud_instance.py diff --git a/apiserver/plane/app/management/commands/__init__.py b/apiserver/plane/app/management/commands/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apiserver/plane/app/management/commands/cloud_instance.py b/apiserver/plane/app/management/commands/cloud_instance.py new file mode 100644 index 0000000000..e6a6bdc6c5 --- /dev/null +++ b/apiserver/plane/app/management/commands/cloud_instance.py @@ -0,0 +1,72 @@ +# Python imports +import json +import secrets + +# Django imports +from django.core.management.base import BaseCommand, CommandError +from django.utils import timezone + +# Module imports +from plane.license.models import Instance, InstanceAdmin +from plane.db.models import User + + +class Command(BaseCommand): + help = "Check if instance in registered else register" + + def add_arguments(self, parser): + # Positional argument + parser.add_argument("admin_email", type=str, help="Admin Email") + + def handle(self, *args, **options): + with open("package.json", "r") as file: + # Load JSON content from the file + data = json.load(file) + + admin_email = options.get("admin_email", False) + + if not admin_email: + raise CommandError("admin email is required") + + user_count = User.objects.filter(is_bot=False).count() + + user = User.objects.filter(email=admin_email).first() + if user is None: + raise CommandError("User not found") + + try: + # Check if the instance is registered + instance = Instance.objects.first() + + if instance is None: + instance = Instance.objects.create( + instance_name="Plane Free", + instance_id=secrets.token_hex(12), + license_key=None, + api_key=secrets.token_hex(8), + version=data.get("version"), + last_checked_at=timezone.now(), + user_count=user_count, + is_verified=True, + is_setup_done=True, + is_signup_screen_visited=True, + ) + + # Get or create an instance admin + _ , created = InstanceAdmin.objects.get_or_create( + user=user, instance=instance, role=20, is_verified=True + ) + + if not created: + raise CommandError("given email is already an instance admin") + + self.stdout.write( + self.style.SUCCESS( + f"Successful" + ) + ) + except Exception as e: + print(e) + raise CommandError( + "Failure" + ) \ No newline at end of file From b2762b44a0c34bee69f51cf74a1e6aeaf5517af7 Mon Sep 17 00:00:00 2001 From: NarayanBavisetti Date: Thu, 14 Dec 2023 17:11:44 +0530 Subject: [PATCH 02/14] chore: changed instance name --- apiserver/plane/app/management/commands/cloud_instance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/plane/app/management/commands/cloud_instance.py b/apiserver/plane/app/management/commands/cloud_instance.py index e6a6bdc6c5..8e08f597ca 100644 --- a/apiserver/plane/app/management/commands/cloud_instance.py +++ b/apiserver/plane/app/management/commands/cloud_instance.py @@ -40,7 +40,7 @@ class Command(BaseCommand): if instance is None: instance = Instance.objects.create( - instance_name="Plane Free", + instance_name="Plane Cloud US", instance_id=secrets.token_hex(12), license_key=None, api_key=secrets.token_hex(8), From 3d565d1c4199276376faafe0fdb57124f9726271 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Thu, 4 Jan 2024 14:46:57 +0530 Subject: [PATCH 03/14] feat: pro plan changes (#65) * feat: adding pro plan pricing modal * fix: pricing changes * fix: env updates * fix: adding payment links --- turbo.json | 17 +- web/components/license/index.ts | 1 + web/components/license/pro-plan-modal.tsx | 193 ++++++++++++++++++++++ web/components/workspace/help-section.tsx | 18 +- web/store/event-tracker.store.ts | 9 + 5 files changed, 225 insertions(+), 13 deletions(-) create mode 100644 web/components/license/index.ts create mode 100644 web/components/license/pro-plan-modal.tsx diff --git a/turbo.json b/turbo.json index 48f0c04223..936b6c398d 100644 --- a/turbo.json +++ b/turbo.json @@ -17,17 +17,14 @@ "NEXT_PUBLIC_POSTHOG_KEY", "NEXT_PUBLIC_POSTHOG_HOST", "JITSU_TRACKER_ACCESS_KEY", - "JITSU_TRACKER_HOST" + "JITSU_TRACKER_HOST", + "PRO_PLAN_MONTHLY_REDIRECT_URL", + "PRO_PLAN_YEARLY_REDIRECT_URL" ], "pipeline": { "build": { - "dependsOn": [ - "^build" - ], - "outputs": [ - ".next/**", - "dist/**" - ] + "dependsOn": ["^build"], + "outputs": [".next/**", "dist/**"] }, "web#develop": { "cache": false, @@ -89,9 +86,7 @@ ] }, "test": { - "dependsOn": [ - "^build" - ], + "dependsOn": ["^build"], "outputs": [] }, "lint": { diff --git a/web/components/license/index.ts b/web/components/license/index.ts new file mode 100644 index 0000000000..ddc06de25f --- /dev/null +++ b/web/components/license/index.ts @@ -0,0 +1 @@ +export * from "./pro-plan-modal"; diff --git a/web/components/license/pro-plan-modal.tsx b/web/components/license/pro-plan-modal.tsx new file mode 100644 index 0000000000..b75eaddc15 --- /dev/null +++ b/web/components/license/pro-plan-modal.tsx @@ -0,0 +1,193 @@ +import { FC, Fragment, useState } from "react"; +import { Dialog, Transition, Tab } from "@headlessui/react"; +import { CheckCircle } from "lucide-react"; +import { useMobxStore } from "lib/mobx/store-provider"; + +function classNames(...classes: any[]) { + return classes.filter(Boolean).join(" "); +} + +const PRICING_CATEGORIES = ["Monthly", "Yearly"]; + +const MONTHLY_PLAN_ITEMS = [ + "White-glove onboarding for your use-cases", + "Bespoke implementation", + "Priority integrations", + "Priority Support and SLAs", + "Early access to all paid features", + "Locked-in discount for a whole year", +]; + +const YEARLY_PLAN_ITEMS = [ + "White-glove onboarding for your use-cases", + "Bespoke implementation", + "Priority integrations", + "Priority Support and SLAs", + "Early access to all paid features", + "Tiered discounts for the second and third years", +]; + +export type ProPlanModalProps = { + isOpen: boolean; + handleClose: () => void; +}; + +export const ProPlanModal: FC = (props) => { + const { isOpen, handleClose } = props; + // store + const { + trackEvent: { captureEvent }, + } = useMobxStore(); + // states + const [tabIndex, setTabIndex] = useState(0); + + const handleProPlaneMonthRedirection = () => { + if (process.env.PRO_PLAN_MONTHLY_REDIRECT_URL) { + window.open(process.env.PRO_PLAN_MONTHLY_REDIRECT_URL, "_blank"); + captureEvent("pro_plan_modal_month_redirection"); + } + }; + + const handleProPlanYearlyRedirection = () => { + if (process.env.PRO_PLAN_YEARLY_REDIRECT_URL) { + window.open(process.env.PRO_PLAN_YEARLY_REDIRECT_URL, "_blank"); + captureEvent("pro_plan_modal_yearly_redirection"); + } + }; + + return ( + + + +
+ + +
+
+ + + + Early-adopter pricing for believers + +
+

+ Build Plane to your specs. You decide what we prioritize and build for everyone. Also get tailored + onboarding + implementation and priority support. +

+ +
+ + {PRICING_CATEGORIES.map((category, index) => ( + + classNames( + "w-full rounded-lg py-2 text-sm font-medium leading-5 text-slate-800", + "ring-white/60 ring-offset-2 ring-offset-blue-400 focus:outline-none", + selected + ? "bg-white text-blue-700 shadow" + : "text-blue-100 hover:bg-white/[0.12] hover:text-slate-600" + ) + } + onClick={() => setTabIndex(index)} + > + <> + {category} + {category === "Yearly" && ( + + -28% + + )} + + + ))} + +
+ + + +

+ $7 + /user/month +

+
    + {MONTHLY_PLAN_ITEMS.map((item) => ( +
  • +

    + + {item} +

    +
  • + ))} +
+
+
+
+ +
+
+ + +

+ $5 + /user/month +

+
    + {YEARLY_PLAN_ITEMS.map((item) => ( +
  • +

    + + {item} +

    +
  • + ))} +
+
+
+
+ +
+
+ + + +
+ + +
+
+
+
+ ); +}; diff --git a/web/components/workspace/help-section.tsx b/web/components/workspace/help-section.tsx index 23663afcf1..37789dbb17 100644 --- a/web/components/workspace/help-section.tsx +++ b/web/components/workspace/help-section.tsx @@ -11,6 +11,8 @@ import { FileText, HelpCircle, MessagesSquare, MoveLeft, Zap } from "lucide-reac import { DiscordIcon, GithubIcon } from "@plane/ui"; // assets import packageJson from "package.json"; +// components +import { ProPlanModal } from "components/license"; const helpOptions = [ { @@ -41,10 +43,13 @@ export interface WorkspaceHelpSectionProps { } export const WorkspaceHelpSection: React.FC = observer(() => { + // states + const [isProPlanModalOpen, setIsProPlanModalOpen] = useState(false); // store const { theme: { sidebarCollapsed, toggleSidebar }, commandPalette: { toggleShortcutModal }, + trackEvent: { captureEvent }, } = useMobxStore(); // states const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false); @@ -55,16 +60,25 @@ export const WorkspaceHelpSection: React.FC = observe const isCollapsed = sidebarCollapsed || false; + const handleProPlanModalOpen = () => { + setIsProPlanModalOpen(true); + captureEvent("pro_plan_modal_opened"); + }; + return ( <> + setIsProPlanModalOpen(false)} />
{!isCollapsed && ( -
- Free Plan +
+ Plan Pro
)}
diff --git a/web/store/event-tracker.store.ts b/web/store/event-tracker.store.ts index ff507177f9..0beaa2fc08 100644 --- a/web/store/event-tracker.store.ts +++ b/web/store/event-tracker.store.ts @@ -10,6 +10,7 @@ export interface ITrackEventStore { payload: object | [] | null, group?: { isGrouping: boolean | null; groupType: string | null; gorupId: string | null } | null ) => void; + captureEvent: (eventName: string, payload?: any) => void; } export class TrackEventStore implements ITrackEventStore { @@ -74,4 +75,12 @@ export class TrackEventStore implements ITrackEventStore { } this.setTrackElement(""); }; + + captureEvent = (eventName: string, payload?: any) => { + try { + posthog?.capture(eventName, payload); + } catch (error) { + console.log(error); + } + }; } From aa70661b2df4a5c7afce3c563ef9e524676bdecf Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Thu, 4 Jan 2024 14:51:01 +0530 Subject: [PATCH 04/14] fix: url changes (#66) --- turbo.json | 4 ++-- web/components/license/pro-plan-modal.tsx | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/turbo.json b/turbo.json index 936b6c398d..8178a65491 100644 --- a/turbo.json +++ b/turbo.json @@ -18,8 +18,8 @@ "NEXT_PUBLIC_POSTHOG_HOST", "JITSU_TRACKER_ACCESS_KEY", "JITSU_TRACKER_HOST", - "PRO_PLAN_MONTHLY_REDIRECT_URL", - "PRO_PLAN_YEARLY_REDIRECT_URL" + "NEXT_PUBLIC_PRO_PLAN_MONTHLY_REDIRECT_URL", + "NEXT_PUBLIC_PRO_PLAN_YEARLY_REDIRECT_URL" ], "pipeline": { "build": { diff --git a/web/components/license/pro-plan-modal.tsx b/web/components/license/pro-plan-modal.tsx index b75eaddc15..ca57182230 100644 --- a/web/components/license/pro-plan-modal.tsx +++ b/web/components/license/pro-plan-modal.tsx @@ -42,15 +42,15 @@ export const ProPlanModal: FC = (props) => { const [tabIndex, setTabIndex] = useState(0); const handleProPlaneMonthRedirection = () => { - if (process.env.PRO_PLAN_MONTHLY_REDIRECT_URL) { - window.open(process.env.PRO_PLAN_MONTHLY_REDIRECT_URL, "_blank"); + if (process.env.NEXT_PUBLIC_PRO_PLAN_MONTHLY_REDIRECT_URL) { + window.open(process.env.NEXT_PUBLIC_PRO_PLAN_MONTHLY_REDIRECT_URL, "_blank"); captureEvent("pro_plan_modal_month_redirection"); } }; const handleProPlanYearlyRedirection = () => { - if (process.env.PRO_PLAN_YEARLY_REDIRECT_URL) { - window.open(process.env.PRO_PLAN_YEARLY_REDIRECT_URL, "_blank"); + if (process.env.NEXT_PUBLIC_PRO_PLAN_YEARLY_REDIRECT_URL) { + window.open(process.env.NEXT_PUBLIC_PRO_PLAN_YEARLY_REDIRECT_URL, "_blank"); captureEvent("pro_plan_modal_yearly_redirection"); } }; From 1a571961e7fb4dae394429c68cd97291df4d74f6 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Mon, 8 Jan 2024 17:23:28 +0530 Subject: [PATCH 05/14] fix: pro plan button styles (#67) --- web/components/workspace/help-section.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/web/components/workspace/help-section.tsx b/web/components/workspace/help-section.tsx index 37789dbb17..d57f1946f6 100644 --- a/web/components/workspace/help-section.tsx +++ b/web/components/workspace/help-section.tsx @@ -8,7 +8,7 @@ import { useMobxStore } from "lib/mobx/store-provider"; import useOutsideClickDetector from "hooks/use-outside-click-detector"; // icons import { FileText, HelpCircle, MessagesSquare, MoveLeft, Zap } from "lucide-react"; -import { DiscordIcon, GithubIcon } from "@plane/ui"; +import { DiscordIcon, GithubIcon, Button } from "@plane/ui"; // assets import packageJson from "package.json"; // components @@ -74,12 +74,13 @@ export const WorkspaceHelpSection: React.FC = observe }`} > {!isCollapsed && ( -
- Plan Pro -
+ Plane Pro + )}
- +

$5 - /user/month + /user/month

    {YEARLY_PLAN_ITEMS.map((item) => (
  • - + {item}

  • @@ -172,7 +169,7 @@ export const ProPlanModal: FC = (props) => {
    + +
    +
+
+
+
+

Progress

+ + {`${cycle.completed_issues + cycle.cancelled_issues}/${cycle.total_issues - cycle.cancelled_issues} ${ + cycle.completed_issues + cycle.cancelled_issues > 1 ? "Issues" : "Issue" + } closed`} + +
+ +
+
+ {Object.keys(groupedIssues).map((group, index) => ( + <> + {groupedIssues[group] > 0 && ( +
+
+ + {group} +
+ {`: ${groupedIssues[group]} ${groupedIssues[group] > 1 ? "Issues" : "Issue"}`} +
+ )} + + ))} + {cycle.cancelled_issues > 0 && ( + + + {`${cycle.cancelled_issues} cancelled ${ + cycle.cancelled_issues > 1 ? "issues are" : "issue is" + } excluded from this report.`}{" "} + + + )} +
+
+
+ +
+
+

Issue Burndown

+
+ +
+ +
+
+
+
+

Priority

+
+
+ {cycleIssues ? ( + cycleIssues.length > 0 ? ( + cycleIssues.map((issue: any) => ( + +
+ + + + {cycle.project_detail?.identifier}-{issue.sequence_id} + + + + {issue.name} + +
+
+ {}} + projectId={projectId?.toString() ?? ""} + disabled={true} + buttonVariant="background-with-text" + /> + {issue.target_date && ( + +
+ + {renderFormattedDateWithoutYear(issue.target_date)} +
+
+ )} +
+ + )) + ) : ( +
+ There are no high priority issues present in this cycle. +
+ ) + ) : ( + + + + + + )} +
+
+
+
+ + ); +}; diff --git a/web/components/cycles/index.ts b/web/components/cycles/index.ts index db5e9de9ee..e95b329720 100644 --- a/web/components/cycles/index.ts +++ b/web/components/cycles/index.ts @@ -1,5 +1,6 @@ export * from "./cycles-view"; export * from "./active-cycle-details"; +export * from "./active-cycle-info"; export * from "./active-cycle-stats"; export * from "./gantt-chart"; export * from "./cycles-view"; diff --git a/web/components/headers/index.ts b/web/components/headers/index.ts index 298658a2a9..46ce8c066d 100644 --- a/web/components/headers/index.ts +++ b/web/components/headers/index.ts @@ -20,3 +20,4 @@ export * from "./project-archived-issue-details"; export * from "./project-archived-issues"; export * from "./project-issue-details"; export * from "./user-profile"; +export * from "./workspace-active-cycle"; diff --git a/web/components/headers/workspace-active-cycle.tsx b/web/components/headers/workspace-active-cycle.tsx new file mode 100644 index 0000000000..79e6bcaf3c --- /dev/null +++ b/web/components/headers/workspace-active-cycle.tsx @@ -0,0 +1,23 @@ +import { observer } from "mobx-react-lite"; +import { SendToBack } from "lucide-react"; +// ui +import { Breadcrumbs } from "@plane/ui"; + +export const WorkspaceActiveCycleHeader = observer(() => ( +
+
+
+ + } + label="Active Cycles" + /> + + + Beta + +
+
+
+)); diff --git a/web/components/workspace/index.ts b/web/components/workspace/index.ts index 9787487e1b..909bc1cfac 100644 --- a/web/components/workspace/index.ts +++ b/web/components/workspace/index.ts @@ -8,3 +8,4 @@ export * from "./send-workspace-invitation-modal"; export * from "./sidebar-dropdown"; export * from "./sidebar-menu"; export * from "./sidebar-quick-action"; +export * from "./workspace-active-cycles-list"; diff --git a/web/components/workspace/sidebar-menu.tsx b/web/components/workspace/sidebar-menu.tsx index 6403189809..fff7696200 100644 --- a/web/components/workspace/sidebar-menu.tsx +++ b/web/components/workspace/sidebar-menu.tsx @@ -46,6 +46,11 @@ export const WorkspaceSidebarMenu = observer(() => { > {} {!themeStore?.sidebarCollapsed && link.label} + {!themeStore?.sidebarCollapsed && link.key === "active-cycles" && ( + + Beta + + )} diff --git a/web/components/workspace/workspace-active-cycles-list.tsx b/web/components/workspace/workspace-active-cycles-list.tsx new file mode 100644 index 0000000000..0dc28065e5 --- /dev/null +++ b/web/components/workspace/workspace-active-cycles-list.tsx @@ -0,0 +1,91 @@ +import { useEffect, useState } from "react"; +import useSWR from "swr"; +import { observer } from "mobx-react-lite"; +import { useRouter } from "next/router"; +import isEqual from "lodash/isEqual"; +// components +import { ActiveCycleInfo } from "components/cycles"; +import { Button, ContrastIcon, Spinner } from "@plane/ui"; +// services +import { CycleService } from "services/cycle.service"; +const cycleService = new CycleService(); +// constants +import { WORKSPACE_ACTIVE_CYCLES_LIST } from "constants/fetch-keys"; +// types +import { ICycle } from "@plane/types"; + +const per_page = 3; + +export const WorkspaceActiveCyclesList = observer(() => { + // state + const [cursor, setCursor] = useState(`3:0:0`); + const [allCyclesData, setAllCyclesData] = useState([]); + const [hasMoreResults, setHasMoreResults] = useState(true); + // router + const router = useRouter(); + const { workspaceSlug } = router.query; + + // fetching active cycles in workspace + const { data: workspaceActiveCycles, isLoading } = useSWR( + workspaceSlug && cursor ? WORKSPACE_ACTIVE_CYCLES_LIST(workspaceSlug as string, cursor, `${per_page}`) : null, + workspaceSlug && cursor + ? () => cycleService.workspaceActiveCycles(workspaceSlug.toString(), cursor, per_page) + : null + ); + + useEffect(() => { + if (workspaceActiveCycles && !isEqual(workspaceActiveCycles.results, allCyclesData)) { + setAllCyclesData((prevData) => [...prevData, ...workspaceActiveCycles.results]); + setHasMoreResults(workspaceActiveCycles.next_page_results); + } + }, [workspaceActiveCycles]); + + const handleLoadMore = () => { + if (hasMoreResults) { + setCursor(workspaceActiveCycles?.next_cursor); + } + }; + + if (allCyclesData.length === 0 && !workspaceActiveCycles) { + return ( +
+ +
+ ); + } + + return ( +
+ {allCyclesData.length > 0 ? ( + <> + {workspaceSlug && + allCyclesData.map((cycle) => ( +
+ +
+ ))} + + {hasMoreResults && ( +
+ +
+ )} + + ) : ( +
+
+
+ +
+

+ Cycles running across all your projects can be seen here. Use this to track how the org is delivering + value across teams +

+
+
+ )} +
+ ); +}); diff --git a/web/constants/fetch-keys.ts b/web/constants/fetch-keys.ts index ec88c8c877..719d889f41 100644 --- a/web/constants/fetch-keys.ts +++ b/web/constants/fetch-keys.ts @@ -142,6 +142,8 @@ export const WORKSPACE_LABELS = (workspaceSlug: string) => `WORKSPACE_LABELS_${w export const PROJECT_GITHUB_REPOSITORY = (projectId: string) => `PROJECT_GITHUB_REPOSITORY_${projectId.toUpperCase()}`; // cycles +export const WORKSPACE_ACTIVE_CYCLES_LIST = (workspaceSlug: string, cursor: string, per_page: string) => + `WORKSPACE_ACTIVE_CYCLES_LIST_${workspaceSlug.toUpperCase()}_${cursor.toUpperCase()}_${per_page.toUpperCase()}`; export const CYCLES_LIST = (projectId: string) => `CYCLE_LIST_${projectId.toUpperCase()}`; export const INCOMPLETE_CYCLES_LIST = (projectId: string) => `INCOMPLETE_CYCLES_LIST_${projectId.toUpperCase()}`; export const CURRENT_CYCLE_LIST = (projectId: string) => `CURRENT_CYCLE_LIST_${projectId.toUpperCase()}`; diff --git a/web/pages/[workspaceSlug]/active-cycles.tsx b/web/pages/[workspaceSlug]/active-cycles.tsx new file mode 100644 index 0000000000..61d57e2e69 --- /dev/null +++ b/web/pages/[workspaceSlug]/active-cycles.tsx @@ -0,0 +1,16 @@ +import { ReactElement } from "react"; +// components +import { WorkspaceActiveCyclesList } from "components/workspace"; +import { WorkspaceActiveCycleHeader } from "components/headers"; +// layouts +import { AppLayout } from "layouts/app-layout"; +// types +import { NextPageWithLayout } from "lib/types"; + +const WorkspaceActiveCyclesPage: NextPageWithLayout = () => ; + +WorkspaceActiveCyclesPage.getLayout = function getLayout(page: ReactElement) { + return }>{page}; +}; + +export default WorkspaceActiveCyclesPage; diff --git a/web/services/cycle.service.ts b/web/services/cycle.service.ts index 6b6d17231c..f624aa2710 100644 --- a/web/services/cycle.service.ts +++ b/web/services/cycle.service.ts @@ -1,7 +1,7 @@ // services import { APIService } from "services/api.service"; // types -import type { CycleDateCheckData, ICycle, TIssue, TIssueMap } from "@plane/types"; +import type { CycleDateCheckData, ICycle, IWorkspaceActiveCyclesResponse, TIssue } from "@plane/types"; // helpers import { API_BASE_URL } from "helpers/common.helper"; @@ -10,6 +10,23 @@ export class CycleService extends APIService { super(API_BASE_URL); } + async workspaceActiveCycles( + workspaceSlug: string, + cursor: string, + per_page: number + ): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/active-cycles/`, { + params: { + per_page, + cursor, + }, + }) + .then((res) => res?.data) + .catch((err) => { + throw err?.response?.data; + }); + } + async createCycle(workspaceSlug: string, projectId: string, data: any): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/`, data) .then((response) => response?.data) From 1a763491b6af2ad9f37603ac0a449a6a51edbece Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Tue, 23 Jan 2024 18:44:06 +0530 Subject: [PATCH 11/14] fix: build issues --- web/components/cycles/active-cycle-info.tsx | 6 +++--- web/components/license/pro-plan-modal.tsx | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/web/components/cycles/active-cycle-info.tsx b/web/components/cycles/active-cycle-info.tsx index 537f62e1ed..5b58bd8c1f 100644 --- a/web/components/cycles/active-cycle-info.tsx +++ b/web/components/cycles/active-cycle-info.tsx @@ -15,7 +15,7 @@ import { renderFormattedDate, findHowManyDaysLeft, renderFormattedDateWithoutYea import { truncateText } from "helpers/string.helper"; import { renderEmoji } from "helpers/emoji.helper"; // constants -import { STATE_GROUPS_DETAILS } from "constants/cycle"; +import { CYCLE_STATE_GROUPS_DETAILS } from "constants/cycle"; export type ActiveCycleInfoProps = { cycle: ICycle; @@ -53,7 +53,7 @@ export const ActiveCycleInfo: FC = (props) => { backlog: cycle.backlog_issues, }; - const progressIndicatorData = STATE_GROUPS_DETAILS.map((group, index) => ({ + const progressIndicatorData = CYCLE_STATE_GROUPS_DETAILS.map((group, index) => ({ id: index, name: group.title, value: cycle.total_issues > 0 ? (cycle[group.key as keyof ICycle] as number) : 0, @@ -156,7 +156,7 @@ export const ActiveCycleInfo: FC = (props) => { {group} diff --git a/web/components/license/pro-plan-modal.tsx b/web/components/license/pro-plan-modal.tsx index d484981828..f67fc825ea 100644 --- a/web/components/license/pro-plan-modal.tsx +++ b/web/components/license/pro-plan-modal.tsx @@ -1,7 +1,7 @@ import { FC, Fragment, useState } from "react"; import { Dialog, Transition, Tab } from "@headlessui/react"; import { CheckCircle } from "lucide-react"; -import { useMobxStore } from "lib/mobx/store-provider"; +import { useApplication } from "hooks/store"; function classNames(...classes: any[]) { return classes.filter(Boolean).join(" "); @@ -36,9 +36,10 @@ export const ProPlanModal: FC = (props) => { const { isOpen, handleClose } = props; // store const { - trackEvent: { captureEvent }, - } = useMobxStore(); + eventTracker: { captureEvent }, + } = useApplication(); // states + // eslint-disable-next-line @typescript-eslint/no-unused-vars const [tabIndex, setTabIndex] = useState(0); const handleProPlaneMonthRedirection = () => { From 45425c741178917ca3989f3fed15e1c42fc47e07 Mon Sep 17 00:00:00 2001 From: Nikhil <118773738+pablohashescobar@users.noreply.github.com> Date: Tue, 23 Jan 2024 19:15:27 +0530 Subject: [PATCH 12/14] dev: enterprise branch build workflows (#86) --- .github/workflows/build-branch-ee.yml | 220 ++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 .github/workflows/build-branch-ee.yml diff --git a/.github/workflows/build-branch-ee.yml b/.github/workflows/build-branch-ee.yml new file mode 100644 index 0000000000..c6098ef633 --- /dev/null +++ b/.github/workflows/build-branch-ee.yml @@ -0,0 +1,220 @@ +name: Branch Build Enterprise + +on: + workflow_dispatch: + inputs: + branch_name: + description: "Branch Name" + required: true + default: "develop" + push: + branches: + - master + - preview + - develop + release: + types: [released, prereleased] + +env: + TARGET_BRANCH: ${{ inputs.branch_name || github.ref_name || github.event.release.target_commitish }} + +jobs: + branch_build_setup: + name: Build-Push Web/Space/API/Proxy Docker Image + runs-on: ubuntu-20.04 + steps: + - name: Check out the repo + uses: actions/checkout@v3.3.0 + outputs: + gh_branch_name: ${{ env.TARGET_BRANCH }} + + branch_build_push_frontend: + runs-on: ubuntu-20.04 + needs: [branch_build_setup] + env: + FRONTEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend-ee:${{ needs.branch_build_setup.outputs.gh_branch_name }} + steps: + - name: Set Frontend Docker Tag + run: | + if [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ] && [ "${{ github.event_name }}" == "release" ]; then + TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend-ee:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend-ee:${{ github.event.release.tag_name }} + elif [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ]; then + TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-frontend-ee:stable + else + TAG=${{ env.FRONTEND_TAG }} + fi + echo "FRONTEND_TAG=${TAG}" >> $GITHUB_ENV + - name: Docker Setup QEMU + uses: docker/setup-qemu-action@v3.0.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3.0.0 + with: + platforms: linux/amd64,linux/arm64 + buildkitd-flags: "--allow-insecure-entitlement security.insecure" + + - name: Login to Docker Hub + uses: docker/login-action@v3.0.0 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Check out the repo + uses: actions/checkout@v4.1.1 + + - name: Build and Push Frontend to Docker Container Registry + uses: docker/build-push-action@v5.1.0 + with: + context: . + file: ./web/Dockerfile.web + platforms: linux/amd64 + tags: ${{ env.FRONTEND_TAG }} + push: true + env: + DOCKER_BUILDKIT: 1 + DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }} + + branch_build_push_space: + runs-on: ubuntu-20.04 + needs: [branch_build_setup] + env: + SPACE_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-space-ee:${{ needs.branch_build_setup.outputs.gh_branch_name }} + steps: + - name: Set Space Docker Tag + run: | + if [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ] && [ "${{ github.event_name }}" == "release" ]; then + TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space-ee:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-space-ee:${{ github.event.release.tag_name }} + elif [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ]; then + TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-space-ee:stable + else + TAG=${{ env.SPACE_TAG }} + fi + echo "SPACE_TAG=${TAG}" >> $GITHUB_ENV + + - name: Docker Setup QEMU + uses: docker/setup-qemu-action@v3.0.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3.0.0 + with: + platforms: linux/amd64,linux/arm64 + buildkitd-flags: "--allow-insecure-entitlement security.insecure" + + - name: Login to Docker Hub + uses: docker/login-action@v3.0.0 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Check out the repo + uses: actions/checkout@v4.1.1 + + - name: Build and Push Space to Docker Hub + uses: docker/build-push-action@v5.1.0 + with: + context: . + file: ./space/Dockerfile.space + platforms: linux/amd64 + tags: ${{ env.SPACE_TAG }} + push: true + env: + DOCKER_BUILDKIT: 1 + DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }} + + branch_build_push_backend: + runs-on: ubuntu-20.04 + needs: [branch_build_setup] + env: + BACKEND_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-ee:${{ needs.branch_build_setup.outputs.gh_branch_name }} + steps: + - name: Set Backend Docker Tag + run: | + if [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ] && [ "${{ github.event_name }}" == "release" ]; then + TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-ee:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-ee:${{ github.event.release.tag_name }} + elif [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ]; then + TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-backend-ee:stable + else + TAG=${{ env.BACKEND_TAG }} + fi + echo "BACKEND_TAG=${TAG}" >> $GITHUB_ENV + + - name: Docker Setup QEMU + uses: docker/setup-qemu-action@v3.0.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3.0.0 + with: + platforms: linux/amd64,linux/arm64 + buildkitd-flags: "--allow-insecure-entitlement security.insecure" + + - name: Login to Docker Hub + uses: docker/login-action@v3.0.0 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Check out the repo + uses: actions/checkout@v4.1.1 + + - name: Build and Push Backend to Docker Hub + uses: docker/build-push-action@v5.1.0 + with: + context: ./apiserver + file: ./apiserver/Dockerfile.api + platforms: linux/amd64 + push: true + tags: ${{ env.BACKEND_TAG }} + env: + DOCKER_BUILDKIT: 1 + DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }} + + branch_build_push_proxy: + runs-on: ubuntu-20.04 + needs: [branch_build_setup] + env: + PROXY_TAG: ${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy-ee:${{ needs.branch_build_setup.outputs.gh_branch_name }} + steps: + - name: Set Proxy Docker Tag + run: | + if [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ] && [ "${{ github.event_name }}" == "release" ]; then + TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy-ee:latest,${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy-ee:${{ github.event.release.tag_name }} + elif [ "${{ needs.branch_build_setup.outputs.gh_branch_name }}" == "master" ]; then + TAG=${{ secrets.DOCKERHUB_USERNAME }}/plane-proxy-ee:stable + else + TAG=${{ env.PROXY_TAG }} + fi + echo "PROXY_TAG=${TAG}" >> $GITHUB_ENV + + - name: Docker Setup QEMU + uses: docker/setup-qemu-action@v3.0.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3.0.0 + with: + platforms: linux/amd64,linux/arm64 + buildkitd-flags: "--allow-insecure-entitlement security.insecure" + + - name: Login to Docker Hub + uses: docker/login-action@v3.0.0 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Check out the repo + uses: actions/checkout@v4.1.1 + + - name: Build and Push Plane-Proxy to Docker Hub + uses: docker/build-push-action@v5.1.0 + with: + context: ./nginx + file: ./nginx/Dockerfile + platforms: linux/amd64 + tags: ${{ env.PROXY_TAG }} + push: true + env: + DOCKER_BUILDKIT: 1 + DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }} From f43e3e3009cd18753d2984b91dea054e1dad7dd4 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Wed, 24 Jan 2024 00:03:01 +0530 Subject: [PATCH 13/14] fix: issue embeds (#88) * fix: issue suggestions not properly rendering embeds * feat: added useIssueEmbed hook in commented form, as it would be removed in develop * feat: added selected states to issue embeds --------- Co-authored-by: Henit Chobisa --- .../issue-suggestion-renderer.tsx | 34 +++++++++++++------ .../editor/document-editor/src/ui/index.tsx | 4 +-- .../document-editor/src/ui/readonly/index.tsx | 4 +-- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/issue-suggestion-renderer.tsx b/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/issue-suggestion-renderer.tsx index 637afe29c2..21360f1232 100644 --- a/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/issue-suggestion-renderer.tsx +++ b/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/issue-suggestion-renderer.tsx @@ -78,7 +78,6 @@ const IssueSuggestionList = ({ const navigationKeys = ["ArrowUp", "ArrowDown", "Enter", "Tab"]; const onKeyDown = (e: KeyboardEvent) => { if (navigationKeys.includes(e.key)) { - e.preventDefault(); // if (editor.isFocused) { // editor.chain().blur(); // commandListContainer.current?.focus(); @@ -107,7 +106,6 @@ const IssueSuggestionList = ({ } if (e.key === "Enter") { selectItem(currentSection, selectedIndex); - e.stopPropagation(); return true; } if (e.key === "Tab") { @@ -150,7 +148,7 @@ const IssueSuggestionList = ({
{sections.map((section) => { const sectionItems = displayedItems[section]; @@ -193,29 +191,35 @@ const IssueSuggestionList = ({
) : null; }; - export const IssueListRenderer = () => { let component: ReactRenderer | null = null; let popup: any | null = null; return { onStart: (props: { editor: Editor; clientRect?: (() => DOMRect | null) | null }) => { + const container = document.querySelector(".frame-renderer") as HTMLElement; component = new ReactRenderer(IssueSuggestionList, { props, // @ts-ignore editor: props.editor, }); - // @ts-ignore - popup = tippy("body", { + popup = tippy(".frame-renderer", { + flipbehavior: ["bottom", "top"], + appendTo: () => document.querySelector(".frame-renderer") as HTMLElement, + flip: true, + flipOnUpdate: true, getReferenceClientRect: props.clientRect, - appendTo: () => document.querySelector("#editor-container"), content: component.element, showOnCreate: true, interactive: true, trigger: "manual", placement: "bottom-start", }); + + container.addEventListener("scroll", () => { + popup?.[0].destroy(); + }); }, onUpdate: (props: { editor: Editor; clientRect?: (() => DOMRect | null) | null }) => { component?.updateProps(props); @@ -230,10 +234,20 @@ export const IssueListRenderer = () => { popup?.[0].hide(); return true; } - // @ts-ignore - return component?.ref?.onKeyDown(props); + + const navigationKeys = ["ArrowUp", "ArrowDown", "Enter", "Tab"]; + if (navigationKeys.includes(props.event.key)) { + // @ts-ignore + component?.ref?.onKeyDown(props); + return true; + } + return false; }, - onExit: (e) => { + onExit: () => { + const container = document.querySelector(".frame-renderer") as HTMLElement; + if (container) { + container.removeEventListener("scroll", () => {}); + } popup?.[0].destroy(); setTimeout(() => { component?.destroy(); diff --git a/packages/editor/document-editor/src/ui/index.tsx b/packages/editor/document-editor/src/ui/index.tsx index e88632c3b4..6b962557d8 100644 --- a/packages/editor/document-editor/src/ui/index.tsx +++ b/packages/editor/document-editor/src/ui/index.tsx @@ -158,11 +158,11 @@ const DocumentEditor = ({ documentDetails={documentDetails} isSubmitting={isSubmitting} /> -
+
-
+
-
+
-
+
Promise.resolve()} From 9c046a893e1151649dc6f11a72be211131111498 Mon Sep 17 00:00:00 2001 From: Henit Chobisa Date: Wed, 24 Jan 2024 20:37:34 +0530 Subject: [PATCH 14/14] fix: sync post fixes in issue suggestions (#90) --- .../document-editor/src/ui/extensions/index.tsx | 2 +- .../issue-suggestion-renderer.tsx | 8 +++++++- .../issue-embed-widget/issue-widget-card.tsx | 16 +++++++--------- .../widgets/issue-embed-widget/types.ts | 2 +- web/hooks/use-issue-embeds.tsx | 7 ++++--- .../projects/[projectId]/pages/[pageId].tsx | 10 +++------- 6 files changed, 23 insertions(+), 22 deletions(-) diff --git a/packages/editor/document-editor/src/ui/extensions/index.tsx b/packages/editor/document-editor/src/ui/extensions/index.tsx index 7de1e29223..25db0b1f28 100644 --- a/packages/editor/document-editor/src/ui/extensions/index.tsx +++ b/packages/editor/document-editor/src/ui/extensions/index.tsx @@ -27,7 +27,7 @@ export const DocumentEditorExtensions = ( .focus() .insertContentAt( range, - "

#issue_

\n" + "

#issue_

" ) .run(); }, diff --git a/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/issue-suggestion-renderer.tsx b/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/issue-suggestion-renderer.tsx index 21360f1232..bccc0e9e17 100644 --- a/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/issue-suggestion-renderer.tsx +++ b/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/issue-suggestion-renderer.tsx @@ -55,11 +55,17 @@ const IssueSuggestionList = ({ useEffect(() => { const newDisplayedItems: { [key: string]: IssueSuggestionProps[] } = {}; let totalLength = 0; + let selectedSection = "Backlog"; + let selectedCurrentSection = false; sections.forEach((section) => { newDisplayedItems[section] = items.filter((item) => item.state === section).slice(0, 5); - + if (newDisplayedItems[section].length > 0 && selectedCurrentSection === false) { + selectedSection = section; + selectedCurrentSection = true; + } totalLength += newDisplayedItems[section].length; }); + setCurrentSection(selectedSection); setDisplayedTotalLength(totalLength); setDisplayedItems(newDisplayedItems); }, [items]); diff --git a/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-widget/issue-widget-card.tsx b/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-widget/issue-widget-card.tsx index caca2ded7b..b1e5e64a74 100644 --- a/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-widget/issue-widget-card.tsx +++ b/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-widget/issue-widget-card.tsx @@ -9,15 +9,13 @@ export const IssueWidgetCard = (props) => { const [issueDetails, setIssueDetails] = useState(); useEffect(() => { - props.issueEmbedConfig - .fetchIssue(props.node.attrs.entity_identifier) - .then((issue) => { - setIssueDetails(issue); - setLoading(0); - }) - .catch(() => { - setLoading(-1); - }); + const issue = props.issueEmbedConfig.fetchIssue(props.node.attrs.entity_identifier); + if (issue) { + setIssueDetails(issue); + setLoading(0); + } else { + setLoading(-1); + } }, []); const completeIssueEmbedAction = () => { diff --git a/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-widget/types.ts b/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-widget/types.ts index 615b55dee5..82aa689546 100644 --- a/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-widget/types.ts +++ b/packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-widget/types.ts @@ -3,7 +3,7 @@ export interface IEmbedConfig { } export interface IIssueEmbedConfig { - fetchIssue: (issueId: string) => Promise; + fetchIssue: (issueId: string) => any; clickAction: (issueId: string, issueTitle: string) => void; issues: Array; } diff --git a/web/hooks/use-issue-embeds.tsx b/web/hooks/use-issue-embeds.tsx index 2c8f7700bc..16c3aca7e7 100644 --- a/web/hooks/use-issue-embeds.tsx +++ b/web/hooks/use-issue-embeds.tsx @@ -19,7 +19,7 @@ export const useIssueEmbeds = () => { const { getStateById } = useProjectState(); const { getUserDetails } = useMember(); - const { data: issuesResponse } = useSWR( + const { data: issuesResponse, isLoading: issueLoading } = useSWR( workspaceSlug && projectId ? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string) : null, workspaceSlug && projectId ? () => issueService.getIssues(workspaceSlug as string, projectId as string) : null ); @@ -29,10 +29,10 @@ export const useIssueEmbeds = () => { ...issue, state_detail: toJS(getStateById(issue.state_id)), project_detail: toJS(getProjectById(issue.project_id)), - assignee_details: issue.assignee_ids.map((assigneeid) => toJS(getUserDetails(assigneeid))), + assignee_details: issue.assignee_ids.map((assigneeid: string) => toJS(getUserDetails(assigneeid))), })); - const fetchIssue = async (issueId: string) => issuesWithStateAndProject.find((issue) => issue.id === issueId); + const fetchIssue = (issueId: string) => issuesWithStateAndProject.find((issue) => issue.id === issueId); const issueWidgetClickAction = (issueId: string) => { if (!workspaceSlug || !projectId) return; @@ -42,6 +42,7 @@ export const useIssueEmbeds = () => { return { issues: issuesWithStateAndProject, + issueLoading, fetchIssue, issueWidgetClickAction, }; diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx index a141455b79..39f917c737 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx @@ -5,7 +5,7 @@ import { useRouter } from "next/router"; import { ReactElement, useEffect, useRef, useState } from "react"; import { Controller, useForm } from "react-hook-form"; // hooks -import { useApplication, useIssues, usePage, useUser } from "hooks/store"; +import { useApplication, usePage, useUser } from "hooks/store"; import useReloadConfirmations from "hooks/use-reload-confirmation"; import useToast from "hooks/use-toast"; // services @@ -29,13 +29,9 @@ import { EUserProjectRoles } from "constants/project"; import { useProjectPages } from "hooks/store/use-project-specific-pages"; import { useIssueEmbeds } from "hooks/use-issue-embeds"; import { IssuePeekOverview } from "components/issues"; -import { PROJECT_ISSUES_LIST } from "constants/fetch-keys"; -import { IssueService } from "services/issue"; -import { EIssuesStoreType } from "constants/issue"; // services const fileService = new FileService(); -const issueService = new IssueService(); const PageDetailsPage: NextPageWithLayout = observer(() => { // states @@ -88,7 +84,7 @@ const PageDetailsPage: NextPageWithLayout = observer(() => { : null ); - const { issues, fetchIssue, issueWidgetClickAction } = useIssueEmbeds(); + const { issues, fetchIssue, issueLoading, issueWidgetClickAction } = useIssueEmbeds(); const pageStore = usePage(pageId as string); @@ -259,7 +255,7 @@ const PageDetailsPage: NextPageWithLayout = observer(() => { const userCanLock = currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole); - return pageIdMobx && issues ? ( + return pageIdMobx && issues && !issueLoading ? (
{isPageReadOnly ? (