From 3e1db0d4414926b511546acaf2da7ceddb64e946 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Fri, 1 Mar 2024 08:29:49 -0800 Subject: [PATCH] Add delete modal; add empty list msg (#93) Signed-off-by: Tyler Ohlsen (cherry picked from commit 454ee82bbcfb1e45dcc41cf4514b0583e575fec0) --- .../delete_workflow_modal.tsx | 45 +++++++++ public/general_components/index.ts | 1 + public/pages/workflows/empty_list_message.tsx | 43 +++++++++ .../workflows/workflow_list/workflow_list.tsx | 94 ++++++++++++------- public/pages/workflows/workflows.tsx | 15 ++- 5 files changed, 161 insertions(+), 37 deletions(-) create mode 100644 public/general_components/delete_workflow_modal.tsx create mode 100644 public/pages/workflows/empty_list_message.tsx diff --git a/public/general_components/delete_workflow_modal.tsx b/public/general_components/delete_workflow_modal.tsx new file mode 100644 index 00000000..ce718090 --- /dev/null +++ b/public/general_components/delete_workflow_modal.tsx @@ -0,0 +1,45 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { + EuiButton, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiText, +} from '@elastic/eui'; +import { Workflow } from '../../common'; + +interface DeleteWorkflowModalProps { + workflow: Workflow; + onClose: () => void; + onConfirm: () => void; +} + +/** + * A general delete workflow modal. + */ +export function DeleteWorkflowModal(props: DeleteWorkflowModalProps) { + return ( + + + +

{`Delete ${props.workflow.name}?`}

+
+
+ + The workflow will be permanently deleted. + + + + Confirm + + +
+ ); +} diff --git a/public/general_components/index.ts b/public/general_components/index.ts index 20e4153c..7fa24f60 100644 --- a/public/general_components/index.ts +++ b/public/general_components/index.ts @@ -4,3 +4,4 @@ */ export { MultiSelectFilter } from './multi_select_filter'; +export { DeleteWorkflowModal } from './delete_workflow_modal'; diff --git a/public/pages/workflows/empty_list_message.tsx b/public/pages/workflows/empty_list_message.tsx new file mode 100644 index 00000000..79e6eda4 --- /dev/null +++ b/public/pages/workflows/empty_list_message.tsx @@ -0,0 +1,43 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; + +interface EmptyListMessageProps { + onClickNewWorkflow: () => void; +} + +export function EmptyListMessage(props: EmptyListMessageProps) { + return ( + + + + + + +

No workflows found

+
+
+ + + Create a workflow to start building and testing your application. + + + + + New workflow + + +
+ ); +} diff --git a/public/pages/workflows/workflow_list/workflow_list.tsx b/public/pages/workflows/workflow_list/workflow_list.tsx index 0f6c3fa0..3e5b4484 100644 --- a/public/pages/workflows/workflow_list/workflow_list.tsx +++ b/public/pages/workflows/workflow_list/workflow_list.tsx @@ -18,7 +18,10 @@ import { import { AppState, deleteWorkflow } from '../../../store'; import { Workflow } from '../../../../common'; import { columns } from './columns'; -import { MultiSelectFilter } from '../../../general_components'; +import { + DeleteWorkflowModal, + MultiSelectFilter, +} from '../../../general_components'; import { getStateOptions } from '../../../utils'; interface WorkflowListProps {} @@ -39,6 +42,16 @@ export function WorkflowList(props: WorkflowListProps) { (state: AppState) => state.workflows ); + // delete workflow state + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + const [workflowToDelete, setWorkflowToDelete] = useState< + Workflow | undefined + >(undefined); + function clearDeleteState() { + setWorkflowToDelete(undefined); + setIsDeleteModalOpen(false); + } + // search bar state const [searchQuery, setSearchQuery] = useState(''); const debounceSearchQuery = debounce((query: string) => { @@ -70,48 +83,57 @@ export function WorkflowList(props: WorkflowListProps) { icon: 'trash', color: 'danger', onClick: (item: Workflow) => { - dispatch(deleteWorkflow(item.id)); + setWorkflowToDelete(item); + setIsDeleteModalOpen(true); }, }, ]; return ( - - - - - debounceSearchQuery(e.target.value)} + <> + {isDeleteModalOpen && workflowToDelete !== undefined && ( + { + clearDeleteState(); + }} + onConfirm={() => { + dispatch(deleteWorkflow(workflowToDelete.id)); + clearDeleteState(); + }} + /> + )} + + + + + debounceSearchQuery(e.target.value)} + /> + + - - + + + + items={filteredWorkflows} + rowHeader="name" + // @ts-ignore + columns={columns(tableActions)} + sorting={sorting} + pagination={true} + message={loading === true ? : null} + hasActions={true} /> - - - - - items={filteredWorkflows} - rowHeader="name" - // @ts-ignore - columns={columns(tableActions)} - sorting={sorting} - pagination={true} - message={ - loading === true ? ( - - ) : ( - 'No existing workflows found' - ) - } - hasActions={true} - /> - - + + + ); } diff --git a/public/pages/workflows/workflows.tsx b/public/pages/workflows/workflows.tsx index 92d7535f..64f5f3f2 100644 --- a/public/pages/workflows/workflows.tsx +++ b/public/pages/workflows/workflows.tsx @@ -20,6 +20,7 @@ import { getCore } from '../../services'; import { WorkflowList } from './workflow_list'; import { NewWorkflow } from './new_workflow'; import { AppState, searchWorkflows } from '../../store'; +import { EmptyListMessage } from './empty_list_message'; export interface WorkflowsRouterProps {} @@ -48,7 +49,9 @@ function replaceActiveTab(activeTab: string, props: WorkflowsProps) { */ export function Workflows(props: WorkflowsProps) { const dispatch = useDispatch(); - const { workflows } = useSelector((state: AppState) => state.workflows); + const { workflows, loading } = useSelector( + (state: AppState) => state.workflows + ); const tabFromUrl = queryString.parse(useLocation().search)[ ACTIVE_TAB_PARAM @@ -130,6 +133,16 @@ export function Workflows(props: WorkflowsProps) { {selectedTabId === WORKFLOWS_TAB.MANAGE && } {selectedTabId === WORKFLOWS_TAB.CREATE && } + {selectedTabId === WORKFLOWS_TAB.MANAGE && + Object.values(workflows).length === 0 && + !loading && ( + { + setSelectedTabId(WORKFLOWS_TAB.CREATE); + replaceActiveTab(WORKFLOWS_TAB.CREATE, props); + }} + /> + )}