From 26431e2cea140a580a9ebd7b0856e729ed2a6c8c Mon Sep 17 00:00:00 2001 From: Minh-Tue Vo Date: Tue, 22 Oct 2024 11:55:01 -0700 Subject: [PATCH 1/3] Toast component refactor (#4957) * Refactored Toast component to support a primary and secondary button and exposed visibility state * Addressed feedback --- .../components/src/components/Toast/Toast.tsx | 32 +++++++++++++------ .../components/src/components/Toast/index.ts | 2 +- .../components/src/components/index.ts | 2 +- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/app/packages/components/src/components/Toast/Toast.tsx b/app/packages/components/src/components/Toast/Toast.tsx index 6cd18102eb..c16c7ec733 100644 --- a/app/packages/components/src/components/Toast/Toast.tsx +++ b/app/packages/components/src/components/Toast/Toast.tsx @@ -1,18 +1,23 @@ -import React, { useState } from "react"; -import { Snackbar, Button, SnackbarContent } from "@mui/material"; -import Box from '@mui/material/Box'; -import Typography from '@mui/material/Typography'; -import BoltIcon from '@mui/icons-material/Bolt'; // Icon for the lightning bolt +import React from "react"; +import { atom, useRecoilState } from "recoil"; +import { Box, Snackbar, SnackbarContent } from "@mui/material"; // Define types for the props interface ToastProps { - action: React.ReactNode; // Accepts any valid React component, element, or JSX - message: React.ReactNode; // Accepts any valid React component, element, or JSX + message: React.ReactNode; + primary: (setOpen: React.Dispatch>) => React.ReactNode; + secondary: (setOpen: React.Dispatch>) => React.ReactNode; duration?: number; // Optional duration, with a default value } -const Toast: React.FC = ({ action, message, duration = 5000 }) => { - const [open, setOpen] = useState(true); +const toastStateAtom = atom({ + key: "toastOpenState", + default: true, +}); + +const Toast: React.FC = ({message, primary, secondary, duration = 5000 }) => { + + const [open, setOpen] = useRecoilState(toastStateAtom); // State management for toast visibility const handleClose = (event, reason) => { if (reason === "clickaway") { @@ -21,6 +26,15 @@ const Toast: React.FC = ({ action, message, duration = 5000 }) => { setOpen(false); }; + const action = ( +
+ + {primary(setOpen)} {/* Pass setOpen to primary button */} + {secondary(setOpen)} {/* Pass setOpen to secondary button */} + +
+ ); + return ( Date: Tue, 22 Oct 2024 12:08:06 -0700 Subject: [PATCH 2/3] Fix documentation error --- docs/source/plugins/developing_plugins.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/plugins/developing_plugins.rst b/docs/source/plugins/developing_plugins.rst index ed6532c423..1ee422ceab 100644 --- a/docs/source/plugins/developing_plugins.rst +++ b/docs/source/plugins/developing_plugins.rst @@ -2261,7 +2261,7 @@ The example code below shows how to access and update panel state. def decrement(self, ctx): count = ctx.panel.get_state("v_stack.h_stack.count", 0) - ctx.panel.set_state("v_stack.h_stack.count", count + 1) + ctx.panel.set_state("v_stack.h_stack.count", count - 1) def render(self, ctx): panel = types.Object() From adf51c974eee0f31480bf85d4d46f2ca544963ec Mon Sep 17 00:00:00 2001 From: Stuart Date: Tue, 22 Oct 2024 17:15:17 -0400 Subject: [PATCH 3/3] delegated operator fixes (#4908) --- fiftyone/factory/repos/delegated_operation.py | 19 ++++++++---- tests/unittests/operators/delegated_tests.py | 30 +++++++++++++++++++ 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/fiftyone/factory/repos/delegated_operation.py b/fiftyone/factory/repos/delegated_operation.py index ac2a9c20c1..0224a07e95 100644 --- a/fiftyone/factory/repos/delegated_operation.py +++ b/fiftyone/factory/repos/delegated_operation.py @@ -262,6 +262,8 @@ def update_run_state( else None ) + needs_pipeline_update = False + if run_state == ExecutionRunState.COMPLETED: update = { "$set": { @@ -272,10 +274,11 @@ def update_run_state( } } - if outputs_schema: - update["$set"]["metadata.outputs_schema"] = { - "$ifNull": [outputs_schema, {}] - } + if outputs_schema: + update["$set"]["metadata.outputs_schema"] = ( + outputs_schema or {} + ) + needs_pipeline_update = True elif run_state == ExecutionRunState.FAILED: update = { @@ -325,9 +328,15 @@ def update_run_state( if required_state is not None: collection_filter["run_state"] = required_state + # Using pipeline update instead of a single update doc fixes a case + # where `metadata` is null and so accessing the dotted field + # `metadata.output_schema` creates the document instead of erroring. + if needs_pipeline_update: + update = [update] + doc = self._collection.find_one_and_update( filter=collection_filter, - update=[update], + update=update, return_document=pymongo.ReturnDocument.AFTER, ) diff --git a/tests/unittests/operators/delegated_tests.py b/tests/unittests/operators/delegated_tests.py index 4939124942..e2f8bdbc40 100644 --- a/tests/unittests/operators/delegated_tests.py +++ b/tests/unittests/operators/delegated_tests.py @@ -11,6 +11,7 @@ from unittest import mock from unittest.mock import patch +import bson import pytest import fiftyone @@ -484,6 +485,35 @@ def test_sets_progress( self.assertEqual(doc.status.label, "halfway there") self.assertIsNotNone(doc.status.updated_at) + def test_output_schema_null_metadata( + self, mock_get_operator, mock_operator_exists + ): + mock_outputs = MockOutputs() + doc = self.svc.queue_operation( + operator="@voxelfiftyone/operator/foo", + delegation_target="test_target", + context=ExecutionContext(request_params={"foo": "bar"}), + ) + + # Set metadata to null instead of being unset, to test that corner case + self.svc._repo._collection.find_one_and_update( + {"_id": bson.ObjectId(doc.id)}, {"$set": {"metadata": None}} + ) + + self.svc.set_completed( + doc.id, + result=ExecutionResult(outputs_schema=mock_outputs.to_json()), + ) + + doc = self.svc.get(doc_id=doc.id) + self.assertEqual(doc.run_state, ExecutionRunState.COMPLETED) + self.assertEqual( + doc.metadata, + { + "outputs_schema": mock_outputs.to_json(), + }, + ) + @patch( "fiftyone.core.odm.utils.load_dataset", )