Skip to content

Commit 0ff78f5

Browse files
author
Andre Turner
committed
engagement status migration
1 parent daa10e3 commit 0ff78f5

File tree

1 file changed

+146
-0
lines changed

1 file changed

+146
-0
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import { ModuleRef } from '@nestjs/core';
2+
import { node, relation } from 'cypher-query-builder';
3+
import { chunk } from 'lodash';
4+
import { DateTime } from 'luxon';
5+
import { Disabled, ID } from '~/common';
6+
import { BaseMigration, Migration } from '~/core/database';
7+
import { ACTIVE, variable } from '~/core/database/query';
8+
import { SystemAgentRepository } from '../../../user/system-agent.repository';
9+
import { Engagement, EngagementStatus } from '../../dto';
10+
import { EngagementWorkflowRepository } from '../engagement-workflow.repository';
11+
import { EngagementWorkflowService } from '../engagement-workflow.service';
12+
13+
@Disabled('Until Carson reviews')(Migration('2024-06-07T18:00:02'))
14+
export class EngagementStatusHistoryToWorkflowEventsMigration extends BaseMigration {
15+
constructor(
16+
private readonly agents: SystemAgentRepository,
17+
private readonly workflow: EngagementWorkflowService,
18+
private readonly moduleRef: ModuleRef,
19+
) {
20+
super();
21+
}
22+
23+
async up() {
24+
const ghost = await this.agents.getGhost();
25+
const engagements = await this.db
26+
.query()
27+
.match(node('engagement', 'Engagement'))
28+
.match(node('ghost', 'Actor', { id: ghost.id }))
29+
.subQuery('engagement', (sub) =>
30+
sub
31+
.match([
32+
node('engagement', 'Engagement'),
33+
relation('out', '', 'status'),
34+
node('status'),
35+
])
36+
.with('status')
37+
.orderBy('status.createdAt', 'asc')
38+
.return('collect(apoc.convert.toMap(status)) as steps'),
39+
)
40+
.with('engagement, steps')
41+
.raw('where size(steps) > 1')
42+
.return<{
43+
engagement: { id: ID };
44+
steps: ReadonlyArray<{ value: EngagementStatus; createdAt: DateTime }>;
45+
}>('apoc.convert.toMap(engagement) as engagement, steps')
46+
.run();
47+
this.logger.notice(
48+
`Found ${engagements.length} engagements to add event history to.`,
49+
);
50+
51+
const events: Array<
52+
Parameters<EngagementWorkflowRepository['recordEvent']>[0] & {
53+
at: DateTime;
54+
}
55+
> = [];
56+
57+
for (const [i, { engagement, steps }] of engagements.entries()) {
58+
if (i % 100 === 0) {
59+
this.logger.notice(
60+
`Processing engagement ${i + 1}/${engagements.length}`,
61+
);
62+
}
63+
64+
for (const [i, next] of steps.entries()) {
65+
if (i === 0) {
66+
continue;
67+
}
68+
const current = steps[i - 1]!;
69+
const prev = steps
70+
.slice(0, Math.max(0, i - 2))
71+
.map((s) => s.value)
72+
.reverse();
73+
const fakeEngagement: Engagement = {
74+
id: engagement.id,
75+
step: { value: current.value, canRead: true, canEdit: true },
76+
} as any;
77+
// @ts-expect-error private but this is a migration
78+
const transitions = await this.workflow.resolveAvailable(
79+
current.value,
80+
{
81+
engagement: fakeEngagement,
82+
moduleRef: this.moduleRef,
83+
migrationPrevSteps: prev,
84+
},
85+
engagement,
86+
// We don't know who did it, so we can't confirm this was an official
87+
// transition instead of a bypass.
88+
// Guess that it was if a transition exists.
89+
this.fakeAdminSession,
90+
);
91+
92+
const transition = transitions.find((t) => t.to === next.value)?.key;
93+
94+
events.push({
95+
engagement: engagement.id,
96+
to: next.value,
97+
transition,
98+
at: next.createdAt,
99+
});
100+
}
101+
}
102+
103+
const transitionsCount = events.filter((e) => e.transition).length;
104+
this.logger.notice(`Resolved events to save`, {
105+
events: events.length,
106+
transitions: transitionsCount,
107+
bypasses: events.length - transitionsCount,
108+
});
109+
110+
for (const [i, someEvents] of chunk(events, 1000).entries()) {
111+
this.logger.notice(`Saving events ${i + 1}k`);
112+
113+
const query = this.db
114+
.query()
115+
.match(node('ghost', 'Actor', { id: ghost.id }))
116+
.unwind(someEvents, 'input')
117+
.match(
118+
node('engagement', 'Engagement', {
119+
id: variable('input.engagement'),
120+
}),
121+
)
122+
.create([
123+
node('engagement'),
124+
relation('out', '', 'workflowEvent', {
125+
...ACTIVE,
126+
createdAt: variable('input.at'),
127+
}),
128+
node('event', ['EngagementWorkflowEvent', 'BaseNode'], {
129+
id: variable('apoc.create.uuid()'),
130+
createdAt: variable('input.at'),
131+
to: variable('input.to'),
132+
transition: variable('input.transition'),
133+
notes: null,
134+
migrated: true,
135+
}),
136+
relation('out', '', 'who', {
137+
...ACTIVE,
138+
createdAt: variable('input.at'),
139+
}),
140+
node('ghost'),
141+
])
142+
.return('count(event) as event');
143+
await query.executeAndLogStats();
144+
}
145+
}
146+
}

0 commit comments

Comments
 (0)