Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/cli/src/merge/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const mergeHandler = async (options: MergeOptions, logger: Logger) => {
command: 'checkout',
projectPath: commandPath,
projectName: final.name || '',
log: options.log,
},
logger
);
Expand Down
3 changes: 3 additions & 0 deletions packages/lexicon/core.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ export type Workflow = {
globals?: string;

openfn?: OpenFnMetadata;

// holds history information of a workflow
history?: string[]
};

export type StepId = string;
Expand Down
19 changes: 19 additions & 0 deletions packages/project/src/Workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class Workflow {

this.workflow = clone(workflow);

// history needs to be on workflow object.
this.workflow.history = workflow.history?.length ? workflow.history : [];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.workflow.history = workflow.history ?? []


const { id, name, openfn, steps, ...options } = workflow;
if (!(id || name)) {
throw new Error('A Workflow MUST have a name or id');
Expand Down Expand Up @@ -167,6 +170,22 @@ class Workflow {
getVersionHash() {
return generateHash(this);
}

pushHistory(versionHash: string) {
this.workflow.history?.push(versionHash);
}

// return true if the current workflow can be merged into the target workflow without losing any changes
canMergeInto(target: Workflow) {
const thisHistory = this.workflow.history?.concat(this.getVersionHash());
const targetHistory = target.workflow.history?.concat(
target.getVersionHash()
);

const targetHead = targetHistory[targetHistory.length - 1];
if (thisHistory.indexOf(targetHead) > -1) return true;
return false;
}
}

export default Workflow;
1 change: 1 addition & 0 deletions packages/project/test/gen/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
export const ab = {
id: 'workflow',
name: 'Workflow',
history: [],
steps: [
{
id: 'a',
Expand Down
7 changes: 7 additions & 0 deletions packages/project/test/gen/generator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ test('it should generate a simple workflow with any letter', (t) => {
const expected = {
id: 'workflow',
name: 'Workflow',
history: [],
steps: [
{
id: 'x',
Expand Down Expand Up @@ -152,6 +153,7 @@ test('it should generate a simple workflow with words, numbers and underscores',
const expected = {
id: 'workflow',
name: 'Workflow',
history: [],
steps: [
{
id: 'node_1',
Expand Down Expand Up @@ -184,6 +186,7 @@ test('it should generate two node pairs', (t) => {
const expected = {
id: 'workflow',
name: 'Workflow',
history: [],
steps: [
{
id: 'a',
Expand Down Expand Up @@ -231,6 +234,7 @@ test('it should generate two node pairs from one parent', (t) => {
const expected = {
id: 'workflow',
name: 'Workflow',
history: [],
steps: [
{
id: 'a',
Expand Down Expand Up @@ -276,6 +280,7 @@ test('it should generate several node pairs', (t) => {
const expected = {
id: 'workflow',
name: 'Workflow',
history: [],
steps: [
{
id: 'a',
Expand Down Expand Up @@ -439,6 +444,7 @@ test('it should generate a simple workflow with mapped uuids', (t) => {
const expected = {
id: 'workflow',
name: 'Workflow',
history: [],
steps: [
{
id: 'a',
Expand Down Expand Up @@ -481,6 +487,7 @@ test('it should generate a project with uuids', (t) => {
const expected = {
id: 'workflow',
name: 'Workflow',
history: [],
steps: [
{
id: 'a',
Expand Down
1 change: 1 addition & 0 deletions packages/project/test/parse/from-app-state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ test('should create a Project from prov state with a workflow', (t) => {
t.deepEqual(project.workflows[0].toJSON(), {
id: 'my-workflow',
name: 'My Workflow',
history: [],
steps: [
{
id: 'trigger',
Expand Down
102 changes: 102 additions & 0 deletions packages/project/test/workflow.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { randomUUID } from 'node:crypto';
import test from 'ava';
import Workflow from '../src/Workflow';
import { generateWorkflow } from '../src';

const simpleWorkflow = {
id: 'my-workflow',
history: [],
name: 'My Workflow',
steps: [
{
Expand Down Expand Up @@ -49,6 +51,11 @@ const simpleWorkflow = {
},
};

// should workflow.toJSON actually do this?
function realJson(v: any) {
return JSON.parse(JSON.stringify(v));
}

test('create a Workflow from json', (t) => {
const w = new Workflow(simpleWorkflow);

Expand Down Expand Up @@ -205,3 +212,98 @@ test('map uuids to ids', (t) => {
t.deepEqual(w.index.id[uuid_ac], 'a-c');
t.deepEqual(w.index.id[uuid_bc], 'b-c');
});

test('canMergeInto: should merge same content source & target', (t) => {
const main = generateWorkflow('trigger-x');
const sbox = generateWorkflow('trigger-x');

t.true(sbox.canMergeInto(main));
});

test("canMergeInto: shouldn't merge different content source & target", (t) => {
const main = generateWorkflow('trigger-x');
const sbox = generateWorkflow('trigger-y');

t.false(sbox.canMergeInto(main));
});

test('canMergeInto: source is target + changes', (t) => {
// initial main code
const main = generateWorkflow('trigger-x');
main.pushHistory(main.getVersionHash());
// main code updated
main.workflow.steps = generateWorkflow('trigger-x x-y').steps;
main.pushHistory(main.getVersionHash());

// clone main for sbox
const sbox = new Workflow(realJson(main.toJSON()));
// do code changes to sbox
sbox.workflow.steps = generateWorkflow('trigger-x x-y y-z').steps;
t.true(sbox.canMergeInto(main));
});

test("canMergeInto: source isn't from target", (t) => {
// initial main code
const main = generateWorkflow('trigger-x');
main.pushHistory(main.getVersionHash());
// main code updated
main.workflow.steps = generateWorkflow('trigger-x x-y').steps;
main.pushHistory(main.getVersionHash());

// clone main for sbox
const sbox = generateWorkflow('trigger-y');
sbox.pushHistory(sbox.getVersionHash());
t.false(sbox.canMergeInto(main));
});

test("canMergeInto: source isn't from target but ended with same code", (t) => {
// initial main code
const main = generateWorkflow('trigger-x');
main.pushHistory(main.getVersionHash());
// main code updated
main.workflow.steps = generateWorkflow('trigger-x x-y').steps;
main.pushHistory(main.getVersionHash());

const sbox = generateWorkflow('trigger-x x-y');
t.true(sbox.canMergeInto(main));
});

test('canMergeInto: source is from target but target has changes', (t) => {
// initial main code
const main = generateWorkflow('trigger-x');
main.pushHistory(main.getVersionHash());
// main code updated
main.workflow.steps = generateWorkflow('trigger-x x-y').steps;
main.pushHistory(main.getVersionHash());

// clone main for sbox
const sbox = new Workflow(realJson(main.toJSON()));

// changes to main after cloning
main.workflow.steps = generateWorkflow('trigger-x x-y x-z').steps;
main.pushHistory(main.getVersionHash());

// merging sbox to main
t.false(sbox.canMergeInto(main));
});

test('canMergeInto: source is from target but target & source have changes', (t) => {
// initial main code
const main = generateWorkflow('trigger-x');
main.pushHistory(main.getVersionHash());
// main code updated
main.workflow.steps = generateWorkflow('trigger-x x-y').steps;
main.pushHistory(main.getVersionHash());

// clone main for sbox
const sbox = new Workflow(realJson(main.toJSON()));
// changes to sbox
sbox.workflow.steps = generateWorkflow('trigger-x x-y y-g').steps;

// changes to main after cloning
main.workflow.steps = generateWorkflow('trigger-x x-y x-z').steps;
main.pushHistory(main.getVersionHash());

// merging sbox to main
t.false(sbox.canMergeInto(main));
});