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/storage.sqlite.ts b/web/core/local-db/storage.sqlite.ts index 55f3e54ce0a..65d5a388581 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"; @@ -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}`); } }; @@ -467,5 +469,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", 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/local-db/worker/db.ts b/web/core/local-db/worker/db.ts index 0fba3b3abf3..d8a7997c0af 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), 2000)); + 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[] = []; @@ -58,7 +66,7 @@ export class DBClass { } runQuery(sql: string) { - return this.instance.exec(sql); + return this.instance?.exec?.(sql); } async exec(props: string | TQueryProps) { @@ -103,14 +111,14 @@ export class DBClass { } if (sql === "COMMIT;" && this.tp) { - await 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(sql); + return await this.instance?.exec?.(sql); } async close() { try { 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); });