From 08a09a752dbdd0443fac364c518742d522c66049 Mon Sep 17 00:00:00 2001 From: Carina Ursu Date: Fri, 15 Apr 2022 14:44:16 -0700 Subject: [PATCH 1/5] feat: archive tasks Signed-off-by: Carina Ursu --- src/components/Project/ProjectTasks.tsx | 20 +- .../Project/test/ProjectTask.test.tsx | 139 ++++++++++++++ .../Task/SearchableTaskNameList.tsx | 181 ++++++++++++++++-- .../SearchableTaskNameList.stories.tsx | 26 ++- .../Task/useTaskShowArchivedState.ts | 33 ++++ src/components/Task/utils.ts | 14 ++ .../common/FilterableNamedEntityList.tsx | 117 +++++++++++ .../common/SearchableNamedEntityList.tsx | 1 + src/models/Task/api.ts | 31 ++- src/models/Task/enums.ts | 11 ++ src/test/modelUtils.ts | 4 + 11 files changed, 557 insertions(+), 20 deletions(-) create mode 100644 src/components/Project/test/ProjectTask.test.tsx create mode 100644 src/components/Task/useTaskShowArchivedState.ts create mode 100644 src/components/Task/utils.ts create mode 100644 src/components/common/FilterableNamedEntityList.tsx create mode 100644 src/models/Task/enums.ts diff --git a/src/components/Project/ProjectTasks.tsx b/src/components/Project/ProjectTasks.tsx index 26e9577a6..b9cc889d6 100644 --- a/src/components/Project/ProjectTasks.tsx +++ b/src/components/Project/ProjectTasks.tsx @@ -1,6 +1,7 @@ import { WaitForData } from 'components/common/WaitForData'; import { useTaskNameList } from 'components/hooks/useNamedEntity'; import { SearchableTaskNameList } from 'components/Task/SearchableTaskNameList'; +import { useTaskShowArchivedState } from 'components/Task/useTaskShowArchivedState'; import { limits } from 'models/AdminEntity/constants'; import { SortDirection } from 'models/AdminEntity/types'; import { taskSortFields } from 'models/Task/constants'; @@ -11,25 +12,34 @@ export interface ProjectTasksProps { domainId: string; } +const DEFAULT_SORT = { + direction: SortDirection.ASCENDING, + key: taskSortFields.name, +}; + /** A listing of the Tasks registered for a project */ export const ProjectTasks: React.FC = ({ domainId: domain, projectId: project, }) => { + const archivedFilter = useTaskShowArchivedState(); + const taskNames = useTaskNameList( { domain, project }, { limit: limits.NONE, - sort: { - direction: SortDirection.ASCENDING, - key: taskSortFields.name, - }, + sort: DEFAULT_SORT, + filter: [archivedFilter.getFilter()], }, ); return ( - + ); }; diff --git a/src/components/Project/test/ProjectTask.test.tsx b/src/components/Project/test/ProjectTask.test.tsx new file mode 100644 index 000000000..5ca97cfbb --- /dev/null +++ b/src/components/Project/test/ProjectTask.test.tsx @@ -0,0 +1,139 @@ +import { fireEvent, render, waitFor } from '@testing-library/react'; +import { APIContext } from 'components/data/apiContext'; +import { mockAPIContextValue } from 'components/data/__mocks__/apiContext'; +import { Admin } from 'flyteidl'; +import { FilterOperationName } from 'models/AdminEntity/types'; +import { getUserProfile, listNamedEntities } from 'models/Common/api'; +import { NamedEntity, UserProfile } from 'models/Common/types'; +import { updateTaskState } from 'models/Task/api'; +import * as React from 'react'; +import { QueryClient, QueryClientProvider } from 'react-query'; +import { MemoryRouter } from 'react-router'; +import { createTask } from 'test/modelUtils'; +import { createTestQueryClient } from 'test/utils'; +import { ProjectTasks } from '../ProjectTasks'; + +const sampleUserProfile: UserProfile = { + subject: 'subject', +} as UserProfile; + +jest.mock('notistack', () => ({ + useSnackbar: () => ({ enqueueSnackbar: jest.fn() }), +})); + +jest.mock('models/Task/api', () => ({ + updateTaskState: jest.fn().mockResolvedValue({}), +})); + +describe('ProjectTasks', () => { + const project = 'TestProject'; + const domain = 'TestDomain'; + let tasks: NamedEntity[]; + let queryClient: QueryClient; + let mockListNamedEntities: jest.Mock>; + let mockGetUserProfile: jest.Mock>; + + beforeEach(() => { + mockGetUserProfile = jest.fn().mockResolvedValue(null); + queryClient = createTestQueryClient(); + tasks = ['MyTask', 'MyOtherTask'].map((name) => createTask({ domain, name, project })); + mockListNamedEntities = jest.fn().mockResolvedValue({ entities: tasks }); + + window.IntersectionObserver = jest.fn().mockReturnValue({ + observe: () => null, + unobserve: () => null, + disconnect: () => null, + }); + }); + + const renderComponent = () => + render( + + + + + , + { wrapper: MemoryRouter }, + ); + + it('does not show archived tasks', async () => { + const { getByText } = renderComponent(); + await waitFor(() => {}); + + expect(mockListNamedEntities).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + filter: [ + { + key: 'state', + operation: FilterOperationName.EQ, + value: Admin.NamedEntityState.NAMED_ENTITY_ACTIVE, + }, + ], + }), + ); + await waitFor(() => expect(getByText('MyTask'))); + }); + + it('should display checkbox if user login', async () => { + mockGetUserProfile.mockResolvedValue(sampleUserProfile); + const { getAllByRole } = renderComponent(); + await waitFor(() => {}); + const checkboxes = getAllByRole(/checkbox/i) as HTMLInputElement[]; + expect(checkboxes).toHaveLength(1); + expect(checkboxes[0]).toBeTruthy(); + expect(checkboxes[0]?.checked).toEqual(false); + }); + + it('should display archive button', async () => { + mockGetUserProfile.mockResolvedValue(sampleUserProfile); + const { getByText, getAllByTitle, findAllByText } = renderComponent(); + await waitFor(() => {}); + + const task = getByText('MyTask'); + expect(task).toBeTruthy(); + + const parent = task?.parentElement?.parentElement?.parentElement!; + fireEvent.mouseOver(parent); + + const archiveButton = getAllByTitle('Archive'); + expect(archiveButton[0]).toBeTruthy(); + + fireEvent.click(archiveButton[0]); + + const cancelButton = await findAllByText('Cancel'); + await waitFor(() => expect(cancelButton.length).toEqual(1)); + const confirmarchiveButton = cancelButton?.[0]?.parentElement?.parentElement?.children[0]!; + + expect(confirmarchiveButton).toBeTruthy(); + + fireEvent.click(confirmarchiveButton!); + + await waitFor(() => { + expect(updateTaskState).toHaveBeenCalledTimes(1); + }); + }); + + it('clicking show archived should hide active tasks', async () => { + mockGetUserProfile.mockResolvedValue(sampleUserProfile); + const { getByText, queryByText, getAllByRole } = renderComponent(); + await waitFor(() => {}); + + // check the checkbox is present + const checkboxes = getAllByRole(/checkbox/i) as HTMLInputElement[]; + expect(checkboxes[0]).toBeTruthy(); + expect(checkboxes[0]?.checked).toEqual(false); + + // check that my task is in document + await waitFor(() => expect(getByText('MyTask'))); + fireEvent.click(checkboxes[0]); + + // when user selects checkbox, table should have no tasks to display + await waitFor(() => expect(queryByText('MyTask')).not.toBeInTheDocument()); + }); +}); diff --git a/src/components/Task/SearchableTaskNameList.tsx b/src/components/Task/SearchableTaskNameList.tsx index b123a5049..cd99741c9 100644 --- a/src/components/Task/SearchableTaskNameList.tsx +++ b/src/components/Task/SearchableTaskNameList.tsx @@ -1,12 +1,9 @@ -import { Typography } from '@material-ui/core'; +import { Typography, IconButton, Button, CircularProgress } from '@material-ui/core'; import { makeStyles, Theme } from '@material-ui/core/styles'; -import ChevronRight from '@material-ui/icons/ChevronRight'; import ErrorOutline from '@material-ui/icons/ErrorOutline'; import classnames from 'classnames'; import { SearchResult } from 'components/common/SearchableList'; import { - SearchableNamedEntity, - SearchableNamedEntityList, SearchableNamedEntityListProps, useNamedEntityListStyles, } from 'components/common/SearchableNamedEntityList'; @@ -14,16 +11,57 @@ import { useCommonStyles } from 'components/common/styles'; import { WaitForData } from 'components/common/WaitForData'; import { NamedEntity } from 'models/Common/types'; import * as React from 'react'; +import { useState } from 'react'; import { IntersectionOptions, useInView } from 'react-intersection-observer'; import reactLoadingSkeleton from 'react-loading-skeleton'; import { Link } from 'react-router-dom'; import { Routes } from 'routes/routes'; -import { SimpleTaskInterface } from './SimpleTaskInterface'; +import UnarchiveOutline from '@material-ui/icons/UnarchiveOutlined'; +import ArchiveOutlined from '@material-ui/icons/ArchiveOutlined'; +import { useSnackbar } from 'notistack'; +import { updateTaskState } from 'models/Task/api'; +import { useMutation } from 'react-query'; +import { TaskExecutionState } from 'models/Task/enums'; +import { FilterableNamedEntityList } from 'components/common/FilterableNamedEntityList'; import { useLatestTaskVersion } from './useLatestTask'; +import t from '../Executions/Tables/WorkflowExecutionTable/strings'; +import { SimpleTaskInterface } from './SimpleTaskInterface'; +import { isTaskArchived } from './utils'; const Skeleton = reactLoadingSkeleton; +export const showOnHoverClass = 'showOnHover'; + const useStyles = makeStyles((theme: Theme) => ({ + container: { + // All children using the showOnHover class will be hidden until + // the mouse enters the container + [`& .${showOnHoverClass}`]: { + opacity: 0, + }, + [`&:hover .${showOnHoverClass}`]: { + opacity: 1, + }, + }, + actionContainer: { + display: 'flex', + right: 0, + top: 0, + position: 'absolute', + height: '100%', + }, + archiveCheckbox: { + whiteSpace: 'nowrap', + }, + centeredChild: { + alignItems: 'center', + marginRight: 24, + }, + confirmationButton: { + borderRadius: 0, + minWidth: '100px', + minHeight: '53px', + }, description: { color: theme.palette.text.secondary, marginTop: theme.spacing(0.5), @@ -46,6 +84,12 @@ interface TaskNameRowProps { entityName: NamedEntity; } +interface SearchableTaskNameListProps { + names: NamedEntity[]; + onArchiveFilterChange: (showArchievedItems: boolean) => void; + showArchived: boolean; +} + const intersectionOptions: IntersectionOptions = { rootMargin: '100px 0px', triggerOnce: true, @@ -62,7 +106,10 @@ const TaskInterfaceError: React.FC = () => { ); }; -const TaskInterface: React.FC<{ taskName: NamedEntity }> = ({ taskName }) => { +const TaskInterface: React.FC<{ + taskName: NamedEntity; + setShowItem: (visible: boolean) => void; +}> = ({ taskName }) => { const styles = useStyles(); const task = useLatestTaskVersion(taskName.id); return ( @@ -74,11 +121,113 @@ const TaskInterface: React.FC<{ taskName: NamedEntity }> = ({ taskName }) => { ); }; +const getArchiveIcon = (isArchived: boolean) => + isArchived ? : ; + +interface SimpleTaskActionsProps { + item: NamedEntity; + setShowItem: (visible: boolean) => void; +} + +const SimpleTaskActions: React.FC = ({ item, setShowItem }) => { + const styles = useStyles(); + const { enqueueSnackbar } = useSnackbar(); + const { id } = item; + const isArchived = isTaskArchived(item); + const [isUpdating, setIsUpdating] = useState(false); + const [showConfirmation, setShowConfirmation] = useState(false); + + const mutation = useMutation((newState: TaskExecutionState) => updateTaskState(id, newState), { + onMutate: () => setIsUpdating(true), + onSuccess: () => { + enqueueSnackbar(t('archiveSuccess', !isArchived), { + variant: 'success', + }); + setShowItem(false); + }, + onError: () => { + enqueueSnackbar(`${mutation.error ?? t('archiveError', !isArchived)}`, { + variant: 'error', + }); + }, + onSettled: () => { + setShowConfirmation(false); + setIsUpdating(false); + }, + }); + + const onArchiveClick = (event: React.MouseEvent) => { + event.stopPropagation(); + event.preventDefault(); + setShowConfirmation(true); + }; + + const onConfirmArchiveClick = (event: React.MouseEvent) => { + event.stopPropagation(); + event.preventDefault(); + mutation.mutate( + isTaskArchived(item) + ? TaskExecutionState.NAMED_ENTITY_ACTIVE + : TaskExecutionState.NAMED_ENTITY_ARCHIVED, + ); + }; + + const onCancelClick = (event: React.MouseEvent) => { + event.stopPropagation(); + event.preventDefault(); + setShowConfirmation(false); + }; + + const singleItemStyle = isUpdating || !showConfirmation ? styles.centeredChild : ''; + + return ( +
+ {isUpdating ? ( + + + + ) : showConfirmation ? ( + <> + + + + ) : ( + + {getArchiveIcon(isArchived)} + + )} +
+ ); +}; + const TaskNameRow: React.FC = ({ label, entityName }) => { const styles = useStyles(); const listStyles = useNamedEntityListStyles(); const [inViewRef, inView] = useInView(intersectionOptions); const description = entityName?.metadata?.description; + const [showItem, setShowItem] = useState(true); + + if (!showItem) { + return null; + } return (
@@ -89,26 +238,34 @@ const TaskNameRow: React.FC = ({ label, entityName }) => { {description} )} - {!!inView && } + {!!inView && } +
- ); }; /** Renders a searchable list of Task names, with associated metadata */ export const SearchableTaskNameList: React.FC< - Omit + Omit & SearchableTaskNameListProps > = (props) => { const commonStyles = useCommonStyles(); - const renderItem = ({ key, value, content }: SearchResult) => ( + const styles = useStyles(); + const renderItem = ({ key, value, content }: SearchResult) => ( ); - return ; + return ( + + ); }; diff --git a/src/components/Task/__stories__/SearchableTaskNameList.stories.tsx b/src/components/Task/__stories__/SearchableTaskNameList.stories.tsx index c6c6a0be3..098e08701 100644 --- a/src/components/Task/__stories__/SearchableTaskNameList.stories.tsx +++ b/src/components/Task/__stories__/SearchableTaskNameList.stories.tsx @@ -1,10 +1,34 @@ +import { Collapse } from '@material-ui/core'; +import { action } from '@storybook/addon-actions'; import { storiesOf } from '@storybook/react'; import { sampleTaskNames } from 'models/__mocks__/sampleTaskNames'; +import { SnackbarProvider } from 'notistack'; import * as React from 'react'; import { SearchableTaskNameList } from '../SearchableTaskNameList'; const baseProps = { names: [...sampleTaskNames] }; +// wrapper - to ensure that error/success notification shown as expected in storybook +const Wrapper = (props) => { + return ( + + {props.children} + + ); +}; + const stories = storiesOf('Task/SearchableTaskNameList', module); stories.addDecorator((story) =>
{story()}
); -stories.add('basic', () => ); +stories.add('basic', () => ( + + + +)); diff --git a/src/components/Task/useTaskShowArchivedState.ts b/src/components/Task/useTaskShowArchivedState.ts new file mode 100644 index 000000000..35fa974e8 --- /dev/null +++ b/src/components/Task/useTaskShowArchivedState.ts @@ -0,0 +1,33 @@ +import { useState } from 'react'; +import { FilterOperation, FilterOperationName } from 'models/AdminEntity/types'; +import { TaskExecutionState } from 'models/Task/enums'; + +interface ArchiveFilterState { + showArchived: boolean; + setShowArchived: (newValue: boolean) => void; + getFilter: () => FilterOperation; +} + +/** + * Allows to filter by Archive state + */ +export function useTaskShowArchivedState(): ArchiveFilterState { + const [showArchived, setShowArchived] = useState(false); + + // By default all values are returned with NAMED_ENTITY_ACTIVE state + const getFilter = (): FilterOperation => { + return { + key: 'state', + operation: FilterOperationName.EQ, + value: showArchived + ? TaskExecutionState.NAMED_ENTITY_ARCHIVED + : TaskExecutionState.NAMED_ENTITY_ACTIVE, + }; + }; + + return { + showArchived, + setShowArchived, + getFilter, + }; +} diff --git a/src/components/Task/utils.ts b/src/components/Task/utils.ts new file mode 100644 index 000000000..9845d4427 --- /dev/null +++ b/src/components/Task/utils.ts @@ -0,0 +1,14 @@ +// import { Task } from 'models/Task/types'; + +import { NamedEntity } from 'models/Common/types'; +import { TaskExecutionState } from 'models/Task/enums'; +import { Task } from 'models/Task/types'; + +function isTaskStateArchive(task: NamedEntity): boolean { + const state = task?.metadata?.state ?? null; + return !!state && state === TaskExecutionState.NAMED_ENTITY_ARCHIVED; +} + +export function isTaskArchived(task: NamedEntity): boolean { + return isTaskStateArchive(task); +} diff --git a/src/components/common/FilterableNamedEntityList.tsx b/src/components/common/FilterableNamedEntityList.tsx new file mode 100644 index 000000000..cf608ebc4 --- /dev/null +++ b/src/components/common/FilterableNamedEntityList.tsx @@ -0,0 +1,117 @@ +import { Checkbox, debounce, FormControlLabel, FormGroup, Typography } from '@material-ui/core'; +import { makeStyles, Theme } from '@material-ui/core/styles'; +import { NamedEntity } from 'models/Common/types'; +import * as React from 'react'; +import { SearchableInput, SearchResult } from './SearchableList'; +import { useCommonStyles } from './styles'; +import { useSearchableListState } from './useSearchableListState'; + +export const useStyles = makeStyles((theme: Theme) => ({ + archiveCheckbox: { + whiteSpace: 'nowrap', + }, + container: { + marginBottom: theme.spacing(2), + width: '100%', + }, + noResults: { + color: theme.palette.text.disabled, + display: 'flex', + justifyContent: 'center', + marginTop: theme.spacing(6), + }, + filterGroup: { + display: 'flex', + flexWrap: 'nowrap', + flexDirection: 'row', + margin: theme.spacing(4, 5, 2, 2), + }, +})); + +const NoResults: React.FC = () => ( + + No matching results + +); + +type ItemRenderer = (item: SearchResult) => React.ReactNode; + +interface SearchResultsProps { + results: SearchResult[]; + renderItem: ItemRenderer; +} + +export interface FilterableNamedEntityListProps { + names: NamedEntity[]; + onArchiveFilterChange: (showArchievedItems: boolean) => void; + showArchived: boolean; + placeholder: string; + archiveCheckboxLabel: string; + renderItem: ItemRenderer; +} + +const VARIANT = 'normal'; + +const SearchResults: React.FC = ({ renderItem, results }) => { + const commonStyles = useCommonStyles(); + return results.length === 0 ? ( + + ) : ( +
    {results.map(renderItem)}
+ ); +}; + +/** Base component functionalityfor rendering NamedEntities (Workflow/Task/LaunchPlan) */ +export const FilterableNamedEntityList: React.FC = ({ + names, + showArchived, + renderItem, + onArchiveFilterChange, + placeholder, + archiveCheckboxLabel, +}) => { + const styles = useStyles(); + const [search, setSearch] = React.useState(''); + + const { results, setSearchString } = useSearchableListState({ + items: names, + propertyGetter: ({ id }) => id.name, + }); + + const onSearchChange = (event: React.ChangeEvent) => { + const searchString = event.target.value; + setSearch(searchString); + const debouncedSearch = debounce(() => setSearchString(searchString), 1000); + debouncedSearch(); + }; + const onClear = () => setSearch(''); + + const renderItems = (results: SearchResult[]) => ( + + ); + + return ( +
+ + + onArchiveFilterChange(checked)} + /> + } + label={archiveCheckboxLabel} + /> + + {renderItems(results)} +
+ ); +}; diff --git a/src/components/common/SearchableNamedEntityList.tsx b/src/components/common/SearchableNamedEntityList.tsx index 302f7fc48..c040e9b71 100644 --- a/src/components/common/SearchableNamedEntityList.tsx +++ b/src/components/common/SearchableNamedEntityList.tsx @@ -29,6 +29,7 @@ export const useNamedEntityListStyles = makeStyles((theme: Theme) => ({ alignItems: 'center', borderBottom: `1px solid ${separatorColor}`, display: 'flex', + position: 'relative', flexDirection: 'row', padding: `0 ${theme.spacing(3)}px`, '&:first-of-type': { diff --git a/src/models/Task/api.ts b/src/models/Task/api.ts index b85500873..9283bbd01 100644 --- a/src/models/Task/api.ts +++ b/src/models/Task/api.ts @@ -1,8 +1,10 @@ -import { Admin } from 'flyteidl'; -import { getAdminEntity } from 'models/AdminEntity/AdminEntity'; +import { Admin, Core } from 'flyteidl'; +import { getAdminEntity, postAdminEntity } from 'models/AdminEntity/AdminEntity'; import { defaultPaginationConfig } from 'models/AdminEntity/constants'; import { RequestConfig } from 'models/AdminEntity/types'; import { Identifier, IdentifierScope } from 'models/Common/types'; +import { makeNamedEntityPath } from 'models/Common/utils'; +import { TaskExecutionState } from './enums'; import { Task } from './types'; import { makeTaskPath, taskListTransformer } from './utils'; @@ -26,3 +28,28 @@ export const getTask = (id: Identifier, config?: RequestConfig) => }, config, ); + +/** Updates `Task` archive state */ +export const updateTaskState = ( + id: Admin.NamedEntityIdentifier, + newState: TaskExecutionState, + config?: RequestConfig, +) => { + const path = makeNamedEntityPath({ resourceType: Core.ResourceType.TASK, ...id }); + return postAdminEntity( + { + data: { + resourceType: Core.ResourceType.TASK, + id, + metadata: { + state: newState, + }, + }, + path, + requestMessageType: Admin.NamedEntityUpdateRequest, + responseMessageType: Admin.NamedEntityUpdateResponse, + method: 'put', + }, + config, + ); +}; diff --git a/src/models/Task/enums.ts b/src/models/Task/enums.ts new file mode 100644 index 000000000..a10cf5314 --- /dev/null +++ b/src/models/Task/enums.ts @@ -0,0 +1,11 @@ +import { Admin } from 'flyteidl'; + +/** These enums are only aliased and exported from this file. They should + * be imported directly from here to avoid runtime errors when TS processes + * modules individually (such as when running with ts-jest) + */ + +/* It's an ENUM exports, and as such need to be exported as both type and const value */ +/* eslint-disable @typescript-eslint/no-redeclare */ +export type TaskExecutionState = Admin.NamedEntityState; +export const TaskExecutionState = Admin.NamedEntityState; diff --git a/src/test/modelUtils.ts b/src/test/modelUtils.ts index 7c49c1eeb..6b0d15960 100644 --- a/src/test/modelUtils.ts +++ b/src/test/modelUtils.ts @@ -41,3 +41,7 @@ export function createWorkflowName( ) { return createNamedEntity(ResourceType.WORKFLOW, id, metadata); } + +export function createTask(id: NamedEntityIdentifier, metadata?: Partial) { + return createNamedEntity(ResourceType.TASK, id, metadata); +} From 2594abc95e4bea16623c58be8bf9425de393d8ee Mon Sep 17 00:00:00 2001 From: Carina Ursu Date: Fri, 15 Apr 2022 14:45:48 -0700 Subject: [PATCH 2/5] chore: cleanup Signed-off-by: Carina Ursu --- src/components/Project/test/ProjectTask.test.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Project/test/ProjectTask.test.tsx b/src/components/Project/test/ProjectTask.test.tsx index 5ca97cfbb..137c66033 100644 --- a/src/components/Project/test/ProjectTask.test.tsx +++ b/src/components/Project/test/ProjectTask.test.tsx @@ -108,11 +108,11 @@ describe('ProjectTasks', () => { const cancelButton = await findAllByText('Cancel'); await waitFor(() => expect(cancelButton.length).toEqual(1)); - const confirmarchiveButton = cancelButton?.[0]?.parentElement?.parentElement?.children[0]!; + const confirmArchiveButton = cancelButton?.[0]?.parentElement?.parentElement?.children[0]!; - expect(confirmarchiveButton).toBeTruthy(); + expect(confirmArchiveButton).toBeTruthy(); - fireEvent.click(confirmarchiveButton!); + fireEvent.click(confirmArchiveButton!); await waitFor(() => { expect(updateTaskState).toHaveBeenCalledTimes(1); From f291ad0767fdbd03bf818b9a3e76919f04225490 Mon Sep 17 00:00:00 2001 From: Carina Ursu Date: Fri, 15 Apr 2022 15:12:06 -0700 Subject: [PATCH 3/5] chore: cleanup Signed-off-by: Carina Ursu --- src/components/Task/utils.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/Task/utils.ts b/src/components/Task/utils.ts index 9845d4427..1189e7134 100644 --- a/src/components/Task/utils.ts +++ b/src/components/Task/utils.ts @@ -1,8 +1,5 @@ -// import { Task } from 'models/Task/types'; - import { NamedEntity } from 'models/Common/types'; import { TaskExecutionState } from 'models/Task/enums'; -import { Task } from 'models/Task/types'; function isTaskStateArchive(task: NamedEntity): boolean { const state = task?.metadata?.state ?? null; From 327ba436cb30a7a0331238ba2b607b180afbc8c4 Mon Sep 17 00:00:00 2001 From: Carina Ursu Date: Fri, 15 Apr 2022 19:06:04 -0700 Subject: [PATCH 4/5] chore: fixes Signed-off-by: Carina Ursu --- src/components/Project/test/ProjectTask.test.tsx | 14 ++++++++++++-- .../common/FilterableNamedEntityList.tsx | 1 + src/test/modelUtils.ts | 6 +----- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/components/Project/test/ProjectTask.test.tsx b/src/components/Project/test/ProjectTask.test.tsx index 137c66033..02d3d0061 100644 --- a/src/components/Project/test/ProjectTask.test.tsx +++ b/src/components/Project/test/ProjectTask.test.tsx @@ -4,15 +4,25 @@ import { mockAPIContextValue } from 'components/data/__mocks__/apiContext'; import { Admin } from 'flyteidl'; import { FilterOperationName } from 'models/AdminEntity/types'; import { getUserProfile, listNamedEntities } from 'models/Common/api'; -import { NamedEntity, UserProfile } from 'models/Common/types'; +import { + NamedEntity, + NamedEntityIdentifier, + NamedEntityMetadata, + ResourceType, + UserProfile, +} from 'models/Common/types'; import { updateTaskState } from 'models/Task/api'; import * as React from 'react'; import { QueryClient, QueryClientProvider } from 'react-query'; import { MemoryRouter } from 'react-router'; -import { createTask } from 'test/modelUtils'; +import { createNamedEntity } from 'test/modelUtils'; import { createTestQueryClient } from 'test/utils'; import { ProjectTasks } from '../ProjectTasks'; +export function createTask(id: NamedEntityIdentifier, metadata?: Partial) { + return createNamedEntity(ResourceType.TASK, id, metadata); +} + const sampleUserProfile: UserProfile = { subject: 'subject', } as UserProfile; diff --git a/src/components/common/FilterableNamedEntityList.tsx b/src/components/common/FilterableNamedEntityList.tsx index cf608ebc4..b4ffe8291 100644 --- a/src/components/common/FilterableNamedEntityList.tsx +++ b/src/components/common/FilterableNamedEntityList.tsx @@ -84,6 +84,7 @@ export const FilterableNamedEntityList: React.FC const debouncedSearch = debounce(() => setSearchString(searchString), 1000); debouncedSearch(); }; + const onClear = () => setSearch(''); const renderItems = (results: SearchResult[]) => ( diff --git a/src/test/modelUtils.ts b/src/test/modelUtils.ts index 6b0d15960..e96584444 100644 --- a/src/test/modelUtils.ts +++ b/src/test/modelUtils.ts @@ -12,7 +12,7 @@ const defaultMetadata = { state: Admin.NamedEntityState.NAMED_ENTITY_ACTIVE, }; -function createNamedEntity( +export function createNamedEntity( resourceType: ResourceType, id: NamedEntityIdentifier, metadataOverrides?: Partial, @@ -41,7 +41,3 @@ export function createWorkflowName( ) { return createNamedEntity(ResourceType.WORKFLOW, id, metadata); } - -export function createTask(id: NamedEntityIdentifier, metadata?: Partial) { - return createNamedEntity(ResourceType.TASK, id, metadata); -} From 5e444bc53eae745c37b81f30618a50ae126bbb17 Mon Sep 17 00:00:00 2001 From: Carina Ursu Date: Fri, 15 Apr 2022 19:30:29 -0700 Subject: [PATCH 5/5] chore: cleanup Signed-off-by: Carina Ursu --- .../Navigation/SearchableProjectList.tsx | 13 +----- .../Project/test/ProjectTask.test.tsx | 4 +- .../Project/test/ProjectWorkflows.test.tsx | 4 +- .../Task/SearchableTaskNameList.tsx | 8 ++-- .../Task/useTaskShowArchivedState.ts | 6 +-- src/components/Task/utils.ts | 4 +- .../Workflow/SearchableWorkflowNameList.tsx | 43 +++++++++---------- .../filters/useWorkflowShowArchivedState.ts | 6 +-- src/components/Workflow/types.ts | 6 +-- src/components/Workflow/utils.ts | 4 +- .../common/FilterableNamedEntityList.tsx | 13 +----- src/components/common/NoResults.tsx | 18 ++++++++ .../common/SearchableNamedEntityList.tsx | 13 +----- src/components/common/strings.ts | 1 + src/models/Task/api.ts | 4 +- src/models/Workflow/api.ts | 4 +- src/models/Workflow/enums.ts | 11 ----- src/models/__mocks__/sampleTaskNames.ts | 5 ++- src/models/__mocks__/sampleWorkflowNames.ts | 4 +- src/models/{Task => }/enums.ts | 5 +-- src/test/modelUtils.ts | 5 ++- 21 files changed, 77 insertions(+), 104 deletions(-) create mode 100644 src/components/common/NoResults.tsx delete mode 100644 src/models/Workflow/enums.ts rename src/models/{Task => }/enums.ts (59%) diff --git a/src/components/Navigation/SearchableProjectList.tsx b/src/components/Navigation/SearchableProjectList.tsx index 15e420e93..731a99d9a 100644 --- a/src/components/Navigation/SearchableProjectList.tsx +++ b/src/components/Navigation/SearchableProjectList.tsx @@ -1,6 +1,7 @@ import { Fade, Tooltip, Typography } from '@material-ui/core'; import { makeStyles, Theme } from '@material-ui/core/styles'; import classnames from 'classnames'; +import { NoResults } from 'components/common/NoResults'; import { SearchableList, SearchResult } from 'components/common/SearchableList'; import { useCommonStyles } from 'components/common/styles'; import { defaultProjectDescription } from 'components/SelectProject/constants'; @@ -16,12 +17,6 @@ const useStyles = makeStyles((theme: Theme) => ({ flex: '1 0 0', fontWeight: 'bold', }, - noResults: { - color: theme.palette.text.disabled, - display: 'flex', - justifyContent: 'center', - marginTop: theme.spacing(4), - }, searchResult: { alignItems: 'center', borderLeft: '4px solid transparent', @@ -44,12 +39,6 @@ const useStyles = makeStyles((theme: Theme) => ({ type ProjectSelectedCallback = (project: Project) => void; -const NoResults: React.FC = () => ( - - No matching results - -); - interface SearchResultsProps { onProjectSelected: ProjectSelectedCallback; results: SearchResult[]; diff --git a/src/components/Project/test/ProjectTask.test.tsx b/src/components/Project/test/ProjectTask.test.tsx index 02d3d0061..9642f01e8 100644 --- a/src/components/Project/test/ProjectTask.test.tsx +++ b/src/components/Project/test/ProjectTask.test.tsx @@ -1,7 +1,6 @@ import { fireEvent, render, waitFor } from '@testing-library/react'; import { APIContext } from 'components/data/apiContext'; import { mockAPIContextValue } from 'components/data/__mocks__/apiContext'; -import { Admin } from 'flyteidl'; import { FilterOperationName } from 'models/AdminEntity/types'; import { getUserProfile, listNamedEntities } from 'models/Common/api'; import { @@ -11,6 +10,7 @@ import { ResourceType, UserProfile, } from 'models/Common/types'; +import { NamedEntityState } from 'models/enums'; import { updateTaskState } from 'models/Task/api'; import * as React from 'react'; import { QueryClient, QueryClientProvider } from 'react-query'; @@ -82,7 +82,7 @@ describe('ProjectTasks', () => { { key: 'state', operation: FilterOperationName.EQ, - value: Admin.NamedEntityState.NAMED_ENTITY_ACTIVE, + value: NamedEntityState.NAMED_ENTITY_ACTIVE, }, ], }), diff --git a/src/components/Project/test/ProjectWorkflows.test.tsx b/src/components/Project/test/ProjectWorkflows.test.tsx index f47090c81..49a43be48 100644 --- a/src/components/Project/test/ProjectWorkflows.test.tsx +++ b/src/components/Project/test/ProjectWorkflows.test.tsx @@ -1,10 +1,10 @@ import { fireEvent, render, waitFor } from '@testing-library/react'; import { APIContext } from 'components/data/apiContext'; import { mockAPIContextValue } from 'components/data/__mocks__/apiContext'; -import { Admin } from 'flyteidl'; import { FilterOperationName } from 'models/AdminEntity/types'; import { getUserProfile, listNamedEntities } from 'models/Common/api'; import { NamedEntity, UserProfile } from 'models/Common/types'; +import { NamedEntityState } from 'models/enums'; import * as React from 'react'; import { QueryClient, QueryClientProvider } from 'react-query'; import { MemoryRouter } from 'react-router'; @@ -63,7 +63,7 @@ describe('ProjectWorkflows', () => { { key: 'state', operation: FilterOperationName.EQ, - value: Admin.NamedEntityState.NAMED_ENTITY_ACTIVE, + value: NamedEntityState.NAMED_ENTITY_ACTIVE, }, ], }), diff --git a/src/components/Task/SearchableTaskNameList.tsx b/src/components/Task/SearchableTaskNameList.tsx index cd99741c9..b1d3fe5f9 100644 --- a/src/components/Task/SearchableTaskNameList.tsx +++ b/src/components/Task/SearchableTaskNameList.tsx @@ -21,8 +21,8 @@ import ArchiveOutlined from '@material-ui/icons/ArchiveOutlined'; import { useSnackbar } from 'notistack'; import { updateTaskState } from 'models/Task/api'; import { useMutation } from 'react-query'; -import { TaskExecutionState } from 'models/Task/enums'; import { FilterableNamedEntityList } from 'components/common/FilterableNamedEntityList'; +import { NamedEntityState } from 'models/enums'; import { useLatestTaskVersion } from './useLatestTask'; import t from '../Executions/Tables/WorkflowExecutionTable/strings'; import { SimpleTaskInterface } from './SimpleTaskInterface'; @@ -137,7 +137,7 @@ const SimpleTaskActions: React.FC = ({ item, setShowItem const [isUpdating, setIsUpdating] = useState(false); const [showConfirmation, setShowConfirmation] = useState(false); - const mutation = useMutation((newState: TaskExecutionState) => updateTaskState(id, newState), { + const mutation = useMutation((newState: NamedEntityState) => updateTaskState(id, newState), { onMutate: () => setIsUpdating(true), onSuccess: () => { enqueueSnackbar(t('archiveSuccess', !isArchived), { @@ -167,8 +167,8 @@ const SimpleTaskActions: React.FC = ({ item, setShowItem event.preventDefault(); mutation.mutate( isTaskArchived(item) - ? TaskExecutionState.NAMED_ENTITY_ACTIVE - : TaskExecutionState.NAMED_ENTITY_ARCHIVED, + ? NamedEntityState.NAMED_ENTITY_ACTIVE + : NamedEntityState.NAMED_ENTITY_ARCHIVED, ); }; diff --git a/src/components/Task/useTaskShowArchivedState.ts b/src/components/Task/useTaskShowArchivedState.ts index 35fa974e8..93b6eb289 100644 --- a/src/components/Task/useTaskShowArchivedState.ts +++ b/src/components/Task/useTaskShowArchivedState.ts @@ -1,6 +1,6 @@ import { useState } from 'react'; import { FilterOperation, FilterOperationName } from 'models/AdminEntity/types'; -import { TaskExecutionState } from 'models/Task/enums'; +import { NamedEntityState } from 'models/enums'; interface ArchiveFilterState { showArchived: boolean; @@ -20,8 +20,8 @@ export function useTaskShowArchivedState(): ArchiveFilterState { key: 'state', operation: FilterOperationName.EQ, value: showArchived - ? TaskExecutionState.NAMED_ENTITY_ARCHIVED - : TaskExecutionState.NAMED_ENTITY_ACTIVE, + ? NamedEntityState.NAMED_ENTITY_ARCHIVED + : NamedEntityState.NAMED_ENTITY_ACTIVE, }; }; diff --git a/src/components/Task/utils.ts b/src/components/Task/utils.ts index 1189e7134..50ac2de1b 100644 --- a/src/components/Task/utils.ts +++ b/src/components/Task/utils.ts @@ -1,9 +1,9 @@ import { NamedEntity } from 'models/Common/types'; -import { TaskExecutionState } from 'models/Task/enums'; +import { NamedEntityState } from 'models/enums'; function isTaskStateArchive(task: NamedEntity): boolean { const state = task?.metadata?.state ?? null; - return !!state && state === TaskExecutionState.NAMED_ENTITY_ARCHIVED; + return !!state && state === NamedEntityState.NAMED_ENTITY_ARCHIVED; } export function isTaskArchived(task: NamedEntity): boolean { diff --git a/src/components/Workflow/SearchableWorkflowNameList.tsx b/src/components/Workflow/SearchableWorkflowNameList.tsx index 14cd92f9d..2719a501a 100644 --- a/src/components/Workflow/SearchableWorkflowNameList.tsx +++ b/src/components/Workflow/SearchableWorkflowNameList.tsx @@ -23,7 +23,7 @@ import { import UnarchiveOutline from '@material-ui/icons/UnarchiveOutlined'; import ArchiveOutlined from '@material-ui/icons/ArchiveOutlined'; import { useMutation } from 'react-query'; -import { WorkflowExecutionState } from 'models/Workflow/enums'; +import { NamedEntityState } from 'models/enums'; import { updateWorkflowState } from 'models/Workflow/api'; import { useState } from 'react'; import { useSnackbar } from 'notistack'; @@ -175,27 +175,24 @@ const SearchableWorkflowNameItemActions: React.FC(false); const [showConfirmation, setShowConfirmation] = useState(false); - const mutation = useMutation( - (newState: WorkflowExecutionState) => updateWorkflowState(id, newState), - { - onMutate: () => setIsUpdating(true), - onSuccess: () => { - enqueueSnackbar(t('archiveSuccess', !isArchived), { - variant: 'success', - }); - setHideItem(true); - }, - onError: () => { - enqueueSnackbar(`${mutation.error ?? t('archiveError', !isArchived)}`, { - variant: 'error', - }); - }, - onSettled: () => { - setShowConfirmation(false); - setIsUpdating(false); - }, + const mutation = useMutation((newState: NamedEntityState) => updateWorkflowState(id, newState), { + onMutate: () => setIsUpdating(true), + onSuccess: () => { + enqueueSnackbar(t('archiveSuccess', !isArchived), { + variant: 'success', + }); + setHideItem(true); }, - ); + onError: () => { + enqueueSnackbar(`${mutation.error ?? t('archiveError', !isArchived)}`, { + variant: 'error', + }); + }, + onSettled: () => { + setShowConfirmation(false); + setIsUpdating(false); + }, + }); const onArchiveClick = (event: React.MouseEvent) => { event.stopPropagation(); @@ -208,8 +205,8 @@ const SearchableWorkflowNameItemActions: React.FC ({ marginBottom: theme.spacing(2), width: '100%', }, - noResults: { - color: theme.palette.text.disabled, - display: 'flex', - justifyContent: 'center', - marginTop: theme.spacing(6), - }, filterGroup: { display: 'flex', flexWrap: 'nowrap', @@ -28,12 +23,6 @@ export const useStyles = makeStyles((theme: Theme) => ({ }, })); -const NoResults: React.FC = () => ( - - No matching results - -); - type ItemRenderer = (item: SearchResult) => React.ReactNode; interface SearchResultsProps { diff --git a/src/components/common/NoResults.tsx b/src/components/common/NoResults.tsx new file mode 100644 index 000000000..1fa1fccb6 --- /dev/null +++ b/src/components/common/NoResults.tsx @@ -0,0 +1,18 @@ +import { makeStyles, Theme, Typography } from '@material-ui/core'; +import * as React from 'react'; +import t from './strings'; + +const useStyles = makeStyles((theme: Theme) => ({ + container: { + color: theme.palette.text.disabled, + display: 'flex', + justifyContent: 'center', + marginTop: theme.spacing(4), + }, +})); + +export const NoResults: React.FC = () => ( + + {t('noMatchingResults')} + +); diff --git a/src/components/common/SearchableNamedEntityList.tsx b/src/components/common/SearchableNamedEntityList.tsx index c040e9b71..6a855080a 100644 --- a/src/components/common/SearchableNamedEntityList.tsx +++ b/src/components/common/SearchableNamedEntityList.tsx @@ -3,6 +3,7 @@ import { makeStyles, Theme } from '@material-ui/core/styles'; import { listhoverColor, separatorColor } from 'components/Theme/constants'; import { NamedEntity } from 'models/Common/types'; import * as React from 'react'; +import { NoResults } from './NoResults'; import { SearchableList, SearchResult } from './SearchableList'; import { useCommonStyles } from './styles'; @@ -19,12 +20,6 @@ export const useNamedEntityListStyles = makeStyles((theme: Theme) => ({ color: theme.palette.grey[500], flex: '0 0 auto', }, - noResults: { - color: theme.palette.text.disabled, - display: 'flex', - justifyContent: 'center', - marginTop: theme.spacing(6), - }, searchResult: { alignItems: 'center', borderBottom: `1px solid ${separatorColor}`, @@ -52,12 +47,6 @@ export interface SearchableNamedEntity extends NamedEntity { const nameKey = ({ id: { domain, name, project } }: NamedEntity) => `${domain}/${name}/${project}`; -const NoResults: React.FC = () => ( - - No matching results - -); - type ItemRenderer = (item: SearchResult) => React.ReactNode; interface SearchResultsProps { diff --git a/src/components/common/strings.ts b/src/components/common/strings.ts index e893ff886..fe53e1d03 100644 --- a/src/components/common/strings.ts +++ b/src/components/common/strings.ts @@ -11,6 +11,7 @@ const str = { rawDataHeader: 'Raw output data config', securityContextHeader: 'Security Context', serviceAccountHeader: 'Service Account', + noMatchingResults: 'No matching results', }; export { patternKey } from 'basics/Locale'; diff --git a/src/models/Task/api.ts b/src/models/Task/api.ts index 9283bbd01..68eee8349 100644 --- a/src/models/Task/api.ts +++ b/src/models/Task/api.ts @@ -4,7 +4,7 @@ import { defaultPaginationConfig } from 'models/AdminEntity/constants'; import { RequestConfig } from 'models/AdminEntity/types'; import { Identifier, IdentifierScope } from 'models/Common/types'; import { makeNamedEntityPath } from 'models/Common/utils'; -import { TaskExecutionState } from './enums'; +import { NamedEntityState } from 'models/enums'; import { Task } from './types'; import { makeTaskPath, taskListTransformer } from './utils'; @@ -32,7 +32,7 @@ export const getTask = (id: Identifier, config?: RequestConfig) => /** Updates `Task` archive state */ export const updateTaskState = ( id: Admin.NamedEntityIdentifier, - newState: TaskExecutionState, + newState: NamedEntityState, config?: RequestConfig, ) => { const path = makeNamedEntityPath({ resourceType: Core.ResourceType.TASK, ...id }); diff --git a/src/models/Workflow/api.ts b/src/models/Workflow/api.ts index 72094e2d9..8f8c6c1d0 100644 --- a/src/models/Workflow/api.ts +++ b/src/models/Workflow/api.ts @@ -4,7 +4,7 @@ import { defaultPaginationConfig } from 'models/AdminEntity/constants'; import { RequestConfig } from 'models/AdminEntity/types'; import { Identifier, IdentifierScope } from 'models/Common/types'; import { makeNamedEntityPath } from 'models/Common/utils'; -import { WorkflowExecutionState } from './enums'; +import { NamedEntityState } from 'models/enums'; import { Workflow } from './types'; import { makeWorkflowPath, workflowListTransformer } from './utils'; @@ -32,7 +32,7 @@ export const getWorkflow = (id: Identifier, config?: RequestConfig) => /** Updates `Workflow` archive state */ export const updateWorkflowState = ( id: Admin.NamedEntityIdentifier, - newState: WorkflowExecutionState, + newState: NamedEntityState, config?: RequestConfig, ) => { const path = makeNamedEntityPath({ resourceType: Core.ResourceType.WORKFLOW, ...id }); diff --git a/src/models/Workflow/enums.ts b/src/models/Workflow/enums.ts deleted file mode 100644 index 4b453637f..000000000 --- a/src/models/Workflow/enums.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Admin } from 'flyteidl'; - -/** These enums are only aliased and exported from this file. They should - * be imported directly from here to avoid runtime errors when TS processes - * modules individually (such as when running with ts-jest) - */ - -/* It's an ENUM exports, and as such need to be exported as both type and const value */ -/* eslint-disable @typescript-eslint/no-redeclare */ -export type WorkflowExecutionState = Admin.NamedEntityState; -export const WorkflowExecutionState = Admin.NamedEntityState; diff --git a/src/models/__mocks__/sampleTaskNames.ts b/src/models/__mocks__/sampleTaskNames.ts index a90e2cd37..38b87e22e 100644 --- a/src/models/__mocks__/sampleTaskNames.ts +++ b/src/models/__mocks__/sampleTaskNames.ts @@ -1,5 +1,6 @@ -import { Admin, Core } from 'flyteidl'; +import { Core } from 'flyteidl'; import { NamedEntity, NamedEntityIdentifier } from 'models/Common/types'; +import { NamedEntityState } from 'models/enums'; export const sampleTaskIds: NamedEntityIdentifier[] = [ 'app.complex_workflows.custom_image.task_object', @@ -53,6 +54,6 @@ export const sampleTaskNames: NamedEntity[] = sampleTaskIds.map((id) => ({ resourceType: Core.ResourceType.TASK, metadata: { description: `A description for ${id.name}`, - state: Admin.NamedEntityState.NAMED_ENTITY_ACTIVE, + state: NamedEntityState.NAMED_ENTITY_ACTIVE, }, })); diff --git a/src/models/__mocks__/sampleWorkflowNames.ts b/src/models/__mocks__/sampleWorkflowNames.ts index f80342771..e27c9d411 100644 --- a/src/models/__mocks__/sampleWorkflowNames.ts +++ b/src/models/__mocks__/sampleWorkflowNames.ts @@ -1,5 +1,5 @@ import { WorkflowListStructureItem } from 'components/Workflow/types'; -import { WorkflowExecutionState } from 'models/Workflow/enums'; +import { NamedEntityState } from 'models/enums'; import { WorkflowId } from 'models/Workflow/types'; export const sampleWorkflowIds: WorkflowId[] = [ @@ -38,5 +38,5 @@ export const sampleWorkflowIds: WorkflowId[] = [ export const sampleWorkflowNames: WorkflowListStructureItem[] = sampleWorkflowIds.map((id) => ({ id, description: '', - state: WorkflowExecutionState.NAMED_ENTITY_ACTIVE, + state: NamedEntityState.NAMED_ENTITY_ACTIVE, })); diff --git a/src/models/Task/enums.ts b/src/models/enums.ts similarity index 59% rename from src/models/Task/enums.ts rename to src/models/enums.ts index a10cf5314..964afc4c6 100644 --- a/src/models/Task/enums.ts +++ b/src/models/enums.ts @@ -5,7 +5,6 @@ import { Admin } from 'flyteidl'; * modules individually (such as when running with ts-jest) */ -/* It's an ENUM exports, and as such need to be exported as both type and const value */ /* eslint-disable @typescript-eslint/no-redeclare */ -export type TaskExecutionState = Admin.NamedEntityState; -export const TaskExecutionState = Admin.NamedEntityState; +export type NamedEntityState = Admin.NamedEntityState; +export const NamedEntityState = Admin.NamedEntityState; diff --git a/src/test/modelUtils.ts b/src/test/modelUtils.ts index e96584444..cac44f7d5 100644 --- a/src/test/modelUtils.ts +++ b/src/test/modelUtils.ts @@ -1,4 +1,4 @@ -import { Admin, Core } from 'flyteidl'; +import { Core } from 'flyteidl'; import { Identifier, NamedEntity, @@ -6,10 +6,11 @@ import { NamedEntityMetadata, ResourceType, } from 'models/Common/types'; +import { NamedEntityState } from 'models/enums'; const defaultMetadata = { description: '', - state: Admin.NamedEntityState.NAMED_ENTITY_ACTIVE, + state: NamedEntityState.NAMED_ENTITY_ACTIVE, }; export function createNamedEntity(