Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 83298a7a69 |
@@ -91,7 +91,6 @@ def issue_on_results(issues, group_by, sub_group_by):
|
||||
Case(
|
||||
When(
|
||||
votes__isnull=False,
|
||||
votes__deleted_at__isnull=True,
|
||||
then=JSONObject(
|
||||
vote=F("votes__vote"),
|
||||
actor_details=JSONObject(
|
||||
@@ -118,14 +117,13 @@ def issue_on_results(issues, group_by, sub_group_by):
|
||||
default=None,
|
||||
output_field=JSONField(),
|
||||
),
|
||||
filter=Q(votes__isnull=False,votes__deleted_at__isnull=True),
|
||||
filter=Q(votes__isnull=False),
|
||||
distinct=True,
|
||||
),
|
||||
reaction_items=ArrayAgg(
|
||||
Case(
|
||||
When(
|
||||
issue_reactions__isnull=False,
|
||||
issue_reactions__deleted_at__isnull=True,
|
||||
then=JSONObject(
|
||||
reaction=F("issue_reactions__reaction"),
|
||||
actor_details=JSONObject(
|
||||
@@ -152,7 +150,7 @@ def issue_on_results(issues, group_by, sub_group_by):
|
||||
default=None,
|
||||
output_field=JSONField(),
|
||||
),
|
||||
filter=Q(issue_reactions__isnull=False, issue_reactions__deleted_at__isnull=True),
|
||||
filter=Q(issue_reactions__isnull=False),
|
||||
distinct=True,
|
||||
),
|
||||
).values(*required_fields, "vote_items", "reaction_items")
|
||||
|
||||
@@ -701,7 +701,6 @@ class IssueRetrievePublicEndpoint(BaseAPIView):
|
||||
Case(
|
||||
When(
|
||||
votes__isnull=False,
|
||||
votes__deleted_at__isnull=True,
|
||||
then=JSONObject(
|
||||
vote=F("votes__vote"),
|
||||
actor_details=JSONObject(
|
||||
@@ -733,11 +732,7 @@ class IssueRetrievePublicEndpoint(BaseAPIView):
|
||||
output_field=JSONField(),
|
||||
),
|
||||
filter=Case(
|
||||
When(
|
||||
votes__isnull=False,
|
||||
votes__deleted_at__isnull=True,
|
||||
then=True,
|
||||
),
|
||||
When(votes__isnull=False, then=True),
|
||||
default=False,
|
||||
output_field=JSONField(),
|
||||
),
|
||||
@@ -747,7 +742,6 @@ class IssueRetrievePublicEndpoint(BaseAPIView):
|
||||
Case(
|
||||
When(
|
||||
issue_reactions__isnull=False,
|
||||
issue_reactions__deleted_at__isnull=True,
|
||||
then=JSONObject(
|
||||
reaction=F("issue_reactions__reaction"),
|
||||
actor_details=JSONObject(
|
||||
@@ -781,11 +775,7 @@ class IssueRetrievePublicEndpoint(BaseAPIView):
|
||||
output_field=JSONField(),
|
||||
),
|
||||
filter=Case(
|
||||
When(
|
||||
issue_reactions__isnull=False,
|
||||
issue_reactions__deleted_at__isnull=True,
|
||||
then=True,
|
||||
),
|
||||
When(issue_reactions__isnull=False, then=True),
|
||||
default=False,
|
||||
output_field=JSONField(),
|
||||
),
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export * from "./projects";
|
||||
export * from "./project-activity";
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
export interface TProjectActivity {
|
||||
id: string;
|
||||
content: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
userId: string;
|
||||
projectId: string;
|
||||
created_at: string;
|
||||
field: string;
|
||||
verb: string;
|
||||
actor_detail: {
|
||||
display_name: string;
|
||||
id: string;
|
||||
};
|
||||
workspace_detail: {
|
||||
slug: string;
|
||||
};
|
||||
project_detail: {
|
||||
name: string;
|
||||
};
|
||||
new_value: string;
|
||||
old_value: string;
|
||||
project: string;
|
||||
new_identifier?: string;
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { useSearchParams } from "next/navigation";
|
||||
// editor
|
||||
import { EditorRefApi } from "@plane/editor";
|
||||
// types
|
||||
import { TPage } from "@plane/types";
|
||||
import { TDocumentPayload, TPage, TPageVersion } from "@plane/types";
|
||||
// ui
|
||||
import { setToast, TOAST_TYPE } from "@plane/ui";
|
||||
// components
|
||||
@@ -14,21 +14,25 @@ import { useProjectPages } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { usePageFallback } from "@/hooks/use-page-fallback";
|
||||
import { useQueryParams } from "@/hooks/use-query-params";
|
||||
// services
|
||||
import { ProjectPageService, ProjectPageVersionService } from "@/services/page";
|
||||
const projectPageService = new ProjectPageService();
|
||||
const projectPageVersionService = new ProjectPageVersionService();
|
||||
// store
|
||||
import { IPage } from "@/store/pages/page";
|
||||
|
||||
type TPageRootProps = {
|
||||
descriptionHandler: {
|
||||
fetch: () => Promise<any>;
|
||||
update: (data: TDocumentPayload) => Promise<void>;
|
||||
};
|
||||
page: IPage;
|
||||
projectId: string;
|
||||
versionHistoryHandler: {
|
||||
fetchAll: (pageId: string) => Promise<TPageVersion[] | undefined>;
|
||||
fetchDetails: (pageId: string, versionId: string) => Promise<TPageVersion | undefined>;
|
||||
};
|
||||
workspaceSlug: string;
|
||||
};
|
||||
|
||||
export const PageRoot = observer((props: TPageRootProps) => {
|
||||
const { projectId, workspaceSlug, page } = props;
|
||||
const { descriptionHandler, page, projectId, versionHistoryHandler, workspaceSlug } = props;
|
||||
// states
|
||||
const [editorReady, setEditorReady] = useState(false);
|
||||
const [hasConnectionFailed, setHasConnectionFailed] = useState(false);
|
||||
@@ -43,16 +47,13 @@ export const PageRoot = observer((props: TPageRootProps) => {
|
||||
// store hooks
|
||||
const { createPage } = useProjectPages();
|
||||
// derived values
|
||||
const { access, description_html, name, isContentEditable, updateDescription } = page;
|
||||
const { access, description_html, name, isContentEditable } = page;
|
||||
// page fallback
|
||||
usePageFallback({
|
||||
editorRef,
|
||||
fetchPageDescription: async () => {
|
||||
if (!page.id) return;
|
||||
return await projectPageService.fetchDescriptionBinary(workspaceSlug, projectId, page.id);
|
||||
},
|
||||
fetchPageDescription: async () => await descriptionHandler.fetch(),
|
||||
hasConnectionFailed,
|
||||
updatePageDescription: async (data) => await updateDescription(data),
|
||||
updatePageDescription: async (data) => await descriptionHandler.update(data),
|
||||
});
|
||||
// update query params
|
||||
const { updateQueryParams } = useQueryParams();
|
||||
@@ -105,23 +106,8 @@ export const PageRoot = observer((props: TPageRootProps) => {
|
||||
activeVersion={version}
|
||||
currentVersionDescription={currentVersionDescription ?? null}
|
||||
editorComponent={PagesVersionEditor}
|
||||
fetchAllVersions={async (pageId) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
return await projectPageVersionService.fetchAllVersions(
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
pageId
|
||||
);
|
||||
}}
|
||||
fetchVersionDetails={async (pageId, versionId) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
return await projectPageVersionService.fetchVersionById(
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
pageId,
|
||||
versionId
|
||||
);
|
||||
}}
|
||||
fetchAllVersions={async (pageId) => await versionHistoryHandler.fetchAll(pageId)}
|
||||
fetchVersionDetails={async (pageId, versionId) => await versionHistoryHandler.fetchDetails(pageId, versionId)}
|
||||
handleRestore={handleRestoreVersion}
|
||||
isOpen={isVersionsOverlayOpen}
|
||||
onClose={handleCloseVersionsOverlay}
|
||||
|
||||
+113
-141
@@ -3,49 +3,37 @@ import * as Comlink from "comlink";
|
||||
import set from "lodash/set";
|
||||
// plane
|
||||
import { EIssueGroupBYServerToProperty } from "@plane/constants";
|
||||
import { TIssue } from "@plane/types";
|
||||
// lib
|
||||
import { rootStore } from "@/lib/store-context";
|
||||
// services
|
||||
import { IssueService } from "@/services/issue/issue.service";
|
||||
//
|
||||
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";
|
||||
import { loadWorkSpaceData } from "./utils/load-workspace";
|
||||
import { issueFilterCountQueryConstructor, issueFilterQueryConstructor } from "./utils/query-constructor";
|
||||
import { runQuery } from "./utils/query-executor";
|
||||
import { deleteOption, formatLocalIssue, getLastSyncTime, getOption, setOption } from "./utils/storage.sqlite.utils";
|
||||
import { createTables } from "./utils/tables";
|
||||
import { clearOPFS, getGroupedIssueResults, getSubGroupedIssueResults, log, logError } from "./utils/utils";
|
||||
|
||||
/** Database version for schema management */
|
||||
const DB_VERSION = 1;
|
||||
/** Number of items per page for pagination */
|
||||
const PAGE_SIZE = 500;
|
||||
/** Number of items to process in a single batch */
|
||||
const BATCH_SIZE = 50;
|
||||
|
||||
/** Project status type definition */
|
||||
type TProjectStatus = {
|
||||
issues: {
|
||||
status: undefined | "loading" | "ready" | "error" | "syncing";
|
||||
sync: Promise<void> | undefined;
|
||||
};
|
||||
issues: { status: undefined | "loading" | "ready" | "error" | "syncing"; sync: Promise<void> | undefined };
|
||||
};
|
||||
|
||||
/** Database status type definition */
|
||||
type TDBStatus = "initializing" | "ready" | "error" | undefined;
|
||||
|
||||
/**
|
||||
* Storage class for managing local SQLite database operations
|
||||
* Handles database initialization, synchronization, and CRUD operations for issues
|
||||
*/
|
||||
export class Storage {
|
||||
public db: any;
|
||||
private status: TDBStatus = undefined;
|
||||
private dbName = "plane";
|
||||
private projectStatus: Record<string, TProjectStatus> = {};
|
||||
private workspaceSlug: string = "";
|
||||
db: any;
|
||||
status: TDBStatus = undefined;
|
||||
dbName = "plane";
|
||||
projectStatus: Record<string, TProjectStatus> = {};
|
||||
workspaceSlug: string = "";
|
||||
|
||||
constructor() {
|
||||
this.db = null;
|
||||
@@ -55,20 +43,13 @@ export class Storage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the database connection
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
private closeDBConnection = async (): Promise<void> => {
|
||||
closeDBConnection = async () => {
|
||||
if (this.db) {
|
||||
await this.db.close();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Resets the database state
|
||||
*/
|
||||
public reset = (): void => {
|
||||
reset = () => {
|
||||
if (this.db) {
|
||||
this.db.close();
|
||||
}
|
||||
@@ -78,27 +59,17 @@ export class Storage {
|
||||
this.workspaceSlug = "";
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears the storage and resets the database
|
||||
* @param force - Force clear storage
|
||||
*/
|
||||
public clearStorage = async (force = false): Promise<void> => {
|
||||
clearStorage = async (force = false) => {
|
||||
try {
|
||||
await this.db?.close();
|
||||
await clearOPFS(force);
|
||||
this.reset();
|
||||
} catch (error) {
|
||||
logError(error);
|
||||
console.error("Error clearing sqlite sync storage", error);
|
||||
} catch (e) {
|
||||
console.error("Error clearing sqlite sync storage", e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the database for a given workspace
|
||||
* @param workspaceSlug - Workspace identifier
|
||||
* @returns Promise<boolean> - True if initialization is successful
|
||||
*/
|
||||
public initialize = async (workspaceSlug: string): Promise<boolean> => {
|
||||
initialize = async (workspaceSlug: string): Promise<boolean> => {
|
||||
if (!rootStore.user.localDBEnabled) return false; // return if the window gets hidden
|
||||
|
||||
if (workspaceSlug !== this.workspaceSlug) {
|
||||
@@ -115,12 +86,7 @@ export class Storage {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the database for a given workspace
|
||||
* @param workspaceSlug - Workspace identifier
|
||||
* @returns Promise<boolean> - True if initialization is successful
|
||||
*/
|
||||
private _initialize = async (workspaceSlug: string): Promise<boolean> => {
|
||||
_initialize = async (workspaceSlug: string): Promise<boolean> => {
|
||||
if (this.status === "initializing") {
|
||||
console.warn(`Initialization already in progress for workspace ${workspaceSlug}`);
|
||||
return false;
|
||||
@@ -156,7 +122,7 @@ export class Storage {
|
||||
// Your SQLite code here.
|
||||
await createTables();
|
||||
|
||||
await setOption("DB_VERSION", DB_VERSION.toString());
|
||||
await this.setOption("DB_VERSION", DB_VERSION.toString());
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.status = "error";
|
||||
@@ -165,10 +131,7 @@ export class Storage {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Synchronizes the workspace data
|
||||
*/
|
||||
public syncWorkspace = async (): Promise<void> => {
|
||||
syncWorkspace = async () => {
|
||||
if (!rootStore.user.localDBEnabled) return;
|
||||
const syncInProgress = await this.getIsWriteInProgress("sync_workspace");
|
||||
if (syncInProgress) {
|
||||
@@ -177,27 +140,23 @@ export class Storage {
|
||||
}
|
||||
try {
|
||||
await startSpan({ name: "LOAD_WS", attributes: { slug: this.workspaceSlug } }, async () => {
|
||||
setOption("sync_workspace", new Date().toUTCString());
|
||||
this.setOption("sync_workspace", new Date().toUTCString());
|
||||
await loadWorkSpaceData(this.workspaceSlug);
|
||||
deleteOption("sync_workspace");
|
||||
this.deleteOption("sync_workspace");
|
||||
});
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
deleteOption("sync_workspace");
|
||||
this.deleteOption("sync_workspace");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Synchronizes the project data
|
||||
* @param projectId - Project identifier
|
||||
*/
|
||||
public syncProject = async (projectId: string): Promise<void> => {
|
||||
syncProject = async (projectId: string) => {
|
||||
if (
|
||||
// document.hidden ||
|
||||
!rootStore.user.localDBEnabled
|
||||
) {
|
||||
return; // return if the window gets hidden
|
||||
}
|
||||
)
|
||||
return false; // return if the window gets hidden
|
||||
|
||||
// Load labels, members, states, modules, cycles
|
||||
await this.syncIssues(projectId);
|
||||
|
||||
@@ -213,13 +172,9 @@ export class Storage {
|
||||
// this.setOption("workspace_synced_at", new Date().toISOString());
|
||||
};
|
||||
|
||||
/**
|
||||
* Synchronizes the issues for a project
|
||||
* @param projectId - Project identifier
|
||||
*/
|
||||
public syncIssues = async (projectId: string): Promise<void> => {
|
||||
syncIssues = async (projectId: string) => {
|
||||
if (!rootStore.user.localDBEnabled || !this.db) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const sync = startSpan({ name: `SYNC_ISSUES` }, () => this._syncIssues(projectId));
|
||||
@@ -231,11 +186,7 @@ export class Storage {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Synchronizes the issues for a project
|
||||
* @param projectId - Project identifier
|
||||
*/
|
||||
private _syncIssues = async (projectId: string): Promise<void> => {
|
||||
_syncIssues = async (projectId: string) => {
|
||||
const activeSpan = getActiveSpan();
|
||||
|
||||
log("### Sync started");
|
||||
@@ -256,8 +207,8 @@ export class Storage {
|
||||
description: true,
|
||||
};
|
||||
|
||||
const syncedAt = await getLastSyncTime(projectId);
|
||||
const projectSync = await getOption(projectId);
|
||||
const syncedAt = await this.getLastSyncTime(projectId);
|
||||
const projectSync = await this.getOption(projectId);
|
||||
|
||||
if (syncedAt) {
|
||||
queryParams["updated_at__gt"] = syncedAt;
|
||||
@@ -294,7 +245,7 @@ export class Storage {
|
||||
if (status === "loading") {
|
||||
await createIndexes();
|
||||
}
|
||||
setOption(projectId, "ready");
|
||||
this.setOption(projectId, "ready");
|
||||
this.setStatus(projectId, "ready");
|
||||
this.setSync(projectId, undefined);
|
||||
|
||||
@@ -304,15 +255,31 @@ export class Storage {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets issues for a project
|
||||
* @param workspaceSlug - Workspace identifier
|
||||
* @param projectId - Project identifier
|
||||
* @param queries - Query parameters
|
||||
* @param config - Configuration options
|
||||
* @returns Promise<any> - Issues data
|
||||
*/
|
||||
public getIssues = async (workspaceSlug: string, projectId: string, queries: any, config: any): Promise<any> => {
|
||||
getIssueCount = async (projectId: string) => {
|
||||
const count = await runQuery(`select count(*) as count from issues where project_id='${projectId}'`);
|
||||
return count[0]["count"];
|
||||
};
|
||||
|
||||
getLastUpdatedIssue = async (projectId: string) => {
|
||||
const lastUpdatedIssue = await runQuery(
|
||||
`select id, name, updated_at , sequence_id from issues WHERE project_id='${projectId}' AND is_local_update IS NULL order by datetime(updated_at) desc limit 1 `
|
||||
);
|
||||
|
||||
if (lastUpdatedIssue.length) {
|
||||
return lastUpdatedIssue[0];
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
getLastSyncTime = async (projectId: string) => {
|
||||
const issue = await this.getLastUpdatedIssue(projectId);
|
||||
if (!issue) {
|
||||
return false;
|
||||
}
|
||||
return issue.updated_at;
|
||||
};
|
||||
|
||||
getIssues = async (workspaceSlug: string, projectId: string, queries: any, config: any) => {
|
||||
log("#### Queries", queries);
|
||||
|
||||
const currentProjectStatus = this.getStatus(projectId);
|
||||
@@ -420,12 +387,7 @@ export class Storage {
|
||||
return out;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets an issue by ID
|
||||
* @param issueId - Issue identifier
|
||||
* @returns Promise<any | undefined> - Issue data or undefined
|
||||
*/
|
||||
public getIssue = async (issueId: string): Promise<any | undefined> => {
|
||||
getIssue = async (issueId: string) => {
|
||||
try {
|
||||
if (!rootStore.user.localDBEnabled || this.status !== "ready") return;
|
||||
|
||||
@@ -441,15 +403,8 @@ export class Storage {
|
||||
return;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets sub-issues for an issue
|
||||
* @param workspaceSlug - Workspace identifier
|
||||
* @param projectId - Project identifier
|
||||
* @param issueId - Issue identifier
|
||||
* @returns Promise<any> - Sub-issues data
|
||||
*/
|
||||
public getSubIssues = async (workspaceSlug: string, projectId: string, issueId: string): Promise<any> => {
|
||||
const workspace_synced_at = await getOption("workspace_synced_at");
|
||||
getSubIssues = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||
const workspace_synced_at = await this.getOption("workspace_synced_at");
|
||||
if (!workspace_synced_at) {
|
||||
const issueService = new IssueService();
|
||||
return await issueService.subIssues(workspaceSlug, projectId, issueId);
|
||||
@@ -457,57 +412,74 @@ export class Storage {
|
||||
return await getSubIssuesWithDistribution(issueId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the status of a project
|
||||
* @param projectId - Project identifier
|
||||
* @returns Project status or undefined
|
||||
*/
|
||||
public getStatus = (projectId: string): "loading" | "ready" | "error" | "syncing" | undefined =>
|
||||
this.projectStatus[projectId]?.issues?.status || undefined;
|
||||
|
||||
/**
|
||||
* Sets the status of a project
|
||||
* @param projectId - Project identifier
|
||||
* @param status - New status
|
||||
*/
|
||||
public setStatus = (
|
||||
projectId: string,
|
||||
status: "loading" | "ready" | "error" | "syncing" | undefined = undefined
|
||||
): void => {
|
||||
getStatus = (projectId: string) => this.projectStatus[projectId]?.issues?.status || undefined;
|
||||
setStatus = (projectId: string, status: "loading" | "ready" | "error" | "syncing" | undefined = undefined) => {
|
||||
set(this.projectStatus, `${projectId}.issues.status`, status);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the sync promise for a project
|
||||
* @param projectId - Project identifier
|
||||
* @returns Sync promise or undefined
|
||||
*/
|
||||
public getSync = (projectId: string): Promise<void> | undefined => this.projectStatus[projectId]?.issues?.sync;
|
||||
|
||||
/**
|
||||
* Sets the sync promise for a project
|
||||
* @param projectId - Project identifier
|
||||
* @param sync - Sync promise
|
||||
*/
|
||||
public setSync = (projectId: string, sync: Promise<void> | undefined): void => {
|
||||
getSync = (projectId: string) => this.projectStatus[projectId]?.issues?.sync;
|
||||
setSync = (projectId: string, sync: Promise<void> | undefined) => {
|
||||
set(this.projectStatus, `${projectId}.issues.sync`, sync);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a write operation is in progress
|
||||
* @param op - Operation identifier
|
||||
* @returns Promise<boolean> - True if write is in progress
|
||||
*/
|
||||
public getIsWriteInProgress = async (op: string): Promise<boolean> => {
|
||||
const writeStartTime = (await getOption(op, "")) as string;
|
||||
getOption = async (key: string, fallback?: string | boolean | number) => {
|
||||
try {
|
||||
const options = await runQuery(`select * from options where key='${key}'`);
|
||||
if (options.length) {
|
||||
return options[0].value;
|
||||
}
|
||||
|
||||
return fallback;
|
||||
} catch (e) {
|
||||
return fallback;
|
||||
}
|
||||
};
|
||||
setOption = async (key: string, value: string) => {
|
||||
await runQuery(`insert or replace into options (key, value) values ('${key}', '${value}')`);
|
||||
};
|
||||
|
||||
deleteOption = async (key: string) => {
|
||||
await runQuery(` DELETE FROM options where key='${key}'`);
|
||||
};
|
||||
getOptions = async (keys: string[]) => {
|
||||
const options = await runQuery(`select * from options where key in ('${keys.join("','")}')`);
|
||||
return options.reduce((acc: any, option: any) => {
|
||||
acc[option.key] = option.value;
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
|
||||
getIsWriteInProgress = async (op: string) => {
|
||||
const writeStartTime = await this.getOption(op, false);
|
||||
if (writeStartTime) {
|
||||
// Check if it has been more than 5seconds
|
||||
const current = new Date();
|
||||
const start = new Date(writeStartTime);
|
||||
|
||||
return current.getTime() - start.getTime() < 5000;
|
||||
if (current.getTime() - start.getTime() < 5000) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
export const persistence = new Storage();
|
||||
|
||||
/**
|
||||
* format the issue fetched from local db into an issue
|
||||
* @param issue
|
||||
* @returns
|
||||
*/
|
||||
export const formatLocalIssue = (issue: any) => {
|
||||
const currIssue = issue;
|
||||
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 };
|
||||
};
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
// Moving functions from storage.sqlite.ts to storage.sqlite.utils.ts
|
||||
import { TIssue } from "@plane/types";
|
||||
import { ARRAY_FIELDS, BOOLEAN_FIELDS } from "./constants";
|
||||
import { runQuery } from "./query-executor";
|
||||
|
||||
/**
|
||||
* Formats an issue fetched from local db into the required format
|
||||
* @param issue - Raw issue data from database
|
||||
* @returns Formatted issue with proper types
|
||||
*/
|
||||
|
||||
export const formatLocalIssue = (
|
||||
issue: any
|
||||
): TIssue & { group_id?: string; total_issues: number; sub_group_id?: string } => {
|
||||
const currIssue = { ...issue };
|
||||
|
||||
// Parse array fields from JSON strings
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the last updated issue for a project
|
||||
* @param projectId - Project identifier
|
||||
* @returns Promise<any | undefined> - Last updated issue or undefined
|
||||
*/
|
||||
export const getLastUpdatedIssue = async (projectId: string): Promise<any | undefined> => {
|
||||
const lastUpdatedIssue = await runQuery(
|
||||
`select id, name, updated_at, sequence_id from issues WHERE project_id='${projectId}' AND is_local_update IS NULL order by datetime(updated_at) desc limit 1`
|
||||
);
|
||||
|
||||
return lastUpdatedIssue.length ? lastUpdatedIssue[0] : undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the last sync time for a project
|
||||
* @param projectId - Project identifier
|
||||
* @returns Promise<string | false> - Last sync time or false
|
||||
*/
|
||||
export const getLastSyncTime = async (projectId: string): Promise<string | false> => {
|
||||
const issue = await getLastUpdatedIssue(projectId);
|
||||
if (!issue) {
|
||||
return false;
|
||||
}
|
||||
return issue.updated_at;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the count of issues for a project
|
||||
* @param projectId - Project identifier
|
||||
* @returns Promise<number> - Count of issues
|
||||
*/
|
||||
export const getIssueCount = async (projectId: string): Promise<number> => {
|
||||
const count = await runQuery(`select count(*) as count from issues where project_id='${projectId}'`);
|
||||
return count[0]["count"];
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets an option value
|
||||
* @param key - Option key
|
||||
* @param fallback - Fallback value
|
||||
* @returns Option value or fallback
|
||||
*/
|
||||
export const getOption = async (
|
||||
key: string,
|
||||
fallback?: string | boolean | number
|
||||
): Promise<string | boolean | number | undefined> => {
|
||||
try {
|
||||
const options = await runQuery(`select * from options where key='${key}'`);
|
||||
if (options.length) {
|
||||
return options[0].value;
|
||||
}
|
||||
|
||||
return fallback;
|
||||
} catch (e) {
|
||||
return fallback;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets an option value
|
||||
* @param key - Option key
|
||||
* @param value - Option value
|
||||
*/
|
||||
export const setOption = async (key: string, value: string): Promise<void> => {
|
||||
await runQuery(`insert or replace into options (key, value) values ('${key}', '${value}')`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes an option
|
||||
* @param key - Option key
|
||||
*/
|
||||
export const deleteOption = async (key: string): Promise<void> => {
|
||||
await runQuery(` DELETE FROM options where key='${key}'`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets multiple options
|
||||
* @param keys - Option keys
|
||||
* @returns Options data
|
||||
*/
|
||||
export const getOptions = async (keys: string[]): Promise<Record<string, string | boolean | number>> => {
|
||||
const options = await runQuery(`select * from options where key in ('${keys.join("','")}')`);
|
||||
return options.reduce((acc: any, option: any) => {
|
||||
acc[option.key] = option.value;
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
@@ -19,7 +19,7 @@ export const logError = (e: any) => {
|
||||
};
|
||||
export const logInfo = console.info;
|
||||
|
||||
export const addIssueToPersistenceLayer = async (issue: TIssue) => {
|
||||
export const addIssueToPersistanceLayer = async (issue: TIssue) => {
|
||||
try {
|
||||
const issuePartial = pick({ ...JSON.parse(JSON.stringify(issue)) }, [
|
||||
"id",
|
||||
@@ -66,7 +66,7 @@ export const updatePersistentLayer = async (issueIds: string | string[]) => {
|
||||
const issue = rootStore.issue.issues.getIssueById(issueId);
|
||||
|
||||
if (issue) {
|
||||
addIssueToPersistenceLayer(issue);
|
||||
addIssueToPersistanceLayer(issue);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -174,7 +174,7 @@ export const clearOPFS = async (force = false) => {
|
||||
return;
|
||||
}
|
||||
// ts-ignore
|
||||
for await (const entry of (root as any)?.values()) {
|
||||
for await (const entry of root.values()) {
|
||||
if (entry.kind === "directory" && entry.name.startsWith(".ahp-")) {
|
||||
// A lock with the same name as the directory protects it from
|
||||
// being deleted.
|
||||
|
||||
@@ -72,10 +72,6 @@ export class DBClass {
|
||||
async exec(props: string | TQueryProps) {
|
||||
// @todo this will fail if the transaction is started any other way
|
||||
// eg: BEGIN, OR BEGIN TRANSACTION
|
||||
|
||||
// Below code ensures the transactions are queued
|
||||
// Ideally we should never have multiple transactions
|
||||
// running at the same time, in rare cases, it shouldn't be waiting for more than 1ms
|
||||
if (props === "BEGIN;") {
|
||||
let promiseToAwait;
|
||||
if (this.tp.length > 0) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { TIssue, TInboxIssue, TInboxIssueStatus, TInboxDuplicateIssueDetails } f
|
||||
// helpers
|
||||
import { EInboxIssueStatus } from "@/helpers/inbox.helper";
|
||||
// local db
|
||||
import { addIssueToPersistenceLayer } from "@/local-db/utils/utils";
|
||||
import { addIssueToPersistanceLayer } from "@/local-db/utils/utils";
|
||||
// services
|
||||
import { InboxIssueService } from "@/services/inbox";
|
||||
import { IssueService } from "@/services/issue";
|
||||
@@ -98,7 +98,7 @@ export class InboxIssueStore implements IInboxIssueStore {
|
||||
|
||||
// If issue accepted sync issue to local db
|
||||
if (status === EInboxIssueStatus.ACCEPTED) {
|
||||
addIssueToPersistenceLayer({ ...this.issue, ...inboxIssue.issue });
|
||||
addIssueToPersistanceLayer({ ...this.issue, ...inboxIssue.issue });
|
||||
}
|
||||
} catch {
|
||||
runInAction(() => set(this, "status", previousData.status));
|
||||
|
||||
@@ -23,7 +23,7 @@ import { EDraftIssuePaginationType } from "@/constants/workspace-drafts";
|
||||
// helpers
|
||||
import { getCurrentDateTimeInISO, convertToISODateString } from "@/helpers/date-time.helper";
|
||||
// local-db
|
||||
import { addIssueToPersistenceLayer } from "@/local-db/utils/utils";
|
||||
import { addIssueToPersistanceLayer } from "@/local-db/utils/utils";
|
||||
// services
|
||||
import workspaceDraftService from "@/services/issue/workspace_draft.service";
|
||||
// types
|
||||
@@ -352,7 +352,7 @@ export class WorkspaceDraftIssues implements IWorkspaceDraftIssues {
|
||||
}
|
||||
|
||||
// sync issue to local db
|
||||
addIssueToPersistenceLayer({ ...payload, ...response });
|
||||
addIssueToPersistanceLayer({ ...payload, ...response });
|
||||
|
||||
// Update draft issue count in workspaceUserInfo
|
||||
this.updateWorkspaceUserDraftIssueCount(workspaceSlug, -1);
|
||||
|
||||
Reference in New Issue
Block a user