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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Optional runtime environment variables:
```
PYTC_AUTH_SECRET=replace-me
PYTC_ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000,null
PYTC_NEUROGLANCER_PUBLIC_BASE=http://localhost:4244
```

If restarting after a crash or interrupted session, kill any lingering processes first:
Expand Down
31 changes: 27 additions & 4 deletions client/src/components/FileTreeSidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,35 @@ const FileTreeSidebar = ({
files,
currentFolder,
onSelect,
onLoadFolder,
onDrop,
onContextMenu,
loadedParents = [],
loadingParents = [],
expandedKeys,
onExpand,
width = 250,
}) => {
// Convert flat folders list to tree data
const treeData = useMemo(() => {
const loadedParentSet = new Set(loadedParents);
const loadingParentSet = new Set(loadingParents);

const buildTree = (parentId) => {
const children = folders
.filter((f) => f.parent === parentId)
.map((f) => ({
title: f.title,
key: `folder-${f.key}`,
isLeaf: false,
loading: loadingParentSet.has(f.key),
icon: ({ expanded }) =>
expanded ? <FolderOpenFilled /> : <FolderFilled />,
children: buildTree(f.key),
children: loadedParentSet.has(f.key) ? buildTree(f.key) : undefined,
}));

// Add files to tree
if (files && files[parentId]) {
if (loadedParentSet.has(parentId) && files && files[parentId]) {
children.push(
...files[parentId].map((f) => ({
title: f.name,
Expand All @@ -53,6 +62,7 @@ const FileTreeSidebar = ({
title: f.title,
key: `folder-${f.key}`,
isLeaf: false,
loading: loadingParentSet.has(f.key),
icon: ({ expanded }) =>
expanded ? <FolderOpenFilled /> : <FolderFilled />,
children: buildTree(f.key),
Expand All @@ -64,7 +74,7 @@ const FileTreeSidebar = ({
}

return rootNodes;
}, [folders, files]);
}, [files, folders, loadedParents, loadingParents]);

const onSelectHandler = (keys, info) => {
if (keys.length > 0) {
Expand All @@ -76,6 +86,15 @@ const FileTreeSidebar = ({
}
};

const handleLoadData = async (node) => {
const key = String(node?.key || "");
if (!key.startsWith("folder-") || !onLoadFolder) {
return;
}

await onLoadFolder(key.replace("folder-", ""));
};

const handleDrop = (info) => {
if (onDrop) {
onDrop(info);
Expand Down Expand Up @@ -110,11 +129,14 @@ const FileTreeSidebar = ({
</div>
<DirectoryTree
multiple={false}
defaultExpandAll
selectedKeys={[`folder-${currentFolder}`]}
onSelect={onSelectHandler}
treeData={treeData}
expandAction="click"
expandedKeys={expandedKeys}
onExpand={onExpand}
loadData={handleLoadData}
loadedKeys={loadedParents.map((key) => `folder-${key}`)}
style={{ backgroundColor: "transparent", fontSize: 13 }}
titleRender={(nodeData) => (
<span
Expand All @@ -130,6 +152,7 @@ const FileTreeSidebar = ({
title={String(nodeData.title)}
>
{nodeData.title}
{nodeData.loading ? " ..." : ""}
</span>
)}
draggable
Expand Down
50 changes: 23 additions & 27 deletions client/src/components/InputSelector.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,34 +81,30 @@ function InputSelector(props) {
}
/>
</Form.Item>
<Form.Item
label={
<Space align="center">
<span>
{type === "training" ? "Input Label" : "Input Label (Optional)"}
</span>
<InlineHelpChat
taskKey={type}
label="Input Label"
yamlKey="DATASET.LABEL_NAME"
value={workflow.inputLabel}
projectContext={projectContext}
taskContext={taskContext}
/>
</Space>
}
>
<UnifiedFileInput
placeholder="Please select or input label path"
onChange={handleLabelChange}
value={getValue(workflow.inputLabel)}
selectionType={
type === "training" || type === "inference"
? "fileOrDirectory"
: "file"
{type === "training" && (
<Form.Item
label={
<Space align="center">
<span>Input Label</span>
<InlineHelpChat
taskKey={type}
label="Input Label"
yamlKey="DATASET.LABEL_NAME"
value={workflow.inputLabel}
projectContext={projectContext}
taskContext={taskContext}
/>
</Space>
}
/>
</Form.Item>
>
<UnifiedFileInput
placeholder="Please select or input label path"
onChange={handleLabelChange}
value={getValue(workflow.inputLabel)}
selectionType="fileOrDirectory"
/>
</Form.Item>
)}
{type === "training" ? (
<Form.Item
label={
Expand Down
98 changes: 98 additions & 0 deletions client/src/components/InputSelector.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React from "react";
import { render, screen } from "@testing-library/react";

import InputSelector from "./InputSelector";
import { AppContext } from "../contexts/GlobalContext";

jest.mock("antd", () => {
const Form = ({ children }) => <form>{children}</form>;
Form.Item = ({ label, children, help }) => (
<div>
{label}
{children}
{help ? <div>{help}</div> : null}
</div>
);

return {
Form,
Space: ({ children }) => <div>{children}</div>,
};
});

jest.mock("./UnifiedFileInput", () => (props) => (
<input
aria-label={props.placeholder}
data-selection-type={props.selectionType}
readOnly
value={typeof props.value === "string" ? props.value : ""}
/>
));

jest.mock("./InlineHelpChat", () => () => null);

beforeAll(() => {
Object.defineProperty(window, "matchMedia", {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
});

function renderWithContext(type) {
const contextValue = {
trainingState: {
inputImage: "",
inputLabel: "",
outputPath: "",
logPath: "",
checkpointPath: "",
setInputImage: jest.fn(),
setInputLabel: jest.fn(),
setOutputPath: jest.fn(),
setLogPath: jest.fn(),
setCheckpointPath: jest.fn(),
},
inferenceState: {
inputImage: "",
inputLabel: "/tmp/stale-label.tif",
outputPath: "",
logPath: "",
checkpointPath: "",
setInputImage: jest.fn(),
setInputLabel: jest.fn(),
setOutputPath: jest.fn(),
setLogPath: jest.fn(),
setCheckpointPath: jest.fn(),
},
};

return render(
<AppContext.Provider value={contextValue}>
<InputSelector type={type} />
</AppContext.Provider>,
);
}

describe("InputSelector", () => {
it("shows an input label field for training", () => {
renderWithContext("training");

expect(screen.getByText("Input Label")).toBeTruthy();
});

it("does not show an input label field for inference", () => {
renderWithContext("inference");

expect(screen.queryByText("Input Label")).toBeNull();
expect(screen.queryByText("Input Label (Optional)")).toBeNull();
});
});
Loading
Loading