From 25ce505628e2de59b354a37ca383514485951143 Mon Sep 17 00:00:00 2001 From: Mary Hipp Rogers Date: Mon, 12 Feb 2024 18:48:32 -0500 Subject: [PATCH] exposed field loading state (#5704) * remove thunk for receivedOpenApiSchema and use RTK query instead. add loading state for exposed fields * clean up * ignore any * fix(ui): do not log on canceled openapi.json queries - Rely on RTK Query for the `loadSchema` query by providing a custom `jsonReplacer` in our `dynamicBaseQuery`, so we don't need to manage error state. - Detect when the query was canceled and do not log the error message in those situations. * feat(ui): `utilitiesApi.endpoints.loadSchema` -> `appInfoApi.endpoints.getOpenAPISchema` - Utilities is for server actions, move this to `appInfo` bc it fits better there. - Rename to match convention for HTTP GET queries. - Fix inverted logic in the `matchRejected` listener (typo'd this) --------- Co-authored-by: Mary Hipp Co-authored-by: psychedelicious <4822129+psychedelicious@users.noreply.github.com> --- .../middleware/devtools/actionSanitizer.ts | 4 +- .../middleware/listenerMiddleware/index.ts | 4 +- ...edOpenAPISchema.ts => getOpenAPISchema.ts} | 18 +++++---- .../listeners/socketio/socketConnected.ts | 16 +------- .../features/nodes/components/NodeEditor.tsx | 8 ++-- .../TopRightPanel/ReloadSchemaButton.tsx | 9 ++--- .../sidePanel/workflow/WorkflowLinearTab.tsx | 6 ++- .../src/features/nodes/store/nodesSlice.ts | 11 ----- .../web/src/features/nodes/store/types.ts | 1 - .../web/src/services/api/endpoints/appInfo.ts | 12 ++++++ .../frontend/web/src/services/api/index.ts | 38 +++++++++++++++++- .../web/src/services/api/thunks/schema.ts | 40 ------------------- 12 files changed, 77 insertions(+), 90 deletions(-) rename invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/{receivedOpenAPISchema.ts => getOpenAPISchema.ts} (55%) delete mode 100644 invokeai/frontend/web/src/services/api/thunks/schema.ts diff --git a/invokeai/frontend/web/src/app/store/middleware/devtools/actionSanitizer.ts b/invokeai/frontend/web/src/app/store/middleware/devtools/actionSanitizer.ts index f4fa2766e6..2e2d2014b2 100644 --- a/invokeai/frontend/web/src/app/store/middleware/devtools/actionSanitizer.ts +++ b/invokeai/frontend/web/src/app/store/middleware/devtools/actionSanitizer.ts @@ -2,7 +2,7 @@ import type { UnknownAction } from '@reduxjs/toolkit'; import { isAnyGraphBuilt } from 'features/nodes/store/actions'; import { nodeTemplatesBuilt } from 'features/nodes/store/nodeTemplatesSlice'; import { cloneDeep } from 'lodash-es'; -import { receivedOpenAPISchema } from 'services/api/thunks/schema'; +import { appInfoApi } from 'services/api/endpoints/appInfo'; import type { Graph } from 'services/api/types'; import { socketGeneratorProgress } from 'services/events/actions'; @@ -18,7 +18,7 @@ export const actionSanitizer = (action: A): A => { } } - if (receivedOpenAPISchema.fulfilled.match(action)) { + if (appInfoApi.endpoints.getOpenAPISchema.matchFulfilled(action)) { return { ...action, payload: '', diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts index 1d21215d9d..322c4eb1ec 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/index.ts @@ -23,6 +23,7 @@ import { addControlNetImageProcessedListener } from './listeners/controlNetImage import { addEnqueueRequestedCanvasListener } from './listeners/enqueueRequestedCanvas'; import { addEnqueueRequestedLinear } from './listeners/enqueueRequestedLinear'; import { addEnqueueRequestedNodes } from './listeners/enqueueRequestedNodes'; +import { addGetOpenAPISchemaListener } from './listeners/getOpenAPISchema'; import { addImageAddedToBoardFulfilledListener, addImageAddedToBoardRejectedListener, @@ -47,7 +48,6 @@ import { addInitialImageSelectedListener } from './listeners/initialImageSelecte import { addModelSelectedListener } from './listeners/modelSelected'; import { addModelsLoadedListener } from './listeners/modelsLoaded'; import { addDynamicPromptsListener } from './listeners/promptChanged'; -import { addReceivedOpenAPISchemaListener } from './listeners/receivedOpenAPISchema'; import { addSocketConnectedEventListener as addSocketConnectedListener } from './listeners/socketio/socketConnected'; import { addSocketDisconnectedEventListener as addSocketDisconnectedListener } from './listeners/socketio/socketDisconnected'; import { addGeneratorProgressEventListener as addGeneratorProgressListener } from './listeners/socketio/socketGeneratorProgress'; @@ -150,7 +150,7 @@ addImageRemovedFromBoardRejectedListener(); addBoardIdSelectedListener(); // Node schemas -addReceivedOpenAPISchemaListener(); +addGetOpenAPISchemaListener(); // Workflows addWorkflowLoadRequestedListener(); diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedOpenAPISchema.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/getOpenAPISchema.ts similarity index 55% rename from invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedOpenAPISchema.ts rename to invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/getOpenAPISchema.ts index 4ab175cfba..b2d3615909 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/receivedOpenAPISchema.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/getOpenAPISchema.ts @@ -3,18 +3,18 @@ import { parseify } from 'common/util/serialize'; import { nodeTemplatesBuilt } from 'features/nodes/store/nodeTemplatesSlice'; import { parseSchema } from 'features/nodes/util/schema/parseSchema'; import { size } from 'lodash-es'; -import { receivedOpenAPISchema } from 'services/api/thunks/schema'; +import { appInfoApi } from 'services/api/endpoints/appInfo'; import { startAppListening } from '..'; -export const addReceivedOpenAPISchemaListener = () => { +export const addGetOpenAPISchemaListener = () => { startAppListening({ - actionCreator: receivedOpenAPISchema.fulfilled, + matcher: appInfoApi.endpoints.getOpenAPISchema.matchFulfilled, effect: (action, { dispatch, getState }) => { const log = logger('system'); const schemaJSON = action.payload; - log.debug({ schemaJSON }, 'Received OpenAPI schema'); + log.debug({ schemaJSON: parseify(schemaJSON) }, 'Received OpenAPI schema'); const { nodesAllowlist, nodesDenylist } = getState().config; const nodeTemplates = parseSchema(schemaJSON, nodesAllowlist, nodesDenylist); @@ -26,10 +26,14 @@ export const addReceivedOpenAPISchemaListener = () => { }); startAppListening({ - actionCreator: receivedOpenAPISchema.rejected, + matcher: appInfoApi.endpoints.getOpenAPISchema.matchRejected, effect: (action) => { - const log = logger('system'); - log.error({ error: parseify(action.error) }, 'Problem retrieving OpenAPI Schema'); + // If action.meta.condition === true, the request was canceled/skipped because another request was in flight or + // the value was already in the cache. We don't want to log these errors. + if (!action.meta.condition) { + const log = logger('system'); + log.error({ error: parseify(action.error) }, 'Problem retrieving OpenAPI Schema'); + } }, }); }; diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts index 05cb0d64b3..df0de6bbda 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected.ts @@ -1,10 +1,9 @@ import { logger } from 'app/logging/logger'; import { $baseUrl } from 'app/store/nanostores/baseUrl'; -import { isEqual, size } from 'lodash-es'; +import { isEqual } from 'lodash-es'; import { atom } from 'nanostores'; import { api } from 'services/api'; import { queueApi, selectQueueStatus } from 'services/api/endpoints/queue'; -import { receivedOpenAPISchema } from 'services/api/thunks/schema'; import { socketConnected } from 'services/events/actions'; import { startAppListening } from '../..'; @@ -77,17 +76,4 @@ export const addSocketConnectedEventListener = () => { } }, }); - - startAppListening({ - actionCreator: socketConnected, - effect: async (action, { dispatch, getState }) => { - const { nodeTemplates, config } = getState(); - // We only want to re-fetch the schema if we don't have any node templates - if (!size(nodeTemplates.templates) && !config.disabledTabs.includes('nodes')) { - // This request is a createAsyncThunk - resetting API state as in the above listener - // will not trigger this request, so we need to manually do it. - dispatch(receivedOpenAPISchema()); - } - }, - }); }; diff --git a/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx b/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx index bb801b6f39..8307997ff9 100644 --- a/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx @@ -1,7 +1,6 @@ import 'reactflow/dist/style.css'; import { Flex } from '@invoke-ai/ui-library'; -import { useAppSelector } from 'app/store/storeHooks'; import { IAINoContentFallback } from 'common/components/IAIImageFallback'; import TopPanel from 'features/nodes/components/flow/panels/TopPanel/TopPanel'; import { SaveWorkflowAsDialog } from 'features/workflowLibrary/components/SaveWorkflowAsDialog/SaveWorkflowAsDialog'; @@ -11,6 +10,7 @@ import type { CSSProperties } from 'react'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { MdDeviceHub } from 'react-icons/md'; +import { useGetOpenAPISchemaQuery } from 'services/api/endpoints/appInfo'; import AddNodePopover from './flow/AddNodePopover/AddNodePopover'; import { Flow } from './flow/Flow'; @@ -40,7 +40,7 @@ const exit: AnimationProps['exit'] = { }; const NodeEditor = () => { - const isReady = useAppSelector((s) => s.nodes.isReady); + const { data, isLoading } = useGetOpenAPISchemaQuery(); const { t } = useTranslation(); return ( { justifyContent="center" > - {isReady && ( + {data && ( @@ -65,7 +65,7 @@ const NodeEditor = () => { )} - {!isReady && ( + {isLoading && ( { const { t } = useTranslation(); - const dispatch = useAppDispatch(); + const [_getOpenAPISchema] = useLazyGetOpenAPISchemaQuery(); const handleReloadSchema = useCallback(() => { - dispatch(receivedOpenAPISchema()); - }, [dispatch]); + _getOpenAPISchema(); + }, [_getOpenAPISchema]); return (