Skip to content

Commit f092c36

Browse files
authored
Merge pull request #146 from AllenCell/feature/show-precomputed-results-tweaks
Feature/show precomputed results tweaks
2 parents e000e1c + 0b84b49 commit f092c36

File tree

7 files changed

+149
-52
lines changed

7 files changed

+149
-52
lines changed

src/App.tsx

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,34 @@ import { getJobStatus, addRecipe } from "./utils/firebase";
55
import { getFirebaseRecipe, jsonToString } from "./utils/recipeLoader";
66
import { getSubmitPackingUrl, JOB_STATUS } from "./constants/aws";
77
import { FIRESTORE_FIELDS } from "./constants/firebase";
8-
import { SIMULARIUM_EMBED_URL } from "./constants/urls";
9-
import { useResultUrl, useSetResultUrl } from "./state/store";
8+
import {
9+
useJobId,
10+
useJobLogs,
11+
useOutputsDirectory,
12+
useRunTime,
13+
useSetJobId,
14+
useSetJobLogs,
15+
useSetPackingResults,
16+
} from "./state/store";
1017
import PackingInput from "./components/PackingInput";
1118
import Viewer from "./components/Viewer";
1219
import StatusBar from "./components/StatusBar";
20+
import { EMPTY_PACKING_RESULTS } from "./state/constants";
21+
1322
import "./App.css";
1423

1524
const { Header, Content, Sider, Footer } = Layout;
1625
const { Link } = Typography;
1726

1827
function App() {
19-
const [jobId, setJobId] = useState("");
20-
const [jobStatus, setJobStatus] = useState("");
21-
const [jobLogs, setJobLogs] = useState<string>("");
22-
const [outputDir, setOutputDir] = useState<string>("");
23-
const [runTime, setRunTime] = useState<number>(0);
24-
const resultUrl = useResultUrl();
25-
const setResultUrl = useSetResultUrl();
28+
const [jobStatus, setJobStatus] = useState<string>("");
29+
const setJobLogs = useSetJobLogs();
30+
const jobLogs = useJobLogs();
31+
const setJobId = useSetJobId();
32+
const jobId = useJobId();
33+
const setPackingResults = useSetPackingResults();
34+
const runTime = useRunTime();
35+
const outputDir = useOutputsDirectory();
2636

2737
let start = 0;
2838

@@ -31,11 +41,7 @@ function App() {
3141
}
3242

3343
const resetState = () => {
34-
setJobId("");
35-
setJobStatus("");
36-
setJobLogs("");
37-
setResultUrl("");
38-
setRunTime(0);
44+
setPackingResults({ ...EMPTY_PACKING_RESULTS });
3945
};
4046

4147
const recipeHasChanged = async (
@@ -136,12 +142,22 @@ function App() {
136142
}
137143
}
138144
const range = (Date.now() - start) / 1000;
139-
setRunTime(range);
140145
if (localJobStatus.status == JOB_STATUS.DONE) {
141-
setResultUrl(localJobStatus.result_path);
142-
setOutputDir(localJobStatus.outputs_directory);
146+
setPackingResults({
147+
jobId: id,
148+
jobLogs: "",
149+
resultUrl: localJobStatus.result_path,
150+
runTime: range,
151+
outputDir: localJobStatus.outputs_directory,
152+
});
143153
} else if (localJobStatus.status == JOB_STATUS.FAILED) {
144-
setJobLogs(localJobStatus.error_message);
154+
setPackingResults({
155+
jobId: id,
156+
jobLogs: `Packing job failed: ${localJobStatus.error_message}`,
157+
resultUrl: "",
158+
runTime: range,
159+
outputDir: "",
160+
});
145161
}
146162
};
147163

@@ -164,7 +180,7 @@ function App() {
164180
<PackingInput startPacking={startPacking} />
165181
</Sider>
166182
<Content className="content-container">
167-
<Viewer resultUrl={resultUrl ? SIMULARIUM_EMBED_URL + resultUrl : ""} />
183+
<Viewer />
168184
</Content>
169185
</Layout>
170186
<Footer className="footer">

src/components/Dropdown/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { Select } from "antd";
22
import { map } from "lodash-es";
3-
import { Dictionary, PackingInputs } from "../../types";
3+
import { Dictionary, RecipeManifest } from "../../types";
44

55
interface DropdownProps {
66
placeholder: string;
77
defaultValue?: string;
8-
options: Dictionary<PackingInputs>;
8+
options: Dictionary<RecipeManifest>;
99
onChange: (value: string) => void;
1010
}
1111

src/components/Viewer/index.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1+
import { SIMULARIUM_EMBED_URL } from "../../constants/urls";
2+
import { useResultUrl } from "../../state/store";
13
import "./style.css";
24

3-
interface ViewerProps {
4-
resultUrl: string;
5-
}
6-
7-
const Viewer = (props: ViewerProps): JSX.Element => {
8-
const { resultUrl } = props;
5+
const Viewer = (): JSX.Element => {
6+
const resultUrl = useResultUrl();
97
return (
108
<div className="viewer-container">
11-
<iframe className="simularium-embed" src={resultUrl} />
9+
<iframe
10+
className="simularium-embed"
11+
src={`${SIMULARIUM_EMBED_URL}${resultUrl}`}
12+
/>
1213
</div>
1314
);
1415
};
1516

16-
export default Viewer;
17+
export default Viewer;

src/state/constants.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { EditableField, PackingResults } from "../types";
2+
3+
// stable/frozen empty array to prevent re-renders
4+
export const EMPTY_FIELDS: readonly EditableField[] = Object.freeze([]);
5+
export const EMPTY_PACKING_RESULTS: PackingResults = Object.freeze({
6+
jobId: "",
7+
jobLogs: "",
8+
resultUrl: "",
9+
runTime: 0,
10+
outputDir: "",
11+
});

src/state/store.ts

Lines changed: 78 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { create } from "zustand";
22
import { subscribeWithSelector } from "zustand/middleware";
33
import { get as lodashGet, set as lodashSet } from "lodash-es";
4-
import { PackingInputs } from "../types";
4+
import { PackingResults, RecipeManifest } from "../types";
55
import { getFirebaseRecipe, jsonToString } from "../utils/recipeLoader";
66
import { getPackingInputsDict } from "../utils/firebase";
7+
import { EMPTY_PACKING_RESULTS } from "./constants";
78

89
export interface RecipeData {
910
id: string;
@@ -14,8 +15,9 @@ export interface RecipeData {
1415

1516
export interface RecipeState {
1617
selectedRecipeId: string;
17-
inputOptions: Record<string, PackingInputs>;
18+
inputOptions: Record<string, RecipeManifest>;
1819
recipes: Record<string, RecipeData>;
20+
packingResults: PackingResults;
1921
}
2022

2123
export interface UIState {
@@ -43,7 +45,9 @@ type Actions = {
4345
recipeString: string
4446
) => Promise<void>
4547
) => Promise<void>;
46-
setResultUrl: (url: string) => void;
48+
setPackingResults: (results: PackingResults) => void;
49+
setJobLogs: (logs: string) => void;
50+
setJobId: (jobId: string) => void;
4751
};
4852

4953
export type RecipeStore = RecipeState & UIState & Actions;
@@ -56,6 +60,7 @@ const initialState: RecipeState & UIState = {
5660
recipes: {},
5761
isLoading: false,
5862
isPacking: false,
63+
packingResults: { ...EMPTY_PACKING_RESULTS },
5964
};
6065

6166
export const useRecipeStore = create<RecipeStore>()(
@@ -115,6 +120,8 @@ export const useRecipeStore = create<RecipeStore>()(
115120
},
116121

117122
selectRecipe: async (recipeId) => {
123+
get().setPackingResults({ ...EMPTY_PACKING_RESULTS });
124+
118125
const sel = get().inputOptions[recipeId];
119126
if (!sel) return;
120127

@@ -127,22 +134,27 @@ export const useRecipeStore = create<RecipeStore>()(
127134
}
128135
},
129136

137+
setPackingResults: (results: PackingResults) => {
138+
set({ packingResults: results });
139+
},
130140

131-
setResultUrl: (url: string) => {
132-
const { inputOptions, selectedRecipeId } = get();
133-
const sel = inputOptions[selectedRecipeId];
134-
if (!sel) return;
141+
setJobLogs: (logs: string) => {
135142
set({
136-
inputOptions: {
137-
...get().inputOptions,
138-
[selectedRecipeId]: {
139-
...sel,
140-
result_path: url,
141-
},
143+
packingResults: {
144+
...get().packingResults,
145+
jobLogs: logs,
142146
},
143147
});
144148
},
145149

150+
setJobId: (jobId: string) => {
151+
set({
152+
packingResults: {
153+
...get().packingResults,
154+
jobId: jobId,
155+
},
156+
});
157+
},
146158

147159
updateRecipeString: (recipeId, newString) => {
148160
set((s) => {
@@ -234,12 +246,13 @@ export const useRecipeStore = create<RecipeStore>()(
234246
}))
235247
);
236248

237-
// tiny helpers/selectors (all derived — not stored)
249+
// simple selectors
238250
export const useSelectedRecipeId = () =>
239251
useRecipeStore((s) => s.selectedRecipeId);
240252
export const useCurrentRecipeString = () =>
241253
useRecipeStore((s) => s.recipes[s.selectedRecipeId]?.currentString ?? "");
242254
export const useInputOptions = () => useRecipeStore((s) => s.inputOptions);
255+
243256
export const useIsLoading = () => useRecipeStore((s) => s.isLoading);
244257
export const useIsPacking = () => useRecipeStore((s) => s.isPacking);
245258
export const useFieldsToDisplay = () =>
@@ -248,8 +261,53 @@ export const useIsCurrentRecipeModified = () =>
248261
useRecipeStore((s) => s.recipes[s.selectedRecipeId]?.isModified ?? false);
249262
export const useGetOriginalValue = () =>
250263
useRecipeStore((s) => s.getOriginalValue);
251-
export const useResultUrl = () =>
252-
useRecipeStore((s) => s.inputOptions[s.selectedRecipeId]?.result_path);
264+
const usePackingResults = () => useRecipeStore((s) => s.packingResults);
265+
266+
// compound selectors
267+
268+
const useCurrentRecipeManifest = () => {
269+
const selectedRecipeId = useSelectedRecipeId();
270+
const inputOptions = useInputOptions();
271+
if (!selectedRecipeId) return undefined;
272+
return inputOptions[selectedRecipeId];
273+
};
274+
const useDefaultResultPath = () => {
275+
const manifest = useCurrentRecipeManifest();
276+
return manifest?.defaultResultPath || "";
277+
};
278+
279+
export const useRunTime = () => {
280+
const results = usePackingResults();
281+
return results.runTime;
282+
};
283+
284+
export const useJobLogs = () => {
285+
const results = usePackingResults();
286+
return results.jobLogs;
287+
};
288+
289+
export const useJobId = () => {
290+
const results = usePackingResults();
291+
return results.jobId;
292+
};
293+
294+
export const useOutputsDirectory = () => {
295+
const results = usePackingResults();
296+
return results.outputDir;
297+
};
298+
299+
export const useResultUrl = () => {
300+
const results = usePackingResults();
301+
const currentRecipeId = useSelectedRecipeId();
302+
const defaultResultPath = useDefaultResultPath();
303+
let path = "";
304+
if (results.resultUrl) {
305+
path = results.resultUrl;
306+
} else if (currentRecipeId) {
307+
path = defaultResultPath;
308+
}
309+
return path;
310+
};
253311

254312
// action selectors (stable identities)
255313
export const useLoadInputOptions = () =>
@@ -265,4 +323,7 @@ export const useRestoreRecipeDefault = () =>
265323
export const useStartPacking = () => useRecipeStore((s) => s.startPacking);
266324
export const useGetCurrentValue = () =>
267325
useRecipeStore((s) => s.getCurrentValue);
268-
export const useSetResultUrl = () => useRecipeStore((s) => s.setResultUrl);
326+
export const useSetPackingResults = () =>
327+
useRecipeStore((s) => s.setPackingResults);
328+
export const useSetJobLogs = () => useRecipeStore((s) => s.setJobLogs);
329+
export const useSetJobId = () => useRecipeStore((s) => s.setJobId);

src/types/index.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ export interface Dictionary<T> {
1616
[Key: string]: T;
1717
}
1818

19-
export type PackingInputs = {
19+
export type RecipeManifest = {
2020
name?: string;
2121
config: string;
2222
recipe: string;
23-
result_path?: string;
23+
defaultResultPath?: string;
2424
editable_fields?: EditableField[];
2525
};
2626

@@ -31,6 +31,14 @@ export type JobStatusObject = {
3131
result_path: string;
3232
};
3333

34+
export type PackingResults = {
35+
jobId: string;
36+
jobLogs: string;
37+
resultUrl: string;
38+
runTime: number;
39+
outputDir: string;
40+
};
41+
3442
export type EditableField = {
3543
id: string;
3644
name: string;

src/utils/firebase.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
} from "../constants/firebase";
2222
import {
2323
FirestoreDoc,
24-
PackingInputs,
24+
RecipeManifest,
2525
Dictionary,
2626
EditableField,
2727
JobStatusObject,
@@ -163,11 +163,11 @@ const getEditableFieldsList = async (
163163
return docs;
164164
};
165165

166-
const getPackingInputsDict = async (): Promise<Dictionary<PackingInputs>> => {
166+
const getPackingInputsDict = async (): Promise<Dictionary<RecipeManifest>> => {
167167
const docs = await getAllDocsFromCollection(
168168
FIRESTORE_COLLECTIONS.PACKING_INPUTS
169169
);
170-
const inputsDict: Dictionary<PackingInputs> = {};
170+
const inputsDict: Dictionary<RecipeManifest> = {};
171171
for (const doc of docs) {
172172
const displayName = doc[FIRESTORE_FIELDS.NAME];
173173
const config = doc[FIRESTORE_FIELDS.CONFIG];
@@ -182,7 +182,7 @@ const getPackingInputsDict = async (): Promise<Dictionary<PackingInputs>> => {
182182
[FIRESTORE_FIELDS.CONFIG]: config,
183183
[FIRESTORE_FIELDS.RECIPE]: recipe,
184184
[FIRESTORE_FIELDS.EDITABLE_FIELDS]: editableFields,
185-
[FIRESTORE_FIELDS.RESULT_PATH]: result,
185+
defaultResultPath: result,
186186
};
187187
}
188188
}

0 commit comments

Comments
 (0)