Skip to content
Draft
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
67 changes: 23 additions & 44 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { useState } from "react";
import { v4 as uuidv4 } from "uuid";
import { Layout, Typography } from "antd";
import { getJobStatus, addRecipe } from "./utils/firebase";
import { getFirebaseRecipe, jsonToString } from "./utils/recipeLoader";
import { getJobStatus, updateJobStatusTimestamp } from "./utils/firebase";
import { getFirebaseRecipe, recipeToString } from "./utils/recipeLoader";
import { getSubmitPackingUrl, JOB_STATUS } from "./constants/aws";
import { FIRESTORE_FIELDS } from "./constants/firebase";
import {
useJobId,
useJobLogs,
Expand Down Expand Up @@ -45,67 +43,40 @@ function App() {
recipeString: string
): Promise<boolean> => {
const originalRecipe = await getFirebaseRecipe(recipeId);
return !(jsonToString(originalRecipe) == recipeString);
};

const recipeToFirebase = (
recipe: string,
path: string,
id: string
): object => {
const recipeJson = JSON.parse(recipe);
if (recipeJson.bounding_box) {
const flattened_array = Object.assign({}, recipeJson.bounding_box);
recipeJson.bounding_box = flattened_array;
}
recipeJson[FIRESTORE_FIELDS.RECIPE_PATH] = path;
recipeJson[FIRESTORE_FIELDS.NAME] = id;
recipeJson[FIRESTORE_FIELDS.TIMESTAMP] = Date.now();
return recipeJson;
return !(recipeToString(originalRecipe) == recipeString);
};

const submitRecipe = async (
recipeId: string,
configId: string,
recipeString: string
) => {
let firebaseRecipe = "firebase:recipes/" + recipeId;
const firebaseConfig = configId
? "firebase:configs/" + configId
: undefined;
const recipeChanged: boolean = await recipeHasChanged(
recipeId,
recipeString
);
if (recipeChanged) {
const recipeId = uuidv4();
firebaseRecipe = "firebase:recipes_edited/" + recipeId;
const recipeJson = recipeToFirebase(
recipeString,
firebaseRecipe,
recipeId
);
try {
await addRecipe(recipeId, recipeJson);
} catch (e) {
setJobStatus(JOB_STATUS.FAILED);
setJobLogs(String(e));
return;
}
}
const firebaseRecipe = recipeChanged
? undefined
: "firebase:recipes/" + recipeId;
const firebaseConfig = configId
? "firebase:configs/" + configId
: undefined;

const url = getSubmitPackingUrl(firebaseRecipe, firebaseConfig);
const request: RequestInfo = new Request(url, { method: "POST" });
const requestBody = recipeChanged ? recipeString : undefined;
const request: RequestInfo = new Request(url, { method: "POST", body: requestBody });
start = Date.now();
const response = await fetch(request);
setJobStatus(JOB_STATUS.SUBMITTED);
const data = await response.json();
if (response.ok) {
const data = await response.json();
setJobId(data.jobId);
setJobStatus(JOB_STATUS.STARTING);
return data.jobId;
} else {
const errorText = await response.text();
setJobStatus(JOB_STATUS.FAILED);
setJobLogs(JSON.stringify(data));
setJobLogs(errorText);
}
};

Expand All @@ -122,6 +93,9 @@ function App() {
const checkStatus = async (jobIdFromSubmit: string) => {
const id = jobIdFromSubmit || jobId;
let localJobStatus = await getJobStatus(id);
if (localJobStatus) {
setJobStatus(localJobStatus.status);
}
while (
localJobStatus?.status !== JOB_STATUS.DONE &&
localJobStatus?.status !== JOB_STATUS.FAILED
Expand All @@ -136,6 +110,11 @@ function App() {
setJobStatus(newJobStatus.status);
}
}

// Update the job status timestamp after reading the final status to
// ensure we have the most recent timestamp for retention policy
await updateJobStatusTimestamp(id);

const range = (Date.now() - start) / 1000;
if (localJobStatus.status == JOB_STATUS.DONE) {
setPackingResults({
Expand Down
2 changes: 1 addition & 1 deletion src/components/InputSwitch/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const InputSwitch = (props: InputSwitchProps): JSX.Element => {
}
if (typeof value == "number") {
value = value * conversion;
value = Number(value.toFixed(4));
value = Number(value.toFixed(2));
}
return value;
}, [getCurrentValue, id, min, conversion, dataType]);
Expand Down
12 changes: 8 additions & 4 deletions src/constants/aws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@ const SUBMIT_PACKING_ECS = "https://bda21vau5c.execute-api.us-west-2.amazonaws.c
const S3_BASE_URL = "https://s3.us-west-2.amazonaws.com";

export const getSubmitPackingUrl = (
recipe: string,
recipe?: string,
config?: string,
) => {
let url = `${SUBMIT_PACKING_ECS}?recipe=${recipe}`;
if (config) {
url += `&config=${config}`;
let url = SUBMIT_PACKING_ECS;
if (recipe && config) {
url += `?recipe=${recipe}&config=${config}`;
} else if (recipe) {
url += `?recipe=${recipe}`;
} else if (config) {
url += `?config=${config}`;
}
return url;
};
Expand Down
2 changes: 1 addition & 1 deletion src/constants/firebase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const FIRESTORE_FIELDS = {
export const RETENTION_POLICY = {
RETENTION_PERIODS: {
RECIPES_EDITED: 24 * 60 * 60 * 1000, // 24 hours
JOB_STATUS: 24 * 60 * 60 * 1000, // 24 hours
JOB_STATUS: 30 * 24 * 60 * 60 * 1000, // 30 days
},

TIMESTAMP_FIELD: "timestamp",
Expand Down
4 changes: 2 additions & 2 deletions src/state/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { create } from "zustand";
import { subscribeWithSelector } from "zustand/middleware";
import { isEmpty, isEqual, get as lodashGet } from "lodash-es";
import { PackingResult, RecipeData, RecipeManifest } from "../types";
import { jsonToString } from "../utils/recipeLoader";
import { recipeToString } from "../utils/recipeLoader";
import {
getRecipeDataFromFirebase,
getRecipeManifestFromFirebase,
Expand Down Expand Up @@ -236,7 +236,7 @@ export const useRecipeStore = create<RecipeStore>()(
edits
);
if (!recipeObject) return;
const recipeString = jsonToString(recipeObject);
const recipeString = recipeToString(recipeObject);
set({ isPacking: true });
try {
await callback(s.selectedRecipeId, configId, recipeString);
Expand Down
6 changes: 3 additions & 3 deletions src/test/recipeLoader.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect, test } from 'vitest';
import fs from 'fs';
import { isFirebaseRef, getFirebaseRecipe, jsonToString } from '../utils/recipeLoader';
import { isFirebaseRef, getFirebaseRecipe, recipeToString } from '../utils/recipeLoader';

test('isFirebaseRef detects Firebase references correctly', () => {
expect(isFirebaseRef('firebase:recipes/some_id')).toBe(true);
Expand All @@ -14,7 +14,7 @@ test('isFirebaseRef detects Firebase references correctly', () => {
test('getFirebaseRecipe works as expected for ER_peroxisome_v_struct_gradient_370574', async () => {
const recipeId = 'ER_peroxisome_v_struct_gradient_370574';
const recipeJson = await getFirebaseRecipe(recipeId);
const recipeString = jsonToString(recipeJson);
const recipeString = recipeToString(recipeJson);

expect(recipeString).toBeDefined();
expect(typeof recipeString).toBe('string');
Expand All @@ -30,7 +30,7 @@ test('getFirebaseRecipe works as expected for ER_peroxisome_v_struct_gradient_37
test('getFirebaseRecipe works as expected for one_sphere', async () => {
const recipeId = 'one_sphere_v_1.0.0';
const recipeJson = await getFirebaseRecipe(recipeId);
const recipeString = jsonToString(recipeJson);
const recipeString = recipeToString(recipeJson);

expect(recipeString).toBeDefined();
expect(typeof recipeString).toBe('string');
Expand Down
48 changes: 0 additions & 48 deletions src/test/test-files/ER_peroxisome.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,54 +132,6 @@
}
},
"gradients": {
"apical_gradient": {
"description": "gradient based on distance from a plane",
"invert": null,
"mode": "vector",
"mode_settings": {
"center": [0, 0, 106.875],
"direction": [0, 0, 1]
},
"name": "apical_gradient",
"pick_mode": "linear",
"reversed": false,
"weight_mode": "exponential",
"weight_mode_settings": {
"decay_length": 0.1
}
},
"membrane_gradient": {
"description": "gradient based on distance from the surface of the membrane mesh",
"invert": null,
"mode": "surface",
"mode_settings": {
"object": "membrane",
"scale_to_next_surface": false
},
"name": "membrane_gradient",
"pick_mode": "linear",
"reversed": false,
"weight_mode": "exponential",
"weight_mode_settings": {
"decay_length": 0.01
}
},
"nucleus_gradient": {
"description": "gradient based on distance from the surface of the nucleus mesh",
"invert": null,
"mode": "surface",
"mode_settings": {
"object": "nucleus",
"scale_to_next_surface": false
},
"name": "nucleus_gradient",
"pick_mode": "linear",
"reversed": false,
"weight_mode": "exponential",
"weight_mode_settings": {
"decay_length": 0.1
}
},
"struct_gradient": {
"name": "struct_gradient",
"invert": null,
Expand Down
15 changes: 9 additions & 6 deletions src/utils/firebase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import {
documentId,
QuerySnapshot,
DocumentData,
setDoc,
doc,
Timestamp,
deleteDoc,
updateDoc
} from "firebase/firestore";
import { sortBy } from "lodash-es";
import {
Expand Down Expand Up @@ -126,6 +126,13 @@ const getJobStatus = async (
return docs[0] || undefined;
};

const updateJobStatusTimestamp = async (jobId: string) => {
const data = {
timestamp: Timestamp.now(),
};
await updateDoc(doc(db, FIRESTORE_COLLECTIONS.JOB_STATUS, jobId), data);
};

const getOutputsDirectory = async (jobId: string) => {
const querySnapshot = await queryDocumentById(
FIRESTORE_COLLECTIONS.JOB_STATUS,
Expand Down Expand Up @@ -228,10 +235,6 @@ const getDocsByIds = async (coll: string, ids: string[]) => {
return docs;
};

const addRecipe = async (id: string, data: object) => {
await setDoc(doc(db, FIRESTORE_COLLECTIONS.EDITED_RECIPES, id), data);
};

const docCleanup = async () => {
const now = Date.now();
const collectionsToClean = [
Expand Down Expand Up @@ -275,9 +278,9 @@ export {
queryDocumentById,
getDocsByIds,
getJobStatus,
addRecipe,
docCleanup,
getRecipeManifestFromFirebase,
getRecipeDataFromFirebase,
getOutputsDirectory,
updateJobStatusTimestamp,
};
31 changes: 28 additions & 3 deletions src/utils/recipeLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,9 +328,34 @@ const getFirebaseRecipe = async (name: string): Promise<ViewableRecipe> => {
return unpackedRecipe;
}

const jsonToString = (json: ViewableRecipe): string => {
return JSON.stringify(json, null, 2);
const recipeToString = (rec: ViewableRecipe): string => {
// Deep copy recipe to avoid mutating original object
const recipe: ViewableRecipe = structuredClone(rec);

// Collect a list of gradients that are referenced by objects the recipe
const referencedGradients: Set<string> = new Set();
if (recipe.objects) {
for (const obj of Object.values(recipe.objects)) {
if (obj.packing_mode === "gradient" && obj.gradient) {
referencedGradients.add(obj.gradient);
} else if (obj.packing_mode === "random" && obj.gradient) {
// If packing mode is random, gradient field is irrelevant
// and should be cleared
delete obj.gradient;
}
}
}

// If the recipe has gradients that aren't referenced in any objects, delete them
if (recipe.gradients) {
for (const gradientName of Object.keys(recipe.gradients)) {
if (!referencedGradients.has(gradientName)) {
delete recipe.gradients[gradientName];
}
}
}
return JSON.stringify(recipe, null, 2);
}


export { getFirebaseRecipe, isFirebaseRef, jsonToString };
export { getFirebaseRecipe, isFirebaseRef, recipeToString };