plugin-git-manager 1.1.10 → 1.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/187.d5545b7cc8b90bfc.js +10 -0
- package/dist/client/index.js +1 -1
- package/dist/locale/en-US.json +2 -0
- package/dist/locale/vi-VN.json +2 -0
- package/dist/server/actions/git-actions.js +15 -12
- package/dist/server/actions/review.d.ts +5 -2
- package/dist/server/actions/review.js +171 -28
- package/dist/server/collections/gitCodeReviews.d.ts +1 -1
- package/dist/server/collections/gitCodeReviews.js +1 -0
- package/dist/server/collections/gitRepositories.d.ts +1 -1
- package/dist/server/collections/gitReviewFlows.d.ts +1 -1
- package/dist/server/plugin.js +5 -0
- package/package.json +1 -1
- package/src/client/components/CommitHistory.tsx +3 -0
- package/src/client/components/FileExplorer.tsx +29 -24
- package/src/client/components/GitOperations.tsx +3 -0
- package/src/client/components/ReviewFlows.tsx +11 -1
- package/src/client/components/ReviewHistory.tsx +14 -1
- package/src/locale/en-US.json +2 -0
- package/src/locale/vi-VN.json +2 -0
- package/src/server/actions/git-actions.ts +15 -12
- package/src/server/actions/review.ts +219 -38
- package/src/server/collections/gitCodeReviews.ts +1 -0
- package/src/server/plugin.ts +25 -20
- package/dist/client/187.08dd0bf4d0f68036.js +0 -10
|
@@ -3,6 +3,17 @@ import type { Application } from '@nocobase/server';
|
|
|
3
3
|
import { parseGitLabProject } from '../utils/gitlab-url';
|
|
4
4
|
import { redactPat } from '../utils/redact';
|
|
5
5
|
|
|
6
|
+
export const WORKER_JOB_GIT_REVIEW_PROCESS = 'git-review:process';
|
|
7
|
+
const REVIEW_QUEUE_CHANNEL = 'plugin-git-manager.review';
|
|
8
|
+
const REVIEW_QUEUE_CONCURRENCY = Math.max(
|
|
9
|
+
1,
|
|
10
|
+
Number.parseInt(process.env.GIT_REVIEW_QUEUE_CONCURRENCY || process.env.GIT_REVIEW_MAX_CONCURRENCY || '3', 10) || 3,
|
|
11
|
+
);
|
|
12
|
+
const REVIEW_QUEUE_TIMEOUT_MS = Math.max(
|
|
13
|
+
60_000,
|
|
14
|
+
Number.parseInt(process.env.GIT_REVIEW_QUEUE_TIMEOUT_MS || '', 10) || 10 * 60 * 1000,
|
|
15
|
+
);
|
|
16
|
+
|
|
6
17
|
interface TriggerArgs {
|
|
7
18
|
flowId?: number | null;
|
|
8
19
|
repositoryId: number;
|
|
@@ -16,6 +27,29 @@ interface TriggerArgs {
|
|
|
16
27
|
userId?: number | string | null;
|
|
17
28
|
}
|
|
18
29
|
|
|
30
|
+
interface ReviewFlowSnapshot {
|
|
31
|
+
id: number;
|
|
32
|
+
name?: string;
|
|
33
|
+
postMode?: string;
|
|
34
|
+
llmService?: string | null;
|
|
35
|
+
model?: string | null;
|
|
36
|
+
instructions?: string | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface ReviewQueueMessage {
|
|
40
|
+
reviewId: number;
|
|
41
|
+
repositoryId: number;
|
|
42
|
+
targetType: 'mr' | 'commit' | 'branch';
|
|
43
|
+
mrIid?: number | null;
|
|
44
|
+
commitSha?: string | null;
|
|
45
|
+
branch?: string | null;
|
|
46
|
+
headSha?: string | null;
|
|
47
|
+
aiEmployeeUsername: string;
|
|
48
|
+
extraInstructions?: string;
|
|
49
|
+
userId?: number | string | null;
|
|
50
|
+
flowSnapshot?: ReviewFlowSnapshot;
|
|
51
|
+
}
|
|
52
|
+
|
|
19
53
|
function getActionParams(ctx: Context) {
|
|
20
54
|
return { ...ctx.action.params, ...ctx.action.params?.values, ...((ctx as any).request?.body || {}) };
|
|
21
55
|
}
|
|
@@ -29,8 +63,8 @@ function getActionParams(ctx: Context) {
|
|
|
29
63
|
* Uses `app.lockManager` so the same code path covers both single-node
|
|
30
64
|
* (in-memory `async-mutex`) and HA cluster (Redis-backed Redlock when
|
|
31
65
|
* `plugin-cluster-manager` is active and `LOCK_ADAPTER_DEFAULT=redis`).
|
|
32
|
-
* The locked region only does an upsert +
|
|
33
|
-
*
|
|
66
|
+
* The locked region only does an upsert + queue publish (a few ms), so a 30s
|
|
67
|
+
* TTL is generous and auto-releases if the process crashes.
|
|
34
68
|
*/
|
|
35
69
|
function targetKey(args: TriggerArgs): string {
|
|
36
70
|
if (args.targetType === 'mr') return `${args.repositoryId}:mr:${args.mrIid}`;
|
|
@@ -47,8 +81,8 @@ async function withTriggerLock<T>(app: Application, key: string, fn: () => Promi
|
|
|
47
81
|
|
|
48
82
|
/**
|
|
49
83
|
* Trigger an AI-driven code review for an MR / commit / branch.
|
|
50
|
-
* The review record is upserted synchronously, then
|
|
51
|
-
*
|
|
84
|
+
* The review record is upserted synchronously, then queued for an available
|
|
85
|
+
* git-review worker. The action returns immediately with the reviewId.
|
|
52
86
|
*/
|
|
53
87
|
export async function triggerReview(ctx: Context, next: () => Promise<void>) {
|
|
54
88
|
const params = getActionParams(ctx);
|
|
@@ -162,13 +196,12 @@ async function triggerReviewInternalLocked(app: Application, args: TriggerArgs):
|
|
|
162
196
|
latestSha: headSha || existingLatestSha || null,
|
|
163
197
|
triggeredBy: args.triggeredBy || 'manual',
|
|
164
198
|
status: 'pending',
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
startedAt: new Date(),
|
|
199
|
+
// `startedAt` is stamped by the queue worker when execution actually
|
|
200
|
+
// starts. Pending rows may be legitimately waiting in Redis.
|
|
201
|
+
startedAt: null,
|
|
169
202
|
finishedAt: null,
|
|
170
203
|
durationMs: null,
|
|
171
|
-
postStatus: flow.
|
|
204
|
+
postStatus: getInitialPostStatus(flow, args.targetType),
|
|
172
205
|
error: null,
|
|
173
206
|
};
|
|
174
207
|
|
|
@@ -193,26 +226,146 @@ async function triggerReviewInternalLocked(app: Application, args: TriggerArgs):
|
|
|
193
226
|
reviewId = review.get('id') as number;
|
|
194
227
|
}
|
|
195
228
|
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
229
|
+
// Queue for background workers; the action returns as soon as the message is published.
|
|
230
|
+
await enqueueReview(app, {
|
|
231
|
+
reviewId,
|
|
232
|
+
repositoryId: args.repositoryId,
|
|
233
|
+
targetType: args.targetType,
|
|
234
|
+
mrIid: args.targetType === 'mr' ? args.mrIid : null,
|
|
235
|
+
commitSha: args.targetType === 'commit' ? args.commitSha : null,
|
|
236
|
+
branch: args.branch || undefined,
|
|
237
|
+
headSha,
|
|
238
|
+
aiEmployeeUsername,
|
|
239
|
+
extraInstructions: args.extraInstructions,
|
|
240
|
+
userId: args.userId ?? null,
|
|
241
|
+
flowSnapshot: createFlowSnapshot(flow),
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
return reviewId;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function registerReviewQueue(app: Application) {
|
|
248
|
+
app.eventQueue.subscribe(REVIEW_QUEUE_CHANNEL, {
|
|
249
|
+
concurrency: REVIEW_QUEUE_CONCURRENCY,
|
|
250
|
+
idle: () => isGitReviewWorker(app),
|
|
251
|
+
process: async (message: ReviewQueueMessage) => {
|
|
252
|
+
await processQueuedReview(app, message);
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function unregisterReviewQueue(app: Application) {
|
|
258
|
+
app.eventQueue.unsubscribe(REVIEW_QUEUE_CHANNEL);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function createFlowSnapshot(flow: any): ReviewFlowSnapshot {
|
|
262
|
+
return {
|
|
263
|
+
id: Number(flow.get('id')),
|
|
264
|
+
name: flow.get('name') as string,
|
|
265
|
+
postMode: flow.get('postMode') as string,
|
|
266
|
+
llmService: flow.get('llmService') as string | null,
|
|
267
|
+
model: flow.get('model') as string | null,
|
|
268
|
+
instructions: flow.get('instructions') as string | null,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function createFlowFromSnapshot(snapshot: ReviewFlowSnapshot | undefined, fallback?: any) {
|
|
273
|
+
return {
|
|
274
|
+
get(name: string) {
|
|
275
|
+
if (snapshot && Object.prototype.hasOwnProperty.call(snapshot, name)) {
|
|
276
|
+
return (snapshot as any)[name];
|
|
277
|
+
}
|
|
278
|
+
return fallback?.get?.(name);
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function isGitReviewWorker(app: Application): boolean {
|
|
284
|
+
const workerMode = process.env.WORKER_MODE || '';
|
|
285
|
+
return (
|
|
286
|
+
app.serving(WORKER_JOB_GIT_REVIEW_PROCESS) ||
|
|
287
|
+
workerMode === 'worker' ||
|
|
288
|
+
workerMode === 'task' ||
|
|
289
|
+
process.env.APP_ROLE === 'worker'
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function enqueueReview(app: Application, message: ReviewQueueMessage) {
|
|
294
|
+
try {
|
|
295
|
+
await app.eventQueue.publish(REVIEW_QUEUE_CHANNEL, message, {
|
|
296
|
+
timeout: REVIEW_QUEUE_TIMEOUT_MS,
|
|
297
|
+
maxRetries: 1,
|
|
298
|
+
});
|
|
299
|
+
} catch (err: any) {
|
|
300
|
+
const safeMessage = redactPat(err?.message || String(err));
|
|
301
|
+
await app.db.getRepository('gitCodeReviews').update({
|
|
302
|
+
filterByTk: message.reviewId,
|
|
303
|
+
values: {
|
|
304
|
+
status: 'failed',
|
|
305
|
+
error: `Failed to enqueue review: ${safeMessage}`,
|
|
306
|
+
finishedAt: new Date(),
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
throw err;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async function failQueuedReview(app: Application, reviewId: number, err: any) {
|
|
314
|
+
const safeMessage = redactPat(err?.message || String(err));
|
|
315
|
+
await app.db.getRepository('gitCodeReviews').update({
|
|
316
|
+
filterByTk: reviewId,
|
|
317
|
+
values: {
|
|
318
|
+
status: 'failed',
|
|
319
|
+
error: safeMessage,
|
|
320
|
+
finishedAt: new Date(),
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async function processQueuedReview(app: Application, message: ReviewQueueMessage) {
|
|
326
|
+
const db = app.db;
|
|
327
|
+
const reviewsRepo = db.getRepository('gitCodeReviews');
|
|
328
|
+
const review = await reviewsRepo.findOne({ filterByTk: message.reviewId });
|
|
329
|
+
if (!review) {
|
|
330
|
+
app.log?.warn?.(`git review queue: review ${message.reviewId} not found, skipping`);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (review.get('status') !== 'pending') {
|
|
335
|
+
app.log?.info?.(`git review queue: review ${message.reviewId} is ${review.get('status')}, skipping`);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
try {
|
|
340
|
+
const repo = await db.getRepository('gitRepositories').findOne({
|
|
341
|
+
filterByTk: message.repositoryId || review.get('repositoryId'),
|
|
342
|
+
});
|
|
343
|
+
if (!repo) throw new Error('Repository not found');
|
|
344
|
+
|
|
345
|
+
const storedFlow = await db.getRepository('gitReviewFlows').findOne({
|
|
346
|
+
filterByTk: message.flowSnapshot?.id || review.get('flowId'),
|
|
347
|
+
});
|
|
348
|
+
const flow = createFlowFromSnapshot(message.flowSnapshot, storedFlow);
|
|
349
|
+
const aiEmployeeUsername = message.aiEmployeeUsername || (flow.get('aiEmployeeUsername') as string);
|
|
350
|
+
if (!aiEmployeeUsername) throw new Error('Flow has no AI employee configured');
|
|
351
|
+
|
|
352
|
+
await runReview(app, {
|
|
353
|
+
reviewId: message.reviewId,
|
|
200
354
|
flow,
|
|
201
355
|
repo,
|
|
202
|
-
targetType:
|
|
203
|
-
mrIid:
|
|
204
|
-
commitSha:
|
|
205
|
-
branch:
|
|
206
|
-
headSha,
|
|
356
|
+
targetType: message.targetType || review.get('targetType'),
|
|
357
|
+
mrIid: message.targetType === 'mr' ? message.mrIid ?? Number(review.get('mrIid')) : null,
|
|
358
|
+
commitSha: message.targetType === 'commit' ? message.commitSha || (review.get('commitSha') as string) : null,
|
|
359
|
+
branch: message.branch || (review.get('branch') as string | undefined),
|
|
360
|
+
headSha: message.headSha || (review.get('headSha') as string | null),
|
|
207
361
|
aiEmployeeUsername,
|
|
208
|
-
extraInstructions:
|
|
209
|
-
userId:
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
return reviewId;
|
|
362
|
+
extraInstructions: message.extraInstructions,
|
|
363
|
+
userId: message.userId ?? null,
|
|
364
|
+
});
|
|
365
|
+
} catch (err) {
|
|
366
|
+
app.log?.error?.('git review queue: failed before review execution', err);
|
|
367
|
+
await failQueuedReview(app, message.reviewId, err);
|
|
368
|
+
}
|
|
216
369
|
}
|
|
217
370
|
|
|
218
371
|
/**
|
|
@@ -248,6 +401,7 @@ export async function reviewApprovePost(ctx: Context, next: () => Promise<void>)
|
|
|
248
401
|
postedNoteId: String(noteId),
|
|
249
402
|
approvedBy: userId ? String(userId) : null,
|
|
250
403
|
approvedAt: new Date(),
|
|
404
|
+
error: null,
|
|
251
405
|
},
|
|
252
406
|
});
|
|
253
407
|
|
|
@@ -432,9 +586,10 @@ async function runReview(app: Application, args: RunReviewArgs) {
|
|
|
432
586
|
const finishedAt = new Date();
|
|
433
587
|
const durationMs = finishedAt.getTime() - startedAt.getTime();
|
|
434
588
|
|
|
435
|
-
const postMode = args.flow
|
|
436
|
-
let postStatus =
|
|
589
|
+
const postMode = getFlowPostMode(args.flow);
|
|
590
|
+
let postStatus = getInitialPostStatus(args.flow, args.targetType);
|
|
437
591
|
let postedNoteId: string | null = null;
|
|
592
|
+
let autoPostError: string | null = null;
|
|
438
593
|
|
|
439
594
|
if (postMode === 'disabled') {
|
|
440
595
|
postStatus = 'skipped';
|
|
@@ -443,6 +598,8 @@ async function runReview(app: Application, args: RunReviewArgs) {
|
|
|
443
598
|
postedNoteId = String(await postNoteToGitLab(args.repo, args.mrIid, content));
|
|
444
599
|
postStatus = 'posted';
|
|
445
600
|
} catch (err: any) {
|
|
601
|
+
autoPostError = redactPat(err?.message || String(err));
|
|
602
|
+
postStatus = 'post_failed';
|
|
446
603
|
app.log?.error?.('Auto-post review note failed', err);
|
|
447
604
|
}
|
|
448
605
|
}
|
|
@@ -461,11 +618,14 @@ async function runReview(app: Application, args: RunReviewArgs) {
|
|
|
461
618
|
finishedAt,
|
|
462
619
|
postStatus,
|
|
463
620
|
postedNoteId,
|
|
621
|
+
error: autoPostError ? `Auto-post failed: ${autoPostError}` : null,
|
|
464
622
|
metadata: {
|
|
465
623
|
flowName: args.flow.get('name'),
|
|
466
624
|
aiEmployeeUsername: args.aiEmployeeUsername,
|
|
467
625
|
llmService,
|
|
468
626
|
model,
|
|
627
|
+
postMode,
|
|
628
|
+
autoPostError,
|
|
469
629
|
},
|
|
470
630
|
},
|
|
471
631
|
});
|
|
@@ -682,6 +842,30 @@ async function postNoteToGitLab(repo: any, mrIid: number, body: string): Promise
|
|
|
682
842
|
const MAX_BRANCH_FILTER_LENGTH = 200;
|
|
683
843
|
const loggedBadFilters = new Set<string>();
|
|
684
844
|
|
|
845
|
+
type ReviewPostMode = 'auto' | 'manual' | 'disabled';
|
|
846
|
+
|
|
847
|
+
function getFlowPostMode(flow: any): ReviewPostMode {
|
|
848
|
+
const rawValue = flow?.get?.('postMode');
|
|
849
|
+
const value = rawValue && typeof rawValue === 'object' && 'value' in rawValue ? rawValue.value : rawValue;
|
|
850
|
+
const normalized = String(value || 'manual')
|
|
851
|
+
.trim()
|
|
852
|
+
.toLowerCase()
|
|
853
|
+
.replace(/[\s-]+/g, '_');
|
|
854
|
+
|
|
855
|
+
if (['auto', 'auto_post', 'autopost', 'auto_post_to_mr'].includes(normalized)) return 'auto';
|
|
856
|
+
if (['disabled', 'disable', 'do_not_post', 'dont_post', 'none', 'skip', 'skipped'].includes(normalized)) {
|
|
857
|
+
return 'disabled';
|
|
858
|
+
}
|
|
859
|
+
return 'manual';
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
function getInitialPostStatus(flow: any, targetType: string): string {
|
|
863
|
+
const postMode = getFlowPostMode(flow);
|
|
864
|
+
if (postMode === 'disabled') return 'skipped';
|
|
865
|
+
if (postMode === 'auto' && targetType !== 'mr') return 'skipped';
|
|
866
|
+
return 'pending_approval';
|
|
867
|
+
}
|
|
868
|
+
|
|
685
869
|
function warnInvalidBranchFilter(filter: string, reason: string) {
|
|
686
870
|
if (loggedBadFilters.has(filter)) return;
|
|
687
871
|
loggedBadFilters.add(filter);
|
|
@@ -721,11 +905,10 @@ function throwHttp(status: number, message: string): never {
|
|
|
721
905
|
}
|
|
722
906
|
|
|
723
907
|
/**
|
|
724
|
-
*
|
|
725
|
-
*
|
|
726
|
-
*
|
|
727
|
-
*
|
|
728
|
-
* timeout (5 min) plus a safety margin and mark it as failed.
|
|
908
|
+
* Review execution is delegated to the distributed event queue. When a worker
|
|
909
|
+
* restarts mid-run, the DB record can stay in `status='running'` indefinitely.
|
|
910
|
+
* On startup we sweep any `running` review whose `startedAt` is older than the
|
|
911
|
+
* in-process timeout (5 min) plus a safety margin and mark it as failed.
|
|
729
912
|
*
|
|
730
913
|
* The cutoff is intentionally larger than the runtime timeout so concurrent
|
|
731
914
|
* reviews running on a *different* node in an HA cluster aren't clobbered.
|
|
@@ -736,13 +919,11 @@ export async function recoverStuckReviews(app: Application): Promise<number> {
|
|
|
736
919
|
try {
|
|
737
920
|
const reviewsRepo = app.db.getRepository('gitCodeReviews');
|
|
738
921
|
const cutoff = new Date(Date.now() - STUCK_REVIEW_CUTOFF_MS);
|
|
739
|
-
//
|
|
740
|
-
//
|
|
741
|
-
// Both have `startedAt` stamped at trigger time so the cutoff applies
|
|
742
|
-
// consistently.
|
|
922
|
+
// Pending rows may be legitimately waiting in Redis, so only sweep reviews
|
|
923
|
+
// that were actually picked up by a worker.
|
|
743
924
|
const stuck = await reviewsRepo.find({
|
|
744
925
|
filter: {
|
|
745
|
-
status:
|
|
926
|
+
status: 'running',
|
|
746
927
|
startedAt: { $lt: cutoff },
|
|
747
928
|
},
|
|
748
929
|
});
|
|
@@ -136,6 +136,7 @@ export default defineCollection({
|
|
|
136
136
|
{ value: 'pending_approval', label: 'Pending Approval' },
|
|
137
137
|
{ value: 'approved', label: 'Approved' },
|
|
138
138
|
{ value: 'posted', label: 'Posted' },
|
|
139
|
+
{ value: 'post_failed', label: 'Post Failed' },
|
|
139
140
|
{ value: 'skipped', label: 'Skipped' },
|
|
140
141
|
{ value: 'rejected', label: 'Rejected' },
|
|
141
142
|
],
|
package/src/server/plugin.ts
CHANGED
|
@@ -3,9 +3,9 @@ import { resolve } from 'path';
|
|
|
3
3
|
import { DataTypes } from 'sequelize';
|
|
4
4
|
import * as gitActions from './actions/git-actions';
|
|
5
5
|
import * as gitlabApi from './actions/gitlab-api';
|
|
6
|
-
import * as reviewActions from './actions/review';
|
|
7
|
-
import * as pollerActions from './actions/poller';
|
|
8
|
-
import { recoverStuckReviews } from './actions/review';
|
|
6
|
+
import * as reviewActions from './actions/review';
|
|
7
|
+
import * as pollerActions from './actions/poller';
|
|
8
|
+
import { recoverStuckReviews, registerReviewQueue, unregisterReviewQueue } from './actions/review';
|
|
9
9
|
import { registerGitReviewAiTools } from './ai-tools';
|
|
10
10
|
import { startPoller, stopPoller } from './poller';
|
|
11
11
|
|
|
@@ -77,9 +77,10 @@ export class PluginGitManagerServer extends Plugin {
|
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
79
|
return next();
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
registerReviewQueue((this as any).app);
|
|
83
|
+
registerGitReviewAiTools((this as any).app);
|
|
83
84
|
|
|
84
85
|
(this as any).app.on('afterStart', async () => {
|
|
85
86
|
await ensureAutoReviewFlowSchema((this as any).app).catch(
|
|
@@ -91,12 +92,14 @@ export class PluginGitManagerServer extends Plugin {
|
|
|
91
92
|
);
|
|
92
93
|
startPoller((this as any).app);
|
|
93
94
|
});
|
|
94
|
-
(this as any).app.on('beforeStop', () => {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
95
|
+
(this as any).app.on('beforeStop', () => {
|
|
96
|
+
unregisterReviewQueue((this as any).app);
|
|
97
|
+
stopPoller();
|
|
98
|
+
});
|
|
99
|
+
(this as any).app.on('beforeDestroy', () => {
|
|
100
|
+
unregisterReviewQueue((this as any).app);
|
|
101
|
+
stopPoller();
|
|
102
|
+
});
|
|
100
103
|
|
|
101
104
|
// Read-only operations available to all plugin users
|
|
102
105
|
(this as any).app.acl.registerSnippet({
|
|
@@ -188,14 +191,16 @@ export class PluginGitManagerServer extends Plugin {
|
|
|
188
191
|
await (this as any).app.db.getCollection('gitRepositories')?.sync();
|
|
189
192
|
}
|
|
190
193
|
|
|
191
|
-
async beforeDisable() {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
194
|
+
async beforeDisable() {
|
|
195
|
+
unregisterReviewQueue((this as any).app);
|
|
196
|
+
stopPoller();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async beforeUnload() {
|
|
200
|
+
unregisterReviewQueue((this as any).app);
|
|
201
|
+
stopPoller();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
199
204
|
|
|
200
205
|
export async function ensureAutoReviewFlowSchema(app: any) {
|
|
201
206
|
const sequelize = app.db?.sequelize;
|