From a8728fc7de776496b1656704432f99c7e3c58739 Mon Sep 17 00:00:00 2001 From: Satish Gandham Date: Thu, 7 Nov 2024 18:53:45 +0530 Subject: [PATCH 1/6] Add fallback when db initialization fails --- web/core/local-db/storage.sqlite.ts | 2 ++ web/core/local-db/worker/db.ts | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/web/core/local-db/storage.sqlite.ts b/web/core/local-db/storage.sqlite.ts index 00b3a6bb149..014851fa3c0 100644 --- a/web/core/local-db/storage.sqlite.ts +++ b/web/core/local-db/storage.sqlite.ts @@ -75,6 +75,7 @@ export class Storage { if (workspaceSlug !== this.workspaceSlug) { this.reset(); } + try { await startSpan({ name: "INIT_DB" }, async () => await this._initialize(workspaceSlug)); return true; @@ -125,6 +126,7 @@ export class Storage { return true; } catch (error) { this.status = "error"; + this.db = null; throw new Error(`Failed to initialize database worker: ${error}`); } }; diff --git a/web/core/local-db/worker/db.ts b/web/core/local-db/worker/db.ts index 0fba3b3abf3..e609cb7afb4 100644 --- a/web/core/local-db/worker/db.ts +++ b/web/core/local-db/worker/db.ts @@ -36,12 +36,20 @@ export class DBClass { this.sqlite3 = SQLite.Factory(m); const vfs = await MyVFS.create("plane", m); this.sqlite3.vfs_register(vfs, true); - const db = await this.sqlite3.open_v2( + // Fallback in rare cases where the database is not initialized in time + const p = new Promise((resolve) => setTimeout(() => resolve(false), 5000)); + const dbPromise = this.sqlite3.open_v2( `${dbName}.sqlite3`, this.sqlite3.OPEN_READWRITE | this.sqlite3.OPEN_CREATE, "plane" ); + const db = await Promise.any([dbPromise, p]); + + if (!db) { + throw new Error("Failed to initialize in time"); + } + this.instance.db = db; this.instance.exec = async (sql: string) => { const rows: any[] = []; From 2d7c10c47b467387ba771ae7792c9f844c1033b9 Mon Sep 17 00:00:00 2001 From: rahulramesha Date: Thu, 7 Nov 2024 20:07:22 +0530 Subject: [PATCH 2/6] add checks for instance.exec --- web/core/local-db/worker/db.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/core/local-db/worker/db.ts b/web/core/local-db/worker/db.ts index e609cb7afb4..05ffdc0d41a 100644 --- a/web/core/local-db/worker/db.ts +++ b/web/core/local-db/worker/db.ts @@ -66,7 +66,7 @@ export class DBClass { } runQuery(sql: string) { - return this.instance.exec(sql); + return this.instance?.exec && this.instance.exec(sql); } async exec(props: string | TQueryProps) { @@ -111,14 +111,14 @@ export class DBClass { } if (sql === "COMMIT;" && this.tp) { - await this.instance.exec(sql); + await (this.instance?.exec && this.instance.exec(sql)); if (this.tp.length > 0) { const { resolve } = this.tpResolver.shift(); resolve(); } return; } - return await this.instance.exec(sql); + return await (this.instance?.exec && this.instance.exec(sql)); } async close() { try { From 9dd5e89040ab53a26d5d8fb48dc35c6cb631d416 Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Fri, 8 Nov 2024 13:29:28 +0530 Subject: [PATCH 3/6] chore: convert issue boolean fields to actual boolean value. --- web/core/local-db/storage.sqlite.ts | 6 +++++- web/core/local-db/utils/constants.ts | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/web/core/local-db/storage.sqlite.ts b/web/core/local-db/storage.sqlite.ts index 55f3e54ce0a..8985cab867f 100644 --- a/web/core/local-db/storage.sqlite.ts +++ b/web/core/local-db/storage.sqlite.ts @@ -9,7 +9,7 @@ import { rootStore } from "@/lib/store-context"; // services import { IssueService } from "@/services/issue/issue.service"; // -import { ARRAY_FIELDS } from "./utils/constants"; +import { ARRAY_FIELDS, BOOLEAN_FIELDS } from "./utils/constants"; import { getSubIssuesWithDistribution } from "./utils/data.utils"; import createIndexes from "./utils/indexes"; import { addIssuesBulk, syncDeletesToLocal } from "./utils/load-issues"; @@ -467,5 +467,9 @@ export const formatLocalIssue = (issue: any) => { ARRAY_FIELDS.forEach((field: string) => { currIssue[field] = currIssue[field] ? JSON.parse(currIssue[field]) : []; }); + // Convert boolean fields to actual boolean values + BOOLEAN_FIELDS.forEach((field: string) => { + currIssue[field] = currIssue[field] === 1; + }); return currIssue as TIssue & { group_id?: string; total_issues: number; sub_group_id?: string }; }; diff --git a/web/core/local-db/utils/constants.ts b/web/core/local-db/utils/constants.ts index cb61fbdf44d..11b60bda045 100644 --- a/web/core/local-db/utils/constants.ts +++ b/web/core/local-db/utils/constants.ts @@ -1,5 +1,7 @@ export const ARRAY_FIELDS = ["label_ids", "assignee_ids", "module_ids"]; +export const BOOLEAN_FIELDS = ["is_draft"]; + export const GROUP_BY_MAP = { state_id: "state_id", priority: "priority", From c3355bd800da1c1c4673c12d408ecddf0820fa7d Mon Sep 17 00:00:00 2001 From: rahulramesha Date: Fri, 8 Nov 2024 14:42:53 +0530 Subject: [PATCH 4/6] change instance exec code --- web/core/local-db/worker/db.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/core/local-db/worker/db.ts b/web/core/local-db/worker/db.ts index 05ffdc0d41a..9f0422f6c7c 100644 --- a/web/core/local-db/worker/db.ts +++ b/web/core/local-db/worker/db.ts @@ -66,7 +66,7 @@ export class DBClass { } runQuery(sql: string) { - return this.instance?.exec && this.instance.exec(sql); + return this.instance?.exec?.(sql); } async exec(props: string | TQueryProps) { @@ -111,14 +111,14 @@ export class DBClass { } if (sql === "COMMIT;" && this.tp) { - await (this.instance?.exec && this.instance.exec(sql)); + await this.instance?.exec?.(sql); if (this.tp.length > 0) { const { resolve } = this.tpResolver.shift(); resolve(); } return; } - return await (this.instance?.exec && this.instance.exec(sql)); + return await this.instance?.exec?.(sql); } async close() { try { From a0267f1522860da18eb05c5d721fa97e4103f9f0 Mon Sep 17 00:00:00 2001 From: rahulramesha Date: Fri, 8 Nov 2024 14:50:44 +0530 Subject: [PATCH 5/6] sync issue to local db when inbox issue is accepted and draft issue is moved to project --- web/core/local-db/utils/utils.ts | 71 ++++++++++--------- .../services/issue/workspace_draft.service.ts | 2 +- web/core/store/inbox/inbox-issue.store.ts | 8 +++ .../issue/workspace-draft/issue.store.ts | 8 ++- 4 files changed, 55 insertions(+), 34 deletions(-) diff --git a/web/core/local-db/utils/utils.ts b/web/core/local-db/utils/utils.ts index bb42d3531f2..236d8287200 100644 --- a/web/core/local-db/utils/utils.ts +++ b/web/core/local-db/utils/utils.ts @@ -19,6 +19,44 @@ export const logError = (e: any) => { }; export const logInfo = console.info; +export const addIssueToPersistanceLayer = async (issue: TIssue) => { + try { + const issuePartial = pick({ ...JSON.parse(JSON.stringify(issue)) }, [ + "id", + "name", + "state_id", + "sort_order", + "completed_at", + "estimate_point", + "priority", + "start_date", + "target_date", + "sequence_id", + "project_id", + "parent_id", + "created_at", + "updated_at", + "created_by", + "updated_by", + "is_draft", + "archived_at", + "state__group", + "cycle_id", + "link_count", + "attachment_count", + "sub_issues_count", + "assignee_ids", + "label_ids", + "module_ids", + "type_id", + "description_html", + ]); + await updateIssue({ ...issuePartial, is_local_update: 1 }); + } catch (e) { + logError("Error while adding issue to db"); + } +}; + export const updatePersistentLayer = async (issueIds: string | string[]) => { if (typeof issueIds === "string") { issueIds = [issueIds]; @@ -28,38 +66,7 @@ export const updatePersistentLayer = async (issueIds: string | string[]) => { const issue = rootStore.issue.issues.getIssueById(issueId); if (issue) { - // JSON.parse(JSON.stringify(issue)) is used to remove the mobx observables - const issuePartial = pick({ ...dbIssue, ...JSON.parse(JSON.stringify(issue)) }, [ - "id", - "name", - "state_id", - "sort_order", - "completed_at", - "estimate_point", - "priority", - "start_date", - "target_date", - "sequence_id", - "project_id", - "parent_id", - "created_at", - "updated_at", - "created_by", - "updated_by", - "is_draft", - "archived_at", - "state__group", - "cycle_id", - "link_count", - "attachment_count", - "sub_issues_count", - "assignee_ids", - "label_ids", - "module_ids", - "type_id", - "description_html", - ]); - await updateIssue({ ...issuePartial, is_local_update: 1 }); + addIssueToPersistanceLayer(issue); } }); }; diff --git a/web/core/services/issue/workspace_draft.service.ts b/web/core/services/issue/workspace_draft.service.ts index 3d7844fe6ba..d1d1ff17697 100644 --- a/web/core/services/issue/workspace_draft.service.ts +++ b/web/core/services/issue/workspace_draft.service.ts @@ -59,7 +59,7 @@ export class WorkspaceDraftService extends APIService { }); } - async moveIssue(workspaceSlug: string, issueId: string, payload: Partial): Promise { + async moveIssue(workspaceSlug: string, issueId: string, payload: Partial): Promise { return this.post(`/api/workspaces/${workspaceSlug}/draft-to-issue/${issueId}/`, payload) .then((response) => response?.data) .catch((error) => { diff --git a/web/core/store/inbox/inbox-issue.store.ts b/web/core/store/inbox/inbox-issue.store.ts index 8d33fddeecd..e080225aaf2 100644 --- a/web/core/store/inbox/inbox-issue.store.ts +++ b/web/core/store/inbox/inbox-issue.store.ts @@ -4,6 +4,8 @@ import { makeObservable, observable, runInAction, action } from "mobx"; import { TIssue, TInboxIssue, TInboxIssueStatus, TInboxDuplicateIssueDetails } from "@plane/types"; // helpers import { EInboxIssueStatus } from "@/helpers/inbox.helper"; +// local db +import { addIssueToPersistanceLayer } from "@/local-db/utils/utils"; // services import { InboxIssueService } from "@/services/inbox"; import { IssueService } from "@/services/issue"; @@ -88,10 +90,16 @@ export class InboxIssueStore implements IInboxIssueStore { try { if (!this.issue.id) return; + const inboxIssue = await this.inboxIssueService.update(this.workspaceSlug, this.projectId, this.issue.id, { status: status, }); runInAction(() => set(this, "status", inboxIssue?.status)); + + // If issue accepted sync issue to local db + if (status === EInboxIssueStatus.ACCEPTED) { + addIssueToPersistanceLayer(inboxIssue.issue); + } } catch { runInAction(() => set(this, "status", previousData.status)); } diff --git a/web/core/store/issue/workspace-draft/issue.store.ts b/web/core/store/issue/workspace-draft/issue.store.ts index aef03d3411c..6518cff0066 100644 --- a/web/core/store/issue/workspace-draft/issue.store.ts +++ b/web/core/store/issue/workspace-draft/issue.store.ts @@ -22,6 +22,8 @@ import { import { EDraftIssuePaginationType } from "@/constants/workspace-drafts"; // helpers import { getCurrentDateTimeInISO, convertToISODateString } from "@/helpers/date-time.helper"; +// local-db +import { addIssueToPersistanceLayer } from "@/local-db/utils/utils"; // services import workspaceDraftService from "@/services/issue/workspace_draft.service"; // types @@ -59,7 +61,7 @@ export interface IWorkspaceDraftIssues { payload: Partial ) => Promise; deleteIssue: (workspaceSlug: string, issueId: string) => Promise; - moveIssue: (workspaceSlug: string, issueId: string, payload: Partial) => Promise; + moveIssue: (workspaceSlug: string, issueId: string, payload: Partial) => Promise; addCycleToIssue: ( workspaceSlug: string, issueId: string, @@ -348,6 +350,10 @@ export class WorkspaceDraftIssues implements IWorkspaceDraftIssues { total_count: this.paginationInfo.total_count - 1, }); } + + // sync issue to local db + addIssueToPersistanceLayer(response); + // Update draft issue count in workspaceUserInfo this.updateWorkspaceUserDraftIssueCount(workspaceSlug, -1); }); From 84d6ba1f5f7a0dfd22d997ed127c3c456f7bb3d3 Mon Sep 17 00:00:00 2001 From: NarayanBavisetti Date: Fri, 8 Nov 2024 15:04:41 +0530 Subject: [PATCH 6/6] chore: added project and workspace keys --- apiserver/plane/app/serializers/issue.py | 2 ++ web/core/local-db/worker/db.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apiserver/plane/app/serializers/issue.py b/apiserver/plane/app/serializers/issue.py index 2323c248a86..91d04636cda 100644 --- a/apiserver/plane/app/serializers/issue.py +++ b/apiserver/plane/app/serializers/issue.py @@ -95,6 +95,8 @@ class IssueCreateSerializer(BaseSerializer): write_only=True, required=False, ) + project_id = serializers.UUIDField(source="project.id", read_only=True) + workspace_id = serializers.UUIDField(source="workspace.id", read_only=True) class Meta: model = Issue diff --git a/web/core/local-db/worker/db.ts b/web/core/local-db/worker/db.ts index 9f0422f6c7c..d8a7997c0af 100644 --- a/web/core/local-db/worker/db.ts +++ b/web/core/local-db/worker/db.ts @@ -37,7 +37,7 @@ export class DBClass { const vfs = await MyVFS.create("plane", m); this.sqlite3.vfs_register(vfs, true); // Fallback in rare cases where the database is not initialized in time - const p = new Promise((resolve) => setTimeout(() => resolve(false), 5000)); + const p = new Promise((resolve) => setTimeout(() => resolve(false), 2000)); const dbPromise = this.sqlite3.open_v2( `${dbName}.sqlite3`, this.sqlite3.OPEN_READWRITE | this.sqlite3.OPEN_CREATE,