1- import { IEstimate , IEstimatePoint , IWorkspaceMember } from "@plane/types" ;
1+ import { difference } from "lodash" ;
2+ import { IEstimate , IEstimatePoint , IWorkspaceMember , TIssue } from "@plane/types" ;
23import { API_BASE_URL } from "@/helpers/common.helper" ;
34import { EstimateService } from "@/plane-web/services/project/estimate.service" ;
45import { CycleService } from "@/services/cycle.service" ;
@@ -7,6 +8,7 @@ import { ModuleService } from "@/services/module.service";
78import { ProjectStateService } from "@/services/project" ;
89import { WorkspaceService } from "@/services/workspace.service" ;
910import { persistence } from "../storage.sqlite" ;
11+ import { updateIssue } from "./load-issues" ;
1012import {
1113 cycleSchema ,
1214 estimatePointSchema ,
@@ -103,6 +105,151 @@ export const getMembers = async (workspaceSlug: string) => {
103105 return objects ;
104106} ;
105107
108+ const syncLabels = async ( currentLabels : any ) => {
109+ const currentIdList = currentLabels . map ( ( label : any ) => label . id ) ;
110+ const existingLabels = await persistence . db . exec ( "SELECT id FROM labels;" ) ;
111+
112+ const existingIdList = existingLabels . map ( ( label : any ) => label . id ) ;
113+
114+ const deletedIds = difference ( existingIdList , currentIdList ) ;
115+
116+ await syncIssuesWithDeletedLabels ( deletedIds as string [ ] ) ;
117+ } ;
118+
119+ export const syncIssuesWithDeletedLabels = async ( deletedLabelIds : string [ ] ) => {
120+ if ( ! deletedLabelIds . length ) {
121+ return ;
122+ }
123+
124+ // Ideally we should use recursion to fetch all the issues, but 10000 issues is more than enough for now.
125+ const issues = await persistence . getIssues ( "" , "" , { labels : deletedLabelIds . join ( "," ) , cursor : "10000:0:0" } , { } ) ;
126+ if ( issues ?. results && Array . isArray ( issues . results ) ) {
127+ const promises = issues . results . map ( async ( issue : TIssue ) => {
128+ const updatedIssue = {
129+ ...issue ,
130+ label_ids : issue . label_ids . filter ( ( id : string ) => ! deletedLabelIds . includes ( id ) ) ,
131+ is_local_update : 1 ,
132+ } ;
133+ // We should await each update because it uses a transaction. But transaction are handled in the query executor.
134+ updateIssue ( updatedIssue ) ;
135+ } ) ;
136+ await Promise . all ( promises ) ;
137+ }
138+ } ;
139+
140+ const syncModules = async ( currentModules : any ) => {
141+ const currentIdList = currentModules . map ( ( module : any ) => module . id ) ;
142+ const existingModules = await persistence . db . exec ( "SELECT id FROM modules;" ) ;
143+ const existingIdList = existingModules . map ( ( module : any ) => module . id ) ;
144+ const deletedIds = difference ( existingIdList , currentIdList ) ;
145+ await syncIssuesWithDeletedModules ( deletedIds as string [ ] ) ;
146+ } ;
147+
148+ export const syncIssuesWithDeletedModules = async ( deletedModuleIds : string [ ] ) => {
149+ if ( ! deletedModuleIds . length ) {
150+ return ;
151+ }
152+
153+ const issues = await persistence . getIssues ( "" , "" , { modules : deletedModuleIds . join ( "," ) , cursor : "10000:0:0" } , { } ) ;
154+ if ( issues ?. results && Array . isArray ( issues . results ) ) {
155+ const promises = issues . results . map ( async ( issue : TIssue ) => {
156+ const updatedIssue = {
157+ ...issue ,
158+ module_ids : issue . module_ids ?. filter ( ( id : string ) => ! deletedModuleIds . includes ( id ) ) || [ ] ,
159+ is_local_update : 1 ,
160+ } ;
161+ updateIssue ( updatedIssue ) ;
162+ } ) ;
163+ await Promise . all ( promises ) ;
164+ }
165+ } ;
166+
167+ const syncCycles = async ( currentCycles : any ) => {
168+ const currentIdList = currentCycles . map ( ( cycle : any ) => cycle . id ) ;
169+ const existingCycles = await persistence . db . exec ( "SELECT id FROM cycles;" ) ;
170+ const existingIdList = existingCycles . map ( ( cycle : any ) => cycle . id ) ;
171+ const deletedIds = difference ( existingIdList , currentIdList ) ;
172+ await syncIssuesWithDeletedCycles ( deletedIds as string [ ] ) ;
173+ } ;
174+
175+ export const syncIssuesWithDeletedCycles = async ( deletedCycleIds : string [ ] ) => {
176+ if ( ! deletedCycleIds . length ) {
177+ return ;
178+ }
179+
180+ const issues = await persistence . getIssues ( "" , "" , { cycles : deletedCycleIds . join ( "," ) , cursor : "10000:0:0" } , { } ) ;
181+ if ( issues ?. results && Array . isArray ( issues . results ) ) {
182+ const promises = issues . results . map ( async ( issue : TIssue ) => {
183+ const updatedIssue = {
184+ ...issue ,
185+ cycle_id : null ,
186+ is_local_update : 1 ,
187+ } ;
188+ updateIssue ( updatedIssue ) ;
189+ } ) ;
190+ await Promise . all ( promises ) ;
191+ }
192+ } ;
193+
194+ const syncStates = async ( currentStates : any ) => {
195+ const currentIdList = currentStates . map ( ( state : any ) => state . id ) ;
196+ const existingStates = await persistence . db . exec ( "SELECT id FROM states;" ) ;
197+ const existingIdList = existingStates . map ( ( state : any ) => state . id ) ;
198+ const deletedIds = difference ( existingIdList , currentIdList ) ;
199+ await syncIssuesWithDeletedStates ( deletedIds as string [ ] ) ;
200+ } ;
201+
202+ export const syncIssuesWithDeletedStates = async ( deletedStateIds : string [ ] ) => {
203+ if ( ! deletedStateIds . length ) {
204+ return ;
205+ }
206+
207+ const issues = await persistence . getIssues ( "" , "" , { states : deletedStateIds . join ( "," ) , cursor : "10000:0:0" } , { } ) ;
208+ if ( issues ?. results && Array . isArray ( issues . results ) ) {
209+ const promises = issues . results . map ( async ( issue : TIssue ) => {
210+ const updatedIssue = {
211+ ...issue ,
212+ state_id : null ,
213+ is_local_update : 1 ,
214+ } ;
215+ updateIssue ( updatedIssue ) ;
216+ } ) ;
217+ await Promise . all ( promises ) ;
218+ }
219+ } ;
220+
221+ const syncMembers = async ( currentMembers : any ) => {
222+ const currentIdList = currentMembers . map ( ( member : any ) => member . id ) ;
223+ const existingMembers = await persistence . db . exec ( "SELECT id FROM members;" ) ;
224+ const existingIdList = existingMembers . map ( ( member : any ) => member . id ) ;
225+ const deletedIds = difference ( existingIdList , currentIdList ) ;
226+ await syncIssuesWithDeletedMembers ( deletedIds as string [ ] ) ;
227+ } ;
228+
229+ export const syncIssuesWithDeletedMembers = async ( deletedMemberIds : string [ ] ) => {
230+ if ( ! deletedMemberIds . length ) {
231+ return ;
232+ }
233+
234+ const issues = await persistence . getIssues (
235+ "" ,
236+ "" ,
237+ { assignees : deletedMemberIds . join ( "," ) , cursor : "10000:0:0" } ,
238+ { }
239+ ) ;
240+ if ( issues ?. results && Array . isArray ( issues . results ) ) {
241+ const promises = issues . results . map ( async ( issue : TIssue ) => {
242+ const updatedIssue = {
243+ ...issue ,
244+ assignee_ids : issue . assignee_ids . filter ( ( id : string ) => ! deletedMemberIds . includes ( id ) ) ,
245+ is_local_update : 1 ,
246+ } ;
247+ updateIssue ( updatedIssue ) ;
248+ } ) ;
249+ await Promise . all ( promises ) ;
250+ }
251+ } ;
252+
106253export const loadWorkSpaceData = async ( workspaceSlug : string ) => {
107254 if ( ! persistence . db || ! persistence . db . exec ) {
108255 return ;
@@ -117,28 +264,45 @@ export const loadWorkSpaceData = async (workspaceSlug: string) => {
117264 promises . push ( getMembers ( workspaceSlug ) ) ;
118265 const [ labels , modules , cycles , states , estimates , members ] = await Promise . all ( promises ) ;
119266
267+ // @todo : we don't need this manual sync here, when backend adds these changes to issue activity and updates the updated_at of the issue.
268+ await syncLabels ( labels ) ;
269+ await syncModules ( modules ) ;
270+ await syncCycles ( cycles ) ;
271+ await syncStates ( states ) ;
272+ // TODO: Not handling sync estimates yet, as we don't know the new estimate point assigned.
273+ // Backend should update the updated_at of the issue when estimate point is updated, or we should have realtime sync on the issues table.
274+ // await syncEstimates(estimates);
275+ await syncMembers ( members ) ;
276+
120277 const start = performance . now ( ) ;
278+
121279 await persistence . db . exec ( "BEGIN;" ) ;
280+ await persistence . db . exec ( "DELETE FROM labels WHERE 1=1;" ) ;
122281 await batchInserts ( labels , "labels" , labelSchema ) ;
123282 await persistence . db . exec ( "COMMIT;" ) ;
124283
125284 await persistence . db . exec ( "BEGIN;" ) ;
285+ await persistence . db . exec ( "DELETE FROM modules WHERE 1=1;" ) ;
126286 await batchInserts ( modules , "modules" , moduleSchema ) ;
127287 await persistence . db . exec ( "COMMIT;" ) ;
128288
129289 await persistence . db . exec ( "BEGIN;" ) ;
290+ await persistence . db . exec ( "DELETE FROM cycles WHERE 1=1;" ) ;
130291 await batchInserts ( cycles , "cycles" , cycleSchema ) ;
131292 await persistence . db . exec ( "COMMIT;" ) ;
132293
133294 await persistence . db . exec ( "BEGIN;" ) ;
295+ await persistence . db . exec ( "DELETE FROM states WHERE 1=1;" ) ;
134296 await batchInserts ( states , "states" , stateSchema ) ;
135297 await persistence . db . exec ( "COMMIT;" ) ;
136298
137299 await persistence . db . exec ( "BEGIN;" ) ;
300+ await persistence . db . exec ( "DELETE FROM estimate_points WHERE 1=1;" ) ;
138301 await batchInserts ( estimates , "estimate_points" , estimatePointSchema ) ;
139302 await persistence . db . exec ( "COMMIT;" ) ;
140303
141304 await persistence . db . exec ( "BEGIN;" ) ;
305+ await persistence . db . exec ( "DELETE FROM members WHERE 1=1;" ) ;
142306 await batchInserts ( members , "members" , memberSchema ) ;
143307 await persistence . db . exec ( "COMMIT;" ) ;
144308
0 commit comments