Skip to content

Commit ee6086f

Browse files
authored
feat: workflow merge compatibility (#1075)
* feat: workflow merge compatibility * feat: push version history * fix: log level when calling checkout from merge * feat: add history to workflow property * tests: update wording * feat: update history checking logic * tests: update tests
1 parent 329d29d commit ee6086f

7 files changed

Lines changed: 134 additions & 0 deletions

File tree

packages/cli/src/merge/handler.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const mergeHandler = async (options: MergeOptions, logger: Logger) => {
6565
command: 'checkout',
6666
projectPath: commandPath,
6767
projectName: final.name || '',
68+
log: options.log,
6869
},
6970
logger
7071
);

packages/lexicon/core.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ export type Workflow = {
8383
globals?: string;
8484

8585
openfn?: OpenFnMetadata;
86+
87+
// holds history information of a workflow
88+
history?: string[]
8689
};
8790

8891
export type StepId = string;

packages/project/src/Workflow.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ class Workflow {
3030

3131
this.workflow = clone(workflow);
3232

33+
// history needs to be on workflow object.
34+
this.workflow.history = workflow.history?.length ? workflow.history : [];
35+
3336
const { id, name, openfn, steps, ...options } = workflow;
3437
if (!(id || name)) {
3538
throw new Error('A Workflow MUST have a name or id');
@@ -167,6 +170,22 @@ class Workflow {
167170
getVersionHash() {
168171
return generateHash(this);
169172
}
173+
174+
pushHistory(versionHash: string) {
175+
this.workflow.history?.push(versionHash);
176+
}
177+
178+
// return true if the current workflow can be merged into the target workflow without losing any changes
179+
canMergeInto(target: Workflow) {
180+
const thisHistory = this.workflow.history?.concat(this.getVersionHash());
181+
const targetHistory = target.workflow.history?.concat(
182+
target.getVersionHash()
183+
);
184+
185+
const targetHead = targetHistory[targetHistory.length - 1];
186+
if (thisHistory.indexOf(targetHead) > -1) return true;
187+
return false;
188+
}
170189
}
171190

172191
export default Workflow;

packages/project/test/gen/fixtures.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
export const ab = {
55
id: 'workflow',
66
name: 'Workflow',
7+
history: [],
78
steps: [
89
{
910
id: 'a',

packages/project/test/gen/generator.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ test('it should generate a simple workflow with any letter', (t) => {
120120
const expected = {
121121
id: 'workflow',
122122
name: 'Workflow',
123+
history: [],
123124
steps: [
124125
{
125126
id: 'x',
@@ -152,6 +153,7 @@ test('it should generate a simple workflow with words, numbers and underscores',
152153
const expected = {
153154
id: 'workflow',
154155
name: 'Workflow',
156+
history: [],
155157
steps: [
156158
{
157159
id: 'node_1',
@@ -184,6 +186,7 @@ test('it should generate two node pairs', (t) => {
184186
const expected = {
185187
id: 'workflow',
186188
name: 'Workflow',
189+
history: [],
187190
steps: [
188191
{
189192
id: 'a',
@@ -231,6 +234,7 @@ test('it should generate two node pairs from one parent', (t) => {
231234
const expected = {
232235
id: 'workflow',
233236
name: 'Workflow',
237+
history: [],
234238
steps: [
235239
{
236240
id: 'a',
@@ -276,6 +280,7 @@ test('it should generate several node pairs', (t) => {
276280
const expected = {
277281
id: 'workflow',
278282
name: 'Workflow',
283+
history: [],
279284
steps: [
280285
{
281286
id: 'a',
@@ -439,6 +444,7 @@ test('it should generate a simple workflow with mapped uuids', (t) => {
439444
const expected = {
440445
id: 'workflow',
441446
name: 'Workflow',
447+
history: [],
442448
steps: [
443449
{
444450
id: 'a',
@@ -481,6 +487,7 @@ test('it should generate a project with uuids', (t) => {
481487
const expected = {
482488
id: 'workflow',
483489
name: 'Workflow',
490+
history: [],
484491
steps: [
485492
{
486493
id: 'a',

packages/project/test/parse/from-app-state.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ test('should create a Project from prov state with a workflow', (t) => {
114114
t.deepEqual(project.workflows[0].toJSON(), {
115115
id: 'my-workflow',
116116
name: 'My Workflow',
117+
history: [],
117118
steps: [
118119
{
119120
id: 'trigger',

packages/project/test/workflow.test.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { randomUUID } from 'node:crypto';
22
import test from 'ava';
33
import Workflow from '../src/Workflow';
4+
import { generateWorkflow } from '../src';
45

56
const simpleWorkflow = {
67
id: 'my-workflow',
8+
history: [],
79
name: 'My Workflow',
810
steps: [
911
{
@@ -49,6 +51,11 @@ const simpleWorkflow = {
4951
},
5052
};
5153

54+
// should workflow.toJSON actually do this?
55+
function realJson(v: any) {
56+
return JSON.parse(JSON.stringify(v));
57+
}
58+
5259
test('create a Workflow from json', (t) => {
5360
const w = new Workflow(simpleWorkflow);
5461

@@ -205,3 +212,98 @@ test('map uuids to ids', (t) => {
205212
t.deepEqual(w.index.id[uuid_ac], 'a-c');
206213
t.deepEqual(w.index.id[uuid_bc], 'b-c');
207214
});
215+
216+
test('canMergeInto: should merge same content source & target', (t) => {
217+
const main = generateWorkflow('trigger-x');
218+
const sbox = generateWorkflow('trigger-x');
219+
220+
t.true(sbox.canMergeInto(main));
221+
});
222+
223+
test("canMergeInto: shouldn't merge different content source & target", (t) => {
224+
const main = generateWorkflow('trigger-x');
225+
const sbox = generateWorkflow('trigger-y');
226+
227+
t.false(sbox.canMergeInto(main));
228+
});
229+
230+
test('canMergeInto: source is target + changes', (t) => {
231+
// initial main code
232+
const main = generateWorkflow('trigger-x');
233+
main.pushHistory(main.getVersionHash());
234+
// main code updated
235+
main.workflow.steps = generateWorkflow('trigger-x x-y').steps;
236+
main.pushHistory(main.getVersionHash());
237+
238+
// clone main for sbox
239+
const sbox = new Workflow(realJson(main.toJSON()));
240+
// do code changes to sbox
241+
sbox.workflow.steps = generateWorkflow('trigger-x x-y y-z').steps;
242+
t.true(sbox.canMergeInto(main));
243+
});
244+
245+
test("canMergeInto: source isn't from target", (t) => {
246+
// initial main code
247+
const main = generateWorkflow('trigger-x');
248+
main.pushHistory(main.getVersionHash());
249+
// main code updated
250+
main.workflow.steps = generateWorkflow('trigger-x x-y').steps;
251+
main.pushHistory(main.getVersionHash());
252+
253+
// clone main for sbox
254+
const sbox = generateWorkflow('trigger-y');
255+
sbox.pushHistory(sbox.getVersionHash());
256+
t.false(sbox.canMergeInto(main));
257+
});
258+
259+
test("canMergeInto: source isn't from target but ended with same code", (t) => {
260+
// initial main code
261+
const main = generateWorkflow('trigger-x');
262+
main.pushHistory(main.getVersionHash());
263+
// main code updated
264+
main.workflow.steps = generateWorkflow('trigger-x x-y').steps;
265+
main.pushHistory(main.getVersionHash());
266+
267+
const sbox = generateWorkflow('trigger-x x-y');
268+
t.true(sbox.canMergeInto(main));
269+
});
270+
271+
test('canMergeInto: source is from target but target has changes', (t) => {
272+
// initial main code
273+
const main = generateWorkflow('trigger-x');
274+
main.pushHistory(main.getVersionHash());
275+
// main code updated
276+
main.workflow.steps = generateWorkflow('trigger-x x-y').steps;
277+
main.pushHistory(main.getVersionHash());
278+
279+
// clone main for sbox
280+
const sbox = new Workflow(realJson(main.toJSON()));
281+
282+
// changes to main after cloning
283+
main.workflow.steps = generateWorkflow('trigger-x x-y x-z').steps;
284+
main.pushHistory(main.getVersionHash());
285+
286+
// merging sbox to main
287+
t.false(sbox.canMergeInto(main));
288+
});
289+
290+
test('canMergeInto: source is from target but target & source have changes', (t) => {
291+
// initial main code
292+
const main = generateWorkflow('trigger-x');
293+
main.pushHistory(main.getVersionHash());
294+
// main code updated
295+
main.workflow.steps = generateWorkflow('trigger-x x-y').steps;
296+
main.pushHistory(main.getVersionHash());
297+
298+
// clone main for sbox
299+
const sbox = new Workflow(realJson(main.toJSON()));
300+
// changes to sbox
301+
sbox.workflow.steps = generateWorkflow('trigger-x x-y y-g').steps;
302+
303+
// changes to main after cloning
304+
main.workflow.steps = generateWorkflow('trigger-x x-y x-z').steps;
305+
main.pushHistory(main.getVersionHash());
306+
307+
// merging sbox to main
308+
t.false(sbox.canMergeInto(main));
309+
});

0 commit comments

Comments
 (0)