workflow library updates (#5568)

* dont show duplicate toasts if workflow actions fail due to auth

* dynamic order by options based on projectId

* add endpointName to authtoast to makeit unique per endpoint

* lint

* update toast logic to check based on endpoint name w type safety

* fix save as endpoit name

* lint

* fix type

---------

Co-authored-by: Mary Hipp <maryhipp@Marys-MacBook-Air.local>
This commit is contained in:
Mary Hipp Rogers 2024-01-25 11:43:47 -05:00 committed by GitHub
parent 57dafd294d
commit 89da976949
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 87 additions and 32 deletions

View File

@ -61,13 +61,21 @@ const WorkflowLibraryList = () => {
const [category, setCategory] = useState<WorkflowCategory>('user'); const [category, setCategory] = useState<WorkflowCategory>('user');
const [page, setPage] = useState(0); const [page, setPage] = useState(0);
const [query, setQuery] = useState(''); const [query, setQuery] = useState('');
const [order_by, setOrderBy] = useState<WorkflowRecordOrderBy>('opened_at'); const projectId = useStore($projectId);
const orderByOptions = useMemo(() => {
return projectId
? ORDER_BY_OPTIONS.filter((option) => option.value !== 'opened_at')
: ORDER_BY_OPTIONS;
}, [projectId]);
const [order_by, setOrderBy] = useState<WorkflowRecordOrderBy>(
orderByOptions[0]?.value as WorkflowRecordOrderBy
);
const [direction, setDirection] = useState<SQLiteDirection>('ASC'); const [direction, setDirection] = useState<SQLiteDirection>('ASC');
const [debouncedQuery] = useDebounce(query, 500); const [debouncedQuery] = useDebounce(query, 500);
const projectId = useStore($projectId);
const queryArg = useMemo<Parameters<typeof useListWorkflowsQuery>[0]>(() => { const queryArg = useMemo<Parameters<typeof useListWorkflowsQuery>[0]>(() => {
if (category === 'user') { if (category !== 'default') {
return { return {
page, page,
per_page: PER_PAGE, per_page: PER_PAGE,
@ -101,8 +109,8 @@ const WorkflowLibraryList = () => {
[order_by] [order_by]
); );
const valueOrderBy = useMemo( const valueOrderBy = useMemo(
() => ORDER_BY_OPTIONS.find((o) => o.value === order_by), () => orderByOptions.find((o) => o.value === order_by),
[order_by] [order_by, orderByOptions]
); );
const onChangeDirection = useCallback<ComboboxOnChange>( const onChangeDirection = useCallback<ComboboxOnChange>(
@ -186,7 +194,7 @@ const WorkflowLibraryList = () => {
<FormLabel>{t('common.orderBy')}</FormLabel> <FormLabel>{t('common.orderBy')}</FormLabel>
<Combobox <Combobox
value={valueOrderBy} value={valueOrderBy}
options={ORDER_BY_OPTIONS} options={orderByOptions}
onChange={onChangeOrderBy} onChange={onChangeOrderBy}
/> />
</FormControl> </FormControl>

View File

@ -1,7 +1,11 @@
import { useToast } from '@invoke-ai/ui';
import { useAppToaster } from 'app/components/Toaster'; import { useAppToaster } from 'app/components/Toaster';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useDeleteWorkflowMutation } from 'services/api/endpoints/workflows'; import {
useDeleteWorkflowMutation,
workflowsApi,
} from 'services/api/endpoints/workflows';
type UseDeleteLibraryWorkflowOptions = { type UseDeleteLibraryWorkflowOptions = {
onSuccess?: () => void; onSuccess?: () => void;
@ -22,6 +26,7 @@ export const useDeleteLibraryWorkflow: UseDeleteLibraryWorkflow = ({
onError, onError,
}) => { }) => {
const toaster = useAppToaster(); const toaster = useAppToaster();
const toast = useToast();
const { t } = useTranslation(); const { t } = useTranslation();
const [_deleteWorkflow, deleteWorkflowResult] = useDeleteWorkflowMutation(); const [_deleteWorkflow, deleteWorkflowResult] = useDeleteWorkflowMutation();
@ -34,14 +39,20 @@ export const useDeleteLibraryWorkflow: UseDeleteLibraryWorkflow = ({
}); });
onSuccess && onSuccess(); onSuccess && onSuccess();
} catch { } catch {
toaster({ if (
title: t('toast.problemDeletingWorkflow'), !toast.isActive(
status: 'error', `auth-error-toast-${workflowsApi.endpoints.deleteWorkflow.name}`
}); )
) {
toaster({
title: t('toast.problemDeletingWorkflow'),
status: 'error',
});
}
onError && onError(); onError && onError();
} }
}, },
[_deleteWorkflow, toaster, t, onSuccess, onError] [_deleteWorkflow, toaster, t, onSuccess, onError, toast]
); );
return { deleteWorkflow, deleteWorkflowResult }; return { deleteWorkflow, deleteWorkflowResult };

View File

@ -1,9 +1,13 @@
import { useToast } from '@invoke-ai/ui';
import { useAppToaster } from 'app/components/Toaster'; import { useAppToaster } from 'app/components/Toaster';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { workflowLoadRequested } from 'features/nodes/store/actions'; import { workflowLoadRequested } from 'features/nodes/store/actions';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useLazyGetWorkflowQuery } from 'services/api/endpoints/workflows'; import {
useLazyGetWorkflowQuery,
workflowsApi,
} from 'services/api/endpoints/workflows';
type UseGetAndLoadLibraryWorkflowOptions = { type UseGetAndLoadLibraryWorkflowOptions = {
onSuccess?: () => void; onSuccess?: () => void;
@ -25,6 +29,7 @@ export const useGetAndLoadLibraryWorkflow: UseGetAndLoadLibraryWorkflow = ({
}) => { }) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const toaster = useAppToaster(); const toaster = useAppToaster();
const toast = useToast();
const { t } = useTranslation(); const { t } = useTranslation();
const [_getAndLoadWorkflow, getAndLoadWorkflowResult] = const [_getAndLoadWorkflow, getAndLoadWorkflowResult] =
useLazyGetWorkflowQuery(); useLazyGetWorkflowQuery();
@ -38,14 +43,20 @@ export const useGetAndLoadLibraryWorkflow: UseGetAndLoadLibraryWorkflow = ({
// No toast - the listener for this action does that after the workflow is loaded // No toast - the listener for this action does that after the workflow is loaded
onSuccess && onSuccess(); onSuccess && onSuccess();
} catch { } catch {
toaster({ if (
title: t('toast.problemRetrievingWorkflow'), !toast.isActive(
status: 'error', `auth-error-toast-${workflowsApi.endpoints.getWorkflow.name}`
}); )
) {
toaster({
title: t('toast.problemRetrievingWorkflow'),
status: 'error',
});
}
onError && onError(); onError && onError();
} }
}, },
[_getAndLoadWorkflow, dispatch, onSuccess, toaster, t, onError] [_getAndLoadWorkflow, dispatch, onSuccess, toaster, t, onError, toast]
); );
return { getAndLoadWorkflow, getAndLoadWorkflowResult }; return { getAndLoadWorkflow, getAndLoadWorkflowResult };

View File

@ -12,6 +12,7 @@ import { useTranslation } from 'react-i18next';
import { import {
useCreateWorkflowMutation, useCreateWorkflowMutation,
useUpdateWorkflowMutation, useUpdateWorkflowMutation,
workflowsApi,
} from 'services/api/endpoints/workflows'; } from 'services/api/endpoints/workflows';
import type { O } from 'ts-toolbelt'; import type { O } from 'ts-toolbelt';
@ -60,12 +61,23 @@ export const useSaveLibraryWorkflow: UseSaveLibraryWorkflow = () => {
isClosable: true, isClosable: true,
}); });
} catch (e) { } catch (e) {
toast.update(toastRef.current, { if (
title: t('workflows.problemSavingWorkflow'), !toast.isActive(
status: 'error', `auth-error-toast-${workflowsApi.endpoints.createWorkflow.name}`
duration: 1000, ) &&
isClosable: true, !toast.isActive(
}); `auth-error-toast-${workflowsApi.endpoints.updateWorkflow.name}`
)
) {
toast.update(toastRef.current, {
title: t('workflows.problemSavingWorkflow'),
status: 'error',
duration: 1000,
isClosable: true,
});
} else {
toast.close(toastRef.current);
}
} }
}, [updateWorkflow, dispatch, toast, t, createWorkflow]); }, [updateWorkflow, dispatch, toast, t, createWorkflow]);
return { return {

View File

@ -9,7 +9,10 @@ import {
} from 'features/nodes/store/workflowSlice'; } from 'features/nodes/store/workflowSlice';
import { useCallback, useRef } from 'react'; import { useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useCreateWorkflowMutation } from 'services/api/endpoints/workflows'; import {
useCreateWorkflowMutation,
workflowsApi,
} from 'services/api/endpoints/workflows';
type SaveWorkflowAsArg = { type SaveWorkflowAsArg = {
name: string; name: string;
@ -59,12 +62,20 @@ export const useSaveWorkflowAs: UseSaveWorkflowAs = () => {
}); });
} catch (e) { } catch (e) {
onError && onError(); onError && onError();
toast.update(toastRef.current, { if (
title: t('workflows.problemSavingWorkflow'), !toast.isActive(
status: 'error', `auth-error-toast-${workflowsApi.endpoints.createWorkflow.name}`
duration: 1000, )
isClosable: true, ) {
}); toast.update(toastRef.current, {
title: t('workflows.problemSavingWorkflow'),
status: 'error',
duration: 1000,
isClosable: true,
});
} else {
toast.close(toastRef.current);
}
} }
}, },
[toast, createWorkflow, dispatch, t] [toast, createWorkflow, dispatch, t]

View File

@ -27,7 +27,8 @@ export const authToastMiddleware: Middleware =
if (isRejectedWithValue(action)) { if (isRejectedWithValue(action)) {
try { try {
const parsed = zRejectedForbiddenAction.parse(action); const parsed = zRejectedForbiddenAction.parse(action);
if (parsed.meta?.arg?.endpointName === 'getImageDTO') { const endpointName = parsed.meta?.arg?.endpointName;
if (endpointName === 'getImageDTO') {
// do not show toast if problem is image access // do not show toast if problem is image access
return; return;
} }
@ -39,6 +40,7 @@ export const authToastMiddleware: Middleware =
: undefined; : undefined;
dispatch( dispatch(
addToast({ addToast({
id: `auth-error-toast-${endpointName}`,
title: t('common.somethingWentWrong'), title: t('common.somethingWentWrong'),
status: 'error', status: 'error',
description: customMessage, description: customMessage,