Skip to content

Commit

Permalink
[sequencer-gallery] chore: bundle example with app
Browse files Browse the repository at this point in the history
  • Loading branch information
LatentDream committed Apr 22, 2024
1 parent 35fabe1 commit fe82571
Show file tree
Hide file tree
Showing 12 changed files with 116 additions and 47 deletions.
2 changes: 2 additions & 0 deletions electron-builder.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ extraResources:
to: "poetry.lock"
- from: "blocks"
to: "blocks"
- from: "examples"
to: "examples"

mac:
icon: ./public/favicon.icns
Expand Down
Original file line number Diff line number Diff line change
@@ -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"}}
Empty file.
28 changes: 28 additions & 0 deletions examples/test-sequencer-conditional-example/test.py
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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"}}
Empty file.
48 changes: 48 additions & 0 deletions examples/test-sequencer-expected-exported-example/test.py
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> =>
Expand Down
5 changes: 5 additions & 0 deletions src/main/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
27 changes: 6 additions & 21 deletions src/renderer/hooks/useTestSequencerProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Result<void, Error>> {
// 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) {
Expand All @@ -168,7 +154,6 @@ export const useDownloadAndImportExampleSequence = () => {
manager,
idx !== 0,
);
console.log(result);
if (result.isErr()) return err(result.error);
}),
);
Expand Down
22 changes: 17 additions & 5 deletions src/renderer/routes/test_sequencer_panel/components/DesignBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -68,6 +68,10 @@ export function DesignBar() {

return (
<div className=" border-b" style={{ height: ACTIONS_HEIGHT }}>
<SequencerGalleryModal
isGalleryOpen={isGalleryOpen}
setIsGalleryOpen={setIsGalleryOpen}
/>
<div className="py-1" />
<div className="flex">
{isAdmin() && (
Expand Down Expand Up @@ -112,6 +116,14 @@ export function DesignBar() {
<Import size={16} className="mr-2 stroke-muted-foreground" />
Import Sequence
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => setIsGalleryOpen(true)}
data-testid="app-gallery-btn"
>
<LayoutGrid size={16} className="mr-2 stroke-muted-foreground" />
Import Example
</DropdownMenuItem>

</DropdownMenuContent>
</DropdownMenu>
<SequencerGalleryModal
Expand Down Expand Up @@ -263,10 +275,10 @@ export const getGlobalStatus = (
const highestCycle =
cycleRuns.length > 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
};

Expand All @@ -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 (
<Dialog open={isGalleryOpen} onOpenChange={setIsGalleryOpen}>
<DialogTrigger asChild>
<Button
onClick={setOpen}
data-testid="app-gallery-btn"
className="gap-2"
variant="ghost"
>
<LayoutGrid size={20} className="stroke-muted-foreground" />
Sequence Gallery
</Button>
</DialogTrigger>

<DialogContent className="flex h-4/5 max-w-5xl flex-col">
<DialogHeader>
<DialogTitle>
Expand Down

0 comments on commit fe82571

Please sign in to comment.