From 3b18c7baed0724073e628c3f51c3d7fbe8110cd7 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Mon, 16 Jan 2023 10:48:44 +0100 Subject: [PATCH 01/42] enable noUncheckedIndexAccess --- client/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/tsconfig.json b/client/tsconfig.json index ba1e713961d..c033f9a3043 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -13,7 +13,8 @@ "allowJs": true, "checkJs": false, - "allowSyntheticDefaultImports": true + "allowSyntheticDefaultImports": true, + "noUncheckedIndexedAccess": true }, "vueCompilerOptions": { "target": 2.7 From 0968636e253e5ff081dcf2d94e29a2a99fd79c88 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Mon, 16 Jan 2023 11:59:16 +0100 Subject: [PATCH 02/42] fix type errors in terminals.ts --- .../Workflow/Editor/modules/terminals.ts | 44 ++++++++++++------- client/src/utils/assertions.ts | 11 +++++ 2 files changed, 40 insertions(+), 15 deletions(-) create mode 100644 client/src/utils/assertions.ts diff --git a/client/src/components/Workflow/Editor/modules/terminals.ts b/client/src/components/Workflow/Editor/modules/terminals.ts index fdcebf890ce..5af806d1b42 100644 --- a/client/src/components/Workflow/Editor/modules/terminals.ts +++ b/client/src/components/Workflow/Editor/modules/terminals.ts @@ -18,6 +18,7 @@ import type { TerminalSource, } from "@/stores/workflowStepStore"; import type { DatatypesMapperModel } from "@/components/Datatypes/model"; +import { assertDefined } from "@/utils/assertions"; class ConnectionAcceptable { reason: string | null; @@ -296,25 +297,33 @@ class BaseInputTerminal extends Terminal { return this.connections.map((connection) => { const outputStep = this.stepStore.getStep(connection.output.stepId); let terminalSource = outputStep.outputs.find((output) => output.name === connection.output.name); - if (!terminalSource) { - /* - / This can't happen, I think, because we'd drop the connection. - / We (probably) want to eventually display invalid connections, - / so maybe generate a NullTerminal when there is no terminalSource ? - */ - throw `Could not find output ${connection.output.name} on step ${connection.output.stepId}`; - } + + /* + This can't happen, I think, because we'd drop the connection. + We (probably) want to eventually display invalid connections, + so maybe generate a NullTerminal when there is no terminalSource ? + */ + assertDefined( + terminalSource, + `Could not find output ${connection.output.name} on step ${connection.output.stepId}` + ); + const postJobActionKey = `ChangeDatatypeAction${connection.output.name}`; + if ( "extensions" in terminalSource && outputStep.post_job_actions && postJobActionKey in outputStep.post_job_actions ) { - terminalSource = { + const extensionType = outputStep.post_job_actions![postJobActionKey]!.action_arguments.newtype; + assertDefined(extensionType); + + (terminalSource as DataOutput | CollectionOutput) = { ...terminalSource, - extensions: [outputStep.post_job_actions[postJobActionKey].action_arguments.newtype], + extensions: [extensionType], }; } + return terminalFactory(outputStep.id, terminalSource, this.datatypesMapper); }); } @@ -453,9 +462,11 @@ export class InputCollectionTerminal extends BaseInputTerminal { const canMatch = collectionTypes.some((collectionType) => collectionType.canMatch(otherCollectionType)); if (!canMatch) { for (const collectionTypeIndex in collectionTypes) { - const collectionType = collectionTypes[collectionTypeIndex]; + const collectionType = collectionTypes[collectionTypeIndex]!; + if (otherCollectionType.canMapOver(collectionType)) { const effectiveMapOver = otherCollectionType.effectiveMapOver(collectionType); + if (effectiveMapOver != NULL_COLLECTION_TYPE_DESCRIPTION) { return effectiveMapOver; } @@ -606,17 +617,20 @@ export function producesAcceptableDatatype( otherDatatypes: string[] ) { for (const t in inputDatatypes) { - const thisDatatype = inputDatatypes[t]; - if (thisDatatype == "input") { + const thisDatatype = inputDatatypes[t]!; + + if (thisDatatype === "input") { return new ConnectionAcceptable(true, null); } + // FIXME: No idea what to do about case when datatype is 'input' const validMatch = otherDatatypes.some( (otherDatatype) => - otherDatatype == "input" || - otherDatatype == "_sniff_" || + otherDatatype === "input" || + otherDatatype === "_sniff_" || datatypesMapper.isSubType(otherDatatype, thisDatatype) ); + if (validMatch) { return new ConnectionAcceptable(true, null); } diff --git a/client/src/utils/assertions.ts b/client/src/utils/assertions.ts new file mode 100644 index 00000000000..4b64beacc7a --- /dev/null +++ b/client/src/utils/assertions.ts @@ -0,0 +1,11 @@ +/** + * Asserts that a value is not undefined or null + * @param value value to test + * @param errorMessage optional error message + */ +export function assertDefined(value: T, errorMessage?: string): asserts value is NonNullable { + if (value === undefined || value === null) { + const message = errorMessage ?? `Value is undefined or null`; + throw message; + } +} From fbce24e0394e73d4729b5a2851d5eca81f39afbc Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Mon, 16 Jan 2023 12:21:35 +0100 Subject: [PATCH 03/42] fix workflowStepStore possible undefined --- client/src/stores/workflowStepStore.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/src/stores/workflowStepStore.ts b/client/src/stores/workflowStepStore.ts index 36663da8434..a452d9dea7a 100644 --- a/client/src/stores/workflowStepStore.ts +++ b/client/src/stores/workflowStepStore.ts @@ -141,7 +141,11 @@ export const useWorkflowStepStore = defineStore("workflowStepStore", { getters: { getStep(state: State) { return (stepId: number): Step => { - return state.steps[stepId.toString()]; + if (stepId.toString() in state.steps) { + return state.steps[stepId.toString()] as Step; + } else { + throw `Failed to find step with id ${stepId} in Workflow Step Store`; + } }; }, getStepIndex(state: State) { From cf9557a82c1d6361fc45cca36d7671f723f36fa4 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Mon, 16 Jan 2023 12:24:12 +0100 Subject: [PATCH 04/42] fix terminals test type annotations --- .../Workflow/Editor/modules/terminals.test.ts | 244 +++++++++--------- 1 file changed, 122 insertions(+), 122 deletions(-) diff --git a/client/src/components/Workflow/Editor/modules/terminals.test.ts b/client/src/components/Workflow/Editor/modules/terminals.test.ts index ded5054db32..824cc12a7fb 100644 --- a/client/src/components/Workflow/Editor/modules/terminals.test.ts +++ b/client/src/components/Workflow/Editor/modules/terminals.test.ts @@ -24,10 +24,10 @@ function setupAdvanced() { if (stepLabel) { terminals[stepLabel] = {}; step.inputs?.map((input) => { - terminals[stepLabel][input.name] = terminalFactory(step.id, input, testDatatypesMapper); + terminals[stepLabel]![input.name] = terminalFactory(step.id, input, testDatatypesMapper); }); step.outputs?.map((output) => { - terminals[stepLabel][output.name] = terminalFactory(step.id, output, testDatatypesMapper); + terminals[stepLabel]![output.name] = terminalFactory(step.id, output, testDatatypesMapper); }); } }); @@ -42,27 +42,27 @@ describe("terminalFactory", () => { }); it("constructs correct class instances", () => { - expect(terminals["data input"]["output"]).toBeInstanceOf(OutputTerminal); - expect(terminals["simple data"]["input"]).toBeInstanceOf(InputTerminal); - expect(terminals["simple data"]["out_file1"]).toBeInstanceOf(OutputTerminal); - expect(terminals["simple data 2"]["input"]).toBeInstanceOf(InputTerminal); - expect(terminals["simple data 2"]["out_file1"]).toBeInstanceOf(OutputTerminal); - expect(terminals["multiple simple data"]["input1"]).toBeInstanceOf(InputTerminal); - expect(terminals["multiple simple data"]["queries_0|input2"]).toBeInstanceOf(InputTerminal); - expect(terminals["multiple simple data"]["out_file1"]).toBeInstanceOf(OutputTerminal); - expect(terminals["optional data input"]["output"]).toBeInstanceOf(OutputTerminal); - expect(terminals["list input"]["output"]).toBeInstanceOf(OutputCollectionTerminal); - expect(terminals["list:list input"]["output"]).toBeInstanceOf(OutputCollectionTerminal); - expect(terminals["paired input"]["output"]).toBeInstanceOf(OutputCollectionTerminal); - expect(terminals["multi data"]["f1"]).toBeInstanceOf(InputTerminal); - expect(terminals["multi data"]["f2"]).toBeInstanceOf(InputTerminal); - expect(terminals["multi data"]["out1"]).toBeInstanceOf(OutputTerminal); - expect(terminals["multi data"]["out2"]).toBeInstanceOf(OutputTerminal); - expect(terminals["integer parameter input"]["output"]).toBeInstanceOf(OutputParameterTerminal); - expect(terminals["any collection"]["input"]).toBeInstanceOf(InputCollectionTerminal); - expect(terminals["any collection"]["output"]).toBeInstanceOf(OutputCollectionTerminal); - expect(terminals["multi data"]["advanced|advanced_threshold"]).toBeInstanceOf(InputParameterTerminal); - expect(terminals["list collection input"]["input1"]).toBeInstanceOf(InputCollectionTerminal); + expect(terminals["data input"]?.["output"]).toBeInstanceOf(OutputTerminal); + expect(terminals["simple data"]?.["input"]).toBeInstanceOf(InputTerminal); + expect(terminals["simple data"]?.["out_file1"]).toBeInstanceOf(OutputTerminal); + expect(terminals["simple data 2"]?.["input"]).toBeInstanceOf(InputTerminal); + expect(terminals["simple data 2"]?.["out_file1"]).toBeInstanceOf(OutputTerminal); + expect(terminals["multiple simple data"]?.["input1"]).toBeInstanceOf(InputTerminal); + expect(terminals["multiple simple data"]?.["queries_0|input2"]).toBeInstanceOf(InputTerminal); + expect(terminals["multiple simple data"]?.["out_file1"]).toBeInstanceOf(OutputTerminal); + expect(terminals["optional data input"]?.["output"]).toBeInstanceOf(OutputTerminal); + expect(terminals["list input"]?.["output"]).toBeInstanceOf(OutputCollectionTerminal); + expect(terminals["list:list input"]?.["output"]).toBeInstanceOf(OutputCollectionTerminal); + expect(terminals["paired input"]?.["output"]).toBeInstanceOf(OutputCollectionTerminal); + expect(terminals["multi data"]?.["f1"]).toBeInstanceOf(InputTerminal); + expect(terminals["multi data"]?.["f2"]).toBeInstanceOf(InputTerminal); + expect(terminals["multi data"]?.["out1"]).toBeInstanceOf(OutputTerminal); + expect(terminals["multi data"]?.["out2"]).toBeInstanceOf(OutputTerminal); + expect(terminals["integer parameter input"]?.["output"]).toBeInstanceOf(OutputParameterTerminal); + expect(terminals["any collection"]?.["input"]).toBeInstanceOf(InputCollectionTerminal); + expect(terminals["any collection"]?.["output"]).toBeInstanceOf(OutputCollectionTerminal); + expect(terminals["multi data"]?.["advanced|advanced_threshold"]).toBeInstanceOf(InputParameterTerminal); + expect(terminals["list collection input"]?.["input1"]).toBeInstanceOf(InputCollectionTerminal); }); it("throws error on invalid terminalSource", () => { const invalidFactory = () => terminalFactory(1, {} as any, testDatatypesMapper); @@ -85,8 +85,8 @@ describe("canAccept", () => { }); it("accepts simple data -> data connections", () => { - const dataOut = terminals["data input"]["output"] as OutputTerminal; - const dataIn = terminals["simple data"]["input"] as InputTerminal; + const dataOut = terminals["data input"]!["output"] as OutputTerminal; + const dataIn = terminals["simple data"]!["input"] as InputTerminal; expect(dataIn.canAccept(dataOut).canAccept).toBe(true); dataIn.connect(dataOut); expect(dataIn.canAccept(dataOut).canAccept).toBe(false); @@ -94,8 +94,8 @@ describe("canAccept", () => { expect(dataIn.canAccept(dataOut).canAccept).toBe(true); }); it("accepts collection data -> data connection", () => { - const collectionOut = terminals["list input"]["output"] as OutputCollectionTerminal; - const dataIn = terminals["simple data"]["input"] as InputTerminal; + const collectionOut = terminals["list input"]!["output"] as OutputCollectionTerminal; + const dataIn = terminals["simple data"]!["input"] as InputTerminal; expect(dataIn.mapOver).toBe(NULL_COLLECTION_TYPE_DESCRIPTION); expect(dataIn.canAccept(collectionOut).canAccept).toBe(true); dataIn.connect(collectionOut); @@ -106,15 +106,15 @@ describe("canAccept", () => { expect(dataIn.mapOver).toEqual(NULL_COLLECTION_TYPE_DESCRIPTION); }); it("accepts mapped over data output on mapped over data input", () => { - const collectionOut = terminals["list input"]["output"] as OutputCollectionTerminal; - const dataIn = terminals["multiple simple data"]["input1"] as InputTerminal; - const dataInTwo = terminals["multiple simple data"]["queries_0|input2"] as InputTerminal; + const collectionOut = terminals["list input"]!["output"] as OutputCollectionTerminal; + const dataIn = terminals["multiple simple data"]!["input1"] as InputTerminal; + const dataInTwo = terminals["multiple simple data"]!["queries_0|input2"] as InputTerminal; dataIn.connect(collectionOut); expect(dataInTwo.canAccept(collectionOut).canAccept).toBe(true); }); it("accepts list:list data -> data connection", () => { - const collectionOut = terminals["list:list input"]["output"] as OutputCollectionTerminal; - const dataIn = terminals["simple data"]["input"] as InputTerminal; + const collectionOut = terminals["list:list input"]!["output"] as OutputCollectionTerminal; + const dataIn = terminals["simple data"]!["input"] as InputTerminal; expect(dataIn.mapOver).toBe(NULL_COLLECTION_TYPE_DESCRIPTION); expect(dataIn.canAccept(collectionOut).canAccept).toBe(true); dataIn.connect(collectionOut); @@ -125,37 +125,37 @@ describe("canAccept", () => { expect(dataIn.mapOver).toEqual(NULL_COLLECTION_TYPE_DESCRIPTION); }); it("treats multi data input as list input", () => { - const collectionOut = terminals["list input"]["output"] as OutputCollectionTerminal; - const multiDataIn = terminals["multi data"]["f1"] as InputTerminal; + const collectionOut = terminals["list input"]!["output"] as OutputCollectionTerminal; + const multiDataIn = terminals["multi data"]!["f1"] as InputTerminal; expect(multiDataIn.canAccept(collectionOut).canAccept).toBe(true); multiDataIn.connect(collectionOut); expect(multiDataIn.mapOver).toBe(NULL_COLLECTION_TYPE_DESCRIPTION); }); it("accepts separate list:list inputs on separate multi-data inputs of same tool", () => { - const collectionOut = terminals["list:list input"]["output"] as OutputCollectionTerminal; - const multiDataInOne = terminals["multi data"]["f1"] as InputTerminal; - const multiDataInTwo = terminals["multi data"]["f2"] as InputTerminal; + const collectionOut = terminals["list:list input"]!["output"] as OutputCollectionTerminal; + const multiDataInOne = terminals["multi data"]!["f1"] as InputTerminal; + const multiDataInTwo = terminals["multi data"]!["f2"] as InputTerminal; multiDataInOne.connect(collectionOut); expect(multiDataInTwo.canAccept(collectionOut).canAccept).toBe(true); }); it("rejects connecting output to input of same step", () => { - const dataOut = terminals["simple data"]["out_file1"] as OutputTerminal; - const dataIn = terminals["simple data"]["input"] as InputTerminal; + const dataOut = terminals["simple data"]!["out_file1"] as OutputTerminal; + const dataIn = terminals["simple data"]!["input"] as InputTerminal; expect(dataIn.canAccept(dataOut).canAccept).toBe(false); expect(dataIn.canAccept(dataOut).reason).toBe("Cannot connection output to input of same step."); }); it("rejects paired input on multi-data input", () => { - const multiDataIn = terminals["multi data"]["f1"] as InputTerminal; - const pairedOut = terminals["paired input"]["output"] as OutputCollectionTerminal; + const multiDataIn = terminals["multi data"]!["f1"] as InputTerminal; + const pairedOut = terminals["paired input"]!["output"] as OutputCollectionTerminal; expect(multiDataIn.canAccept(pairedOut).canAccept).toBe(false); expect(multiDataIn.canAccept(pairedOut).reason).toBe( "Cannot attach paired inputs to multiple data parameters, only lists may be treated this way." ); }); it("rejects collections on multi data inputs if non-collection already connected", () => { - const multiDataIn = terminals["multi data"]["f1"] as InputTerminal; - const dataOut = terminals["data input"]["output"] as OutputTerminal; - const collectionOut = terminals["list input"]["output"] as OutputCollectionTerminal; + const multiDataIn = terminals["multi data"]!["f1"] as InputTerminal; + const dataOut = terminals["data input"]!["output"] as OutputTerminal; + const collectionOut = terminals["list input"]!["output"] as OutputCollectionTerminal; multiDataIn.connect(dataOut); expect(multiDataIn.canAccept(collectionOut).canAccept).toBe(false); expect(multiDataIn.canAccept(collectionOut).reason).toBe( @@ -163,16 +163,16 @@ describe("canAccept", () => { ); }); it("maps list:list over multi data input", () => { - const collectionOut = terminals["list:list input"]["output"] as OutputCollectionTerminal; - const multiDataIn = terminals["multi data"]["f1"] as InputTerminal; + const collectionOut = terminals["list:list input"]!["output"] as OutputCollectionTerminal; + const multiDataIn = terminals["multi data"]!["f1"] as InputTerminal; expect(multiDataIn.canAccept(collectionOut).canAccept).toBe(true); multiDataIn.connect(collectionOut); expect(multiDataIn.mapOver).toEqual({ collectionType: "list", isCollection: true, rank: 1 }); }); it("rejects attaching multiple collections to a single multi data input", () => { - const collectionOut = terminals["list:list input"]["output"] as OutputCollectionTerminal; - const otherCollectionOut = terminals["list:list input"]["output"] as OutputCollectionTerminal; - const multiDataIn = terminals["multi data"]["f1"] as InputTerminal; + const collectionOut = terminals["list:list input"]!["output"] as OutputCollectionTerminal; + const otherCollectionOut = terminals["list:list input"]!["output"] as OutputCollectionTerminal; + const multiDataIn = terminals["multi data"]!["f1"] as InputTerminal; multiDataIn.connect(collectionOut); expect(multiDataIn.canAccept(otherCollectionOut).canAccept).toBe(false); expect(multiDataIn.canAccept(otherCollectionOut).reason).toBe( @@ -180,50 +180,50 @@ describe("canAccept", () => { ); }); it("rejects data -> collection connection", () => { - const dataOut = terminals["data input"]["output"] as OutputTerminal; - const collectionInput = terminals["any collection"]["input"] as InputCollectionTerminal; + const dataOut = terminals["data input"]!["output"] as OutputTerminal; + const collectionInput = terminals["any collection"]!["input"] as InputCollectionTerminal; expect(collectionInput.canAccept(dataOut).canAccept).toBe(false); expect(collectionInput.canAccept(dataOut).reason).toBe("Cannot attach a data output to a collection input."); }); it("rejects optional data -> required data", () => { - const optionalDataOut = terminals["optional data input"]["output"] as OutputTerminal; - const dataIn = terminals["simple data"]["input"] as InputTerminal; + const optionalDataOut = terminals["optional data input"]!["output"] as OutputTerminal; + const dataIn = terminals["simple data"]!["input"] as InputTerminal; expect(dataIn.canAccept(optionalDataOut).canAccept).toBe(false); expect(dataIn.canAccept(optionalDataOut).reason).toBe( "Cannot connect an optional output to a non-optional input" ); }); it("rejects parameter to data connection", () => { - const dataIn = terminals["simple data"]["input"] as InputTerminal; + const dataIn = terminals["simple data"]!["input"] as InputTerminal; // # type system would reject this, but test runtime too - const integerParam = terminals["integer parameter input"]["output"] as any; + const integerParam = terminals["integer parameter input"]!["output"] as any; expect(dataIn.canAccept(integerParam).canAccept).toBe(false); expect(dataIn.canAccept(integerParam).reason).toBe("Cannot connect workflow parameter to data input."); }); it("accepts integer parameter to integer parameter connection", () => { - const integerInputParam = terminals["multi data"]["advanced|advanced_threshold"] as InputParameterTerminal; - const integerOutputParam = terminals["integer parameter input"]["output"] as OutputParameterTerminal; + const integerInputParam = terminals["multi data"]!["advanced|advanced_threshold"] as InputParameterTerminal; + const integerOutputParam = terminals["integer parameter input"]!["output"] as OutputParameterTerminal; expect(integerInputParam.canAccept(integerOutputParam).canAccept).toBe(true); }); it("rejects text to integer parameter connection", () => { - const integerInputParam = terminals["multi data"]["advanced|advanced_threshold"] as InputParameterTerminal; - const textOutputParam = terminals["text parameter input"]["output"] as OutputParameterTerminal; + const integerInputParam = terminals["multi data"]!["advanced|advanced_threshold"] as InputParameterTerminal; + const textOutputParam = terminals["text parameter input"]!["output"] as OutputParameterTerminal; expect(integerInputParam.canAccept(textOutputParam).canAccept).toBe(false); expect(integerInputParam.canAccept(textOutputParam).reason).toBe( "Cannot attach a text parameter to a integer input" ); }); it("rejects data to parameter connection", () => { - const dataOut = terminals["data input"]["output"] as OutputTerminal; - const integerInputParam = terminals["multi data"]["advanced|advanced_threshold"] as InputParameterTerminal; + const dataOut = terminals["data input"]!["output"] as OutputTerminal; + const integerInputParam = terminals["multi data"]!["advanced|advanced_threshold"] as InputParameterTerminal; expect(integerInputParam.canAccept(dataOut).canAccept).toBe(false); expect(integerInputParam.canAccept(dataOut).reason).toBe("Cannot attach a data parameter to a integer input"); }); it("rejects increasing map over if output connected to data input", () => { - const dataIn = terminals["simple data"]["input"] as InputTerminal; - const dataOut = terminals["simple data"]["out_file1"] as OutputTerminal; - const constrainingDataIn = terminals["simple data 2"]["input"] as InputTerminal; - const collectionOut = terminals["list input"]["output"] as OutputCollectionTerminal; + const dataIn = terminals["simple data"]!["input"] as InputTerminal; + const dataOut = terminals["simple data"]!["out_file1"] as OutputTerminal; + const constrainingDataIn = terminals["simple data 2"]!["input"] as InputTerminal; + const collectionOut = terminals["list input"]!["output"] as OutputCollectionTerminal; // connect simple data to simple data 2 constrainingDataIn.connect(dataOut); // now we can't connect a collection out to the data input of simple data @@ -233,11 +233,11 @@ describe("canAccept", () => { ); }); it("rejects increasing map over to list:list if data is mapped over a list input", () => { - const collectionOut = terminals["list input"]["output"] as OutputCollectionTerminal; - const listListOut = terminals["list:list input"]["output"] as OutputCollectionTerminal; - const dataIn = terminals["simple data"]["input"] as InputTerminal; - const dataOut = terminals["simple data"]["out_file1"] as OutputTerminal; - const dataInTwo = terminals["simple data 2"]["input"] as InputTerminal; + const collectionOut = terminals["list input"]!["output"] as OutputCollectionTerminal; + const listListOut = terminals["list:list input"]!["output"] as OutputCollectionTerminal; + const dataIn = terminals["simple data"]!["input"] as InputTerminal; + const dataOut = terminals["simple data"]!["out_file1"] as OutputTerminal; + const dataInTwo = terminals["simple data 2"]!["input"] as InputTerminal; dataIn.connect(collectionOut); dataInTwo.connect(dataOut); expect(dataIn.mapOver).toEqual({ collectionType: "list", isCollection: true, rank: 1 }); @@ -252,11 +252,11 @@ describe("canAccept", () => { ); }); it("rejects attaching non-collection outputs to mapper over inputs", () => { - const collectionOut = terminals["list input"]["output"] as OutputCollectionTerminal; - const simpleDataOut = terminals["data input"]["output"] as OutputTerminal; - const dataIn = terminals["simple data"]["input"] as InputTerminal; - const dataOut = terminals["simple data"]["out_file1"] as OutputTerminal; - const dataInTwo = terminals["simple data 2"]["input"] as InputTerminal; + const collectionOut = terminals["list input"]!["output"] as OutputCollectionTerminal; + const simpleDataOut = terminals["data input"]!["output"] as OutputTerminal; + const dataIn = terminals["simple data"]!["input"] as InputTerminal; + const dataOut = terminals["simple data"]!["out_file1"] as OutputTerminal; + const dataInTwo = terminals["simple data 2"]!["input"] as InputTerminal; dataIn.connect(collectionOut); dataInTwo.connect(dataOut); expect(dataIn.mapOver).toEqual({ collectionType: "list", isCollection: true, rank: 1 }); @@ -272,10 +272,10 @@ describe("canAccept", () => { }); // TODO: test mapOver reset when constraint removed it("resets mapOver when constraint is lifted", () => { - const collectionOut = terminals["list input"]["output"] as OutputCollectionTerminal; - const dataIn = terminals["simple data"]["input"] as InputTerminal; - const dataOut = terminals["simple data"]["out_file1"] as OutputTerminal; - const dataInTwo = terminals["simple data 2"]["input"] as InputTerminal; + const collectionOut = terminals["list input"]!["output"] as OutputCollectionTerminal; + const dataIn = terminals["simple data"]!["input"] as InputTerminal; + const dataOut = terminals["simple data"]!["out_file1"] as OutputTerminal; + const dataInTwo = terminals["simple data 2"]!["input"] as InputTerminal; dataIn.connect(collectionOut); dataInTwo.connect(dataOut); expect(dataIn.mapOver).toEqual({ collectionType: "list", isCollection: true, rank: 1 }); @@ -288,16 +288,16 @@ describe("canAccept", () => { expect(dataIn.mapOver).toEqual(NULL_COLLECTION_TYPE_DESCRIPTION); }); it("rejects connecting incompatible connection types", () => { - const pairedOut = terminals["paired input"]["output"] as OutputCollectionTerminal; - const collectionIn = terminals["list collection input"]["input1"] as InputCollectionTerminal; + const pairedOut = terminals["paired input"]!["output"] as OutputCollectionTerminal; + const collectionIn = terminals["list collection input"]!["input1"] as InputCollectionTerminal; expect(collectionIn.canAccept(pairedOut).canAccept).toBe(false); expect(collectionIn.canAccept(pairedOut).reason).toBe("Incompatible collection type(s) for attachment."); }); it("rejects mapping over collection input if other inputs have an incompatible map over collection type", () => { - const collectionOut = terminals["list input"]["output"] as OutputCollectionTerminal; - const listListOut = terminals["list:list input"]["output"] as OutputCollectionTerminal; - const listOneIn = terminals["two list inputs"]["kind|f1"] as InputCollectionTerminal; - const listTwoIn = terminals["two list inputs"]["kind|f2"] as InputCollectionTerminal; + const collectionOut = terminals["list input"]!["output"] as OutputCollectionTerminal; + const listListOut = terminals["list:list input"]!["output"] as OutputCollectionTerminal; + const listOneIn = terminals["two list inputs"]!["kind|f1"] as InputCollectionTerminal; + const listTwoIn = terminals["two list inputs"]!["kind|f2"] as InputCollectionTerminal; listOneIn.connect(listListOut); expect(listTwoIn.canAccept(collectionOut).canAccept).toBe(false); expect(listTwoIn.canAccept(collectionOut).reason).toBe( @@ -305,12 +305,12 @@ describe("canAccept", () => { ); }); it("rejects mapping over collection input if outputs constrain input to incompatible collection type", () => { - const collectionOut = terminals["list input"]["output"] as OutputCollectionTerminal; - const listListOut = terminals["list:list input"]["output"] as OutputCollectionTerminal; - const listOneIn = terminals["two list inputs"]["kind|f1"] as InputCollectionTerminal; - const listTwoIn = terminals["two list inputs"]["kind|f2"] as InputCollectionTerminal; - const mapOverOut = terminals["two list inputs"]["out1"] as OutputTerminal; - const dataIn = terminals["simple data"]["input"] as InputTerminal; + const collectionOut = terminals["list input"]!["output"] as OutputCollectionTerminal; + const listListOut = terminals["list:list input"]!["output"] as OutputCollectionTerminal; + const listOneIn = terminals["two list inputs"]!["kind|f1"] as InputCollectionTerminal; + const listTwoIn = terminals["two list inputs"]!["kind|f2"] as InputCollectionTerminal; + const mapOverOut = terminals["two list inputs"]!["out1"] as OutputTerminal; + const dataIn = terminals["simple data"]!["input"] as InputTerminal; listOneIn.connect(listListOut); dataIn.connect(mapOverOut); listOneIn.disconnect(listListOut); @@ -321,17 +321,17 @@ describe("canAccept", () => { ); }); it("tracks transitive map over", () => { - const collectionOut = terminals["list:list input"]["output"] as OutputCollectionTerminal; - const listListListOut = terminals["list:list:list input"]["output"] as OutputCollectionTerminal; - const dataIn = terminals["simple data"]["input"] as InputTerminal; - const simpleDataOut = terminals["simple data"]["out_file1"] as OutputTerminal; + const collectionOut = terminals["list:list input"]!["output"] as OutputCollectionTerminal; + const listListListOut = terminals["list:list:list input"]!["output"] as OutputCollectionTerminal; + const dataIn = terminals["simple data"]!["input"] as InputTerminal; + const simpleDataOut = terminals["simple data"]!["out_file1"] as OutputTerminal; dataIn.connect(collectionOut); expect(dataIn.mapOver).toEqual({ collectionType: "list:list", isCollection: true, rank: 2 }); - const otherDataIn = terminals["multi data"]["f1"] as InputTerminal; + const otherDataIn = terminals["multi data"]!["f1"] as InputTerminal; expect(otherDataIn.canAccept(simpleDataOut).canAccept).toBe(true); otherDataIn.connect(simpleDataOut); expect(otherDataIn.mapOver).toEqual({ collectionType: "list", isCollection: true, rank: 1 }); - const otherDataInTwo = terminals["multi data"]["f2"] as InputTerminal; + const otherDataInTwo = terminals["multi data"]!["f2"] as InputTerminal; expect(otherDataInTwo.canAccept(collectionOut).canAccept).toBe(true); expect(otherDataInTwo.canAccept(listListListOut).canAccept).toBe(false); expect(otherDataInTwo.canAccept(listListListOut).reason).toBe( @@ -339,23 +339,23 @@ describe("canAccept", () => { ); }); it("tracks transitive map over through collection inputs", () => { - const collectionOut = terminals["list:list input"]["output"] as OutputCollectionTerminal; - const collectionIn = terminals["list collection input"]["input1"] as InputCollectionTerminal; + const collectionOut = terminals["list:list input"]!["output"] as OutputCollectionTerminal; + const collectionIn = terminals["list collection input"]!["input1"] as InputCollectionTerminal; expect(collectionIn.canAccept(collectionOut).canAccept).toBe(true); collectionIn.connect(collectionOut); expect(collectionIn.mapOver).toEqual({ collectionType: "list", isCollection: true, rank: 1 }); - const intermediateOut = terminals["list collection input"]["out_file1"] as OutputCollectionTerminal; - const otherListIn = terminals["list collection input 2"]["input1"] as InputCollectionTerminal; + const intermediateOut = terminals["list collection input"]!["out_file1"] as OutputCollectionTerminal; + const otherListIn = terminals["list collection input 2"]!["input1"] as InputCollectionTerminal; expect(otherListIn.canAccept(intermediateOut).canAccept).toBe(true); otherListIn.connect(intermediateOut); expect(otherListIn.mapOver).toEqual(NULL_COLLECTION_TYPE_DESCRIPTION); }); it("rejects connections to input collection constrained by output connection", () => { - const collectionOut = terminals["list input"]["output"] as OutputCollectionTerminal; - const collectionIn = terminals["list collection input"]["input1"] as InputCollectionTerminal; - const dataIn = terminals["simple data"]["input"] as InputTerminal; - const mappedOverListOut = terminals["list collection input"]["out_file1"] as OutputCollectionTerminal; - const listListOut = terminals["list:list input"]["output"] as OutputCollectionTerminal; + const collectionOut = terminals["list input"]!["output"] as OutputCollectionTerminal; + const collectionIn = terminals["list collection input"]!["input1"] as InputCollectionTerminal; + const dataIn = terminals["simple data"]!["input"] as InputTerminal; + const mappedOverListOut = terminals["list collection input"]!["out_file1"] as OutputCollectionTerminal; + const listListOut = terminals["list:list input"]!["output"] as OutputCollectionTerminal; // This constrains collectionIn to list because it's output is mapped over dataIn.connect(mappedOverListOut); collectionIn.connect(collectionOut); @@ -368,12 +368,12 @@ describe("canAccept", () => { ); }); it("rejects connections to input collection constrained by other input", () => { - const collectionOut = terminals["list input"]["output"] as OutputCollectionTerminal; - const dataIn = terminals["simple data"]["input"] as InputTerminal; - const listOneIn = terminals["two list inputs"]["kind|f1"] as InputCollectionTerminal; - const listTwoIn = terminals["two list inputs"]["kind|f2"] as InputCollectionTerminal; - const mapOverOut = terminals["two list inputs"]["out1"] as OutputTerminal; - const listListOut = terminals["list:list input"]["output"] as OutputCollectionTerminal; + const collectionOut = terminals["list input"]!["output"] as OutputCollectionTerminal; + const dataIn = terminals["simple data"]!["input"] as InputTerminal; + const listOneIn = terminals["two list inputs"]!["kind|f1"] as InputCollectionTerminal; + const listTwoIn = terminals["two list inputs"]!["kind|f2"] as InputCollectionTerminal; + const mapOverOut = terminals["two list inputs"]!["out1"] as OutputTerminal; + const listListOut = terminals["list:list input"]!["output"] as OutputCollectionTerminal; // This constrains "two list inputs" to list:list because it's output is mapped over listOneIn.connect(listListOut); dataIn.connect(mapOverOut); @@ -384,8 +384,8 @@ describe("canAccept", () => { ); }); it("disconnects invalid input terminals", () => { - const dataOut = terminals["simple data"]["out_file1"] as OutputTerminal; - const dataIn = terminals["simple data 2"]["input"] as InputTerminal; + const dataOut = terminals["simple data"]!["out_file1"] as OutputTerminal; + const dataIn = terminals["simple data 2"]!["input"] as InputTerminal; dataIn.connect(dataOut); const oldStep = stepStore.getStep(dataOut.stepId); const stepOutput = oldStep.outputs[0] as DataOutput; @@ -400,8 +400,8 @@ describe("canAccept", () => { ); }); it("disconnects invalid output terminals", () => { - const dataOut = terminals["simple data"]["out_file1"] as OutputTerminal; - const dataIn = terminals["simple data 2"]["input"] as InputTerminal; + const dataOut = terminals["simple data"]!["out_file1"] as OutputTerminal; + const dataIn = terminals["simple data 2"]!["input"] as InputTerminal; dataIn.connect(dataOut); dataIn.datatypes = ["ab1"]; dataIn.destroyInvalidConnections(); @@ -426,7 +426,7 @@ describe("Input terminal", () => { Object.values(simpleSteps).map((step) => { stepStore.addStep(step); terminals[step.id] = {}; - const stepTerminals = terminals[step.id]; + const stepTerminals = terminals[step.id]!; step.inputs?.map((input) => { stepTerminals[input.name] = terminalFactory(step.id, input, testDatatypesMapper); }); @@ -440,9 +440,9 @@ describe("Input terminal", () => { expect(stepStore.getStep(1)).toEqual(simpleSteps["1"]); }); it("infers correct state", () => { - const firstInputTerminal = terminals[1]["input"] as InputTerminal; + const firstInputTerminal = terminals[1]!["input"] as InputTerminal; expect(firstInputTerminal).toBeInstanceOf(InputTerminal); - const dataInputOutputTerminal = terminals[0]["output"] as OutputTerminal; + const dataInputOutputTerminal = terminals[0]!["output"] as OutputTerminal; expect(dataInputOutputTerminal).toBeInstanceOf(OutputTerminal); expect(firstInputTerminal.connections.length).toBe(1); expect(firstInputTerminal.mapOver).toBe(NULL_COLLECTION_TYPE_DESCRIPTION); @@ -462,9 +462,9 @@ describe("Input terminal", () => { expect(firstInputTerminal._producesAcceptableDatatype(dataInputOutputTerminal).canAccept).toBe(true); }); it("can accept new connection", () => { - const firstInputTerminal = terminals[1]["input"] as InputTerminal; - const dataInputOutputTerminal = terminals[0]["output"] as OutputTerminal; - const connection = firstInputTerminal.connections[0]; + const firstInputTerminal = terminals[1]!["input"] as InputTerminal; + const dataInputOutputTerminal = terminals[0]!["output"] as OutputTerminal; + const connection = firstInputTerminal.connections[0]!; expect(firstInputTerminal.canAccept(dataInputOutputTerminal).canAccept).toBe(false); expect(dataInputOutputTerminal.validInputTerminals().length).toBe(0); firstInputTerminal.disconnect(connection); From 964606662365faf3e0450440520709e8c0de33e4 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Mon, 16 Jan 2023 12:37:06 +0100 Subject: [PATCH 05/42] remove repeating code fix type error --- client/src/stores/workflowConnectionStore.ts | 43 ++++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/client/src/stores/workflowConnectionStore.ts b/client/src/stores/workflowConnectionStore.ts index 45f08a05653..d2730db36f1 100644 --- a/client/src/stores/workflowConnectionStore.ts +++ b/client/src/stores/workflowConnectionStore.ts @@ -41,6 +41,20 @@ interface TerminalToInputTerminals { [index: string]: InputTerminal[]; } +/** + * Pushes a value to an array in an object, if the array exists. Else creates a new array containing value. + * @param object Object which contains array + * @param key Key which array is in + * @param value Value to push + */ +function pushOrSet(object: { [key: string | number]: Array }, key: string | number, value: T) { + if (key in object) { + object[key]!.push(value); + } else { + object[key] = [value]; + } +} + export const useConnectionStore = defineStore("workflowConnectionStore", { state: (): State => ({ connections: [] as Connection[], @@ -51,9 +65,7 @@ export const useConnectionStore = defineStore("workflowConnectionStore", { state.connections.map((connection) => { const terminals = getTerminals(connection); const inputTerminalId = getTerminalId(terminals.input); - inputTerminalId in inputTerminalToOutputTerminals - ? inputTerminalToOutputTerminals[inputTerminalId].push(terminals.output) - : (inputTerminalToOutputTerminals[inputTerminalId] = [terminals.output]); + pushOrSet(inputTerminalToOutputTerminals, inputTerminalId, terminals.output); }); return (terminalId: string): OutputTerminal[] => { return inputTerminalToOutputTerminals[terminalId] || []; @@ -64,9 +76,7 @@ export const useConnectionStore = defineStore("workflowConnectionStore", { state.connections.map((connection) => { const terminals = getTerminals(connection); const outputTerminalId = getTerminalId(terminals.output); - outputTerminalId in outputTerminalToInputTerminals - ? outputTerminalToInputTerminals[outputTerminalId].push(terminals.input) - : (outputTerminalToInputTerminals[outputTerminalId] = [terminals.input]); + pushOrSet(outputTerminalToInputTerminals, outputTerminalId, terminals.input); }); return (terminalId: string): BaseTerminal[] => { return outputTerminalToInputTerminals[terminalId] || []; @@ -77,17 +87,10 @@ export const useConnectionStore = defineStore("workflowConnectionStore", { state.connections.map((connection) => { const terminals = getTerminals(connection); const outputTerminalId = getTerminalId(terminals.output); - if (outputTerminalId in terminalToConnection) { - terminalToConnection[outputTerminalId].push(connection); - } else { - terminalToConnection[outputTerminalId] = [connection]; - } + pushOrSet(terminalToConnection, outputTerminalId, connection); + const inputTerminalId = getTerminalId(terminals.input); - if (inputTerminalId in terminalToConnection) { - terminalToConnection[inputTerminalId].push(connection); - } else { - terminalToConnection[inputTerminalId] = [connection]; - } + pushOrSet(terminalToConnection, inputTerminalId, connection); }); return (terminalId: string): Connection[] => { return terminalToConnection[terminalId] || []; @@ -96,12 +99,8 @@ export const useConnectionStore = defineStore("workflowConnectionStore", { getConnectionsForStep(state: State) { const stepToConnections: { [index: number]: Connection[] } = {}; state.connections.map((connection) => { - connection.input.stepId in stepToConnections - ? stepToConnections[connection.input.stepId].push(connection) - : (stepToConnections[connection.input.stepId] = [connection]); - connection.output.stepId in stepToConnections - ? stepToConnections[connection.output.stepId].push(connection) - : (stepToConnections[connection.output.stepId] = [connection]); + pushOrSet(stepToConnections, connection.input.stepId, connection); + pushOrSet(stepToConnections, connection.output.stepId, connection); }); return (stepId: number): Connection[] => stepToConnections[stepId] || []; }, From f098f26ee3653e2bacc2cdb4a706ab2b2d1571fd Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Mon, 16 Jan 2023 12:54:54 +0100 Subject: [PATCH 06/42] update prettier --- client/package.json | 2 +- client/yarn.lock | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/client/package.json b/client/package.json index 777badca0eb..2b7c854824f 100644 --- a/client/package.json +++ b/client/package.json @@ -173,7 +173,7 @@ "karma-webpack": "^5.0.0", "mini-css-extract-plugin": "^2.5.3", "postcss-loader": "^7.0.1", - "prettier": "^2.7.1", + "prettier": "^2.8.3", "process": "^0.11.10", "qunit": "^2.17.2", "raw-loader": "^4.0.2", diff --git a/client/yarn.lock b/client/yarn.lock index f8301aad7da..ecccf5a872b 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -8972,11 +8972,16 @@ prelude-ls@~1.1.2: resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz" integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== -"prettier@^1.18.2 || ^2.0.0", prettier@^2.7.1: +"prettier@^1.18.2 || ^2.0.0": version "2.7.1" resolved "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz" integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== +prettier@^2.8.3: + version "2.8.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.3.tgz#ab697b1d3dd46fb4626fbe2f543afe0cc98d8632" + integrity sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw== + pretty-bytes@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.0.0.tgz" From 6215847dab2eee27407db64cbd2bab6450d8ac8b Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Mon, 16 Jan 2023 13:04:23 +0100 Subject: [PATCH 07/42] fix types in filtering.ts add edge case handling in filtering.ts --- client/src/utils/filtering.ts | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/client/src/utils/filtering.ts b/client/src/utils/filtering.ts index 616bd3a3858..fcba8af25cd 100644 --- a/client/src/utils/filtering.ts +++ b/client/src/utils/filtering.ts @@ -13,7 +13,7 @@ type Converter = (value: T) => T; type Handler = (v: T, q: T) => boolean; /** Add comparison aliases i.e. '*>value' is converted to '*_gt=value' */ -const defaultValidAliases = [ +const defaultValidAliases: Array<[string, string]> = [ [">", "_gt"], ["<", "_lt"], ]; @@ -144,14 +144,18 @@ export function compare(attribute: string, variant: string, converter?: Conve export default class Filtering { validFilters: Record>; - validAliases: string[][]; + validAliases: Array<[string, string]>; useDefaultFilters: boolean; defaultFilters: Record = { deleted: false, visible: true, }; - constructor(validFilters: Record>, useDefaultFilters = true, validAliases?: string[][]) { + constructor( + validFilters: Record>, + useDefaultFilters = true, + validAliases?: Array<[string, string]> + ) { this.validFilters = validFilters; this.useDefaultFilters = useDefaultFilters; this.validAliases = validAliases || defaultValidAliases; @@ -224,9 +228,9 @@ export default class Filtering { const elgRE = /(\S+)([:><])(.+)/g; const elgMatch = elgRE.exec(pair); if (elgMatch) { - let field = elgMatch[1]; - const elg = elgMatch[2]; - const value = elgMatch[3]; + let field = elgMatch[1]!; + const elg = elgMatch[2]!; + const value = elgMatch[3]!; // replace alias for less and greater symbol for (const [alias, substitute] of this.validAliases) { if (elg === alias) { @@ -298,8 +302,8 @@ export default class Filtering { const queryDict: Record = {}; const filters = this.getFilters(filterText); for (const [key, value] of filters) { - const query = this.validFilters[key].query; - const converter = this.validFilters[key].converter; + const query = this.validFilters[key]!.query; + const converter = this.validFilters[key]!.converter; queryDict[query] = converter ? converter(value) : value; } return queryDict; @@ -336,10 +340,13 @@ export default class Filtering { * */ testFilters(filters: [string, T][], item: Record): boolean { for (const [key, filterValue] of filters) { - const filterAttribute = this.validFilters[key].attribute; - const filterHandler = this.validFilters[key].handler; + if (!(key in this.validFilters)) { + throw `Invalid filter ${key}`; + } + const filterAttribute = this.validFilters[key]!.attribute; + const filterHandler = this.validFilters[key]!.handler; const itemValue = item[filterAttribute]; - if (!filterHandler(itemValue, filterValue)) { + if (!itemValue || !filterHandler(itemValue, filterValue)) { return false; } } From 5dd6a1d4b7b30d2de91ad0f56aecbff6d8cef6dc Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Mon, 16 Jan 2023 13:28:32 +0100 Subject: [PATCH 08/42] remove duplicate identifiers remove unneeded startsWith remove quoted without array conversion --- client/src/utils/filtering.ts | 90 +++++++++++++++++------------------ 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/client/src/utils/filtering.ts b/client/src/utils/filtering.ts index fcba8af25cd..0c202726520 100644 --- a/client/src/utils/filtering.ts +++ b/client/src/utils/filtering.ts @@ -19,44 +19,44 @@ const defaultValidAliases: Array<[string, string]> = [ ]; /** Converts user input to backend compatible date - * @param {string} value - * @returns {Number} seconds since epoch + * @param value + * @returns seconds since epoch * */ export function toDate(value: string): number { return Date.parse(value) / 1000; } /** Converts user input for case-insensitive filtering - * @param {string} value - * @returns {string} Lowercase value + * @param value + * @returns Lowercase value * */ export function toLower(value: T): string { return String(value).toLowerCase(); } /** Converts user input to boolean - * @param {string} value - * @returns {boolean} true if value is 'true', false if value is 'false' + * @param value + * @returns true if value is 'true', false if value is 'false' * */ export function toBool(value: T): boolean { return toLower(value) === "true"; } /** Converts user input to lower case and strips quotation marks - * @param {string} value - * @returns {string} Lowercase value without quotation marks + * @param value + * @returns Lowercase value without quotation marks * */ export function toLowerNoQuotes(value: T): string { - return toLower(value).split("'").join(""); + return toLower(value).replace(/'/g, ""); } /** Converts name tags starting with '#' to 'name:' - * @param {string} value - * @returns {string} Lowercase value with 'name:' replaced with '#' + * @param value + * @returns Lowercase value with 'name:' replaced with '#' * */ export function expandNameTag(value: string | object): string { - if (value && typeof value === "string" && value.startsWith("#")) { - value = value.replace("#", "name:"); + if (value && typeof value === "string") { + value = value.replace(/^#/, "name:"); } return toLower(value); } @@ -70,9 +70,9 @@ type HandlerReturn = { /** * Checks if a query value is equal to the item value - * @param {string} attribute of the content item - * @param {string} [query] parameter if the attribute does not match the server query key - * @param {function} [converter] if item attribute value has to be transformed e.g. to a date. + * @param attribute of the content item + * @param query parameter if the attribute does not match the server query key + * @param converter if item attribute value has to be transformed e.g. to a date. */ export function equals(attribute: string, query?: string, converter?: Converter): HandlerReturn { return { @@ -91,9 +91,9 @@ export function equals(attribute: string, query?: string, converter?: Convert /** * Checks if a query value is part of the item value - * @param {string} attribute of the content item - * @param {string} [query] parameter if the attribute does not match the server query key - * @param {function} [converter] if item attribute value has to be transformed e.g. to a date. + * @param attribute of the content item + * @param query parameter if the attribute does not match the server query key + * @param converter if item attribute value has to be transformed e.g. to a date. */ export function contains(attribute: string, query?: string, converter?: Converter): HandlerReturn { return { @@ -112,9 +112,9 @@ export function contains(attribute: string, query?: string, converter?: Conve /** * Checks if a value is greater or smaller than the item value - * @param {string} attribute of the content item - * @param {string} variant specifying the comparison operation e.g. le(<=) and gt(>) - * @param {function} [converter] if item attribute value has to be transformed e.g. to a date. + * @param attribute of the content item + * @param variant specifying the comparison operation e.g. le(<=) and gt(>) + * @param converter if item attribute value has to be transformed e.g. to a date. */ export function compare(attribute: string, variant: string, converter?: Converter): HandlerReturn { return { @@ -162,7 +162,7 @@ export default class Filtering { } /** Returns normalize defaults by adding the operator to the key identifier - * @returns {Object} Dictionary with query key and values for default filters + * @returns Dictionary with query key and values for default filters * */ getDefaults(): Record { const normalized: Record = {}; @@ -173,8 +173,8 @@ export default class Filtering { } /** Returns true if default filter values are not changed - * @param {Object} filterSettings Object containing filter settings - * @returns {Boolean} True if default filter values are not changed + * @param filterSettings Object containing filter settings + * @returns true if default filter values are not changed * **/ containsDefaults(filterSettings: Record): boolean { const normalized = this.getDefaults(); @@ -191,8 +191,8 @@ export default class Filtering { } /** Build a text filter from filter settings - * @param {Object} filterSettings Object containing filter settings - * @returns {String} Parsed filter text string + * @param filterSettings Object containing filter settings + * @returns Parsed filter text string * */ getFilterText(filterSettings: Record): string { const normalized = this.getDefaults(); @@ -215,8 +215,8 @@ export default class Filtering { } /** Parses single text input into a dict of field->value pairs. - * @param {string} filterText Raw filter text string - * @returns {object} Filters as dict of field->value pairs + * @param filterText Raw filter text string + * @returns Filters as dict of field->value pairs * */ getFilters(filterText: string): [string, T][] { const pairSplitRE = /[^\s']+(?:'[^']*'[^\s']*)*|(?:'[^']*'[^\s']*)+/g; @@ -272,10 +272,10 @@ export default class Filtering { * e.g.: Unlike getFilters or getQueryDict, this maintains "hid>":"3" instead * of changing it to "hid-gt":"3" * Only used to sync filterSettings (in HistoryFilters) - * @param {Object} filters Parsed filterText from getFilters() - * @returns {Object} filterSettings + * @param filters Parsed filterText from getFilters() + * @returns filterSettings */ - toAlias(filters: [string, T][]): object { + toAlias(filters: [string, T][]) { const result: Record = {}; for (const [key, value] of filters) { let hasAlias = false; @@ -295,10 +295,10 @@ export default class Filtering { } /** Returns a dictionary with query key and values. - * @param {String} filterText Raw filter text string - * @returns {Object} Dictionary with query key and values + * @param filterText Raw filter text string + * @returns Dictionary with query key and values */ - getQueryDict(filterText: string): object { + getQueryDict(filterText: string) { const queryDict: Record = {}; const filters = this.getFilters(filterText); for (const [key, value] of filters) { @@ -310,8 +310,8 @@ export default class Filtering { } /** Returns query string from filter text. - * @param {String} filterText Raw filter text string to be parsed - * @returns {String} Parsed query string + * @param filterText Raw filter text string to be parsed + * @returns Parsed query string * */ getQueryString(filterText: string): string { const filterDict = this.getQueryDict(filterText); @@ -321,12 +321,12 @@ export default class Filtering { } /** Check the value of a particular filter. - * @param {String} filterText Raw filter text string - * @param {String} filterName Filter key to check - * @param {String | Object | Boolean} filterValue The filter value to check - * @returns {Boolean} True if the filter is set to the given value + * @param filterText Raw filter text string + * @param filterName Filter key to check + * @param filterValue The filter value to check + * @returns True if the filter is set to the given value * */ - checkFilter(filterText: string, filterName: string, filterValue: T): boolean { + checkFilter(filterText: string, filterName: string, filterValue: string | object | boolean): boolean { const re = new RegExp(`${filterName}:(\\S+)`); const reMatch = re.exec(filterText); const testValue = reMatch ? reMatch[1] : this.defaultFilters[filterName]; @@ -334,9 +334,9 @@ export default class Filtering { } /** Test if an item passes all filters. - * @param {Object} filters Parsed in key-value pairs from getFilters() - * @param {Object} item Item to test against the filters - * @returns {Boolean} True if the item passes all filters + * @param filters Parsed in key-value pairs from getFilters() + * @param item Item to test against the filters + * @returns True if the item passes all filters * */ testFilters(filters: [string, T][], item: Record): boolean { for (const [key, filterValue] of filters) { From e8e2e3aa7c7296e6555e3c7e09718ad716ed0b50 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Mon, 16 Jan 2023 13:35:03 +0100 Subject: [PATCH 09/42] attempt to fix "No collection type or collection type source defined." --- client/src/components/Workflow/Editor/modules/terminals.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/src/components/Workflow/Editor/modules/terminals.ts b/client/src/components/Workflow/Editor/modules/terminals.ts index 5af806d1b42..2dc9da19697 100644 --- a/client/src/components/Workflow/Editor/modules/terminals.ts +++ b/client/src/components/Workflow/Editor/modules/terminals.ts @@ -316,11 +316,10 @@ class BaseInputTerminal extends Terminal { postJobActionKey in outputStep.post_job_actions ) { const extensionType = outputStep.post_job_actions![postJobActionKey]!.action_arguments.newtype; - assertDefined(extensionType); (terminalSource as DataOutput | CollectionOutput) = { ...terminalSource, - extensions: [extensionType], + extensions: extensionType ? [extensionType] : [], }; } From 7cb6f1597965ae021cc4c41ae46cb345d3ea938a Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Mon, 16 Jan 2023 13:42:54 +0100 Subject: [PATCH 10/42] fix linting.ts possible undefined --- .../Workflow/Editor/modules/linting.ts | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/client/src/components/Workflow/Editor/modules/linting.ts b/client/src/components/Workflow/Editor/modules/linting.ts index c59217561a5..3a30db1a7db 100644 --- a/client/src/components/Workflow/Editor/modules/linting.ts +++ b/client/src/components/Workflow/Editor/modules/linting.ts @@ -2,6 +2,7 @@ import { terminalFactory } from "./terminals"; import type { Step, Steps } from "@/stores/workflowStepStore"; import type { DatatypesMapperModel } from "@/components/Datatypes/model"; import type { UntypedParameters } from "@/components/Workflow/Editor/modules/parameters"; +import { assertDefined } from "@/utils/assertions"; interface LintState { stepId: number; @@ -90,16 +91,23 @@ export function getUntypedParameters(untypedParameters: UntypedParameters) { const items: LintState[] = []; if (untypedParameters) { untypedParameters.parameters.forEach((parameter) => { - const parameterReference = parameter.references[0]; - // TODO: Not sure this is right, but I think this may have been broken previously? - const stepLabel = "toolInput" in parameterReference ? parameterReference.toolInput.label : parameter.name; - items.push({ - stepId: parameterReference.stepId, - stepLabel: stepLabel, - warningLabel: parameter.name, - name: parameter.name, - autofix: parameter.canExtract(), - }); + try { + const parameterReference = parameter.references[0]; + assertDefined(parameterReference, `Error: parameter references for ${parameter.name} are empty.`); + + // TODO: Not sure this is right, but I think this may have been broken previously? + const stepLabel = + "toolInput" in parameterReference ? parameterReference.toolInput.label : parameter.name; + items.push({ + stepId: parameterReference.stepId, + stepLabel: stepLabel, + warningLabel: parameter.name, + name: parameter.name, + autofix: parameter.canExtract(), + }); + } catch (errorMessage) { + console.error(errorMessage); + } }); } return items; From 144d41ff152e6d2d8fc24d9131e1e4fa4425b988 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Mon, 16 Jan 2023 14:13:06 +0100 Subject: [PATCH 11/42] run prettier update pre-commit hook --- .pre-commit-config.yaml.sample | 4 +++- client/src/mvc/ui/ui-modal.js | 5 +---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml.sample b/.pre-commit-config.yaml.sample index 0523e68b212..8de06e00314 100644 --- a/.pre-commit-config.yaml.sample +++ b/.pre-commit-config.yaml.sample @@ -8,11 +8,13 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.6.0 # Use the sha or tag you want to point at + rev: v2.7.1 # Use the sha or tag you want to point at hooks: - id: prettier types: [file] types_or: [javascript, jsx, ts, tsx, vue] + additional_dependencies: + - prettier@2.8.3 # Workaround. See https://github.com/pre-commit/mirrors-prettier/issues/29 - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.3.0 # Use the ref you want to point at hooks: diff --git a/client/src/mvc/ui/ui-modal.js b/client/src/mvc/ui/ui-modal.js index e36f52e199d..cae8e5eb341 100644 --- a/client/src/mvc/ui/ui-modal.js +++ b/client/src/mvc/ui/ui-modal.js @@ -108,10 +108,7 @@ export var View = Backbone.View.extend({ if (this.options.buttons) { var counter = 0; $.each(this.options.buttons, (name, callback) => { - var $button = $("