[WEB-2706] fix: Add fallback when db initialisation fails (#5973)

* Add fallback when db initialization fails

* add checks for instance.exec

* chore: convert issue boolean fields to actual boolean value.

* change instance exec code

* sync issue to local db when inbox issue is accepted and draft issue is moved to project

* chore: added project and workspace keys

---------

Co-authored-by: rahulramesha <rahulramesham@gmail.com>
Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
This commit is contained in:
Satish Gandham 2024-11-08 17:09:26 +05:30 committed by GitHub
parent 2193e8c79c
commit acba451803
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 78 additions and 39 deletions

View file

@ -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

View file

@ -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 };
};

View file

@ -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",

View file

@ -19,17 +19,9 @@ export const logError = (e: any) => {
};
export const logInfo = console.info;
export const updatePersistentLayer = async (issueIds: string | string[]) => {
if (typeof issueIds === "string") {
issueIds = [issueIds];
}
issueIds.forEach(async (issueId) => {
const dbIssue = await persistence.getIssue(issueId);
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)) }, [
export const addIssueToPersistanceLayer = async (issue: TIssue) => {
try {
const issuePartial = pick({ ...JSON.parse(JSON.stringify(issue)) }, [
"id",
"name",
"state_id",
@ -60,6 +52,21 @@ export const updatePersistentLayer = async (issueIds: string | string[]) => {
"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];
}
issueIds.forEach(async (issueId) => {
const dbIssue = await persistence.getIssue(issueId);
const issue = rootStore.issue.issues.getIssueById(issueId);
if (issue) {
addIssueToPersistanceLayer(issue);
}
});
};

View file

@ -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 {

View file

@ -59,7 +59,7 @@ export class WorkspaceDraftService extends APIService {
});
}
async moveIssue(workspaceSlug: string, issueId: string, payload: Partial<TWorkspaceDraftIssue>): Promise<void> {
async moveIssue(workspaceSlug: string, issueId: string, payload: Partial<TWorkspaceDraftIssue>): Promise<TIssue> {
return this.post(`/api/workspaces/${workspaceSlug}/draft-to-issue/${issueId}/`, payload)
.then((response) => response?.data)
.catch((error) => {

View file

@ -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));
}

View file

@ -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<TWorkspaceDraftIssue | TIssue>
) => Promise<TWorkspaceDraftIssue | undefined>;
deleteIssue: (workspaceSlug: string, issueId: string) => Promise<void>;
moveIssue: (workspaceSlug: string, issueId: string, payload: Partial<TWorkspaceDraftIssue>) => Promise<void>;
moveIssue: (workspaceSlug: string, issueId: string, payload: Partial<TWorkspaceDraftIssue>) => Promise<TIssue>;
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);
});