From 104497b486361a24d323b9f3be70e03d61a9dd31 Mon Sep 17 00:00:00 2001 From: Guillaume Thibault Date: Tue, 16 Apr 2024 21:17:13 -0400 Subject: [PATCH 01/49] [stu-346-placeholder-test] feat: new Test type: "Placeholder" --- captain/models/test_sequencer.py | 1 + .../utils/test_sequencer/run_test_sequence.py | 22 ++++ src/renderer/hooks/useTestSequencerState.ts | 13 ++- .../components/DesignBar.tsx | 30 +++-- .../modals/CreatePlaceholderTestModal.tsx | 108 ++++++++++++++++++ src/renderer/types/test-sequencer.ts | 2 +- 6 files changed, 158 insertions(+), 18 deletions(-) create mode 100644 src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx diff --git a/captain/models/test_sequencer.py b/captain/models/test_sequencer.py index b48eab714..0c02e7408 100644 --- a/captain/models/test_sequencer.py +++ b/captain/models/test_sequencer.py @@ -19,6 +19,7 @@ class TestTypes(StrEnum): python = "python" flojoy = "flojoy" matlab = "matlab" + placeholder = "placeholder" class StatusTypes(StrEnum): diff --git a/captain/utils/test_sequencer/run_test_sequence.py b/captain/utils/test_sequencer/run_test_sequence.py index 8d40f064b..d7e998f12 100644 --- a/captain/utils/test_sequencer/run_test_sequence.py +++ b/captain/utils/test_sequencer/run_test_sequence.py @@ -165,6 +165,27 @@ def _run_pytest(node: TestNode) -> Extract: ) +@_with_stream_test_result +def _run_placeholder(node: TestNode) -> Extract: + """ + @params file_path: path to the file + @returns: + bool: result of the test + float: time taken to execute the test + str: error message if any + """ + return ( + lambda _: None, + TestResult( + node, + False, + 0, + "Placeholder test not implemented", + utcnow_str(), + ), + ) + + def _eval_condition( result_dict: dict[str, TestResult], condition: str, identifiers: set[str] ): @@ -244,6 +265,7 @@ def get_next_children_from_context(context: Context): { TestTypes.python: (None, _run_python), TestTypes.pytest: (None, _run_pytest), + TestTypes.placeholder: (None, _run_placeholder) }, ), "conditional": ( diff --git a/src/renderer/hooks/useTestSequencerState.ts b/src/renderer/hooks/useTestSequencerState.ts index 4e461f697..1e48ee56b 100644 --- a/src/renderer/hooks/useTestSequencerState.ts +++ b/src/renderer/hooks/useTestSequencerState.ts @@ -17,7 +17,7 @@ import useWithPermission from "@/renderer/hooks/useWithPermission"; import { useSequencerStore } from "@/renderer/stores/sequencer"; import { useShallow } from "zustand/react/shallow"; import { v4 as uuidv4 } from "uuid"; -import { Err, Ok, Result } from "neverthrow"; +import { Err, Ok, Result, err, ok } from "neverthrow"; import { verifyElementCompatibleWithSequence } from "@/renderer/routes/test_sequencer_panel/utils/SequenceHandler"; import { toast } from "sonner"; import { SendJsonMessage } from "react-use-websocket/dist/lib/types"; @@ -172,7 +172,7 @@ export function useDisplayedSequenceState() { p: | TestSequenceElement[] | ((elems: TestSequenceElement[]) => TestSequenceElement[]), - ) { + ): Result { let candidateElems: TestSequenceElement[]; // handle overloads @@ -189,7 +189,7 @@ export function useDisplayedSequenceState() { ); if (!res) { console.error("Validation failed"); - return; + return err(new Error("Validation failed")); } // PASS @@ -198,13 +198,14 @@ export function useDisplayedSequenceState() { // creates tree to send to backend setTree(createTestSequenceTree(candidateElems)); + return ok(undefined); } const setElemsWithPermissions = withPermissionCheck(setElems); async function AddNewElems( newElems: TestSequenceElement[], - ): Promise> { + ): Promise> { // Validate with project if (project !== null) { const result = await verifyElementCompatibleWithSequence( @@ -216,8 +217,8 @@ export function useDisplayedSequenceState() { } } // Add new elements - setElems((elems) => [...elems, ...newElems]); - return new Ok(null); + const result = setElems((elems) => [...elems, ...newElems]); + return result; } const addNewElemsWithPermissions = withPermissionCheck(AddNewElems); diff --git a/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx b/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx index de4b98f31..9955b8bc5 100644 --- a/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx @@ -28,6 +28,7 @@ import { HoverCardTrigger, } from "@/renderer/components/ui/hover-card"; import _ from "lodash"; +import { CreatePlaceholderTestModal } from "./modals/CreatePlaceholderTestModal"; export function DesignBar() { const { setIsImportTestModalOpen, setIsCreateProjectModalOpen } = @@ -63,9 +64,15 @@ export function DesignBar() { }, [elems, sequences, cycleRuns]); const [displayTotal, setDisplayTotal] = useState(false); + const [isCreatePlaceholderTestModalOpen, setIsCreatePlaceholderTestModalOpen] = + useState(false); return (
+
{isAdmin() && ( @@ -94,6 +101,18 @@ export function DesignBar() { /> New Test + { + setIsCreatePlaceholderTestModalOpen(true); + }} + data-testid="placeholder-test-button" + > + + New Placeholder + { setIsCreateProjectModalOpen(true); @@ -122,17 +141,6 @@ export function DesignBar() {
)} - {/* Comming soon - - */} {sequences.length <= 1 ? ( diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx new file mode 100644 index 000000000..114a257df --- /dev/null +++ b/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx @@ -0,0 +1,108 @@ +import { Button } from "@/renderer/components/ui/button"; +import { Dialog, DialogContent } from "@/renderer/components/ui/dialog"; +import { Input } from "@/renderer/components/ui/input"; +import { createNewTest, useDisplayedSequenceState } from "@/renderer/hooks/useTestSequencerState"; +import { useEffect, useState } from "react"; +import { toast } from "sonner"; + +export const CreatePlaceholderTestModal = ({ + isModalOpen, + setModalOpen, +}: { + isModalOpen: boolean; + setModalOpen: (value: boolean) => void; +}) => { + const [name, setName] = useState(""); + const [min, setMin] = useState(0); + const [max, setMax] = useState(0); + const [unit, setUnit] = useState(""); + + const { addNewElems } = useDisplayedSequenceState(); + + const handleCreate = async () => { + if (name === "") { + toast.error("Please enter a test name"); + } + const res = await addNewElems( + [createNewTest( + name, + "", + "placeholder", + false, + undefined, + undefined, + min, + max, + unit + )] + ); + console.log(res); + if (res.isErr()) { + return; + } + setModalOpen(false); + } + + return ( + + +

Create a placeholder test step

+

This will create a new test step with the given name and expected value without being link to any code. The code can be added later.

+ +
+

+ Test Name +

+ setName(e.target.value)} + /> +
+ +
+

+ Expected Value +

+
+
+

Mininum

+ { + setMin(parseFloat(e.target.value)); + }} + /> +
+ +
+

Maximum

+ { + setMax(parseFloat(e.target.value)); + }} + /> +
+ +
+

Displayed Unit

+ { + setUnit(e.target.value); + }} + /> +
+
+
+ +
+
+ ); +}; diff --git a/src/renderer/types/test-sequencer.ts b/src/renderer/types/test-sequencer.ts index a711fdf3e..c5db2c839 100644 --- a/src/renderer/types/test-sequencer.ts +++ b/src/renderer/types/test-sequencer.ts @@ -4,7 +4,7 @@ export type LockedContextType = { isLocked: boolean; }; -export const TestType = z.enum(["pytest", "python", "flojoy", "matlab"]); +export const TestType = z.enum(["pytest", "python", "flojoy", "matlab", "placeholder"]); export type TestType = z.infer; export const ResultType = z.enum(["pass", "fail", "aborted"]); From 97b030c5a08d1b64851197c8a380af2f0b139655 Mon Sep 17 00:00:00 2001 From: Guillaume Thibault Date: Tue, 16 Apr 2024 23:14:03 -0400 Subject: [PATCH 02/49] [stu-346-placeholder-test] feat: Change the path and test type of placeholder --- src/renderer/hooks/useTestImport.ts | 65 ++++++++- .../components/data-table/TestTable.tsx | 60 +++++++- .../components/modals/ChangeLinkedTest.tsx | 138 ++++++++++++++++++ .../modals/CreatePlaceholderTestModal.tsx | 1 - .../components/modals/ImportTestModal.tsx | 4 +- 5 files changed, 259 insertions(+), 9 deletions(-) create mode 100644 src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx diff --git a/src/renderer/hooks/useTestImport.ts b/src/renderer/hooks/useTestImport.ts index d5f7cdbde..cf6886abb 100644 --- a/src/renderer/hooks/useTestImport.ts +++ b/src/renderer/hooks/useTestImport.ts @@ -1,4 +1,4 @@ -import { TestDiscoverContainer } from "@/renderer/types/test-sequencer"; +import { TestDiscoverContainer, TestSequenceElement } from "@/renderer/types/test-sequencer"; import { createNewTest, useDisplayedSequenceState, @@ -26,7 +26,7 @@ function parseDiscoverContainer( }); } -export const useTestImport = () => { +export const useDiscoverAndImportTests = () => { const { addNewElems } = useDisplayedSequenceState(); const { openErrorModal } = useSequencerModalStore(); @@ -125,3 +125,64 @@ export const useTestImport = () => { return openFilePicker; }; + + +export async function useDiscoverPytestElements() { + + const handleUserDepInstall = useCallback(async (depName: string) => { + const promise = () => window.api.poetryInstallDepUserGroup(depName); + toast.promise(promise, { + loading: `Installing ${depName}...`, + success: () => { + return `${depName} has been added.`; + }, + error: + "Could not install the library. Please consult the Dependency Manager in the settings.", + }); + }, []); + + async function getTests( + path: string, + ): Promise> { + let data: TestDiscoverContainer; + const res = await discoverPytest(path, false); + if (res.isErr()) { + return err(res.error); + } + data = res.value; + if (data.error) { + return err(Error(data.error)); + } + for (const lib of data.missingLibraries) { + toast.error(`Missing Python Library: ${lib}`, { + action: { + label: "Install", + onClick: () => { + handleUserDepInstall(lib); + }, + }, + }); + return err(Error("Please retry after installing the missing libraries.")); + } + const newElems = parseDiscoverContainer(data, { importAsOneRef: false, importType: "pytest" } ); + if (newElems.length === 0) { + return err(Error("No tests were found in the specified file.")); + } + return ok(newElems); + } + + const openFilePicker = (): Promise> => { + return window.api.openTestPicker() + .then(result => { + if (!result) + return err(Error("No file selected.")); + toast.info("Importing tests..."); + const { filePath } = result; + return getTests(filePath); + }); + } + + return openFilePicker; + +}; + diff --git a/src/renderer/routes/test_sequencer_panel/components/data-table/TestTable.tsx b/src/renderer/routes/test_sequencer_panel/components/data-table/TestTable.tsx index 9d45de90a..191b50000 100644 --- a/src/renderer/routes/test_sequencer_panel/components/data-table/TestTable.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/data-table/TestTable.tsx @@ -58,6 +58,8 @@ import useWithPermission from "@/renderer/hooks/useWithPermission"; import { useSequencerModalStore } from "@/renderer/stores/modal"; import { WriteMinMaxModal } from "@/renderer/routes/test_sequencer_panel/components/modals/WriteMinMaxModal"; import { toast } from "sonner"; +import { ChangeLinkedTestModal } from "../modals/ChangeLinkedTest"; +import { ImportType } from "../modals/ImportTestModal"; export function TestTable() { const { elems, setElems } = useDisplayedSequenceState(); @@ -324,6 +326,8 @@ export function TestTable() { }, }); + // Remove ---------------------- + const handleClickRemoveTests = () => { onRemoveTest(map(Object.keys(rowSelection), (idxStr) => parseInt(idxStr))); setRowSelection([]); @@ -341,6 +345,8 @@ export function TestTable() { }); }; + // Conditional ------------------ + const addConditionalAfterIdx = useRef(-1); const [showWriteConditionalModal, setShowWriteConditionalModal] = @@ -386,6 +392,8 @@ export function TestTable() { setShowWriteConditionalModal(true); }; + // Edit MinMax ------------------ + const onClickWriteMinMax = (idx: number) => { writeMinMaxForIdx.current = idx; setShowWriteMinMaxModal(true); @@ -409,6 +417,27 @@ export function TestTable() { }); }; + + // Change linked test ------------ + + const [openLinkedTestModal, setOpenLinkedTestModal] = useState(false); + const testRef = useRef(-1); + + const handleChangeLinkedTest = (newPath: string, testType: ImportType) => { + setElems((data) => { + const new_data = [...data]; + const test = new_data[testRef.current] as Test; + new_data[testRef.current] = { + ...test, + path: newPath, + testType: testType, + }; + return new_data; + }); + } + + // Context Menu ------------------ + const getSpecificContextMenuItems = (row: Row) => { switch (row.original.type) { case "test": @@ -445,6 +474,11 @@ export function TestTable() { setModalOpen={setShowWriteMinMaxModal} handleWrite={onSubmitWriteMinMax} /> + {openPyTestFileModal && ( ); })} @@ -549,7 +583,7 @@ export function TestTable() { > Add Conditional - {row.original.type === "test" && ( + {row.original.type === "test" && row.original.testType !== "placeholder" && ( <> Consult Code + + )} + {row.original.type === "test" && row.original.testType === "placeholder" && ( + <> + + { + setOpenLinkedTestModal(true); + testRef.current = parseInt(row.id); + setTestToDisplay(row.original as Test); + }} + > + Link to Code + + + )} + {row.original.type === "test" && ( + <> { diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx new file mode 100644 index 000000000..2ac75c490 --- /dev/null +++ b/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx @@ -0,0 +1,138 @@ +import { Button } from "@/renderer/components/ui/button"; +import { Checkbox } from "@/renderer/components/ui/checkbox"; +import { Dialog, DialogContent } from "@/renderer/components/ui/dialog"; +import { Separator } from "@/renderer/components/ui/separator"; +import { useDisplayedSequenceState } from "@/renderer/hooks/useTestSequencerState"; +import { useAppStore } from "@/renderer/stores/app"; +import { useSequencerModalStore } from "@/renderer/stores/modal"; +import { ExternalLinkIcon } from "lucide-react"; +import { useState } from "react"; +import { useShallow } from "zustand/react/shallow"; +import { ImportType } from "./ImportTestModal"; +import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/renderer/components/ui/select"; +import { useDiscoverAndImportTests, useDiscoverPytestElements } from "@/renderer/hooks/useTestImport"; +import { TestSequenceElement } from "@/renderer/types/test-sequencer"; +import { toast } from "sonner"; + + +export const ChangeLinkedTestModal = ({ + isModalOpen, + setModalOpen, + handleSubmit, +}: { + isModalOpen: boolean; + setModalOpen: (value: boolean) => void; + handleSubmit: (path: string, testType: ImportType) => void; +}) => { + const [availableTests, setAvailableTests] = useState([]); + const [selectedPath, setSelectedPath] = useState(""); + + const { setIsDepManagerModalOpen } = useAppStore( + useShallow((state) => ({ + setIsDepManagerModalOpen: state.setIsDepManagerModalOpen, + })), + ); + + + const openFilePickerPromise = useDiscoverPytestElements(); + + const handleDiscoverPytestElements = () => { + openFilePickerPromise + .then(useDiscover => { + return useDiscover(); + }) + .then(potentialElementsResult => { + potentialElementsResult.match( + (elements) => { + setAvailableTests(elements); + }, + (error) => { + // TODO: Handle error in a toast, don't forget to check for size of message ;) + console.error(error); + } + ); + }) + .catch(error => { + console.log(error); + }); + } + + const handleSubmitByType = (testType: ImportType) => { + if (testType === "pytest") { + if (selectedPath === "") { + toast.error("Please select a test to link to"); + } + handleSubmit(selectedPath, testType); + } else { + window.api.openTestPicker() + .then(result => { + if (!result) { + return; + } + const { filePath } = result; + handleSubmit(filePath, testType); + }); + } + setModalOpen(false); + } + + return ( + + +

Select a test to link to

+

Pytest

+
+ +
+ +
+
+ + +

Python script

+ +
+
+
+ +
+
+ +
+ ); +}; diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx index 114a257df..c84f0327e 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx @@ -36,7 +36,6 @@ export const CreatePlaceholderTestModal = ({ unit )] ); - console.log(res); if (res.isErr()) { return; } diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/ImportTestModal.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/ImportTestModal.tsx index dfea5358a..4df0c7234 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/ImportTestModal.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/ImportTestModal.tsx @@ -2,7 +2,7 @@ import { Button } from "@/renderer/components/ui/button"; import { Checkbox } from "@/renderer/components/ui/checkbox"; import { Dialog, DialogContent } from "@/renderer/components/ui/dialog"; import { Separator } from "@/renderer/components/ui/separator"; -import { useTestImport } from "@/renderer/hooks/useTestImport"; +import { useDiscoverAndImportTests } from "@/renderer/hooks/useTestImport"; import { useDisplayedSequenceState } from "@/renderer/hooks/useTestSequencerState"; import { useAppStore } from "@/renderer/stores/app"; import { useSequencerModalStore } from "@/renderer/stores/modal"; @@ -28,7 +28,7 @@ export const ImportTestModal = () => { })), ); - const openFilePicker = useTestImport(); + const openFilePicker = useDiscoverAndImportTests(); const { setIsLocked } = useDisplayedSequenceState(); const handleImportTest = (importType: ImportType) => { From 8d8870914dcd20442821f10dbdceaf5b441f6435 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Wed, 17 Apr 2024 11:32:47 -0400 Subject: [PATCH 03/49] chore: all test step can be edited --- .../components/data-table/TestTable.tsx | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/renderer/routes/test_sequencer_panel/components/data-table/TestTable.tsx b/src/renderer/routes/test_sequencer_panel/components/data-table/TestTable.tsx index 191b50000..3f75bb310 100644 --- a/src/renderer/routes/test_sequencer_panel/components/data-table/TestTable.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/data-table/TestTable.tsx @@ -600,13 +600,9 @@ export function TestTable() { <> { - setOpenLinkedTestModal(true); - testRef.current = parseInt(row.id); - setTestToDisplay(row.original as Test); - }} + disabled={true} > - Link to Code + Not Linked to any code )} @@ -636,6 +632,15 @@ export function TestTable() { > Edit Expected Value
+ { + setOpenLinkedTestModal(true); + testRef.current = parseInt(row.id); + setTestToDisplay(row.original as Test); + }} + > + Change executable + )} From 7180c825a4c7ea1d015847a2000ca0d8fdefba4e Mon Sep 17 00:00:00 2001 From: Guillaume Date: Wed, 17 Apr 2024 13:13:44 -0400 Subject: [PATCH 04/49] fix: css overflow --- .../components/modals/ChangeLinkedTest.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx index 2ac75c490..ab48d198a 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx @@ -84,9 +84,9 @@ export const ChangeLinkedTestModal = ({

Select a test to link to

Pytest

-
+
@@ -101,14 +106,17 @@ export const ChangeLinkedTestModal = ({ Discover a file to display its tests {availableTests.map((test) => { if (test.type === "test") { - return {`...${test.path.slice(-35)}`}; + return ( + {`...${test.path.slice(-35)}`} + ); } - }) - } + })} -
+
+ > + {" "} + Submit Selected Test{" "} +

Python Script

-
diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx index c84f0327e..0bfe08adc 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx @@ -1,7 +1,10 @@ import { Button } from "@/renderer/components/ui/button"; import { Dialog, DialogContent } from "@/renderer/components/ui/dialog"; import { Input } from "@/renderer/components/ui/input"; -import { createNewTest, useDisplayedSequenceState } from "@/renderer/hooks/useTestSequencerState"; +import { + createNewTest, + useDisplayedSequenceState, +} from "@/renderer/hooks/useTestSequencerState"; import { useEffect, useState } from "react"; import { toast } from "sonner"; @@ -23,8 +26,8 @@ export const CreatePlaceholderTestModal = ({ if (name === "") { toast.error("Please enter a test name"); } - const res = await addNewElems( - [createNewTest( + const res = await addNewElems([ + createNewTest( name, "", "placeholder", @@ -33,73 +36,75 @@ export const CreatePlaceholderTestModal = ({ undefined, min, max, - unit - )] - ); + unit, + ), + ]); if (res.isErr()) { return; } setModalOpen(false); - } + }; return ( -

Create a placeholder test step

-

This will create a new test step with the given name and expected value without being link to any code. The code can be added later.

+

+ Create a placeholder test step +

+

+ {" "} + This will create a new test step with the given name and expected + value without being link to any code. The code can be added later. +

-

- Test Name -

- setName(e.target.value)} - /> +

Test Name

+ setName(e.target.value)} + />
-

- Expected Value -

-
-
-

Mininum

- { - setMin(parseFloat(e.target.value)); - }} - /> -
+

Expected Value

+
+
+

Mininum

+ { + setMin(parseFloat(e.target.value)); + }} + /> +
-
-

Maximum

- { - setMax(parseFloat(e.target.value)); - }} - /> -
+
+

Maximum

+ { + setMax(parseFloat(e.target.value)); + }} + /> +
-
-

Displayed Unit

- { - setUnit(e.target.value); - }} - /> +
+

Displayed Unit

+ { + setUnit(e.target.value); + }} + /> +
-
diff --git a/src/renderer/types/test-sequencer.ts b/src/renderer/types/test-sequencer.ts index c5db2c839..f012d7782 100644 --- a/src/renderer/types/test-sequencer.ts +++ b/src/renderer/types/test-sequencer.ts @@ -4,7 +4,13 @@ export type LockedContextType = { isLocked: boolean; }; -export const TestType = z.enum(["pytest", "python", "flojoy", "matlab", "placeholder"]); +export const TestType = z.enum([ + "pytest", + "python", + "flojoy", + "matlab", + "placeholder", +]); export type TestType = z.infer; export const ResultType = z.enum(["pass", "fail", "aborted"]); From fde3542438015113a0b176fb029302781626600e Mon Sep 17 00:00:00 2001 From: Guillaume Date: Wed, 17 Apr 2024 13:23:30 -0400 Subject: [PATCH 07/49] fix: Eslint --- src/renderer/hooks/useTestImport.ts | 3 +-- .../components/modals/CreatePlaceholderTestModal.tsx | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/renderer/hooks/useTestImport.ts b/src/renderer/hooks/useTestImport.ts index f1e93217d..492ea9cb1 100644 --- a/src/renderer/hooks/useTestImport.ts +++ b/src/renderer/hooks/useTestImport.ts @@ -145,12 +145,11 @@ export async function useDiscoverPytestElements() { async function getTests( path: string, ): Promise> { - let data: TestDiscoverContainer; const res = await discoverPytest(path, false); if (res.isErr()) { return err(res.error); } - data = res.value; + const data = res.value; if (data.error) { return err(Error(data.error)); } diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx index 0bfe08adc..b2465ad57 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx @@ -5,7 +5,7 @@ import { createNewTest, useDisplayedSequenceState, } from "@/renderer/hooks/useTestSequencerState"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { toast } from "sonner"; export const CreatePlaceholderTestModal = ({ From 68361b031f930fccd82027d63a81bc438a8abd31 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Wed, 17 Apr 2024 13:26:01 -0400 Subject: [PATCH 08/49] chore: python formatting --- captain/utils/test_sequencer/run_test_sequence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/captain/utils/test_sequencer/run_test_sequence.py b/captain/utils/test_sequencer/run_test_sequence.py index d7e998f12..1783f2e00 100644 --- a/captain/utils/test_sequencer/run_test_sequence.py +++ b/captain/utils/test_sequencer/run_test_sequence.py @@ -265,7 +265,7 @@ def get_next_children_from_context(context: Context): { TestTypes.python: (None, _run_python), TestTypes.pytest: (None, _run_pytest), - TestTypes.placeholder: (None, _run_placeholder) + TestTypes.placeholder: (None, _run_placeholder), }, ), "conditional": ( From be7f5cab49319819bd82f64cb3990ef4ef7bbcb6 Mon Sep 17 00:00:00 2001 From: Gui Date: Wed, 17 Apr 2024 14:41:36 -0400 Subject: [PATCH 09/49] Update src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx Co-authored-by: Jeff Zhang <47371088+39bytes@users.noreply.github.com> --- .../test_sequencer_panel/components/modals/ChangeLinkedTest.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx index 1c881bff3..a4c928259 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx @@ -119,7 +119,7 @@ export const ChangeLinkedTestModal = ({
-
- { - setChecked(checked as boolean); - }} - /> -
+ + + + + +
Sequence Gallery
+
+
+ + + + { data.map((SeqExample) => ( + +
+
+ + + +
+ +
+
{SeqExample.title}
+
{SeqExample.description}
+ +
+ +
+ +
+
+
+
+ ))} + + + + ); +}; + + From c1f0de1c0300417c02b4fd1dcf383847bc8a60de Mon Sep 17 00:00:00 2001 From: Guillaume Date: Sat, 20 Apr 2024 10:42:25 -0400 Subject: [PATCH 17/49] fix: new return Result when test are duplicated doesn't close Modal. -> Fix test --- .../13_create_test_sequence.spec.ts | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/playwright-test/13_create_test_sequence.spec.ts b/playwright-test/13_create_test_sequence.spec.ts index ae0471e56..13bb93178 100644 --- a/playwright-test/13_create_test_sequence.spec.ts +++ b/playwright-test/13_create_test_sequence.spec.ts @@ -67,25 +67,8 @@ test.describe("Create a test sequence", () => { await expect(window.getByTestId(Selectors.newDropdown)).toBeEnabled({ timeout: 15000, }); - await window.getByTestId(Selectors.newDropdown).click(); - await window.getByTestId(Selectors.importTestBtn).click(); - - // Select the fixture file - const customTestFile = join(__dirname, "fixtures/custom-sequences/test.py"); - await app.evaluate(async ({ dialog }, customTestFile) => { - dialog.showOpenDialog = () => - Promise.resolve({ filePaths: [customTestFile], canceled: false }); - }, customTestFile); - - // Click on Pytest test to open modal - await window.getByTestId(Selectors.pytestBtn).click(); - - // Expect test to be loaded - await expect( - window.locator("div", { hasText: "test_one" }).first(), - ).toBeVisible(); - // Ctrl/meta + p key shortcut to save the sequence + // Ctrl/meta + s key shortcut to save the sequence if (process.platform === "darwin") { await window.keyboard.press("Meta+s"); } else { From 0ab4c071d7fd31bdfe47ae80c29aad31c174a3b9 Mon Sep 17 00:00:00 2001 From: Guillaume Thibault Date: Sat, 20 Apr 2024 11:58:33 -0400 Subject: [PATCH 18/49] [sequencer-gallery] chore: Working import demo sequence --- src/api/index.ts | 2 + src/main/utils.ts | 4 ++ .../conditional/Conditional Demo.tjoy | 1 + .../conditional/flojoy_requirements.txt | 0 .../data/apps/sequencer/conditional/test.py | 28 ++++++++ src/renderer/hooks/useTestSequencerProject.ts | 55 +++++++++++++++ .../modals/SequencerGalleryModal.tsx | 68 ++++++++----------- 7 files changed, 120 insertions(+), 38 deletions(-) create mode 100644 src/renderer/data/apps/sequencer/conditional/Conditional Demo.tjoy create mode 100644 src/renderer/data/apps/sequencer/conditional/flojoy_requirements.txt create mode 100644 src/renderer/data/apps/sequencer/conditional/test.py diff --git a/src/api/index.ts b/src/api/index.ts index a3314bb44..34d0d6b30 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -148,11 +148,13 @@ export default { openAllFilesInFolder: ( folderPath: string, allowedExtensions: string[] = ["json"], + relativePath: boolean = false, ): Promise<{ filePath: string; fileContent: string }[] | undefined> => ipcRenderer.invoke( API.openAllFilesInFolderPicker, folderPath, allowedExtensions, + relativePath, ), getFileContent: (filepath: string): Promise => diff --git a/src/main/utils.ts b/src/main/utils.ts index f2b4b6185..ecd00d514 100644 --- a/src/main/utils.ts +++ b/src/main/utils.ts @@ -190,8 +190,12 @@ export const openAllFilesInFolderPicker = ( _, folderPath: string, allowedExtensions: string[] = ["json"], + relativePath: boolean = false, ): { filePath: string; fileContent: string }[] | undefined => { // Return multiple files or all files with the allowed extensions if a folder is selected + if (relativePath) { + folderPath = join(__dirname, folderPath); + } if (!fs.existsSync(folderPath) || !fs.lstatSync(folderPath).isDirectory()) { return undefined; } diff --git a/src/renderer/data/apps/sequencer/conditional/Conditional Demo.tjoy b/src/renderer/data/apps/sequencer/conditional/Conditional Demo.tjoy new file mode 100644 index 000000000..c2ba83066 --- /dev/null +++ b/src/renderer/data/apps/sequencer/conditional/Conditional Demo.tjoy @@ -0,0 +1 @@ +{"name":"Conditional Demo","description":"Example","elems":[{"type":"test","id":"ca204090-41bb-4236-97ba-623c4257ef0a","groupId":"41717403-d6ba-49c3-b451-a4a0ae815b2c","path":"test.py::test_will_pass","testName":"test_will_pass","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"conditional","id":"5ec59132-f8f5-45be-b0ad-31124a3a6ba0","role":"start","groupId":"0a78f451-a9b6-4fa9-9fe3-16dea93fa46b","conditionalType":"if","condition":" $test_will_pass"},{"type":"test","id":"67014fb0-9a93-433b-8398-df103de3dcc1","groupId":"8a8ecab5-b765-4944-a46c-63111fd59e03","path":"test.py::test_for_example_1","testName":"test_for_example_1","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"test","id":"7d6d00ee-f418-49d4-9bcd-5b9cc2e28470","groupId":"e2de5be6-ba8c-4aa8-b6ac-b32b1f30dec1","path":"test.py::test_will_fail","testName":"test_will_fail","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"conditional","id":"5ec59132-f8f5-45be-b0ad-31124a3a6ba0","role":"start","groupId":"a45f0b58-b74e-4626-8a0a-2dec3d8e9cfa","conditionalType":"if","condition":" $test_will_fail"},{"type":"test","id":"b4927c15-00e8-4ea4-8788-af395be14775","groupId":"b824ead8-84ad-4de3-88c1-21e4b46343fe","path":"test.py::test_for_example_2","testName":"test_for_example_2","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"conditional","id":"d5b1cf12-f50d-4f5a-88d4-b7a9f982a447","role":"between","groupId":"a45f0b58-b74e-4626-8a0a-2dec3d8e9cfa","conditionalType":"else","condition":""},{"type":"test","id":"5ad4da63-40fe-45a2-ab1e-235fd067bde2","groupId":"8f02f062-19f5-448f-83b4-1fcc4e9dc07a","path":"test.py::test_for_example_3","testName":"test_for_example_3","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"conditional","id":"c45616fd-cc5a-4469-9bb6-5f3a8ae74bd7","role":"end","groupId":"a45f0b58-b74e-4626-8a0a-2dec3d8e9cfa","conditionalType":"end","condition":""},{"type":"conditional","id":"d5b1cf12-f50d-4f5a-88d4-b7a9f982a447","role":"between","groupId":"0a78f451-a9b6-4fa9-9fe3-16dea93fa46b","conditionalType":"else","condition":""},{"type":"test","id":"01cafc7f-0c21-47b9-8246-305b36a0c23a","groupId":"2e320c36-4b8b-48ec-97af-732e623c3776","path":"test.py::test_for_example_4","testName":"test_for_example_4","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"conditional","id":"c45616fd-cc5a-4469-9bb6-5f3a8ae74bd7","role":"end","groupId":"0a78f451-a9b6-4fa9-9fe3-16dea93fa46b","conditionalType":"end","condition":""}],"projectPath":"C:/Users/zzzgu/Documents/flojoy/repo/studio/src/renderer/data/apps/sequencer/conditional/","interpreter":{"type":"flojoy","path":null,"requirementsPath":"flojoy_requirements.txt"}} \ No newline at end of file diff --git a/src/renderer/data/apps/sequencer/conditional/flojoy_requirements.txt b/src/renderer/data/apps/sequencer/conditional/flojoy_requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/renderer/data/apps/sequencer/conditional/test.py b/src/renderer/data/apps/sequencer/conditional/test.py new file mode 100644 index 000000000..ac1b4a8a7 --- /dev/null +++ b/src/renderer/data/apps/sequencer/conditional/test.py @@ -0,0 +1,28 @@ +""" +Simple test file to demo how to build conditional tests +- With pytest, all test files should start with 'test_' to be recognized +""" + + +def test_will_pass(): + assert True + + +def test_will_fail(): + assert False + + +def test_for_example_1(): + assert 1 == 1 + + +def test_for_example_2(): + assert 2 == 2 + + +def test_for_example_3(): + assert 3 == 3 + + +def test_for_example_4(): + assert 4 == 4 diff --git a/src/renderer/hooks/useTestSequencerProject.ts b/src/renderer/hooks/useTestSequencerProject.ts index 14bc58454..dd0a00cb4 100644 --- a/src/renderer/hooks/useTestSequencerProject.ts +++ b/src/renderer/hooks/useTestSequencerProject.ts @@ -118,6 +118,61 @@ export const useImportSequences = () => { return handleImport; }; + +export const useImportAllSequencesInFolder = () => { + const manager = usePrepareStateManager(); + const { isAdmin } = useWithPermission(); + + const handleImport = async (path: string, relative: boolean=false) => { + async function importSequences(): Promise> { + // Confirmation if admin + if (!isAdmin()) { + return err(Error("Admin only, Connect to Flojoy Cloud and select a Test Profile")); + } + + // Find .tjoy files from the profile + const result = await window.api.openAllFilesInFolder( + path, + ["tjoy"], + relative, + ); + if (result === undefined) { + return err( + Error(`Failed to find the directory ${path}`), + ); + } + if (!result || result.length === 0) { + return err(Error("No .tjoy file found in the selected directory")); + } + + // Import them in the sequencer + await Promise.all( + result.map(async (res, idx) => { + const { filePath, fileContent } = res; + const result = await importSequence( + filePath, + fileContent, + manager, + idx !== 0, + ); + if (result.isErr()) return err(result.error); + }), + ); + + return ok(undefined); + } + + toastResultPromise(importSequences(), { + loading: `Importing Sequences...`, + success: () => `Sequences imported`, + error: (e) => `${e}`, + }); + }; + + return handleImport; +}; + + export const useLoadTestProfile = () => { const manager = usePrepareStateManager(); const { isAdmin } = useWithPermission(); diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx index 497fd4abb..38b0bbc34 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx @@ -9,7 +9,7 @@ import { LayoutGrid } from "lucide-react"; import { ScrollArea } from "@/renderer/components/ui/scroll-area"; import { Separator } from "@/renderer/components/ui/separator"; import { Button } from "@/renderer/components/ui/button"; -import { Avatar, AvatarImage } from "@/renderer/components/ui/avatar"; +import { useImportAllSequencesInFolder } from "@/renderer/hooks/useTestSequencerProject"; type AppGalleryModalProps = { isGalleryOpen: boolean; @@ -24,24 +24,25 @@ export const SequencerGalleryModal = ({ setIsGalleryOpen(true); }; - const handleAppLoad = async (link: string) => { - console.log("Loading app", link); + const useImport = useImportAllSequencesInFolder(); + + const handleSequenceLoad = async (BaseFolderName: string) => { + const relativePath = `src/renderer/data/apps/sequencer/${BaseFolderName}/`; + await useImport(relativePath); }; const data = [ { - title: "Simplest Sequence", + title: "Creating Sequences with Conditional", description: "Learn how to create a simple sequence with conditional logic.", - imagePath: "assets/appGallery/introToLoops.png", - link: "", + path: "conditional", }, { - title: "Sequence - Expected Values and Export", - description: "Learn how to inject the minimum and maximum expected values into a sequence and export the result. Right click on a test to consult the code and edit the expected values!.", - imagePath: "assets/appGallery/introToLoops.png", - link: "", + title: "Test Step with Expected and Exported Values", + description: "Learn how to inject the minimum and maximum expected values into a test and export the result. Right-click on a test to consult the code and edit the expected values!", + path: "expected_exported_values", }, - ] + ]; return ( @@ -63,40 +64,31 @@ export const SequencerGalleryModal = ({
Sequence Gallery
- - - { data.map((SeqExample) => ( - -
-
- - - -
- + {data.map((SeqExample) => ( + <> + +
+
{SeqExample.title}
{SeqExample.description}
- -
- -
- -
+
+
+ ))} From f4fd87042c05faf9e94ef68474b05ad3b1f4559b Mon Sep 17 00:00:00 2001 From: Guillaume Thibault Date: Sat, 20 Apr 2024 12:13:48 -0400 Subject: [PATCH 19/49] [sequencer-gallery] Export & Expected Demo --- .../Export & Expected Demo.tjoy | 1 + .../flojoy_requirements.txt | 0 .../expected_exported_values/test.py | 48 +++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 src/renderer/data/apps/sequencer/expected_exported_values/Export & Expected Demo.tjoy create mode 100644 src/renderer/data/apps/sequencer/expected_exported_values/flojoy_requirements.txt create mode 100644 src/renderer/data/apps/sequencer/expected_exported_values/test.py diff --git a/src/renderer/data/apps/sequencer/expected_exported_values/Export & Expected Demo.tjoy b/src/renderer/data/apps/sequencer/expected_exported_values/Export & Expected Demo.tjoy new file mode 100644 index 000000000..dc7740213 --- /dev/null +++ b/src/renderer/data/apps/sequencer/expected_exported_values/Export & Expected Demo.tjoy @@ -0,0 +1 @@ +{"name":"Export & Expected Demo","description":"Consult the Test Step Code!","elems":[{"type":"test","id":"acb47b74-d1dc-4f4b-b3ef-506acb2f53e1","groupId":"f90f758a-a705-4dd0-b3b6-4ae032d22ea3","path":"test.py::test_min_max","testName":"test_min_max","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T16:13:35.911Z","minValue":5,"maxValue":10,"unit":""},{"type":"test","id":"53bb5746-c720-45c4-8121-9dc469d07927","groupId":"397475ec-c600-4478-8725-b7b8e824d599","path":"test.py::test_min","testName":"test_min","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T16:13:35.911Z","minValue":5,"unit":""},{"type":"test","id":"55b82abb-37fb-4e32-9379-3609642d01af","groupId":"0377f366-edb7-4a51-97cd-2867bd944cd2","path":"test.py::test_max","testName":"test_max","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T16:13:35.911Z","maxValue":7,"unit":""},{"type":"test","id":"029a20ad-4c3f-47a5-82aa-2d558eda418a","groupId":"97ba9e80-3133-4dfa-9806-770d47ffb1e4","path":"test.py::test_export_dataframe","testName":"test_export_dataframe","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T16:13:35.911Z"},{"type":"test","id":"2a75451e-49f6-4810-827e-2b9769d12bc4","groupId":"096274ef-132f-498a-a67a-a279c65b9a8a","path":"test.py::test_export","testName":"test_export","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T16:13:35.911Z"}],"projectPath":"C:/Users/zzzgu/Documents/flojoy/repo/studio/src/renderer/data/apps/sequencer/expected_exported_values/","interpreter":{"type":"flojoy","path":null,"requirementsPath":"flojoy_requirements.txt"}} \ No newline at end of file diff --git a/src/renderer/data/apps/sequencer/expected_exported_values/flojoy_requirements.txt b/src/renderer/data/apps/sequencer/expected_exported_values/flojoy_requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/renderer/data/apps/sequencer/expected_exported_values/test.py b/src/renderer/data/apps/sequencer/expected_exported_values/test.py new file mode 100644 index 000000000..e3c486dcd --- /dev/null +++ b/src/renderer/data/apps/sequencer/expected_exported_values/test.py @@ -0,0 +1,48 @@ +from flojoy_cloud import test_sequencer +import pandas as pd + + +def test_min_max(): + value = 6.15 + # Always export as early as possible to avoid missing data + test_sequencer.export(value) + assert test_sequencer.is_in_range(value) + + +def test_min(): + value = 6.15 + + test_sequencer.export(value) + assert test_sequencer.is_in_range(value) + + +def test_max(): + value = 6.15 + test_sequencer.export(value) + + assert test_sequencer.is_in_range(value) + # If multiple assert statements are defined and one of them fails: + # - the rest of the assert statements will not be executed, and the result will be reported to the sequencer + # - the sequencer will report the error, and the test will be marked as failed + assert 0 < value + + +def test_export_dataframe(): + + df = pd.DataFrame({'value': [6.15, 6.15, 6.15]}) + # Boolean and DataFrame values will be exported to the Cloud + test_sequencer.export(df) + + assert df is not None + + +def test_export(): + value = 6.15 + # Always export as early as possible to avoid missing data + test_sequencer.export(value) + assert 12 < value # <-- FAIL + + # Only the last executed export statement will be exported to the Cloud and reported to the sequencer + test_sequencer.export(20) + + assert 0 < value From f35faa3ff94e0523b912ac05c0cbf6dc23ceb809 Mon Sep 17 00:00:00 2001 From: Guillaume Thibault Date: Sat, 20 Apr 2024 12:19:07 -0400 Subject: [PATCH 20/49] [sequencer-gallery] ui: removed weird yellow and added better comment in example --- src/renderer/assets/FlojoyTheme.ts | 2 +- .../sequencer/expected_exported_values/test.py | 15 ++++++++------- .../components/modals/SequencerGalleryModal.tsx | 1 + 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/renderer/assets/FlojoyTheme.ts b/src/renderer/assets/FlojoyTheme.ts index bc21231c8..81d2b32b2 100644 --- a/src/renderer/assets/FlojoyTheme.ts +++ b/src/renderer/assets/FlojoyTheme.ts @@ -12,7 +12,7 @@ export const flojoySyntaxTheme: SyntaxTheme = { background: "rgb(var(--color-modal))", }, "hljs-comment": { - color: "rgb(var(--foreground))", + color: "rgb(var(--color-accent4))", fontStyle: "italic", }, "hljs-quote": { diff --git a/src/renderer/data/apps/sequencer/expected_exported_values/test.py b/src/renderer/data/apps/sequencer/expected_exported_values/test.py index e3c486dcd..661291541 100644 --- a/src/renderer/data/apps/sequencer/expected_exported_values/test.py +++ b/src/renderer/data/apps/sequencer/expected_exported_values/test.py @@ -4,14 +4,13 @@ def test_min_max(): value = 6.15 - # Always export as early as possible to avoid missing data test_sequencer.export(value) assert test_sequencer.is_in_range(value) def test_min(): value = 6.15 - + # If not Max value is defined, the value will be checked against the Min value. test_sequencer.export(value) assert test_sequencer.is_in_range(value) @@ -22,15 +21,16 @@ def test_max(): assert test_sequencer.is_in_range(value) # If multiple assert statements are defined and one of them fails: - # - the rest of the assert statements will not be executed, and the result will be reported to the sequencer - # - the sequencer will report the error, and the test will be marked as failed + # - the rest of the assert statements will not be executed, and the result will + # be reported to the sequencer. + # - the sequencer will report the error, and the test will be marked as failed. assert 0 < value def test_export_dataframe(): df = pd.DataFrame({'value': [6.15, 6.15, 6.15]}) - # Boolean and DataFrame values will be exported to the Cloud + # Boolean and DataFrame values will be exported to the Cloud. test_sequencer.export(df) assert df is not None @@ -38,11 +38,12 @@ def test_export_dataframe(): def test_export(): value = 6.15 - # Always export as early as possible to avoid missing data + # Always export as early as possible to avoid missing data. test_sequencer.export(value) assert 12 < value # <-- FAIL - # Only the last executed export statement will be exported to the Cloud and reported to the sequencer + # Only the last executed export statement will be exported to the Cloud and + # reported to the sequencer. test_sequencer.export(20) assert 0 < value diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx index 38b0bbc34..7b2cc7ac4 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx @@ -29,6 +29,7 @@ export const SequencerGalleryModal = ({ const handleSequenceLoad = async (BaseFolderName: string) => { const relativePath = `src/renderer/data/apps/sequencer/${BaseFolderName}/`; await useImport(relativePath); + setIsGalleryOpen(false); }; const data = [ From ad14376af3d1545b73a821b204824c157c22317a Mon Sep 17 00:00:00 2001 From: Guillaume Date: Sat, 20 Apr 2024 12:22:52 -0400 Subject: [PATCH 21/49] chore: formatting --- .../expected_exported_values/test.py | 5 +- src/renderer/hooks/useTestSequencerProject.ts | 14 ++--- .../components/DesignBar.tsx | 2 +- .../modals/SequencerGalleryModal.tsx | 53 +++++++++++-------- 4 files changed, 40 insertions(+), 34 deletions(-) diff --git a/src/renderer/data/apps/sequencer/expected_exported_values/test.py b/src/renderer/data/apps/sequencer/expected_exported_values/test.py index 661291541..1d764c5bb 100644 --- a/src/renderer/data/apps/sequencer/expected_exported_values/test.py +++ b/src/renderer/data/apps/sequencer/expected_exported_values/test.py @@ -28,8 +28,7 @@ def test_max(): def test_export_dataframe(): - - df = pd.DataFrame({'value': [6.15, 6.15, 6.15]}) + df = pd.DataFrame({"value": [6.15, 6.15, 6.15]}) # Boolean and DataFrame values will be exported to the Cloud. test_sequencer.export(df) @@ -40,7 +39,7 @@ def test_export(): value = 6.15 # Always export as early as possible to avoid missing data. test_sequencer.export(value) - assert 12 < value # <-- FAIL + assert 12 < value # <-- FAIL # Only the last executed export statement will be exported to the Cloud and # reported to the sequencer. diff --git a/src/renderer/hooks/useTestSequencerProject.ts b/src/renderer/hooks/useTestSequencerProject.ts index dd0a00cb4..ed2b08236 100644 --- a/src/renderer/hooks/useTestSequencerProject.ts +++ b/src/renderer/hooks/useTestSequencerProject.ts @@ -118,16 +118,19 @@ export const useImportSequences = () => { return handleImport; }; - export const useImportAllSequencesInFolder = () => { const manager = usePrepareStateManager(); const { isAdmin } = useWithPermission(); - const handleImport = async (path: string, relative: boolean=false) => { + const handleImport = async (path: string, relative: boolean = false) => { async function importSequences(): Promise> { // Confirmation if admin if (!isAdmin()) { - return err(Error("Admin only, Connect to Flojoy Cloud and select a Test Profile")); + return err( + Error( + "Admin only, Connect to Flojoy Cloud and select a Test Profile", + ), + ); } // Find .tjoy files from the profile @@ -137,9 +140,7 @@ export const useImportAllSequencesInFolder = () => { relative, ); if (result === undefined) { - return err( - Error(`Failed to find the directory ${path}`), - ); + return err(Error(`Failed to find the directory ${path}`)); } if (!result || result.length === 0) { return err(Error("No .tjoy file found in the selected directory")); @@ -172,7 +173,6 @@ export const useImportAllSequencesInFolder = () => { return handleImport; }; - export const useLoadTestProfile = () => { const manager = usePrepareStateManager(); const { isAdmin } = useWithPermission(); diff --git a/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx b/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx index 021884bcc..f678734d2 100644 --- a/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx @@ -114,7 +114,7 @@ export function DesignBar() { - diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx index 7b2cc7ac4..f87f5f772 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx @@ -35,12 +35,14 @@ export const SequencerGalleryModal = ({ const data = [ { title: "Creating Sequences with Conditional", - description: "Learn how to create a simple sequence with conditional logic.", + description: + "Learn how to create a simple sequence with conditional logic.", path: "conditional", }, { title: "Test Step with Expected and Exported Values", - description: "Learn how to inject the minimum and maximum expected values into a test and export the result. Right-click on a test to consult the code and edit the expected values!", + description: + "Learn how to inject the minimum and maximum expected values into a test and export the result. Right-click on a test to consult the code and edit the expected values!", path: "expected_exported_values", }, ]; @@ -68,27 +70,34 @@ export const SequencerGalleryModal = ({ {data.map((SeqExample) => ( <> - -
-
-
-
{SeqExample.title}
-
{SeqExample.description}
+ +
+
+
+
+ {SeqExample.title} +
+
+ {SeqExample.description} +
+
+
+
-
- -
))} @@ -96,5 +105,3 @@ export const SequencerGalleryModal = ({
); }; - - From 773758cabf0dea263cc3d12251e098f044bb6a86 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Sat, 20 Apr 2024 12:30:57 -0400 Subject: [PATCH 22/49] fix: Eslint --- .../routes/test_sequencer_panel/components/DesignBar.tsx | 2 +- .../components/modals/SequencerGalleryModal.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx b/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx index f678734d2..514b831df 100644 --- a/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx @@ -2,7 +2,7 @@ import { useSequencerModalStore } from "@/renderer/stores/modal"; import { useDisplayedSequenceState } from "@/renderer/hooks/useTestSequencerState"; import { Button } from "@/renderer/components/ui/button"; import { ACTIONS_HEIGHT } from "@/renderer/routes/common/Layout"; -import { FlaskConical, Import, LayoutGrid, Plus, Route } from "lucide-react"; +import { FlaskConical, Import, Plus, Route } from "lucide-react"; import { StatusType, Test, diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx index f87f5f772..81b952943 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx @@ -26,9 +26,9 @@ export const SequencerGalleryModal = ({ const useImport = useImportAllSequencesInFolder(); - const handleSequenceLoad = async (BaseFolderName: string) => { + const handleSequenceLoad = (BaseFolderName: string) => { const relativePath = `src/renderer/data/apps/sequencer/${BaseFolderName}/`; - await useImport(relativePath); + useImport(relativePath); setIsGalleryOpen(false); }; From 747c2a1d308865ef90d1ff9fc2d4cab6d06b661e Mon Sep 17 00:00:00 2001 From: Guillaume Thibault Date: Sun, 21 Apr 2024 13:02:37 -0400 Subject: [PATCH 23/49] [stu-346-placeholder-test] chore: placeholder test => Using form --- .../modals/CreatePlaceholderTestModal.tsx | 164 +++++++++++------- 1 file changed, 104 insertions(+), 60 deletions(-) diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx index b2465ad57..2b4b90a74 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx @@ -1,12 +1,21 @@ import { Button } from "@/renderer/components/ui/button"; import { Dialog, DialogContent } from "@/renderer/components/ui/dialog"; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/renderer/components/ui/form"; import { Input } from "@/renderer/components/ui/input"; import { createNewTest, useDisplayedSequenceState, } from "@/renderer/hooks/useTestSequencerState"; -import { useState } from "react"; -import { toast } from "sonner"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { z } from "zod" + +const formSchema = z.object({ + name: z.string().min(1).regex(/\S/), + min: z.coerce.number().optional(), + max: z.coerce.number().optional(), + unit: z.string().optional(), +}) export const CreatePlaceholderTestModal = ({ isModalOpen, @@ -15,35 +24,37 @@ export const CreatePlaceholderTestModal = ({ isModalOpen: boolean; setModalOpen: (value: boolean) => void; }) => { - const [name, setName] = useState(""); - const [min, setMin] = useState(0); - const [max, setMax] = useState(0); - const [unit, setUnit] = useState(""); - const { addNewElems } = useDisplayedSequenceState(); - const handleCreate = async () => { - if (name === "") { - toast.error("Please enter a test name"); - } + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + name: "", + unit: undefined, + min: undefined, + max: undefined, + }, + }) + + async function onSubmit(values: z.infer) { const res = await addNewElems([ createNewTest( - name, + values.name, "", "placeholder", false, undefined, undefined, - min, - max, - unit, + values.min, + values.max, + values.unit, ), ]); if (res.isErr()) { return; } setModalOpen(false); - }; + } return ( @@ -57,55 +68,88 @@ export const CreatePlaceholderTestModal = ({ value without being link to any code. The code can be added later.

-
-

Test Name

- setName(e.target.value)} - /> -
+
+ + ( + + Test Name + + + + + + )} + /> -
-

Expected Value

-
-

Mininum

- { - setMin(parseFloat(e.target.value)); - }} - /> -
+

Expected Value

+
+
+ ( + + Minimum + + + + + + )} + /> +
-
-

Maximum

- { - setMax(parseFloat(e.target.value)); - }} - /> -
+
+ ( + + Maximun + + + + + + )} + /> +
-
-

Displayed Unit

- { - setUnit(e.target.value); - }} - /> +
+ ( + + Displayed Unit + + + + + + )} + /> +
+
-
-
- + + +
); From c77637215149af4a344efbba07b75eec26ec35bf Mon Sep 17 00:00:00 2001 From: Guillaume Thibault Date: Sun, 21 Apr 2024 13:14:18 -0400 Subject: [PATCH 24/49] [stu-346-placeholder-test] chore: Using object as input for better clarity --- src/renderer/hooks/useTestImport.ts | 6 +-- src/renderer/hooks/useTestSequencerState.ts | 43 ++++++++++-------- .../modals/CreatePlaceholderTestModal.tsx | 20 ++++----- .../utils/SequenceHandler.ts | 44 +++++++++---------- 4 files changed, 56 insertions(+), 57 deletions(-) diff --git a/src/renderer/hooks/useTestImport.ts b/src/renderer/hooks/useTestImport.ts index c526ea9cf..6b30f8049 100644 --- a/src/renderer/hooks/useTestImport.ts +++ b/src/renderer/hooks/useTestImport.ts @@ -17,11 +17,7 @@ function parseDiscoverContainer( settings: ImportTestSettings, ) { return map(data.response, (container) => { - const new_elem = createNewTest( - container.testName, - container.path, - settings.importType, - ); + const new_elem = createNewTest({ name: container.testName, path: container.path, type: settings.importType }); return new_elem; }); } diff --git a/src/renderer/hooks/useTestSequencerState.ts b/src/renderer/hooks/useTestSequencerState.ts index d6c201b2d..4f5fc39f6 100644 --- a/src/renderer/hooks/useTestSequencerState.ts +++ b/src/renderer/hooks/useTestSequencerState.ts @@ -28,6 +28,7 @@ import { testSequenceStopRequest, } from "../routes/test_sequencer_panel/models/models"; import { produce } from "immer"; +import { z } from "zod"; // sync this with the definition of setElems export type SetElemsFn = { @@ -98,34 +99,38 @@ const validateElements = ( return !validators.some((validator) => !validator(elems), validators); }; -export function createNewTest( - name: string, - path: string, +export const NewTest = z.object({ + name: z.string(), + path: z.string(), type: TestType, - exportToCloud?: boolean, - id?: string, - groupId?: string, - minValue?: number, - maxValue?: number, - unit?: string, -): Test { + exportToCloud: z.boolean().optional(), + id: z.string().optional(), + groupId: z.string().optional(), + minValue: z.number().optional(), + maxValue: z.number().optional(), + unit: z.string().optional(), +}); + +export type NewTest = z.infer; + +export function createNewTest(test: NewTest): Test { const newTest: Test = { type: "test", - id: id || uuidv4(), - groupId: groupId || uuidv4(), - path: path, - testName: name, + id: test.id || uuidv4(), + groupId: test.groupId || uuidv4(), + path: test.path, + testName: test.name, runInParallel: false, - testType: type, + testType: test.type, status: "pending", completionTime: undefined, error: null, isSavedToCloud: false, - exportToCloud: exportToCloud === undefined ? true : exportToCloud, + exportToCloud: test.exportToCloud || true, createdAt: new Date().toISOString(), - minValue: minValue, - maxValue: maxValue, - unit: unit, + minValue: test.minValue, + maxValue: test.maxValue, + unit: test.unit, }; return newTest; } diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx index 2b4b90a74..3e704d3a1 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx @@ -38,17 +38,15 @@ export const CreatePlaceholderTestModal = ({ async function onSubmit(values: z.infer) { const res = await addNewElems([ - createNewTest( - values.name, - "", - "placeholder", - false, - undefined, - undefined, - values.min, - values.max, - values.unit, - ), + createNewTest({ + name: values.name, + path: "", + type: "placeholder", + exportToCloud: false, + minValue: values.min, + maxValue: values.max, + unit: values.unit, + }), ]); if (res.isErr()) { return; diff --git a/src/renderer/routes/test_sequencer_panel/utils/SequenceHandler.ts b/src/renderer/routes/test_sequencer_panel/utils/SequenceHandler.ts index bb539dabe..6a06dd56b 100644 --- a/src/renderer/routes/test_sequencer_panel/utils/SequenceHandler.ts +++ b/src/renderer/routes/test_sequencer_panel/utils/SequenceHandler.ts @@ -272,17 +272,17 @@ async function createExportableSequenceElementsFromTestSequencerElements( } const elements = [...elems].map((elem) => { return elem.type === "test" - ? createNewTest( - removeBaseFolderFromName(elem.testName, baseFolder), - elem.path.replaceAll(baseFolder, ""), - elem.testType, - elem.exportToCloud, - elem.id, - elem.groupId, - elem.minValue, - elem.maxValue, - elem.unit, - ) + ? createNewTest({ + name: removeBaseFolderFromName(elem.testName, baseFolder), + path: elem.path.replaceAll(baseFolder, ""), + type: elem.testType, + exportToCloud: elem.exportToCloud, + id: elem.id, + groupId: elem.groupId, + minValue: elem.minValue, + maxValue: elem.maxValue, + unit: elem.unit, + }) : { ...elem, condition: elem.condition.replaceAll(baseFolder, ""), @@ -298,17 +298,17 @@ async function createTestSequencerElementsFromSequenceElements( ): Promise> { const elements: TestSequenceElement[] = [...sequence.elems].map((elem) => { return elem.type === "test" - ? createNewTest( - removeBaseFolderFromName(elem.testName, baseFolder), - baseFolder + elem.path, - elem.testType, - elem.exportToCloud, - elem.id, - elem.groupId, - elem.minValue, - elem.maxValue, - elem.unit, - ) + ? createNewTest({ + name: removeBaseFolderFromName(elem.testName, baseFolder), + path: baseFolder + elem.path, + type: elem.testType, + exportToCloud: elem.exportToCloud, + id: elem.id, + groupId: elem.groupId, + minValue: elem.minValue, + maxValue: elem.maxValue, + unit: elem.unit, + }) : { ...elem, }; From 407976f4d978867cca15ce3f75ec646610bcfcf7 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Sun, 21 Apr 2024 13:16:15 -0400 Subject: [PATCH 25/49] chore: formatting --- src/renderer/hooks/useTestImport.ts | 6 ++- .../modals/CreatePlaceholderTestModal.tsx | 42 +++++++++---------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/renderer/hooks/useTestImport.ts b/src/renderer/hooks/useTestImport.ts index 6b30f8049..9fc5cabd3 100644 --- a/src/renderer/hooks/useTestImport.ts +++ b/src/renderer/hooks/useTestImport.ts @@ -17,7 +17,11 @@ function parseDiscoverContainer( settings: ImportTestSettings, ) { return map(data.response, (container) => { - const new_elem = createNewTest({ name: container.testName, path: container.path, type: settings.importType }); + const new_elem = createNewTest({ + name: container.testName, + path: container.path, + type: settings.importType, + }); return new_elem; }); } diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx index 3e704d3a1..b92b68137 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/CreatePlaceholderTestModal.tsx @@ -1,6 +1,13 @@ import { Button } from "@/renderer/components/ui/button"; import { Dialog, DialogContent } from "@/renderer/components/ui/dialog"; -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/renderer/components/ui/form"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/renderer/components/ui/form"; import { Input } from "@/renderer/components/ui/input"; import { createNewTest, @@ -8,14 +15,14 @@ import { } from "@/renderer/hooks/useTestSequencerState"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; -import { z } from "zod" +import { z } from "zod"; const formSchema = z.object({ name: z.string().min(1).regex(/\S/), min: z.coerce.number().optional(), max: z.coerce.number().optional(), unit: z.string().optional(), -}) +}); export const CreatePlaceholderTestModal = ({ isModalOpen, @@ -34,7 +41,7 @@ export const CreatePlaceholderTestModal = ({ min: undefined, max: undefined, }, - }) + }); async function onSubmit(values: z.infer) { const res = await addNewElems([ @@ -75,10 +82,7 @@ export const CreatePlaceholderTestModal = ({ Test Name - + @@ -86,7 +90,9 @@ export const CreatePlaceholderTestModal = ({ />
-

Expected Value

+

+ Expected Value +

Minimum - + @@ -115,10 +118,7 @@ export const CreatePlaceholderTestModal = ({ Maximun - + @@ -134,9 +134,7 @@ export const CreatePlaceholderTestModal = ({ Displayed Unit - + @@ -145,7 +143,9 @@ export const CreatePlaceholderTestModal = ({
- + From b4d24b220d66b706d55209b06833b3b24e19633a Mon Sep 17 00:00:00 2001 From: Guillaume Thibault Date: Sun, 21 Apr 2024 14:02:46 -0400 Subject: [PATCH 26/49] [sequencer-gallery] chore: using test profile workflow to load example --- src/api/index.ts | 2 - src/main/utils.ts | 4 -- .../conditional/Conditional Demo.tjoy | 1 - .../conditional/flojoy_requirements.txt | 0 .../data/apps/sequencer/conditional/test.py | 28 ----------- .../Export & Expected Demo.tjoy | 1 - .../flojoy_requirements.txt | 0 .../expected_exported_values/test.py | 48 ------------------- src/renderer/hooks/useTestSequencerProject.ts | 23 +++++++-- .../modals/SequencerGalleryModal.tsx | 19 ++++---- .../utils/SequenceHandler.ts | 3 ++ src/renderer/types/test-sequencer.ts | 4 +- 12 files changed, 32 insertions(+), 101 deletions(-) delete mode 100644 src/renderer/data/apps/sequencer/conditional/Conditional Demo.tjoy delete mode 100644 src/renderer/data/apps/sequencer/conditional/flojoy_requirements.txt delete mode 100644 src/renderer/data/apps/sequencer/conditional/test.py delete mode 100644 src/renderer/data/apps/sequencer/expected_exported_values/Export & Expected Demo.tjoy delete mode 100644 src/renderer/data/apps/sequencer/expected_exported_values/flojoy_requirements.txt delete mode 100644 src/renderer/data/apps/sequencer/expected_exported_values/test.py diff --git a/src/api/index.ts b/src/api/index.ts index 34d0d6b30..a3314bb44 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -148,13 +148,11 @@ export default { openAllFilesInFolder: ( folderPath: string, allowedExtensions: string[] = ["json"], - relativePath: boolean = false, ): Promise<{ filePath: string; fileContent: string }[] | undefined> => ipcRenderer.invoke( API.openAllFilesInFolderPicker, folderPath, allowedExtensions, - relativePath, ), getFileContent: (filepath: string): Promise => diff --git a/src/main/utils.ts b/src/main/utils.ts index ecd00d514..f2b4b6185 100644 --- a/src/main/utils.ts +++ b/src/main/utils.ts @@ -190,12 +190,8 @@ export const openAllFilesInFolderPicker = ( _, folderPath: string, allowedExtensions: string[] = ["json"], - relativePath: boolean = false, ): { filePath: string; fileContent: string }[] | undefined => { // Return multiple files or all files with the allowed extensions if a folder is selected - if (relativePath) { - folderPath = join(__dirname, folderPath); - } if (!fs.existsSync(folderPath) || !fs.lstatSync(folderPath).isDirectory()) { return undefined; } diff --git a/src/renderer/data/apps/sequencer/conditional/Conditional Demo.tjoy b/src/renderer/data/apps/sequencer/conditional/Conditional Demo.tjoy deleted file mode 100644 index c2ba83066..000000000 --- a/src/renderer/data/apps/sequencer/conditional/Conditional Demo.tjoy +++ /dev/null @@ -1 +0,0 @@ -{"name":"Conditional Demo","description":"Example","elems":[{"type":"test","id":"ca204090-41bb-4236-97ba-623c4257ef0a","groupId":"41717403-d6ba-49c3-b451-a4a0ae815b2c","path":"test.py::test_will_pass","testName":"test_will_pass","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"conditional","id":"5ec59132-f8f5-45be-b0ad-31124a3a6ba0","role":"start","groupId":"0a78f451-a9b6-4fa9-9fe3-16dea93fa46b","conditionalType":"if","condition":" $test_will_pass"},{"type":"test","id":"67014fb0-9a93-433b-8398-df103de3dcc1","groupId":"8a8ecab5-b765-4944-a46c-63111fd59e03","path":"test.py::test_for_example_1","testName":"test_for_example_1","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"test","id":"7d6d00ee-f418-49d4-9bcd-5b9cc2e28470","groupId":"e2de5be6-ba8c-4aa8-b6ac-b32b1f30dec1","path":"test.py::test_will_fail","testName":"test_will_fail","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"conditional","id":"5ec59132-f8f5-45be-b0ad-31124a3a6ba0","role":"start","groupId":"a45f0b58-b74e-4626-8a0a-2dec3d8e9cfa","conditionalType":"if","condition":" $test_will_fail"},{"type":"test","id":"b4927c15-00e8-4ea4-8788-af395be14775","groupId":"b824ead8-84ad-4de3-88c1-21e4b46343fe","path":"test.py::test_for_example_2","testName":"test_for_example_2","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"conditional","id":"d5b1cf12-f50d-4f5a-88d4-b7a9f982a447","role":"between","groupId":"a45f0b58-b74e-4626-8a0a-2dec3d8e9cfa","conditionalType":"else","condition":""},{"type":"test","id":"5ad4da63-40fe-45a2-ab1e-235fd067bde2","groupId":"8f02f062-19f5-448f-83b4-1fcc4e9dc07a","path":"test.py::test_for_example_3","testName":"test_for_example_3","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"conditional","id":"c45616fd-cc5a-4469-9bb6-5f3a8ae74bd7","role":"end","groupId":"a45f0b58-b74e-4626-8a0a-2dec3d8e9cfa","conditionalType":"end","condition":""},{"type":"conditional","id":"d5b1cf12-f50d-4f5a-88d4-b7a9f982a447","role":"between","groupId":"0a78f451-a9b6-4fa9-9fe3-16dea93fa46b","conditionalType":"else","condition":""},{"type":"test","id":"01cafc7f-0c21-47b9-8246-305b36a0c23a","groupId":"2e320c36-4b8b-48ec-97af-732e623c3776","path":"test.py::test_for_example_4","testName":"test_for_example_4","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"conditional","id":"c45616fd-cc5a-4469-9bb6-5f3a8ae74bd7","role":"end","groupId":"0a78f451-a9b6-4fa9-9fe3-16dea93fa46b","conditionalType":"end","condition":""}],"projectPath":"C:/Users/zzzgu/Documents/flojoy/repo/studio/src/renderer/data/apps/sequencer/conditional/","interpreter":{"type":"flojoy","path":null,"requirementsPath":"flojoy_requirements.txt"}} \ No newline at end of file diff --git a/src/renderer/data/apps/sequencer/conditional/flojoy_requirements.txt b/src/renderer/data/apps/sequencer/conditional/flojoy_requirements.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/renderer/data/apps/sequencer/conditional/test.py b/src/renderer/data/apps/sequencer/conditional/test.py deleted file mode 100644 index ac1b4a8a7..000000000 --- a/src/renderer/data/apps/sequencer/conditional/test.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -Simple test file to demo how to build conditional tests -- With pytest, all test files should start with 'test_' to be recognized -""" - - -def test_will_pass(): - assert True - - -def test_will_fail(): - assert False - - -def test_for_example_1(): - assert 1 == 1 - - -def test_for_example_2(): - assert 2 == 2 - - -def test_for_example_3(): - assert 3 == 3 - - -def test_for_example_4(): - assert 4 == 4 diff --git a/src/renderer/data/apps/sequencer/expected_exported_values/Export & Expected Demo.tjoy b/src/renderer/data/apps/sequencer/expected_exported_values/Export & Expected Demo.tjoy deleted file mode 100644 index dc7740213..000000000 --- a/src/renderer/data/apps/sequencer/expected_exported_values/Export & Expected Demo.tjoy +++ /dev/null @@ -1 +0,0 @@ -{"name":"Export & Expected Demo","description":"Consult the Test Step Code!","elems":[{"type":"test","id":"acb47b74-d1dc-4f4b-b3ef-506acb2f53e1","groupId":"f90f758a-a705-4dd0-b3b6-4ae032d22ea3","path":"test.py::test_min_max","testName":"test_min_max","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T16:13:35.911Z","minValue":5,"maxValue":10,"unit":""},{"type":"test","id":"53bb5746-c720-45c4-8121-9dc469d07927","groupId":"397475ec-c600-4478-8725-b7b8e824d599","path":"test.py::test_min","testName":"test_min","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T16:13:35.911Z","minValue":5,"unit":""},{"type":"test","id":"55b82abb-37fb-4e32-9379-3609642d01af","groupId":"0377f366-edb7-4a51-97cd-2867bd944cd2","path":"test.py::test_max","testName":"test_max","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T16:13:35.911Z","maxValue":7,"unit":""},{"type":"test","id":"029a20ad-4c3f-47a5-82aa-2d558eda418a","groupId":"97ba9e80-3133-4dfa-9806-770d47ffb1e4","path":"test.py::test_export_dataframe","testName":"test_export_dataframe","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T16:13:35.911Z"},{"type":"test","id":"2a75451e-49f6-4810-827e-2b9769d12bc4","groupId":"096274ef-132f-498a-a67a-a279c65b9a8a","path":"test.py::test_export","testName":"test_export","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T16:13:35.911Z"}],"projectPath":"C:/Users/zzzgu/Documents/flojoy/repo/studio/src/renderer/data/apps/sequencer/expected_exported_values/","interpreter":{"type":"flojoy","path":null,"requirementsPath":"flojoy_requirements.txt"}} \ No newline at end of file diff --git a/src/renderer/data/apps/sequencer/expected_exported_values/flojoy_requirements.txt b/src/renderer/data/apps/sequencer/expected_exported_values/flojoy_requirements.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/renderer/data/apps/sequencer/expected_exported_values/test.py b/src/renderer/data/apps/sequencer/expected_exported_values/test.py deleted file mode 100644 index 1d764c5bb..000000000 --- a/src/renderer/data/apps/sequencer/expected_exported_values/test.py +++ /dev/null @@ -1,48 +0,0 @@ -from flojoy_cloud import test_sequencer -import pandas as pd - - -def test_min_max(): - value = 6.15 - test_sequencer.export(value) - assert test_sequencer.is_in_range(value) - - -def test_min(): - value = 6.15 - # If not Max value is defined, the value will be checked against the Min value. - test_sequencer.export(value) - assert test_sequencer.is_in_range(value) - - -def test_max(): - value = 6.15 - test_sequencer.export(value) - - assert test_sequencer.is_in_range(value) - # If multiple assert statements are defined and one of them fails: - # - the rest of the assert statements will not be executed, and the result will - # be reported to the sequencer. - # - the sequencer will report the error, and the test will be marked as failed. - assert 0 < value - - -def test_export_dataframe(): - df = pd.DataFrame({"value": [6.15, 6.15, 6.15]}) - # Boolean and DataFrame values will be exported to the Cloud. - test_sequencer.export(df) - - assert df is not None - - -def test_export(): - value = 6.15 - # Always export as early as possible to avoid missing data. - test_sequencer.export(value) - assert 12 < value # <-- FAIL - - # Only the last executed export statement will be exported to the Cloud and - # reported to the sequencer. - test_sequencer.export(20) - - assert 0 < value diff --git a/src/renderer/hooks/useTestSequencerProject.ts b/src/renderer/hooks/useTestSequencerProject.ts index ed2b08236..9899b26c9 100644 --- a/src/renderer/hooks/useTestSequencerProject.ts +++ b/src/renderer/hooks/useTestSequencerProject.ts @@ -118,11 +118,11 @@ export const useImportSequences = () => { return handleImport; }; -export const useImportAllSequencesInFolder = () => { +export const useDownloadAndImportExampleSequence = () => { const manager = usePrepareStateManager(); const { isAdmin } = useWithPermission(); - const handleImport = async (path: string, relative: boolean = false) => { + const handleImport = async (url: string) => { async function importSequences(): Promise> { // Confirmation if admin if (!isAdmin()) { @@ -133,14 +133,26 @@ export const useImportAllSequencesInFolder = () => { ); } + // Load test example + const res = await installTestProfile(url); + if (res.isErr()) { + return err(Error(`Failed to download example: ${res.error}`)); + } + + console.log(res.value); + // Find .tjoy files from the profile const result = await window.api.openAllFilesInFolder( - path, + res.value.profile_root, ["tjoy"], - relative, ); + + console.log(result); + if (result === undefined) { - return err(Error(`Failed to find the directory ${path}`)); + return err( + Error(`Failed to find the directory ${res.value.profile_root}`), + ); } if (!result || result.length === 0) { return err(Error("No .tjoy file found in the selected directory")); @@ -156,6 +168,7 @@ export const useImportAllSequencesInFolder = () => { manager, idx !== 0, ); + console.log(result); if (result.isErr()) return err(result.error); }), ); diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx index 81b952943..821b77b32 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx @@ -9,7 +9,7 @@ import { LayoutGrid } from "lucide-react"; import { ScrollArea } from "@/renderer/components/ui/scroll-area"; import { Separator } from "@/renderer/components/ui/separator"; import { Button } from "@/renderer/components/ui/button"; -import { useImportAllSequencesInFolder } from "@/renderer/hooks/useTestSequencerProject"; +import { useDownloadAndImportExampleSequence } from "@/renderer/hooks/useTestSequencerProject"; type AppGalleryModalProps = { isGalleryOpen: boolean; @@ -24,11 +24,10 @@ export const SequencerGalleryModal = ({ setIsGalleryOpen(true); }; - const useImport = useImportAllSequencesInFolder(); + const useDownloadAndImport = useDownloadAndImportExampleSequence(); - const handleSequenceLoad = (BaseFolderName: string) => { - const relativePath = `src/renderer/data/apps/sequencer/${BaseFolderName}/`; - useImport(relativePath); + const handleSequenceLoad = (url: string) => { + useDownloadAndImport(url); setIsGalleryOpen(false); }; @@ -37,13 +36,13 @@ export const SequencerGalleryModal = ({ title: "Creating Sequences with Conditional", description: "Learn how to create a simple sequence with conditional logic.", - path: "conditional", + url: "https://github.com/flojoy-ai/test-sequencer-conditional-example", }, { title: "Test Step with Expected and Exported Values", description: - "Learn how to inject the minimum and maximum expected values into a test and export the result. Right-click on a test to consult the code and edit the expected values!", - path: "expected_exported_values", + "Learn how to inject the minimum and maximum expected values into a test and export the result.", + url: "https://github.com/flojoy-ai/test-sequencer-expected-exported-example.git", }, ]; @@ -91,8 +90,8 @@ export const SequencerGalleryModal = ({ .toLowerCase() .split(" ") .join("_")} - onClick={async () => { - await handleSequenceLoad(SeqExample.path); + onClick={() => { + handleSequenceLoad(SeqExample.url); }} > Load diff --git a/src/renderer/routes/test_sequencer_panel/utils/SequenceHandler.ts b/src/renderer/routes/test_sequencer_panel/utils/SequenceHandler.ts index bb539dabe..9554eb67f 100644 --- a/src/renderer/routes/test_sequencer_panel/utils/SequenceHandler.ts +++ b/src/renderer/routes/test_sequencer_panel/utils/SequenceHandler.ts @@ -229,6 +229,9 @@ async function saveToDisk( } async function installDeps(sequence: TestSequencerProject): Promise { + if (sequence.interpreter.requirementsPath !== null) { + return true; + } const success = await window.api.poetryInstallRequirementsUserGroup( sequence.projectPath + sequence.interpreter.requirementsPath, ); diff --git a/src/renderer/types/test-sequencer.ts b/src/renderer/types/test-sequencer.ts index a711fdf3e..9041fe679 100644 --- a/src/renderer/types/test-sequencer.ts +++ b/src/renderer/types/test-sequencer.ts @@ -145,8 +145,8 @@ export type InterpreterType = z.infer; export const Interpreter = z.object({ type: InterpreterType, - path: z.union([z.null(), z.string()]), - requirementsPath: z.union([z.null(), z.string()]), + path: z.string().nullable(), + requirementsPath: z.string().nullable(), }); export type Interpreter = z.infer; From 35fabe13af18fc1f078d4cc45283c99463ab17b2 Mon Sep 17 00:00:00 2001 From: Guillaume Thibault Date: Sun, 21 Apr 2024 14:13:42 -0400 Subject: [PATCH 27/49] [sequencer-gallery] chore: remove "use" prefix as react thinks it's a hook --- .../components/modals/SequencerGalleryModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx index 821b77b32..c50353da1 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx @@ -24,10 +24,10 @@ export const SequencerGalleryModal = ({ setIsGalleryOpen(true); }; - const useDownloadAndImport = useDownloadAndImportExampleSequence(); + const downloadAndImportSequence = useDownloadAndImportExampleSequence(); const handleSequenceLoad = (url: string) => { - useDownloadAndImport(url); + downloadAndImportSequence(url); setIsGalleryOpen(false); }; From fe8257105010dc852578b5ad5a3e706cd43602c4 Mon Sep 17 00:00:00 2001 From: Guillaume Thibault Date: Mon, 22 Apr 2024 11:32:21 -0400 Subject: [PATCH 28/49] [sequencer-gallery] chore: bundle example with app --- electron-builder.yaml | 2 + .../Conditional_Demo.tjoy | 1 + .../flojoy_requirements.txt | 0 .../test.py | 28 +++++++++++ .../Export_&_Expected_Demo.tjoy | 1 + .../flojoy_requirements.txt | 0 .../test.py | 48 +++++++++++++++++++ src/api/index.ts | 2 + src/main/utils.ts | 5 ++ src/renderer/hooks/useTestSequencerProject.ts | 27 +++-------- .../components/DesignBar.tsx | 22 +++++++-- .../modals/SequencerGalleryModal.tsx | 27 +++-------- 12 files changed, 116 insertions(+), 47 deletions(-) create mode 100644 examples/test-sequencer-conditional-example/Conditional_Demo.tjoy create mode 100644 examples/test-sequencer-conditional-example/flojoy_requirements.txt create mode 100644 examples/test-sequencer-conditional-example/test.py create mode 100644 examples/test-sequencer-expected-exported-example/Export_&_Expected_Demo.tjoy create mode 100644 examples/test-sequencer-expected-exported-example/flojoy_requirements.txt create mode 100644 examples/test-sequencer-expected-exported-example/test.py diff --git a/electron-builder.yaml b/electron-builder.yaml index 590714b0b..a36db7144 100644 --- a/electron-builder.yaml +++ b/electron-builder.yaml @@ -29,6 +29,8 @@ extraResources: to: "poetry.lock" - from: "blocks" to: "blocks" + - from: "examples" + to: "examples" mac: icon: ./public/favicon.icns diff --git a/examples/test-sequencer-conditional-example/Conditional_Demo.tjoy b/examples/test-sequencer-conditional-example/Conditional_Demo.tjoy new file mode 100644 index 000000000..c2ba83066 --- /dev/null +++ b/examples/test-sequencer-conditional-example/Conditional_Demo.tjoy @@ -0,0 +1 @@ +{"name":"Conditional Demo","description":"Example","elems":[{"type":"test","id":"ca204090-41bb-4236-97ba-623c4257ef0a","groupId":"41717403-d6ba-49c3-b451-a4a0ae815b2c","path":"test.py::test_will_pass","testName":"test_will_pass","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"conditional","id":"5ec59132-f8f5-45be-b0ad-31124a3a6ba0","role":"start","groupId":"0a78f451-a9b6-4fa9-9fe3-16dea93fa46b","conditionalType":"if","condition":" $test_will_pass"},{"type":"test","id":"67014fb0-9a93-433b-8398-df103de3dcc1","groupId":"8a8ecab5-b765-4944-a46c-63111fd59e03","path":"test.py::test_for_example_1","testName":"test_for_example_1","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"test","id":"7d6d00ee-f418-49d4-9bcd-5b9cc2e28470","groupId":"e2de5be6-ba8c-4aa8-b6ac-b32b1f30dec1","path":"test.py::test_will_fail","testName":"test_will_fail","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"conditional","id":"5ec59132-f8f5-45be-b0ad-31124a3a6ba0","role":"start","groupId":"a45f0b58-b74e-4626-8a0a-2dec3d8e9cfa","conditionalType":"if","condition":" $test_will_fail"},{"type":"test","id":"b4927c15-00e8-4ea4-8788-af395be14775","groupId":"b824ead8-84ad-4de3-88c1-21e4b46343fe","path":"test.py::test_for_example_2","testName":"test_for_example_2","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"conditional","id":"d5b1cf12-f50d-4f5a-88d4-b7a9f982a447","role":"between","groupId":"a45f0b58-b74e-4626-8a0a-2dec3d8e9cfa","conditionalType":"else","condition":""},{"type":"test","id":"5ad4da63-40fe-45a2-ab1e-235fd067bde2","groupId":"8f02f062-19f5-448f-83b4-1fcc4e9dc07a","path":"test.py::test_for_example_3","testName":"test_for_example_3","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"conditional","id":"c45616fd-cc5a-4469-9bb6-5f3a8ae74bd7","role":"end","groupId":"a45f0b58-b74e-4626-8a0a-2dec3d8e9cfa","conditionalType":"end","condition":""},{"type":"conditional","id":"d5b1cf12-f50d-4f5a-88d4-b7a9f982a447","role":"between","groupId":"0a78f451-a9b6-4fa9-9fe3-16dea93fa46b","conditionalType":"else","condition":""},{"type":"test","id":"01cafc7f-0c21-47b9-8246-305b36a0c23a","groupId":"2e320c36-4b8b-48ec-97af-732e623c3776","path":"test.py::test_for_example_4","testName":"test_for_example_4","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"conditional","id":"c45616fd-cc5a-4469-9bb6-5f3a8ae74bd7","role":"end","groupId":"0a78f451-a9b6-4fa9-9fe3-16dea93fa46b","conditionalType":"end","condition":""}],"projectPath":"C:/Users/zzzgu/Documents/flojoy/repo/studio/src/renderer/data/apps/sequencer/conditional/","interpreter":{"type":"flojoy","path":null,"requirementsPath":"flojoy_requirements.txt"}} \ No newline at end of file diff --git a/examples/test-sequencer-conditional-example/flojoy_requirements.txt b/examples/test-sequencer-conditional-example/flojoy_requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/examples/test-sequencer-conditional-example/test.py b/examples/test-sequencer-conditional-example/test.py new file mode 100644 index 000000000..ac1b4a8a7 --- /dev/null +++ b/examples/test-sequencer-conditional-example/test.py @@ -0,0 +1,28 @@ +""" +Simple test file to demo how to build conditional tests +- With pytest, all test files should start with 'test_' to be recognized +""" + + +def test_will_pass(): + assert True + + +def test_will_fail(): + assert False + + +def test_for_example_1(): + assert 1 == 1 + + +def test_for_example_2(): + assert 2 == 2 + + +def test_for_example_3(): + assert 3 == 3 + + +def test_for_example_4(): + assert 4 == 4 diff --git a/examples/test-sequencer-expected-exported-example/Export_&_Expected_Demo.tjoy b/examples/test-sequencer-expected-exported-example/Export_&_Expected_Demo.tjoy new file mode 100644 index 000000000..dc7740213 --- /dev/null +++ b/examples/test-sequencer-expected-exported-example/Export_&_Expected_Demo.tjoy @@ -0,0 +1 @@ +{"name":"Export & Expected Demo","description":"Consult the Test Step Code!","elems":[{"type":"test","id":"acb47b74-d1dc-4f4b-b3ef-506acb2f53e1","groupId":"f90f758a-a705-4dd0-b3b6-4ae032d22ea3","path":"test.py::test_min_max","testName":"test_min_max","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T16:13:35.911Z","minValue":5,"maxValue":10,"unit":""},{"type":"test","id":"53bb5746-c720-45c4-8121-9dc469d07927","groupId":"397475ec-c600-4478-8725-b7b8e824d599","path":"test.py::test_min","testName":"test_min","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T16:13:35.911Z","minValue":5,"unit":""},{"type":"test","id":"55b82abb-37fb-4e32-9379-3609642d01af","groupId":"0377f366-edb7-4a51-97cd-2867bd944cd2","path":"test.py::test_max","testName":"test_max","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T16:13:35.911Z","maxValue":7,"unit":""},{"type":"test","id":"029a20ad-4c3f-47a5-82aa-2d558eda418a","groupId":"97ba9e80-3133-4dfa-9806-770d47ffb1e4","path":"test.py::test_export_dataframe","testName":"test_export_dataframe","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T16:13:35.911Z"},{"type":"test","id":"2a75451e-49f6-4810-827e-2b9769d12bc4","groupId":"096274ef-132f-498a-a67a-a279c65b9a8a","path":"test.py::test_export","testName":"test_export","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T16:13:35.911Z"}],"projectPath":"C:/Users/zzzgu/Documents/flojoy/repo/studio/src/renderer/data/apps/sequencer/expected_exported_values/","interpreter":{"type":"flojoy","path":null,"requirementsPath":"flojoy_requirements.txt"}} \ No newline at end of file diff --git a/examples/test-sequencer-expected-exported-example/flojoy_requirements.txt b/examples/test-sequencer-expected-exported-example/flojoy_requirements.txt new file mode 100644 index 000000000..e69de29bb diff --git a/examples/test-sequencer-expected-exported-example/test.py b/examples/test-sequencer-expected-exported-example/test.py new file mode 100644 index 000000000..1d764c5bb --- /dev/null +++ b/examples/test-sequencer-expected-exported-example/test.py @@ -0,0 +1,48 @@ +from flojoy_cloud import test_sequencer +import pandas as pd + + +def test_min_max(): + value = 6.15 + test_sequencer.export(value) + assert test_sequencer.is_in_range(value) + + +def test_min(): + value = 6.15 + # If not Max value is defined, the value will be checked against the Min value. + test_sequencer.export(value) + assert test_sequencer.is_in_range(value) + + +def test_max(): + value = 6.15 + test_sequencer.export(value) + + assert test_sequencer.is_in_range(value) + # If multiple assert statements are defined and one of them fails: + # - the rest of the assert statements will not be executed, and the result will + # be reported to the sequencer. + # - the sequencer will report the error, and the test will be marked as failed. + assert 0 < value + + +def test_export_dataframe(): + df = pd.DataFrame({"value": [6.15, 6.15, 6.15]}) + # Boolean and DataFrame values will be exported to the Cloud. + test_sequencer.export(df) + + assert df is not None + + +def test_export(): + value = 6.15 + # Always export as early as possible to avoid missing data. + test_sequencer.export(value) + assert 12 < value # <-- FAIL + + # Only the last executed export statement will be exported to the Cloud and + # reported to the sequencer. + test_sequencer.export(20) + + assert 0 < value diff --git a/src/api/index.ts b/src/api/index.ts index a3314bb44..809aee6b7 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -148,11 +148,13 @@ export default { openAllFilesInFolder: ( folderPath: string, allowedExtensions: string[] = ["json"], + relative: boolean = false, ): Promise<{ filePath: string; fileContent: string }[] | undefined> => ipcRenderer.invoke( API.openAllFilesInFolderPicker, folderPath, allowedExtensions, + relative, ), getFileContent: (filepath: string): Promise => diff --git a/src/main/utils.ts b/src/main/utils.ts index f2b4b6185..075fc7ea5 100644 --- a/src/main/utils.ts +++ b/src/main/utils.ts @@ -190,7 +190,12 @@ export const openAllFilesInFolderPicker = ( _, folderPath: string, allowedExtensions: string[] = ["json"], + relative: boolean = false, ): { filePath: string; fileContent: string }[] | undefined => { + // Append the current working directory if the path is relative + if (relative) { + folderPath = join(process.cwd(), folderPath); + } // Return multiple files or all files with the allowed extensions if a folder is selected if (!fs.existsSync(folderPath) || !fs.lstatSync(folderPath).isDirectory()) { return undefined; diff --git a/src/renderer/hooks/useTestSequencerProject.ts b/src/renderer/hooks/useTestSequencerProject.ts index 9899b26c9..3bb67d239 100644 --- a/src/renderer/hooks/useTestSequencerProject.ts +++ b/src/renderer/hooks/useTestSequencerProject.ts @@ -118,40 +118,26 @@ export const useImportSequences = () => { return handleImport; }; -export const useDownloadAndImportExampleSequence = () => { +export const useImportAllSequencesInFolder = () => { const manager = usePrepareStateManager(); const { isAdmin } = useWithPermission(); - const handleImport = async (url: string) => { + const handleImport = async (path: string, relative: boolean=false) => { async function importSequences(): Promise> { // Confirmation if admin if (!isAdmin()) { - return err( - Error( - "Admin only, Connect to Flojoy Cloud and select a Test Profile", - ), - ); - } - - // Load test example - const res = await installTestProfile(url); - if (res.isErr()) { - return err(Error(`Failed to download example: ${res.error}`)); + return err(Error("Admin only, Connect to Flojoy Cloud and select a Test Profile")); } - console.log(res.value); - // Find .tjoy files from the profile const result = await window.api.openAllFilesInFolder( - res.value.profile_root, + path, ["tjoy"], + relative, ); - - console.log(result); - if (result === undefined) { return err( - Error(`Failed to find the directory ${res.value.profile_root}`), + Error(`Failed to find the directory ${path}`), ); } if (!result || result.length === 0) { @@ -168,7 +154,6 @@ export const useDownloadAndImportExampleSequence = () => { manager, idx !== 0, ); - console.log(result); if (result.isErr()) return err(result.error); }), ); diff --git a/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx b/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx index 514b831df..9fdb6c6a0 100644 --- a/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx @@ -2,7 +2,7 @@ import { useSequencerModalStore } from "@/renderer/stores/modal"; import { useDisplayedSequenceState } from "@/renderer/hooks/useTestSequencerState"; import { Button } from "@/renderer/components/ui/button"; import { ACTIONS_HEIGHT } from "@/renderer/routes/common/Layout"; -import { FlaskConical, Import, Plus, Route } from "lucide-react"; +import { FlaskConical, Import, LayoutGrid, Plus, Route } from "lucide-react"; import { StatusType, Test, @@ -68,6 +68,10 @@ export function DesignBar() { return (
+
{isAdmin() && ( @@ -112,6 +116,14 @@ export function DesignBar() { Import Sequence + setIsGalleryOpen(true)} + data-testid="app-gallery-btn" + > + + Import Example + + 0 ? cycleRuns - .map((el) => getGlobalStatus([], el, [])) - .reduce((prev, curr) => - priority[prev] > priority[curr] ? prev : curr, - ) + .map((el) => getGlobalStatus([], el, [])) + .reduce((prev, curr) => + priority[prev] > priority[curr] ? prev : curr, + ) : "pending"; if (sequences.length === 0 && data.length === 0) return highestCycle; diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx index c50353da1..5546bb77f 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx @@ -9,7 +9,7 @@ import { LayoutGrid } from "lucide-react"; import { ScrollArea } from "@/renderer/components/ui/scroll-area"; import { Separator } from "@/renderer/components/ui/separator"; import { Button } from "@/renderer/components/ui/button"; -import { useDownloadAndImportExampleSequence } from "@/renderer/hooks/useTestSequencerProject"; +import { useImportAllSequencesInFolder } from "@/renderer/hooks/useTestSequencerProject"; type AppGalleryModalProps = { isGalleryOpen: boolean; @@ -20,14 +20,11 @@ export const SequencerGalleryModal = ({ isGalleryOpen, setIsGalleryOpen, }: AppGalleryModalProps) => { - const setOpen = () => { - setIsGalleryOpen(true); - }; - const downloadAndImportSequence = useDownloadAndImportExampleSequence(); + const importSequence = useImportAllSequencesInFolder(); - const handleSequenceLoad = (url: string) => { - downloadAndImportSequence(url); + const handleSequenceLoad = (relativePath: string) => { + importSequence(relativePath, true); setIsGalleryOpen(false); }; @@ -36,30 +33,18 @@ export const SequencerGalleryModal = ({ title: "Creating Sequences with Conditional", description: "Learn how to create a simple sequence with conditional logic.", - url: "https://github.com/flojoy-ai/test-sequencer-conditional-example", + url: "examples/test-sequencer-conditional-example", }, { title: "Test Step with Expected and Exported Values", description: "Learn how to inject the minimum and maximum expected values into a test and export the result.", - url: "https://github.com/flojoy-ai/test-sequencer-expected-exported-example.git", + url: "examples/test-sequencer-expected-exported-example", }, ]; return ( - - - - From fbdfad96ec0b653f808dfda64b383389078c78f1 Mon Sep 17 00:00:00 2001 From: Guillaume Thibault Date: Mon, 22 Apr 2024 11:40:59 -0400 Subject: [PATCH 29/49] [sequencer-gallery] fix: space in sequence name --- .../Conditional_Demo.tjoy | 2 +- .../Export_&_Expected_Demo.tjoy | 2 +- .../components/modals/SequencerGalleryModal.tsx | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/test-sequencer-conditional-example/Conditional_Demo.tjoy b/examples/test-sequencer-conditional-example/Conditional_Demo.tjoy index c2ba83066..8f3abc995 100644 --- a/examples/test-sequencer-conditional-example/Conditional_Demo.tjoy +++ b/examples/test-sequencer-conditional-example/Conditional_Demo.tjoy @@ -1 +1 @@ -{"name":"Conditional Demo","description":"Example","elems":[{"type":"test","id":"ca204090-41bb-4236-97ba-623c4257ef0a","groupId":"41717403-d6ba-49c3-b451-a4a0ae815b2c","path":"test.py::test_will_pass","testName":"test_will_pass","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"conditional","id":"5ec59132-f8f5-45be-b0ad-31124a3a6ba0","role":"start","groupId":"0a78f451-a9b6-4fa9-9fe3-16dea93fa46b","conditionalType":"if","condition":" $test_will_pass"},{"type":"test","id":"67014fb0-9a93-433b-8398-df103de3dcc1","groupId":"8a8ecab5-b765-4944-a46c-63111fd59e03","path":"test.py::test_for_example_1","testName":"test_for_example_1","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"test","id":"7d6d00ee-f418-49d4-9bcd-5b9cc2e28470","groupId":"e2de5be6-ba8c-4aa8-b6ac-b32b1f30dec1","path":"test.py::test_will_fail","testName":"test_will_fail","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"conditional","id":"5ec59132-f8f5-45be-b0ad-31124a3a6ba0","role":"start","groupId":"a45f0b58-b74e-4626-8a0a-2dec3d8e9cfa","conditionalType":"if","condition":" $test_will_fail"},{"type":"test","id":"b4927c15-00e8-4ea4-8788-af395be14775","groupId":"b824ead8-84ad-4de3-88c1-21e4b46343fe","path":"test.py::test_for_example_2","testName":"test_for_example_2","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"conditional","id":"d5b1cf12-f50d-4f5a-88d4-b7a9f982a447","role":"between","groupId":"a45f0b58-b74e-4626-8a0a-2dec3d8e9cfa","conditionalType":"else","condition":""},{"type":"test","id":"5ad4da63-40fe-45a2-ab1e-235fd067bde2","groupId":"8f02f062-19f5-448f-83b4-1fcc4e9dc07a","path":"test.py::test_for_example_3","testName":"test_for_example_3","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"conditional","id":"c45616fd-cc5a-4469-9bb6-5f3a8ae74bd7","role":"end","groupId":"a45f0b58-b74e-4626-8a0a-2dec3d8e9cfa","conditionalType":"end","condition":""},{"type":"conditional","id":"d5b1cf12-f50d-4f5a-88d4-b7a9f982a447","role":"between","groupId":"0a78f451-a9b6-4fa9-9fe3-16dea93fa46b","conditionalType":"else","condition":""},{"type":"test","id":"01cafc7f-0c21-47b9-8246-305b36a0c23a","groupId":"2e320c36-4b8b-48ec-97af-732e623c3776","path":"test.py::test_for_example_4","testName":"test_for_example_4","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"conditional","id":"c45616fd-cc5a-4469-9bb6-5f3a8ae74bd7","role":"end","groupId":"0a78f451-a9b6-4fa9-9fe3-16dea93fa46b","conditionalType":"end","condition":""}],"projectPath":"C:/Users/zzzgu/Documents/flojoy/repo/studio/src/renderer/data/apps/sequencer/conditional/","interpreter":{"type":"flojoy","path":null,"requirementsPath":"flojoy_requirements.txt"}} \ No newline at end of file +{"name":"Conditional_Demo","description":"Example","elems":[{"type":"test","id":"ca204090-41bb-4236-97ba-623c4257ef0a","groupId":"41717403-d6ba-49c3-b451-a4a0ae815b2c","path":"test.py::test_will_pass","testName":"test_will_pass","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"conditional","id":"5ec59132-f8f5-45be-b0ad-31124a3a6ba0","role":"start","groupId":"0a78f451-a9b6-4fa9-9fe3-16dea93fa46b","conditionalType":"if","condition":" $test_will_pass"},{"type":"test","id":"67014fb0-9a93-433b-8398-df103de3dcc1","groupId":"8a8ecab5-b765-4944-a46c-63111fd59e03","path":"test.py::test_for_example_1","testName":"test_for_example_1","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"test","id":"7d6d00ee-f418-49d4-9bcd-5b9cc2e28470","groupId":"e2de5be6-ba8c-4aa8-b6ac-b32b1f30dec1","path":"test.py::test_will_fail","testName":"test_will_fail","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"conditional","id":"5ec59132-f8f5-45be-b0ad-31124a3a6ba0","role":"start","groupId":"a45f0b58-b74e-4626-8a0a-2dec3d8e9cfa","conditionalType":"if","condition":" $test_will_fail"},{"type":"test","id":"b4927c15-00e8-4ea4-8788-af395be14775","groupId":"b824ead8-84ad-4de3-88c1-21e4b46343fe","path":"test.py::test_for_example_2","testName":"test_for_example_2","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"conditional","id":"d5b1cf12-f50d-4f5a-88d4-b7a9f982a447","role":"between","groupId":"a45f0b58-b74e-4626-8a0a-2dec3d8e9cfa","conditionalType":"else","condition":""},{"type":"test","id":"5ad4da63-40fe-45a2-ab1e-235fd067bde2","groupId":"8f02f062-19f5-448f-83b4-1fcc4e9dc07a","path":"test.py::test_for_example_3","testName":"test_for_example_3","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"conditional","id":"c45616fd-cc5a-4469-9bb6-5f3a8ae74bd7","role":"end","groupId":"a45f0b58-b74e-4626-8a0a-2dec3d8e9cfa","conditionalType":"end","condition":""},{"type":"conditional","id":"d5b1cf12-f50d-4f5a-88d4-b7a9f982a447","role":"between","groupId":"0a78f451-a9b6-4fa9-9fe3-16dea93fa46b","conditionalType":"else","condition":""},{"type":"test","id":"01cafc7f-0c21-47b9-8246-305b36a0c23a","groupId":"2e320c36-4b8b-48ec-97af-732e623c3776","path":"test.py::test_for_example_4","testName":"test_for_example_4","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T15:57:40.507Z"},{"type":"conditional","id":"c45616fd-cc5a-4469-9bb6-5f3a8ae74bd7","role":"end","groupId":"0a78f451-a9b6-4fa9-9fe3-16dea93fa46b","conditionalType":"end","condition":""}],"projectPath":"C:/Users/zzzgu/Documents/flojoy/repo/studio/src/renderer/data/apps/sequencer/conditional/","interpreter":{"type":"flojoy","path":null,"requirementsPath":"flojoy_requirements.txt"}} diff --git a/examples/test-sequencer-expected-exported-example/Export_&_Expected_Demo.tjoy b/examples/test-sequencer-expected-exported-example/Export_&_Expected_Demo.tjoy index dc7740213..0296bb559 100644 --- a/examples/test-sequencer-expected-exported-example/Export_&_Expected_Demo.tjoy +++ b/examples/test-sequencer-expected-exported-example/Export_&_Expected_Demo.tjoy @@ -1 +1 @@ -{"name":"Export & Expected Demo","description":"Consult the Test Step Code!","elems":[{"type":"test","id":"acb47b74-d1dc-4f4b-b3ef-506acb2f53e1","groupId":"f90f758a-a705-4dd0-b3b6-4ae032d22ea3","path":"test.py::test_min_max","testName":"test_min_max","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T16:13:35.911Z","minValue":5,"maxValue":10,"unit":""},{"type":"test","id":"53bb5746-c720-45c4-8121-9dc469d07927","groupId":"397475ec-c600-4478-8725-b7b8e824d599","path":"test.py::test_min","testName":"test_min","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T16:13:35.911Z","minValue":5,"unit":""},{"type":"test","id":"55b82abb-37fb-4e32-9379-3609642d01af","groupId":"0377f366-edb7-4a51-97cd-2867bd944cd2","path":"test.py::test_max","testName":"test_max","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T16:13:35.911Z","maxValue":7,"unit":""},{"type":"test","id":"029a20ad-4c3f-47a5-82aa-2d558eda418a","groupId":"97ba9e80-3133-4dfa-9806-770d47ffb1e4","path":"test.py::test_export_dataframe","testName":"test_export_dataframe","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T16:13:35.911Z"},{"type":"test","id":"2a75451e-49f6-4810-827e-2b9769d12bc4","groupId":"096274ef-132f-498a-a67a-a279c65b9a8a","path":"test.py::test_export","testName":"test_export","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T16:13:35.911Z"}],"projectPath":"C:/Users/zzzgu/Documents/flojoy/repo/studio/src/renderer/data/apps/sequencer/expected_exported_values/","interpreter":{"type":"flojoy","path":null,"requirementsPath":"flojoy_requirements.txt"}} \ No newline at end of file +{"name":"Export_&_Expected_Demo","description":"Consult the Test Step Code!","elems":[{"type":"test","id":"acb47b74-d1dc-4f4b-b3ef-506acb2f53e1","groupId":"f90f758a-a705-4dd0-b3b6-4ae032d22ea3","path":"test.py::test_min_max","testName":"test_min_max","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T16:13:35.911Z","minValue":5,"maxValue":10,"unit":""},{"type":"test","id":"53bb5746-c720-45c4-8121-9dc469d07927","groupId":"397475ec-c600-4478-8725-b7b8e824d599","path":"test.py::test_min","testName":"test_min","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T16:13:35.911Z","minValue":5,"unit":""},{"type":"test","id":"55b82abb-37fb-4e32-9379-3609642d01af","groupId":"0377f366-edb7-4a51-97cd-2867bd944cd2","path":"test.py::test_max","testName":"test_max","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T16:13:35.911Z","maxValue":7,"unit":""},{"type":"test","id":"029a20ad-4c3f-47a5-82aa-2d558eda418a","groupId":"97ba9e80-3133-4dfa-9806-770d47ffb1e4","path":"test.py::test_export_dataframe","testName":"test_export_dataframe","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T16:13:35.911Z"},{"type":"test","id":"2a75451e-49f6-4810-827e-2b9769d12bc4","groupId":"096274ef-132f-498a-a67a-a279c65b9a8a","path":"test.py::test_export","testName":"test_export","runInParallel":false,"testType":"pytest","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-20T16:13:35.911Z"}],"projectPath":"C:/Users/zzzgu/Documents/flojoy/repo/studio/src/renderer/data/apps/sequencer/expected_exported_values/","interpreter":{"type":"flojoy","path":null,"requirementsPath":"flojoy_requirements.txt"}} diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx index 5546bb77f..8e054ed7a 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx @@ -33,13 +33,13 @@ export const SequencerGalleryModal = ({ title: "Creating Sequences with Conditional", description: "Learn how to create a simple sequence with conditional logic.", - url: "examples/test-sequencer-conditional-example", + dirPath: "examples/test-sequencer-conditional-example/", }, { title: "Test Step with Expected and Exported Values", description: "Learn how to inject the minimum and maximum expected values into a test and export the result.", - url: "examples/test-sequencer-expected-exported-example", + dirPath: "examples/test-sequencer-expected-exported-example/", }, ]; @@ -76,7 +76,7 @@ export const SequencerGalleryModal = ({ .split(" ") .join("_")} onClick={() => { - handleSequenceLoad(SeqExample.url); + handleSequenceLoad(SeqExample.dirPath); }} > Load From 932bf87640cd9fc0007da6896ebd103f50196aee Mon Sep 17 00:00:00 2001 From: Guillaume Date: Mon, 22 Apr 2024 11:42:55 -0400 Subject: [PATCH 30/49] chore: formatting --- src/renderer/hooks/useTestSequencerProject.ts | 12 +++++++----- .../test_sequencer_panel/components/DesignBar.tsx | 14 ++++++++------ .../components/modals/SequencerGalleryModal.tsx | 1 - 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/renderer/hooks/useTestSequencerProject.ts b/src/renderer/hooks/useTestSequencerProject.ts index 3bb67d239..ed2b08236 100644 --- a/src/renderer/hooks/useTestSequencerProject.ts +++ b/src/renderer/hooks/useTestSequencerProject.ts @@ -122,11 +122,15 @@ export const useImportAllSequencesInFolder = () => { const manager = usePrepareStateManager(); const { isAdmin } = useWithPermission(); - const handleImport = async (path: string, relative: boolean=false) => { + const handleImport = async (path: string, relative: boolean = false) => { async function importSequences(): Promise> { // Confirmation if admin if (!isAdmin()) { - return err(Error("Admin only, Connect to Flojoy Cloud and select a Test Profile")); + return err( + Error( + "Admin only, Connect to Flojoy Cloud and select a Test Profile", + ), + ); } // Find .tjoy files from the profile @@ -136,9 +140,7 @@ export const useImportAllSequencesInFolder = () => { relative, ); if (result === undefined) { - return err( - Error(`Failed to find the directory ${path}`), - ); + return err(Error(`Failed to find the directory ${path}`)); } if (!result || result.length === 0) { return err(Error("No .tjoy file found in the selected directory")); diff --git a/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx b/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx index 9fdb6c6a0..0ec7f7020 100644 --- a/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx @@ -120,10 +120,12 @@ export function DesignBar() { onClick={() => setIsGalleryOpen(true)} data-testid="app-gallery-btn" > - + Import Example - 0 ? cycleRuns - .map((el) => getGlobalStatus([], el, [])) - .reduce((prev, curr) => - priority[prev] > priority[curr] ? prev : curr, - ) + .map((el) => getGlobalStatus([], el, [])) + .reduce((prev, curr) => + priority[prev] > priority[curr] ? prev : curr, + ) : "pending"; if (sequences.length === 0 && data.length === 0) return highestCycle; diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx index 8e054ed7a..7871c47e5 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx @@ -20,7 +20,6 @@ export const SequencerGalleryModal = ({ isGalleryOpen, setIsGalleryOpen, }: AppGalleryModalProps) => { - const importSequence = useImportAllSequencesInFolder(); const handleSequenceLoad = (relativePath: string) => { From 0c4a16157dd5e0813c47e4e7da41f36bd566415c Mon Sep 17 00:00:00 2001 From: Guillaume Thibault Date: Mon, 22 Apr 2024 12:11:39 -0400 Subject: [PATCH 31/49] [sequencer-gallery] chore: using process.resourcesPath --- src/api/index.ts | 4 ++-- src/main/utils.ts | 6 +++--- .../components/modals/SequencerGalleryModal.tsx | 2 -- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/api/index.ts b/src/api/index.ts index 809aee6b7..17c45fd13 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -148,13 +148,13 @@ export default { openAllFilesInFolder: ( folderPath: string, allowedExtensions: string[] = ["json"], - relative: boolean = false, + relativeToResources: boolean = false, ): Promise<{ filePath: string; fileContent: string }[] | undefined> => ipcRenderer.invoke( API.openAllFilesInFolderPicker, folderPath, allowedExtensions, - relative, + relativeToResources, ), getFileContent: (filepath: string): Promise => diff --git a/src/main/utils.ts b/src/main/utils.ts index 075fc7ea5..35355d36f 100644 --- a/src/main/utils.ts +++ b/src/main/utils.ts @@ -190,11 +190,11 @@ export const openAllFilesInFolderPicker = ( _, folderPath: string, allowedExtensions: string[] = ["json"], - relative: boolean = false, + relativeToResources: boolean = false, ): { filePath: string; fileContent: string }[] | undefined => { // Append the current working directory if the path is relative - if (relative) { - folderPath = join(process.cwd(), folderPath); + if (relativeToResources) { + folderPath = join(process.resourcesPath, folderPath); } // Return multiple files or all files with the allowed extensions if a folder is selected if (!fs.existsSync(folderPath) || !fs.lstatSync(folderPath).isDirectory()) { diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx index 7871c47e5..4f7599197 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx @@ -3,9 +3,7 @@ import { DialogContent, DialogHeader, DialogTitle, - DialogTrigger, } from "@/renderer/components/ui/dialog"; -import { LayoutGrid } from "lucide-react"; import { ScrollArea } from "@/renderer/components/ui/scroll-area"; import { Separator } from "@/renderer/components/ui/separator"; import { Button } from "@/renderer/components/ui/button"; From f9ea6630a8e50d72e9a2bf8061d000f4cd103f1b Mon Sep 17 00:00:00 2001 From: Guillaume Thibault Date: Mon, 22 Apr 2024 13:20:39 -0400 Subject: [PATCH 32/49] [sequencer-gallery] chore: adding CI test for Gallery + testing injected min & max --- playwright-test/15_sequences_gallery.spec.ts | 64 +++++++++++++++++++ playwright-test/selectors.ts | 1 + .../components/DesignBar.tsx | 2 +- .../components/data-table/TestTable.tsx | 2 +- 4 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 playwright-test/15_sequences_gallery.spec.ts diff --git a/playwright-test/15_sequences_gallery.spec.ts b/playwright-test/15_sequences_gallery.spec.ts new file mode 100644 index 000000000..3a1140a83 --- /dev/null +++ b/playwright-test/15_sequences_gallery.spec.ts @@ -0,0 +1,64 @@ +import { test, expect, Page, ElectronApplication } from "@playwright/test"; +import { _electron as electron } from "playwright"; +import { + STARTUP_TIMEOUT, + getExecutablePath, + mockDialogMessage, + standbyStatus, +} from "./utils"; +import { Selectors } from "./selectors"; + +test.describe("Load a demo test sequence", () => { + let window: Page; + let app: ElectronApplication; + test.beforeAll(async () => { + test.setTimeout(STARTUP_TIMEOUT); + const executablePath = getExecutablePath(); + app = await electron.launch({ executablePath }); + await mockDialogMessage(app); + window = await app.firstWindow(); + await expect( + window.locator("code", { hasText: standbyStatus }), + ).toBeVisible({ timeout: STARTUP_TIMEOUT }); + await window.getByTestId(Selectors.closeWelcomeModalBtn).click(); + // Switch to sequencer tab + await window.getByTestId(Selectors.testSequencerTabBtn).click(); + }); + + test.afterAll(async () => { + await app.close(); + }); + + test("Should load and run a sequence", async () => { + await expect(window.getByTestId(Selectors.newDropdown)).toBeEnabled({ + timeout: 15000, + }); + + // Open the sequence gallery + await window.getByTestId(Selectors.newDropdown).click(); + await window.getByTestId(Selectors.openSequenceGalleryBtn).click(); + + // Open a sequence + await window.getByTestId("test_step_with_expected_and_exported_values").nth(1).click(); + + // Expect sequence and tests to be loaded + await expect( + window.locator("div", { hasText: "Export_&_Expected_Demo" }).first(), + ).toBeVisible(); + + // Expect test steps to bey loaded + await expect( + window.locator("div", { hasText: "test_min_max" }).first(), + ).toBeVisible(); + + // Run the sequence + await window.getByTestId(Selectors.runBtn).click(); + await window.waitForTimeout(10000); + + // Check the status + await expect(window.getByTestId(Selectors.globalStatusBadge)).toContainText( + "FAIL", + ); + await expect(window.getByTestId("status-test_min_max")).toContainText("PASS"); + }); +}); diff --git a/playwright-test/selectors.ts b/playwright-test/selectors.ts index ff2009122..a6edc2090 100644 --- a/playwright-test/selectors.ts +++ b/playwright-test/selectors.ts @@ -43,6 +43,7 @@ export enum Selectors { pytestBtn = "pytest-btn", newDropdown = "new-dropdown", importTestBtn = "import-test-button", + openSequenceGalleryBtn = "seq-gallery-btn", globalStatusBadge = "global-status-badge", newSeqModalNameInput = "new-seq-modal-name-input", newSeqModalDescInput = "new-seq-modal-desc-input", diff --git a/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx b/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx index 0ec7f7020..249e9bb84 100644 --- a/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx @@ -118,7 +118,7 @@ export function DesignBar() { setIsGalleryOpen(true)} - data-testid="app-gallery-btn" + data-testid="seq-gallery-btn" >
Status
, cell: ({ row }) => { return row.original.type === "test" ? ( -
+
{typeof mapStatusToDisplay[row.original.status] === "function" ? mapStatusToDisplay[row.original.status](row.original.error) : mapStatusToDisplay[row.original.status]} From 054fab77823bb009b4969ac5cae843b36fb79465 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Mon, 22 Apr 2024 13:21:11 -0400 Subject: [PATCH 33/49] chore: formatting --- playwright-test/15_sequences_gallery.spec.ts | 9 +++++++-- .../components/data-table/TestTable.tsx | 5 ++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/playwright-test/15_sequences_gallery.spec.ts b/playwright-test/15_sequences_gallery.spec.ts index 3a1140a83..e1442b30b 100644 --- a/playwright-test/15_sequences_gallery.spec.ts +++ b/playwright-test/15_sequences_gallery.spec.ts @@ -39,7 +39,10 @@ test.describe("Load a demo test sequence", () => { await window.getByTestId(Selectors.openSequenceGalleryBtn).click(); // Open a sequence - await window.getByTestId("test_step_with_expected_and_exported_values").nth(1).click(); + await window + .getByTestId("test_step_with_expected_and_exported_values") + .nth(1) + .click(); // Expect sequence and tests to be loaded await expect( @@ -59,6 +62,8 @@ test.describe("Load a demo test sequence", () => { await expect(window.getByTestId(Selectors.globalStatusBadge)).toContainText( "FAIL", ); - await expect(window.getByTestId("status-test_min_max")).toContainText("PASS"); + await expect(window.getByTestId("status-test_min_max")).toContainText( + "PASS", + ); }); }); diff --git a/src/renderer/routes/test_sequencer_panel/components/data-table/TestTable.tsx b/src/renderer/routes/test_sequencer_panel/components/data-table/TestTable.tsx index 7a81c3331..d04b44300 100644 --- a/src/renderer/routes/test_sequencer_panel/components/data-table/TestTable.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/data-table/TestTable.tsx @@ -267,7 +267,10 @@ export function TestTable() { header: () =>
Status
, cell: ({ row }) => { return row.original.type === "test" ? ( -
+
{typeof mapStatusToDisplay[row.original.status] === "function" ? mapStatusToDisplay[row.original.status](row.original.error) : mapStatusToDisplay[row.original.status]} From fafe621536b9adc3ac469402442174cd62402aac Mon Sep 17 00:00:00 2001 From: Guillaume Thibault Date: Tue, 23 Apr 2024 16:44:45 -0400 Subject: [PATCH 34/49] chore(robot): discoverer --- captain/routes/test_sequence.py | 26 +++++++++++++++++++++++--- captain/utils/pytest/discover_tests.py | 21 +++++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/captain/routes/test_sequence.py b/captain/routes/test_sequence.py index 51b2fa216..010d2d7dd 100644 --- a/captain/routes/test_sequence.py +++ b/captain/routes/test_sequence.py @@ -3,7 +3,7 @@ import pydantic from captain.models.pytest.pytest_models import TestDiscoverContainer from captain.models.test_sequencer import TestSequenceRun -from captain.utils.pytest.discover_tests import discover_pytest_file +from captain.utils.pytest.discover_tests import discover_pytest_file, discover_robot_file from captain.utils.config import ts_manager from captain.utils.test_sequencer.handle_data import handle_data from captain.utils.logger import logger @@ -34,13 +34,13 @@ async def websocket_endpoint(websocket: WebSocket, socket_id: str): logger.info(f"Client {socket_id} is disconnected") -class DiscoverPytestParams(BaseModel): +class DiscoverParams(BaseModel): path: str one_file: bool = Field(..., alias="oneFile") @router.get("/discover-pytest/") -async def discover_pytest(params: DiscoverPytestParams = Depends()): +async def discover_pytest(params: DiscoverParams = Depends()): path = params.path one_file = params.one_file return_val, missing_lib, errors = [], [], [] # For passing info between threads @@ -55,3 +55,23 @@ async def discover_pytest(params: DiscoverPytestParams = Depends()): missing_libraries=missing_lib, error=errors[0] if len(errors) > 0 else None, ) + + +@router.get("/discover-robot/") +async def discover_robot(params: DiscoverParams = Depends()): + path = params.path + one_file = params.one_file + return_val, errors = [], [] # For passing info between threads + thread = Thread( + target=discover_robot_file, + args=(path, one_file, return_val, errors), + ) + thread.start() + thread.join() + return TestDiscoverContainer( + response=return_val, + missing_libraries=[], + error=errors[0] if len(errors) > 0 else None, + ) + + diff --git a/captain/utils/pytest/discover_tests.py b/captain/utils/pytest/discover_tests.py index c6b183306..76bf9ae0a 100644 --- a/captain/utils/pytest/discover_tests.py +++ b/captain/utils/pytest/discover_tests.py @@ -1,3 +1,4 @@ +import logging import os from captain.utils.logger import logger from typing import List, Union @@ -12,6 +13,7 @@ from captain.utils.import_utils import unload_module import re import pathlib +from robot.running.builder import TestSuiteBuilder def extract_error(report: RootModel): @@ -84,3 +86,22 @@ def dfs( ) else: return_val.extend(test_list) + + +def discover_robot_file( + path: str, one_file: bool, return_val: list, errors: list +): + try: + builder = TestSuiteBuilder() + suite = builder.build(path) + logging.info(f"Suite: {suite} - suites in it: {suite.suites} - tests in it: {suite.tests}") + if one_file: + return_val.append(suite.full_name) + else: + for test in suite.tests: + return_val.append(TestDiscoveryResponse( + test_name=test.full_name, path=pathlib.Path(path).as_posix() + )) + + except Exception as e: + errors.append(str(e)) From 111b37e72af73cff038b5c59e5fcd5a5b63e0fad Mon Sep 17 00:00:00 2001 From: Guillaume Thibault Date: Tue, 23 Apr 2024 16:45:11 -0400 Subject: [PATCH 35/49] chore(robot): Robot import frontend - experimental --- src/main/ipc-main-handlers.ts | 2 +- src/renderer/hooks/useTestImport.ts | 9 +- src/renderer/lib/api.ts | 10 ++ .../components/modals/ImportTestModal.tsx | 163 +++++++++++++----- 4 files changed, 137 insertions(+), 47 deletions(-) diff --git a/src/main/ipc-main-handlers.ts b/src/main/ipc-main-handlers.ts index 59af85398..6b252b129 100644 --- a/src/main/ipc-main-handlers.ts +++ b/src/main/ipc-main-handlers.ts @@ -154,7 +154,7 @@ export const registerIpcMainHandlers = () => { ipcMain.handle( API.openTestPicker, - async (e) => await openFilePicker(e, "Test", ["json", "py"]), + async (e) => await openFilePicker(e, "Test", ["json", "py", "robot"]), ); ipcMain.handle(API.openEditorWindow, (_, filepath) => { createEditorWindow(filepath); diff --git a/src/renderer/hooks/useTestImport.ts b/src/renderer/hooks/useTestImport.ts index 492ea9cb1..f70552b78 100644 --- a/src/renderer/hooks/useTestImport.ts +++ b/src/renderer/hooks/useTestImport.ts @@ -10,7 +10,7 @@ import { map } from "lodash"; import { ImportTestSettings } from "@/renderer/routes/test_sequencer_panel/components/modals/ImportTestModal"; import { toast } from "sonner"; import { useCallback } from "react"; -import { discoverPytest } from "@/renderer/lib/api"; +import { discoverPytest, discoverRobot } from "@/renderer/lib/api"; import { useSequencerModalStore } from "../stores/modal"; import { toastResultPromise } from "../utils/report-error"; import { Result, err, ok } from "neverthrow"; @@ -58,7 +58,12 @@ export const useDiscoverAndImportTests = () => { error: null, }; } else { - const res = await discoverPytest(path, settings.importAsOneRef); + let res: Result; + if (settings.importType === "pytest") { + res = await discoverPytest(path, settings.importAsOneRef); + } else { + res = await discoverRobot(path, settings.importAsOneRef); + } if (res.isErr()) { return err(res.error); } diff --git a/src/renderer/lib/api.ts b/src/renderer/lib/api.ts index 2a60fad49..aa8cc1e8c 100644 --- a/src/renderer/lib/api.ts +++ b/src/renderer/lib/api.ts @@ -147,6 +147,16 @@ export const discoverPytest = async (path: string, oneFile: boolean) => { }); }; +export const discoverRobot = async (path: string, oneFile: boolean) => { + return get("discover-robot", TestDiscoverContainer, { + searchParams: { + path, + oneFile, + }, + }); +}; + + const User = z.object({ email: z.string(), }); diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/ImportTestModal.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/ImportTestModal.tsx index 2c6e90496..a6b8b2db2 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/ImportTestModal.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/ImportTestModal.tsx @@ -32,65 +32,53 @@ export const ImportTestModal = () => { const openFilePicker = useDiscoverAndImportTests(); const { setIsLocked } = useDisplayedSequenceState(); - const { addNewElems } = useDisplayedSequenceState(); - const handleImportTest = async (importType: ImportType) => { + const handleImportTest = (importType: ImportType) => { setIsLocked(true); - if (importType === "robotframework") { - const newElem = createNewTest( - testName, - filePath, - "robotframework", - false, - undefined, - undefined, - undefined, - undefined, - undefined, - [testName] - ) - console.log(newElem); - const res = await addNewElems([newElem]); - console.log(res); - setIsImportTestModalOpen(false); - } else { - openFilePicker( - { - importType: importType, - importAsOneRef: checked, - }, - setIsImportTestModalOpen, - ); - } + openFilePicker( + { + importType: importType, + importAsOneRef: checked, + }, + setIsImportTestModalOpen, + ); setIsLocked(false); }; - const [testName, setRobotPath] = useState(""); - const [filePath, setProjectDirPath] = useState(""); - return ( -

Robot Framework Test

- setRobotPath(e.target.value)} /> - { - setProjectDirPath(event.target.value); - }} - pickerType="file" - allowDirectoryCreation={true} - /> - + +
+ { + setChecked(checked as boolean); + }} + /> +
); }; + +// export const ImportTestModal = () => { +// const { isImportTestModalOpen, setIsImportTestModalOpen } = +// useSequencerModalStore(); +// const [checked, setChecked] = useState(false); +// +// const { setIsDepManagerModalOpen } = useAppStore( +// useShallow((state) => ({ +// setIsDepManagerModalOpen: state.setIsDepManagerModalOpen, +// })), +// ); +// +// const openFilePicker = useDiscoverAndImportTests(); +// const { setIsLocked } = useDisplayedSequenceState(); +// const { addNewElems } = useDisplayedSequenceState(); +// +// const handleImportTest = async (importType: ImportType) => { +// setIsLocked(true); +// if (importType === "robotframework") { +// const newElem = createNewTest( +// testName, +// filePath, +// "robotframework", +// false, +// undefined, +// undefined, +// undefined, +// undefined, +// undefined, +// [testName] +// ) +// console.log(newElem); +// const res = await addNewElems([newElem]); +// console.log(res); +// setIsImportTestModalOpen(false); +// } else { +// openFilePicker( +// { +// importType: importType, +// importAsOneRef: checked, +// }, +// setIsImportTestModalOpen, +// ); +// } +// setIsLocked(false); +// }; +// +// const [testName, setRobotPath] = useState(""); +// const [filePath, setProjectDirPath] = useState(""); +// +// return ( +// +// +//

Robot Framework Test

+// setRobotPath(e.target.value)} /> +// { +// setProjectDirPath(event.target.value); +// }} +// pickerType="file" +// allowDirectoryCreation={true} +// /> +// +// +//
+//
+//
+//
+// +//
+//
+//
+//
+// ); +// }; From b82177b1f31d1d479a1315aa7c5c8e3b4015eef3 Mon Sep 17 00:00:00 2001 From: Guillaume Thibault Date: Tue, 23 Apr 2024 17:02:21 -0400 Subject: [PATCH 36/49] chore(robot): robot framework => import in batches --- captain/utils/pytest/discover_tests.py | 2 +- src/renderer/hooks/useTestImport.ts | 7 ++ src/renderer/hooks/useTestSequencerState.ts | 2 +- .../components/DesignBar.tsx | 6 +- .../components/modals/ImportTestModal.tsx | 91 +------------------ 5 files changed, 13 insertions(+), 95 deletions(-) diff --git a/captain/utils/pytest/discover_tests.py b/captain/utils/pytest/discover_tests.py index 76bf9ae0a..eb47657b2 100644 --- a/captain/utils/pytest/discover_tests.py +++ b/captain/utils/pytest/discover_tests.py @@ -96,7 +96,7 @@ def discover_robot_file( suite = builder.build(path) logging.info(f"Suite: {suite} - suites in it: {suite.suites} - tests in it: {suite.tests}") if one_file: - return_val.append(suite.full_name) + return_val.append(TestDiscoveryResponse(test_name=suite.full_name, path=pathlib.Path(path).as_posix())) else: for test in suite.tests: return_val.append(TestDiscoveryResponse( diff --git a/src/renderer/hooks/useTestImport.ts b/src/renderer/hooks/useTestImport.ts index f70552b78..c98bae90f 100644 --- a/src/renderer/hooks/useTestImport.ts +++ b/src/renderer/hooks/useTestImport.ts @@ -24,6 +24,13 @@ function parseDiscoverContainer( container.testName, container.path, settings.importType, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + settings.importType === "robotframework" && !settings.importAsOneRef ? [container.testName] : undefined, ); return new_elem; }); diff --git a/src/renderer/hooks/useTestSequencerState.ts b/src/renderer/hooks/useTestSequencerState.ts index 9619bb13c..52a12efee 100644 --- a/src/renderer/hooks/useTestSequencerState.ts +++ b/src/renderer/hooks/useTestSequencerState.ts @@ -17,7 +17,7 @@ import useWithPermission from "@/renderer/hooks/useWithPermission"; import { useSequencerStore } from "@/renderer/stores/sequencer"; import { useShallow } from "zustand/react/shallow"; import { v4 as uuidv4 } from "uuid"; -import { Err, Ok, Result, err, ok } from "neverthrow"; +import { Err, Result, err, ok } from "neverthrow"; import { verifyElementCompatibleWithSequence } from "@/renderer/routes/test_sequencer_panel/utils/SequenceHandler"; import { toast } from "sonner"; import { SendJsonMessage } from "react-use-websocket/dist/lib/types"; diff --git a/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx b/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx index 49dfc504c..63a4fd842 100644 --- a/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx @@ -2,7 +2,7 @@ import { useSequencerModalStore } from "@/renderer/stores/modal"; import { useDisplayedSequenceState } from "@/renderer/hooks/useTestSequencerState"; import { Button } from "@/renderer/components/ui/button"; import { ACTIONS_HEIGHT } from "@/renderer/routes/common/Layout"; -import { BotIcon, FlaskConical, Import, LayoutGrid, Plus, Route } from "lucide-react"; +import { FlaskConical, Import, LayoutGrid, Plus, Route, TestTube } from "lucide-react"; import { StatusType, Test, @@ -97,11 +97,11 @@ export function DesignBar() { }} data-testid="import-test-button" > - - New Robot Test + New Test { diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/ImportTestModal.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/ImportTestModal.tsx index a6b8b2db2..f52ffc90d 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/ImportTestModal.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/ImportTestModal.tsx @@ -1,11 +1,9 @@ import { Button } from "@/renderer/components/ui/button"; import { Checkbox } from "@/renderer/components/ui/checkbox"; import { Dialog, DialogContent } from "@/renderer/components/ui/dialog"; -import { Input } from "@/renderer/components/ui/input"; -import { PathInput } from "@/renderer/components/ui/path-input"; import { Separator } from "@/renderer/components/ui/separator"; import { useDiscoverAndImportTests } from "@/renderer/hooks/useTestImport"; -import { createNewTest, useDisplayedSequenceState } from "@/renderer/hooks/useTestSequencerState"; +import { useDisplayedSequenceState } from "@/renderer/hooks/useTestSequencerState"; import { useAppStore } from "@/renderer/stores/app"; import { useSequencerModalStore } from "@/renderer/stores/modal"; import { ExternalLinkIcon } from "lucide-react"; @@ -93,90 +91,3 @@ export const ImportTestModal = () => {
); }; - -// export const ImportTestModal = () => { -// const { isImportTestModalOpen, setIsImportTestModalOpen } = -// useSequencerModalStore(); -// const [checked, setChecked] = useState(false); -// -// const { setIsDepManagerModalOpen } = useAppStore( -// useShallow((state) => ({ -// setIsDepManagerModalOpen: state.setIsDepManagerModalOpen, -// })), -// ); -// -// const openFilePicker = useDiscoverAndImportTests(); -// const { setIsLocked } = useDisplayedSequenceState(); -// const { addNewElems } = useDisplayedSequenceState(); -// -// const handleImportTest = async (importType: ImportType) => { -// setIsLocked(true); -// if (importType === "robotframework") { -// const newElem = createNewTest( -// testName, -// filePath, -// "robotframework", -// false, -// undefined, -// undefined, -// undefined, -// undefined, -// undefined, -// [testName] -// ) -// console.log(newElem); -// const res = await addNewElems([newElem]); -// console.log(res); -// setIsImportTestModalOpen(false); -// } else { -// openFilePicker( -// { -// importType: importType, -// importAsOneRef: checked, -// }, -// setIsImportTestModalOpen, -// ); -// } -// setIsLocked(false); -// }; -// -// const [testName, setRobotPath] = useState(""); -// const [filePath, setProjectDirPath] = useState(""); -// -// return ( -// -// -//

Robot Framework Test

-// setRobotPath(e.target.value)} /> -// { -// setProjectDirPath(event.target.value); -// }} -// pickerType="file" -// allowDirectoryCreation={true} -// /> -// -// -//
-//
-//
-//
-// -//
-//
-//
-//
-// ); -// }; From 20ccc4e95c171d1728952cd8a43604fbb20c2ef8 Mon Sep 17 00:00:00 2001 From: Guillaume Thibault Date: Tue, 23 Apr 2024 17:47:35 -0400 Subject: [PATCH 37/49] chore(robot): handle Robot framework in change executable --- log.html | 2464 --------------- output.xml | 28 - report.html | 2745 ----------------- src/renderer/hooks/useTestImport.ts | 15 +- .../components/data-table/TestTable.tsx | 3 +- .../components/modals/ChangeLinkedTest.tsx | 65 +- src/renderer/types/test-sequencer.ts | 2 - 7 files changed, 50 insertions(+), 5272 deletions(-) delete mode 100644 log.html delete mode 100644 output.xml delete mode 100644 report.html diff --git a/log.html b/log.html deleted file mode 100644 index e55fb64e3..000000000 --- a/log.html +++ /dev/null @@ -1,2464 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

Opening Robot Framework log failed

-
    -
  • Verify that you have JavaScript enabled in your browser.
  • -
  • Make sure you are using a modern enough browser. If using Internet Explorer, version 11 is required.
  • -
  • Check are there messages in your browser's JavaScript error log. Please report the problem if you suspect you have encountered a bug.
  • -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/output.xml b/output.xml deleted file mode 100644 index bae6554d4..000000000 --- a/output.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - -${MESSAGE} -Hello, world! -Fails if the given objects are unequal. - - - - -Example using the pipe separated format. - - - - -All Tests - - - - -Demo - - - - - diff --git a/report.html b/report.html deleted file mode 100644 index d06a1a00d..000000000 --- a/report.html +++ /dev/null @@ -1,2745 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

Opening Robot Framework report failed

-
    -
  • Verify that you have JavaScript enabled in your browser.
  • -
  • Make sure you are using a modern enough browser. If using Internet Explorer, version 11 is required.
  • -
  • Check are there messages in your browser's JavaScript error log. Please report the problem if you suspect you have encountered a bug.
  • -
-
- -
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/renderer/hooks/useTestImport.ts b/src/renderer/hooks/useTestImport.ts index c98bae90f..ff7cd64ad 100644 --- a/src/renderer/hooks/useTestImport.ts +++ b/src/renderer/hooks/useTestImport.ts @@ -141,7 +141,7 @@ export const useDiscoverAndImportTests = () => { return openFilePicker; }; -export async function useDiscoverPytestElements() { +export async function useDiscoverElements() { const handleUserDepInstall = useCallback(async (depName: string) => { const promise = () => window.api.poetryInstallDepUserGroup(depName); toast.promise(promise, { @@ -157,7 +157,15 @@ export async function useDiscoverPytestElements() { async function getTests( path: string, ): Promise> { - const res = await discoverPytest(path, false); + let res: Result; + let type: "pytest" | "robotframework"; + if (path.endsWith(".robot")) { + res = await discoverRobot(path, false); + type = "robotframework"; + } else { + res = await discoverPytest(path, false); + type = "pytest"; + } if (res.isErr()) { return err(res.error); } @@ -178,7 +186,7 @@ export async function useDiscoverPytestElements() { } const newElems = parseDiscoverContainer(data, { importAsOneRef: false, - importType: "pytest", + importType: type, }); if (newElems.length === 0) { return err(Error("No tests were found in the specified file.")); @@ -189,7 +197,6 @@ export async function useDiscoverPytestElements() { const openFilePicker = (): Promise> => { return window.api.openTestPicker().then((result) => { if (!result) return err(Error("No file selected.")); - toast.info("Importing tests..."); const { filePath } = result; return getTests(filePath); }); diff --git a/src/renderer/routes/test_sequencer_panel/components/data-table/TestTable.tsx b/src/renderer/routes/test_sequencer_panel/components/data-table/TestTable.tsx index 4f98c9d9d..3c7094703 100644 --- a/src/renderer/routes/test_sequencer_panel/components/data-table/TestTable.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/data-table/TestTable.tsx @@ -444,7 +444,7 @@ export function TestTable() { const [openLinkedTestModal, setOpenLinkedTestModal] = useState(false); const testRef = useRef(-1); - const handleChangeLinkedTest = (newPath: string, testType: ImportType) => { + const handleChangeLinkedTest = (newPath: string, testType: ImportType, args: string[] | undefined) => { setElems((data) => { const new_data = [...data]; const test = new_data[testRef.current] as Test; @@ -452,6 +452,7 @@ export function TestTable() { ...test, path: newPath, testType: testType, + args: args, }; return new_data; }); diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx index a4c928259..c39f2faf3 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx @@ -15,7 +15,7 @@ import { SelectTrigger, SelectValue, } from "@/renderer/components/ui/select"; -import { useDiscoverPytestElements } from "@/renderer/hooks/useTestImport"; +import { useDiscoverElements } from "@/renderer/hooks/useTestImport"; import { TestSequenceElement } from "@/renderer/types/test-sequencer"; import { toast } from "sonner"; import { useSequencerModalStore } from "@/renderer/stores/modal"; @@ -27,12 +27,12 @@ export const ChangeLinkedTestModal = ({ }: { isModalOpen: boolean; setModalOpen: (value: boolean) => void; - handleSubmit: (path: string, testType: ImportType) => void; + handleSubmit: (path: string, testType: ImportType, args: string[] | undefined) => void; }) => { const [availableTests, setAvailableTests] = useState( [], ); - const [selectedPath, setSelectedPath] = useState(""); + const [selectedTestName, setSelectedPath] = useState(""); const { openErrorModal } = useSequencerModalStore(); const { setIsDepManagerModalOpen } = useAppStore( @@ -41,9 +41,9 @@ export const ChangeLinkedTestModal = ({ })), ); - const openFilePickerPromise = useDiscoverPytestElements(); + const openFilePickerPromise = useDiscoverElements(); - const handleDiscoverPytestElements = () => { + const handleDiscoverElements = () => { openFilePickerPromise .then((useDiscover) => { return useDiscover(); @@ -52,6 +52,7 @@ export const ChangeLinkedTestModal = ({ potentialElementsResult.match( (elements) => { setAvailableTests(elements); + toast.info("Tests discovered successfully"); }, (error) => { toast("Error while attempting to discover tests", { @@ -71,31 +72,40 @@ export const ChangeLinkedTestModal = ({ }); }; - const handleSubmitByType = (testType: ImportType) => { - if (testType === "pytest") { - if (selectedPath === "") { - toast.error("Please select a test to link to"); - } - handleSubmit(selectedPath, testType); - } else { - window.api.openTestPicker().then((result) => { - if (!result) { - return; - } - const { filePath } = result; - handleSubmit(filePath, testType); - }); + const handleSubmitIndividualTest = () => { + if (selectedTestName === "") { + toast.error("Please select a test to link to"); } + const test = availableTests.find((test) => test.type === "test" && test.testName === selectedTestName); + if (test?.type !== "test" || test.testType === "placeholder") { + return + } + handleSubmit(test.path, test.testType, test.args) setModalOpen(false); }; + const handleSubmitPythonScript = () => { + window.api.openTestPicker().then((result) => { + if (!result) { + return; + } + const { filePath } = result; + if (!filePath.endsWith(".py")) { + toast.error("Please select a Python file"); + return; + } + handleSubmit(filePath, "python", undefined); + setModalOpen(false); + }); + } + return (

Select a test to link to

-

Pytest

+

Pytest & Robot Framework

-
-
diff --git a/src/renderer/types/test-sequencer.ts b/src/renderer/types/test-sequencer.ts index 4670b1d6f..75bc82d9f 100644 --- a/src/renderer/types/test-sequencer.ts +++ b/src/renderer/types/test-sequencer.ts @@ -7,8 +7,6 @@ export type LockedContextType = { export const TestType = z.enum([ "pytest", "python", - "flojoy", - "matlab", "placeholder", "robotframework", ]); From 626d3977eae11058a5e7a7e82840b10d9fd7f62f Mon Sep 17 00:00:00 2001 From: Guillaume Date: Tue, 23 Apr 2024 17:49:16 -0400 Subject: [PATCH 38/49] chore(robot): formatting --- captain/routes/test_sequence.py | 7 +-- captain/utils/pytest/discover_tests.py | 22 +++++---- .../utils/test_sequencer/run_test_sequence.py | 5 +-- src/renderer/hooks/useTestImport.ts | 4 +- src/renderer/lib/api.ts | 1 - .../components/DesignBar.tsx | 9 +++- .../components/data-table/TestTable.tsx | 6 ++- .../components/modals/ChangeLinkedTest.tsx | 45 ++++++++++--------- 8 files changed, 60 insertions(+), 39 deletions(-) diff --git a/captain/routes/test_sequence.py b/captain/routes/test_sequence.py index 010d2d7dd..97ed4a336 100644 --- a/captain/routes/test_sequence.py +++ b/captain/routes/test_sequence.py @@ -3,7 +3,10 @@ import pydantic from captain.models.pytest.pytest_models import TestDiscoverContainer from captain.models.test_sequencer import TestSequenceRun -from captain.utils.pytest.discover_tests import discover_pytest_file, discover_robot_file +from captain.utils.pytest.discover_tests import ( + discover_pytest_file, + discover_robot_file, +) from captain.utils.config import ts_manager from captain.utils.test_sequencer.handle_data import handle_data from captain.utils.logger import logger @@ -73,5 +76,3 @@ async def discover_robot(params: DiscoverParams = Depends()): missing_libraries=[], error=errors[0] if len(errors) > 0 else None, ) - - diff --git a/captain/utils/pytest/discover_tests.py b/captain/utils/pytest/discover_tests.py index eb47657b2..b84c16b60 100644 --- a/captain/utils/pytest/discover_tests.py +++ b/captain/utils/pytest/discover_tests.py @@ -88,20 +88,26 @@ def dfs( return_val.extend(test_list) -def discover_robot_file( - path: str, one_file: bool, return_val: list, errors: list -): +def discover_robot_file(path: str, one_file: bool, return_val: list, errors: list): try: builder = TestSuiteBuilder() suite = builder.build(path) - logging.info(f"Suite: {suite} - suites in it: {suite.suites} - tests in it: {suite.tests}") + logging.info( + f"Suite: {suite} - suites in it: {suite.suites} - tests in it: {suite.tests}" + ) if one_file: - return_val.append(TestDiscoveryResponse(test_name=suite.full_name, path=pathlib.Path(path).as_posix())) + return_val.append( + TestDiscoveryResponse( + test_name=suite.full_name, path=pathlib.Path(path).as_posix() + ) + ) else: for test in suite.tests: - return_val.append(TestDiscoveryResponse( - test_name=test.full_name, path=pathlib.Path(path).as_posix() - )) + return_val.append( + TestDiscoveryResponse( + test_name=test.full_name, path=pathlib.Path(path).as_posix() + ) + ) except Exception as e: errors.append(str(e)) diff --git a/captain/utils/test_sequencer/run_test_sequence.py b/captain/utils/test_sequencer/run_test_sequence.py index 3ac1c47c5..2a3707ba8 100644 --- a/captain/utils/test_sequencer/run_test_sequence.py +++ b/captain/utils/test_sequencer/run_test_sequence.py @@ -202,9 +202,7 @@ def _run_robotframework(node: TestNode) -> Extract: cmd = ["robot", "--test", *node.args, node.path] else: cmd = ["robot", node.path] - result = subprocess.run( - cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) + result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) logger.info(f"[Robot Framework Runner] Running {result}") end_time = time.time() if result.returncode == 0: @@ -226,7 +224,6 @@ def _run_robotframework(node: TestNode) -> Extract: ) - def _eval_condition( result_dict: dict[str, TestResult], condition: str, identifiers: set[str] ): diff --git a/src/renderer/hooks/useTestImport.ts b/src/renderer/hooks/useTestImport.ts index ff7cd64ad..993ea0f6e 100644 --- a/src/renderer/hooks/useTestImport.ts +++ b/src/renderer/hooks/useTestImport.ts @@ -30,7 +30,9 @@ function parseDiscoverContainer( undefined, undefined, undefined, - settings.importType === "robotframework" && !settings.importAsOneRef ? [container.testName] : undefined, + settings.importType === "robotframework" && !settings.importAsOneRef + ? [container.testName] + : undefined, ); return new_elem; }); diff --git a/src/renderer/lib/api.ts b/src/renderer/lib/api.ts index aa8cc1e8c..76506df4e 100644 --- a/src/renderer/lib/api.ts +++ b/src/renderer/lib/api.ts @@ -156,7 +156,6 @@ export const discoverRobot = async (path: string, oneFile: boolean) => { }); }; - const User = z.object({ email: z.string(), }); diff --git a/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx b/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx index 63a4fd842..a7cabc723 100644 --- a/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx @@ -2,7 +2,14 @@ import { useSequencerModalStore } from "@/renderer/stores/modal"; import { useDisplayedSequenceState } from "@/renderer/hooks/useTestSequencerState"; import { Button } from "@/renderer/components/ui/button"; import { ACTIONS_HEIGHT } from "@/renderer/routes/common/Layout"; -import { FlaskConical, Import, LayoutGrid, Plus, Route, TestTube } from "lucide-react"; +import { + FlaskConical, + Import, + LayoutGrid, + Plus, + Route, + TestTube, +} from "lucide-react"; import { StatusType, Test, diff --git a/src/renderer/routes/test_sequencer_panel/components/data-table/TestTable.tsx b/src/renderer/routes/test_sequencer_panel/components/data-table/TestTable.tsx index 3c7094703..c47e0cbb9 100644 --- a/src/renderer/routes/test_sequencer_panel/components/data-table/TestTable.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/data-table/TestTable.tsx @@ -444,7 +444,11 @@ export function TestTable() { const [openLinkedTestModal, setOpenLinkedTestModal] = useState(false); const testRef = useRef(-1); - const handleChangeLinkedTest = (newPath: string, testType: ImportType, args: string[] | undefined) => { + const handleChangeLinkedTest = ( + newPath: string, + testType: ImportType, + args: string[] | undefined, + ) => { setElems((data) => { const new_data = [...data]; const test = new_data[testRef.current] as Test; diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx index c39f2faf3..05107e7f5 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx @@ -27,7 +27,11 @@ export const ChangeLinkedTestModal = ({ }: { isModalOpen: boolean; setModalOpen: (value: boolean) => void; - handleSubmit: (path: string, testType: ImportType, args: string[] | undefined) => void; + handleSubmit: ( + path: string, + testType: ImportType, + args: string[] | undefined, + ) => void; }) => { const [availableTests, setAvailableTests] = useState( [], @@ -76,11 +80,13 @@ export const ChangeLinkedTestModal = ({ if (selectedTestName === "") { toast.error("Please select a test to link to"); } - const test = availableTests.find((test) => test.type === "test" && test.testName === selectedTestName); + const test = availableTests.find( + (test) => test.type === "test" && test.testName === selectedTestName, + ); if (test?.type !== "test" || test.testType === "placeholder") { - return + return; } - handleSubmit(test.path, test.testType, test.args) + handleSubmit(test.path, test.testType, test.args); setModalOpen(false); }; @@ -97,7 +103,7 @@ export const ChangeLinkedTestModal = ({ handleSubmit(filePath, "python", undefined); setModalOpen(false); }); - } + }; return ( @@ -117,23 +123,25 @@ export const ChangeLinkedTestModal = ({ {availableTests.map((test) => { if (test.type === "test") { return ( - {test.testName.length > 35 ? `...${test.testName.slice(-35)}`: test.testName} + + {test.testName.length > 35 + ? `...${test.testName.slice(-35)}` + : test.testName} + ); } })} - +
From 34de28d4d3b75526b450f7a77a9eab23ca6c665f Mon Sep 17 00:00:00 2001 From: Guillaume Thibault Date: Wed, 24 Apr 2024 11:20:25 -0400 Subject: [PATCH 39/49] chore(robot): sample code for robot example --- .../TestExample.robot | 19 +++++++++++++++++++ .../calculate.py | 5 +++++ 2 files changed, 24 insertions(+) create mode 100644 examples/test-sequencer-conditional-example/TestExample.robot create mode 100644 examples/test-sequencer-conditional-example/calculate.py diff --git a/examples/test-sequencer-conditional-example/TestExample.robot b/examples/test-sequencer-conditional-example/TestExample.robot new file mode 100644 index 000000000..935164cb1 --- /dev/null +++ b/examples/test-sequencer-conditional-example/TestExample.robot @@ -0,0 +1,19 @@ +*** Settings *** +Library flojoy_cloud.test_sequencer +Library calculate.py + +*** Test Cases *** +TEST EXPORT + ${result} Calculate 3 + 1 + # Export the `result` so it's display in the sequencer + # + this value will be upload to Flojoy Cloud + Export ${result} + Should Not Be Equal 4 ${result} + +TEST ASSERT + ${result} Calculate 1 + 1 + Export ${result} + # Call the `is_in_range` from the test_sequencer + ${ok} Is In Range ${result} + Should Be True ${ok} + diff --git a/examples/test-sequencer-conditional-example/calculate.py b/examples/test-sequencer-conditional-example/calculate.py new file mode 100644 index 000000000..4df04fb4e --- /dev/null +++ b/examples/test-sequencer-conditional-example/calculate.py @@ -0,0 +1,5 @@ +def calculate(term): + if term == "": + return 0 + else: + return eval(term) From 7c59be738f23c0a431c4127b5c03ed1519c5bbaf Mon Sep 17 00:00:00 2001 From: Guillaume Thibault Date: Wed, 24 Apr 2024 11:36:40 -0400 Subject: [PATCH 40/49] chore(robot): Robot Framework Sequence Example --- .../Robot_Sequence.tjoy | 1 + .../TestExample.robot | 0 .../calculate.py | 0 .../flojoy_requirements.txt | 1 + .../components/modals/SequencerGalleryModal.tsx | 6 ++++++ 5 files changed, 8 insertions(+) create mode 100644 examples/test-sequencer-robot-framework-example/Robot_Sequence.tjoy rename examples/{test-sequencer-conditional-example => test-sequencer-robot-framework-example}/TestExample.robot (100%) rename examples/{test-sequencer-conditional-example => test-sequencer-robot-framework-example}/calculate.py (100%) create mode 100644 examples/test-sequencer-robot-framework-example/flojoy_requirements.txt diff --git a/examples/test-sequencer-robot-framework-example/Robot_Sequence.tjoy b/examples/test-sequencer-robot-framework-example/Robot_Sequence.tjoy new file mode 100644 index 000000000..4c6042803 --- /dev/null +++ b/examples/test-sequencer-robot-framework-example/Robot_Sequence.tjoy @@ -0,0 +1 @@ +{"name":"Robot_Sequence","description":"Consult the code to learn more!","elems":[{"type":"test","id":"35d63b42-c0b9-4a2e-900e-e327404a8c41","groupId":"40216fc8-785a-4610-913e-90bd54f157af","path":"TestExample.robot","testName":"TEST EXPORT","runInParallel":false,"testType":"robotframework","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-24T15:35:41.832Z"},{"type":"test","id":"36374991-c721-40c6-8a35-68a996fe6ed4","groupId":"d8a898b4-a012-4177-9f9a-d07c3ab5f84e","path":"TestExample.robot","testName":"TEST ASSERT","runInParallel":false,"testType":"robotframework","status":"pending","error":null,"isSavedToCloud":false,"exportToCloud":true,"createdAt":"2024-04-24T15:35:41.832Z","minValue":2,"maxValue":4.2,"unit":""}],"projectPath":"C:/Users/zzzgu/Documents/flojoy/repo/studio/examples/test-sequencer-robot-framework-example/","interpreter":{"type":"flojoy","path":null,"requirementsPath":"flojoy_requirements.txt"}} \ No newline at end of file diff --git a/examples/test-sequencer-conditional-example/TestExample.robot b/examples/test-sequencer-robot-framework-example/TestExample.robot similarity index 100% rename from examples/test-sequencer-conditional-example/TestExample.robot rename to examples/test-sequencer-robot-framework-example/TestExample.robot diff --git a/examples/test-sequencer-conditional-example/calculate.py b/examples/test-sequencer-robot-framework-example/calculate.py similarity index 100% rename from examples/test-sequencer-conditional-example/calculate.py rename to examples/test-sequencer-robot-framework-example/calculate.py diff --git a/examples/test-sequencer-robot-framework-example/flojoy_requirements.txt b/examples/test-sequencer-robot-framework-example/flojoy_requirements.txt new file mode 100644 index 000000000..f1e0ac08a --- /dev/null +++ b/examples/test-sequencer-robot-framework-example/flojoy_requirements.txt @@ -0,0 +1 @@ +psutil==5.9.8 \ No newline at end of file diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx index 4f7599197..793e996ba 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/SequencerGalleryModal.tsx @@ -38,6 +38,12 @@ export const SequencerGalleryModal = ({ "Learn how to inject the minimum and maximum expected values into a test and export the result.", dirPath: "examples/test-sequencer-expected-exported-example/", }, + { + title: "Robot Framework & Flojoy", + description: + "Learn how to inject the minimum and maximum expected values into a robot test and export the result.", + dirPath: "examples/test-sequencer-robot-framework-example/", + }, ]; return ( From f2e13881bd4f52ad67e4693644b6b648f2a7aa43 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Wed, 24 Apr 2024 11:40:24 -0400 Subject: [PATCH 41/49] chore(linter): fix E711 --- captain/utils/test_sequencer/run_test_sequence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/captain/utils/test_sequencer/run_test_sequence.py b/captain/utils/test_sequencer/run_test_sequence.py index 2a3707ba8..c17221c43 100644 --- a/captain/utils/test_sequencer/run_test_sequence.py +++ b/captain/utils/test_sequencer/run_test_sequence.py @@ -198,7 +198,7 @@ def _run_robotframework(node: TestNode) -> Extract: """ start_time = time.time() logger.info(f"[Robot Framework Runner] Running {node.path}") - if node.args != None: + if node.args is not None: cmd = ["robot", "--test", *node.args, node.path] else: cmd = ["robot", node.path] From 1ccb7a75ed9eccb915e3660916aa2148200c0536 Mon Sep 17 00:00:00 2001 From: Guillaume Thibault Date: Wed, 24 Apr 2024 13:38:25 -0400 Subject: [PATCH 42/49] chore(robot): cleanup --- src/renderer/hooks/useTestImport.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/renderer/hooks/useTestImport.ts b/src/renderer/hooks/useTestImport.ts index 00f4d0d1d..5fd6b6c95 100644 --- a/src/renderer/hooks/useTestImport.ts +++ b/src/renderer/hooks/useTestImport.ts @@ -193,10 +193,10 @@ export async function useDiscoverElements() { const { filePath } = result; return getTests(filePath); }); - // Return a function that takes the file path as an argument - // return async (filePath: string) => { - // const result = await getTests(filePath); - // return result; + // Return a function that takes the file path as an argument + // return async (filePath: string) => { + // const result = await getTests(filePath); + // return result; }; return openFilePicker; }; From 4efb9f9d08138a8f22907383a93691c277669938 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Wed, 24 Apr 2024 13:40:15 -0400 Subject: [PATCH 43/49] chore(robot): formatting --- src/renderer/hooks/useTestImport.ts | 14 +++++++++----- .../components/modals/ChangeLinkedTest.tsx | 1 - 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/renderer/hooks/useTestImport.ts b/src/renderer/hooks/useTestImport.ts index 5fd6b6c95..5cb3fbe99 100644 --- a/src/renderer/hooks/useTestImport.ts +++ b/src/renderer/hooks/useTestImport.ts @@ -1,4 +1,7 @@ -import { TestDiscoverContainer, TestSequenceElement } from "@/renderer/types/test-sequencer"; +import { + TestDiscoverContainer, + TestSequenceElement, +} from "@/renderer/types/test-sequencer"; import { createNewTest, useDisplayedSequenceState, @@ -21,9 +24,10 @@ function parseDiscoverContainer( name: container.testName, path: container.path, type: settings.importType, - args: settings.importType === "robotframework" && !settings.importAsOneRef - ? [container.testName] - : undefined, + args: + settings.importType === "robotframework" && !settings.importAsOneRef + ? [container.testName] + : undefined, }); return new_elem; }); @@ -199,4 +203,4 @@ export async function useDiscoverElements() { // return result; }; return openFilePicker; -}; +} diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx index fe7164a70..88893385a 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx @@ -76,7 +76,6 @@ export const ChangeLinkedTestModal = ({ }); }; - const handleSubmitIndividualTest = () => { if (selectedTestName === "") { toast.error("Please select a test to link to"); From 4f1bddc6083a1c3404654cb195ff263e12c78042 Mon Sep 17 00:00:00 2001 From: Guillaume Thibault Date: Thu, 25 Apr 2024 11:30:01 -0400 Subject: [PATCH 44/49] chore(robot): adding @itsjoeoui pattern matching recommendation --- captain/routes/test_sequence.py | 4 ++-- src/renderer/hooks/useTestImport.ts | 33 +++++++++++++---------------- src/renderer/lib/api.ts | 4 ++-- 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/captain/routes/test_sequence.py b/captain/routes/test_sequence.py index 97ed4a336..84971ea4b 100644 --- a/captain/routes/test_sequence.py +++ b/captain/routes/test_sequence.py @@ -42,7 +42,7 @@ class DiscoverParams(BaseModel): one_file: bool = Field(..., alias="oneFile") -@router.get("/discover-pytest/") +@router.get("/discover/pytest/") async def discover_pytest(params: DiscoverParams = Depends()): path = params.path one_file = params.one_file @@ -60,7 +60,7 @@ async def discover_pytest(params: DiscoverParams = Depends()): ) -@router.get("/discover-robot/") +@router.get("/discover/robot/") async def discover_robot(params: DiscoverParams = Depends()): path = params.path one_file = params.one_file diff --git a/src/renderer/hooks/useTestImport.ts b/src/renderer/hooks/useTestImport.ts index 5cb3fbe99..ea0a75392 100644 --- a/src/renderer/hooks/useTestImport.ts +++ b/src/renderer/hooks/useTestImport.ts @@ -14,6 +14,7 @@ import { discoverPytest, discoverRobot } from "@/renderer/lib/api"; import { useSequencerModalStore } from "../stores/modal"; import { toastResultPromise } from "../utils/report-error"; import { Result, err, ok } from "neverthrow"; +import { match } from "ts-pattern"; function parseDiscoverContainer( data: TestDiscoverContainer, @@ -54,25 +55,21 @@ export const useDiscoverAndImportTests = () => { settings: ImportTestSettings, setModalOpen: (val: boolean) => void, ): Promise> { - let data: TestDiscoverContainer; - if (settings.importType == "python") { - data = { - response: [{ testName: path, path: path }], - missingLibraries: [], - error: null, - }; - } else { - let res: Result; - if (settings.importType === "pytest") { - res = await discoverPytest(path, settings.importAsOneRef); - } else { - res = await discoverRobot(path, settings.importAsOneRef); - } - if (res.isErr()) { - return err(res.error); - } - data = res.value; + const dataResponse = await match(settings.importType) + .with("python", async () => { + return ok({ + response: [{ testName: path, path: path }], + missingLibraries: [], + error: null, + }) + }) + .with("pytest", async () => await discoverPytest(path, settings.importAsOneRef)) + .with("robotframework", async () => await discoverRobot(path, settings.importAsOneRef)) + .exhaustive(); + if (dataResponse.isErr()) { + return err(dataResponse.error); } + const data = dataResponse.value; if (data.error) { return err(Error(data.error)); } diff --git a/src/renderer/lib/api.ts b/src/renderer/lib/api.ts index 76506df4e..42ad21952 100644 --- a/src/renderer/lib/api.ts +++ b/src/renderer/lib/api.ts @@ -139,7 +139,7 @@ export const setLogLevel = async (level: string) => { }; export const discoverPytest = async (path: string, oneFile: boolean) => { - return get("discover-pytest", TestDiscoverContainer, { + return get("discover/pytest", TestDiscoverContainer, { searchParams: { path, oneFile, @@ -148,7 +148,7 @@ export const discoverPytest = async (path: string, oneFile: boolean) => { }; export const discoverRobot = async (path: string, oneFile: boolean) => { - return get("discover-robot", TestDiscoverContainer, { + return get("discover/robot", TestDiscoverContainer, { searchParams: { path, oneFile, From 2a7ea6fe045c47f8b92ce720fccf05c5c8e5eff7 Mon Sep 17 00:00:00 2001 From: Guillaume Thibault Date: Thu, 25 Apr 2024 11:32:08 -0400 Subject: [PATCH 45/49] chore(robot): exposing discoverable test type as global type --- src/renderer/hooks/useTestImport.ts | 4 ++-- .../components/modals/ImportTestModal.tsx | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/renderer/hooks/useTestImport.ts b/src/renderer/hooks/useTestImport.ts index ea0a75392..75cfd9c6f 100644 --- a/src/renderer/hooks/useTestImport.ts +++ b/src/renderer/hooks/useTestImport.ts @@ -7,7 +7,7 @@ import { useDisplayedSequenceState, } from "./useTestSequencerState"; import { map } from "lodash"; -import { ImportTestSettings } from "@/renderer/routes/test_sequencer_panel/components/modals/ImportTestModal"; +import { ImportTestSettings, discoverableTestTypes as DiscoverableTestTypes } from "@/renderer/routes/test_sequencer_panel/components/modals/ImportTestModal"; import { toast } from "sonner"; import { useCallback } from "react"; import { discoverPytest, discoverRobot } from "@/renderer/lib/api"; @@ -152,7 +152,7 @@ export async function useDiscoverElements() { path: string, ): Promise> { let res: Result; - let type: "pytest" | "robotframework"; + let type: DiscoverableTestTypes; if (path.endsWith(".robot")) { res = await discoverRobot(path, false); type = "robotframework"; diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/ImportTestModal.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/ImportTestModal.tsx index f52ffc90d..98a2fb9bc 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/ImportTestModal.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/ImportTestModal.tsx @@ -15,7 +15,8 @@ export type ImportTestSettings = { importType: ImportType; }; -export type ImportType = "pytest" | "python" | "robotframework"; +export type discoverableTestTypes = "pytest" | "robotframework"; +export type ImportType = discoverableTestTypes | "python"; export const ImportTestModal = () => { const { isImportTestModalOpen, setIsImportTestModalOpen } = From 8cd66e9824a97b0046dc84c640d61c7a8b2a9d76 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Thu, 25 Apr 2024 11:49:58 -0400 Subject: [PATCH 46/49] chore(robot): formatting --- src/renderer/hooks/useTestImport.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/renderer/hooks/useTestImport.ts b/src/renderer/hooks/useTestImport.ts index 75cfd9c6f..9655885ff 100644 --- a/src/renderer/hooks/useTestImport.ts +++ b/src/renderer/hooks/useTestImport.ts @@ -7,7 +7,10 @@ import { useDisplayedSequenceState, } from "./useTestSequencerState"; import { map } from "lodash"; -import { ImportTestSettings, discoverableTestTypes as DiscoverableTestTypes } from "@/renderer/routes/test_sequencer_panel/components/modals/ImportTestModal"; +import { + ImportTestSettings, + discoverableTestTypes as DiscoverableTestTypes, +} from "@/renderer/routes/test_sequencer_panel/components/modals/ImportTestModal"; import { toast } from "sonner"; import { useCallback } from "react"; import { discoverPytest, discoverRobot } from "@/renderer/lib/api"; @@ -61,10 +64,16 @@ export const useDiscoverAndImportTests = () => { response: [{ testName: path, path: path }], missingLibraries: [], error: null, - }) + }); }) - .with("pytest", async () => await discoverPytest(path, settings.importAsOneRef)) - .with("robotframework", async () => await discoverRobot(path, settings.importAsOneRef)) + .with( + "pytest", + async () => await discoverPytest(path, settings.importAsOneRef), + ) + .with( + "robotframework", + async () => await discoverRobot(path, settings.importAsOneRef), + ) .exhaustive(); if (dataResponse.isErr()) { return err(dataResponse.error); From 3f3287de24828ba1cc1d1a0c81df59d9ba0915f0 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Thu, 25 Apr 2024 17:22:38 -0400 Subject: [PATCH 47/49] chore(robot): format --- .../test_sequencer_panel/components/modals/ChangeLinkedTest.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx index 522552c74..c42591106 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx @@ -95,7 +95,6 @@ export const ChangeLinkedTestModal = ({ }); }; - return ( From 726d99cc5cb193d53b75d24ee695c19b90f2758f Mon Sep 17 00:00:00 2001 From: Guillaume Date: Thu, 25 Apr 2024 17:25:38 -0400 Subject: [PATCH 48/49] chore(robot): fix esLint --- .../test_sequencer_panel/components/modals/ChangeLinkedTest.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx b/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx index c42591106..55862aef2 100644 --- a/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx +++ b/src/renderer/routes/test_sequencer_panel/components/modals/ChangeLinkedTest.tsx @@ -18,7 +18,6 @@ import { import { useDiscoverElements } from "@/renderer/hooks/useTestImport"; import { TestSequenceElement } from "@/renderer/types/test-sequencer"; import { toast } from "sonner"; -import { useSequencerModalStore } from "@/renderer/stores/modal"; export const ChangeLinkedTestModal = ({ isModalOpen, From 4358c15a4c7105b7507e736049014a2cbfc65e57 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Thu, 25 Apr 2024 17:28:18 -0400 Subject: [PATCH 49/49] chore(robot): fix esLint part #2 --- src/renderer/hooks/useTestImport.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/renderer/hooks/useTestImport.ts b/src/renderer/hooks/useTestImport.ts index 3f6a36ef7..deefc0a0a 100644 --- a/src/renderer/hooks/useTestImport.ts +++ b/src/renderer/hooks/useTestImport.ts @@ -1,6 +1,5 @@ import { TestDiscoverContainer, - TestSequenceElement, } from "@/renderer/types/test-sequencer"; import { createNewTest,