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); + }} + /> + )}
{`Delete ${props.workflow.name}?`}