diff --git a/changelogs/fragments/7640.yml b/changelogs/fragments/7640.yml new file mode 100644 index 000000000000..5a93e3bdb5d9 --- /dev/null +++ b/changelogs/fragments/7640.yml @@ -0,0 +1,2 @@ +feat: +- [Workspace] Update workspace list page table ([#7640](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7640)) \ No newline at end of file diff --git a/src/plugins/workspace/public/components/delete_workspace_modal/__snapshots__/delete_workspace_modal.test.tsx.snap b/src/plugins/workspace/public/components/delete_workspace_modal/__snapshots__/delete_workspace_modal.test.tsx.snap index c34ad92fcc2e..604d6a5edb66 100644 --- a/src/plugins/workspace/public/components/delete_workspace_modal/__snapshots__/delete_workspace_modal.test.tsx.snap +++ b/src/plugins/workspace/public/components/delete_workspace_modal/__snapshots__/delete_workspace_modal.test.tsx.snap @@ -47,49 +47,7 @@ exports[`DeleteWorkspaceModal should render normally 1`] = ` >
-
-

- The following workspace will be permanently deleted. This action cannot be undone. -

-
+ />
{ const onDeleteSuccessFn = jest.fn(); const newProps = { ...defaultProps, - selectedWorkspace: { - id: 'test', - name: 'test', - }, + selectedWorkspaces: [ + { + id: 'test', + name: 'test', + }, + ], onClose: onCloseFn, onDeleteSuccess: onDeleteSuccessFn, }; @@ -99,10 +101,10 @@ describe('DeleteWorkspaceModal', () => { }); }); - it('should not call deleteWorkspace if passed selectedWorkspace is null', async () => { + it('should not call deleteWorkspace modal if passed selectedWorkspace is null', async () => { const newProps = { ...defaultProps, - selectedWorkspace: null, + selectedWorkspace: [], }; const deleteFn = jest.fn().mockReturnValue({ success: true, @@ -114,26 +116,20 @@ describe('DeleteWorkspaceModal', () => { delete: deleteFn, }, }; - const { getByTestId, findByTestId } = render( - getWrapWorkspaceDeleteModalInContext(newProps, newServices) - ); - await findByTestId('delete-workspace-modal-input'); - const input = getByTestId('delete-workspace-modal-input'); - fireEvent.change(input, { - target: { value: 'delete' }, - }); - const confirmButton = getByTestId('delete-workspace-modal-confirm'); - fireEvent.click(confirmButton); - expect(deleteFn).not.toHaveBeenCalled(); + const { queryByTestId } = render(getWrapWorkspaceDeleteModalInContext(newProps, newServices)); + const input = queryByTestId('delete-workspace-modal-input'); + expect(input).not.toBeInTheDocument(); }); - it('should add danger is returned data is unsuccess', async () => { + it('should add danger if returned data is unsuccess', async () => { const newProps = { ...defaultProps, - selectedWorkspace: { - id: 'test', - name: 'test', - }, + selectedWorkspaces: [ + { + id: 'test', + name: 'test', + }, + ], }; const deleteFn = jest.fn().mockReturnValue({ success: false, @@ -165,10 +161,12 @@ describe('DeleteWorkspaceModal', () => { it('confirm button should be disabled if not input delete', async () => { const newProps = { ...defaultProps, - selectedWorkspace: { - id: 'test', - name: 'test', - }, + selectedWorkspaces: [ + { + id: 'test', + name: 'test', + }, + ], }; const deleteFn = jest.fn().mockReturnValue({ success: false, @@ -186,7 +184,7 @@ describe('DeleteWorkspaceModal', () => { await findByTestId('delete-workspace-modal-input'); const input = getByTestId('delete-workspace-modal-input'); fireEvent.change(input, { - target: { value: 'delet' }, + target: { value: 'delete' }, }); const confirmButton = getByTestId('delete-workspace-modal-confirm'); expect(confirmButton.hasAttribute('disabled')); @@ -196,10 +194,12 @@ describe('DeleteWorkspaceModal', () => { const onCloseFn = jest.fn(); const newProps = { ...defaultProps, - selectedWorkspace: { - id: 'test', - name: 'test', - }, + selectedWorkspaces: [ + { + id: 'test', + name: 'test', + }, + ], onclose: onCloseFn, }; const deleteFn = jest.fn().mockImplementation(() => { diff --git a/src/plugins/workspace/public/components/delete_workspace_modal/delete_workspace_modal.tsx b/src/plugins/workspace/public/components/delete_workspace_modal/delete_workspace_modal.tsx index e6a3558a50d2..01a98d82bdd3 100644 --- a/src/plugins/workspace/public/components/delete_workspace_modal/delete_workspace_modal.tsx +++ b/src/plugins/workspace/public/components/delete_workspace_modal/delete_workspace_modal.tsx @@ -23,49 +23,53 @@ import { WorkspaceClient } from '../../workspace_client'; export interface DeleteWorkspaceModalProps { onClose: () => void; - selectedWorkspace?: WorkspaceAttribute | null; + selectedWorkspaces?: WorkspaceAttribute[]; onDeleteSuccess?: () => void; } export function DeleteWorkspaceModal(props: DeleteWorkspaceModalProps) { const [value, setValue] = useState(''); - const { onClose, selectedWorkspace, onDeleteSuccess } = props; + const { onClose, selectedWorkspaces, onDeleteSuccess } = props; const { services: { notifications, workspaceClient }, } = useOpenSearchDashboards<{ workspaceClient: WorkspaceClient }>(); - const deleteWorkspace = async () => { - if (selectedWorkspace?.id) { - let result; - try { - result = await workspaceClient.delete(selectedWorkspace?.id); - } catch (error) { - notifications?.toasts.addDanger({ - title: i18n.translate('workspace.delete.failed', { - defaultMessage: 'Failed to delete workspace', - }), - text: error instanceof Error ? error.message : JSON.stringify(error), - }); - return onClose(); - } - if (result?.success) { - notifications?.toasts.addSuccess({ - title: i18n.translate('workspace.delete.success', { - defaultMessage: 'Delete workspace successfully', - }), - }); - onClose(); - if (onDeleteSuccess) { - onDeleteSuccess(); + const deleteWorkspaces = async () => { + if (selectedWorkspaces && selectedWorkspaces.length > 0) { + selectedWorkspaces.forEach(async (selectedWorkspace) => { + if (selectedWorkspace?.id) { + let result; + try { + result = await workspaceClient.delete(selectedWorkspace?.id); + } catch (error) { + notifications?.toasts.addDanger({ + title: i18n.translate('workspace.delete.failed', { + defaultMessage: 'Failed to delete workspace', + }), + text: error instanceof Error ? error.message : JSON.stringify(error), + }); + return onClose(); + } + if (result?.success) { + notifications?.toasts.addSuccess({ + title: i18n.translate('workspace.delete.success', { + defaultMessage: 'Delete workspace successfully', + }), + }); + onClose(); + if (onDeleteSuccess) { + onDeleteSuccess(); + } + } else { + notifications?.toasts.addDanger({ + title: i18n.translate('workspace.delete.failed', { + defaultMessage: 'Failed to delete workspace', + }), + text: result?.error, + }); + } } - } else { - notifications?.toasts.addDanger({ - title: i18n.translate('workspace.delete.failed', { - defaultMessage: 'Failed to delete workspace', - }), - text: result?.error, - }); - } + }); } }; @@ -76,23 +80,31 @@ export function DeleteWorkspaceModal(props: DeleteWorkspaceModalProps) { -
-

The following workspace will be permanently deleted. This action cannot be undone.

-
    - {selectedWorkspace?.name ?
  • {selectedWorkspace.name}
  • : null} -
- - - To confirm your action, type delete. - - setValue(e.target.value)} - /> -
+ {selectedWorkspaces && selectedWorkspaces.length > 0 ? ( +
+

+ The following workspace will be permanently deleted. This action cannot be undone. +

+
    + {selectedWorkspaces.map((selectedWorkspace) => { + return selectedWorkspace?.name ? ( +
  • {selectedWorkspace.name}
  • + ) : null; + })} +
+ + + To confirm your action, type delete. + + setValue(e.target.value)} + /> +
+ ) : null}
@@ -104,7 +116,7 @@ export function DeleteWorkspaceModal(props: DeleteWorkspaceModalProps) {
+
+
+
+
+ +
+
+
+
+ > +
+ +
+ +
+
@@ -130,6 +192,29 @@ exports[`WorkspaceList should render title and table normally 1`] = ` /> + +
+
+ +
+
+
+ + - Use case + Last updated @@ -240,8 +321,32 @@ exports[`WorkspaceList should render title and table normally 1`] = ` + +
+
+ +
+
+
+
- name1 +
- ID + Use case
- id1 + Analytics (All)
+ class="euiToolTipAnchor" + > +
+ should be able to see the description tooltip when hovering over the description +
+
- Use case + Last updated
- Analytics (All) + Aug 5, 1999 @ 22:00:00.000
- - - - Edit - - - - - - Delete - - +
+ + + +
+
+
+ +
+
+ +
+
+
+
- name2 +
- ID + Use case
- id2 + Observability
+ class="euiToolTipAnchor" + > +
+ should be able to see the description tooltip when hovering over the description +
+
- Use case + Last updated
+ > + Aug 5, 1999 @ 20:00:00.000 +
- - - - Edit - - - - - - Delete - - +
+ + + +
+
+
+ +
+
+ +
+
+
+
- name3 +
- ID + Use case
- id3 - + />
+ class="euiToolTipAnchor" + > +
+
- Use case + Last updated
- Observability + Aug 5, 1999 @ 21:00:00.000
- - - - Edit - - - - - - Delete - - +
+ + + +
+
+
diff --git a/src/plugins/workspace/public/components/workspace_list/index.test.tsx b/src/plugins/workspace/public/components/workspace_list/index.test.tsx index 2284008d9d36..f45f5feaf860 100644 --- a/src/plugins/workspace/public/components/workspace_list/index.test.tsx +++ b/src/plugins/workspace/public/components/workspace_list/index.test.tsx @@ -4,6 +4,7 @@ */ import React from 'react'; +import moment from 'moment'; import { BehaviorSubject, of } from 'rxjs'; import { render, fireEvent, screen } from '@testing-library/react'; import { I18nProvider } from '@osd/i18n/react'; @@ -15,6 +16,18 @@ import { WorkspaceList } from './index'; jest.mock('../utils/workspace'); +const mockNavigatorWrite = jest.fn(); + +jest.mock('@elastic/eui', () => { + const original = jest.requireActual('@elastic/eui'); + return { + ...original, + copyToClipboard: jest.fn().mockImplementation((id) => { + mockNavigatorWrite(id); + }), + }; +}); + jest.mock('../delete_workspace_modal', () => ({ DeleteWorkspaceModal: ({ onClose }: { onClose: () => void }) => (
@@ -25,9 +38,29 @@ jest.mock('../delete_workspace_modal', () => ({ function getWrapWorkspaceListInContext( workspaceList = [ - { id: 'id1', name: 'name1', features: ['use-case-all'] }, - { id: 'id2', name: 'name2' }, - { id: 'id3', name: 'name3', features: ['use-case-observability'] }, + { + id: 'id1', + name: 'name1', + features: ['use-case-all'], + description: + 'should be able to see the description tooltip when hovering over the description', + lastUpdatedTime: '1999-08-06T02:00:00.00Z', + }, + { + id: 'id2', + name: 'name2', + features: ['use-case-observability'], + description: + 'should be able to see the description tooltip when hovering over the description', + lastUpdatedTime: '1999-08-06T00:00:00.00Z', + }, + { + id: 'id3', + name: 'name3', + features: ['use-case-search'], + description: '', + lastUpdatedTime: '1999-08-06T01:00:00.00Z', + }, ], isDashboardAdmin = true ) { @@ -48,6 +81,14 @@ function getWrapWorkspaceListInContext( workspaces: { workspaceList$: of(workspaceList), }, + uiSettings: { + get: jest.fn().mockImplementation((key) => { + if (key === 'dateFormat') { + return 'MMM D, YYYY @ HH:mm:ss.SSS'; + } + return null; + }), + }, navigationUI: { HeaderControl: mockHeaderControl, }, @@ -84,26 +125,27 @@ describe('WorkspaceList', () => { expect(getByText('Analytics (All)')).toBeInTheDocument(); expect(getByText('Observability')).toBeInTheDocument(); }); - it('should be able to apply debounce search after input', async () => { - const list = [ - { id: 'id1', name: 'name1' }, - { id: 'id2', name: 'name2' }, - { id: 'id3', name: 'name3' }, - { id: 'id4', name: 'name4' }, - { id: 'id5', name: 'name5' }, - { id: 'id6', name: 'name6' }, - ]; - const { getByText, getByRole, queryByText } = render(getWrapWorkspaceListInContext(list)); - expect(getByText('name1')).toBeInTheDocument(); - expect(queryByText('name6')).not.toBeInTheDocument(); + + it('should be able to search and re-render the list', async () => { + const { getByText, getByRole, queryByText } = render(getWrapWorkspaceListInContext()); const input = getByRole('searchbox'); fireEvent.change(input, { - target: { value: 'nam' }, + target: { value: 'name2' }, }); + expect(getByText('name2')).toBeInTheDocument(); + expect(queryByText('name1')).not.toBeInTheDocument(); + expect(queryByText('name3')).not.toBeInTheDocument(); + }); + + it('should be able to apply debounce search after input', async () => { + const { getByText, getByRole, queryByText } = render(getWrapWorkspaceListInContext()); + const input = getByRole('searchbox'); fireEvent.change(input, { - target: { value: 'name6' }, + target: { value: 'name2' }, }); - expect(queryByText('name6')).not.toBeInTheDocument(); + expect(getByText('name2')).toBeInTheDocument(); + expect(queryByText('name1')).not.toBeInTheDocument(); + expect(queryByText('name3')).not.toBeInTheDocument(); }); it('should be able to switch workspace after clicking name', async () => { @@ -113,16 +155,51 @@ describe('WorkspaceList', () => { expect(navigateToWorkspaceDetail).toBeCalled(); }); + it('should be able to perform the time format transformation', async () => { + const { getByText } = render(getWrapWorkspaceListInContext()); + expect( + getByText(moment('1999-08-06T00:00:00.00Z').format('MMM D, YYYY @ HH:mm:ss.SSS')) + ).toBeInTheDocument(); + expect( + getByText(moment('1999-08-06T01:00:00.00Z').format('MMM D, YYYY @ HH:mm:ss.SSS')) + ).toBeInTheDocument(); + expect( + getByText(moment('1999-08-06T02:00:00.00Z').format('MMM D, YYYY @ HH:mm:ss.SSS')) + ).toBeInTheDocument(); + }); + + it('should be able to see the 3 operations: copy, update, delete after click in the meatballs button', async () => { + const { getAllByTestId, getByText } = render(getWrapWorkspaceListInContext()); + const operationIcons = getAllByTestId('euiCollapsedItemActionsButton')[0]; + fireEvent.click(operationIcons); + expect(getByText('Copy ID')).toBeInTheDocument(); + expect(getByText('Edit')).toBeInTheDocument(); + expect(getByText('Delete')).toBeInTheDocument(); + }); + + it('should be able to copy workspace ID after clicking copy button', async () => { + const { getByText, getAllByTestId } = render(getWrapWorkspaceListInContext()); + const operationIcons = getAllByTestId('euiCollapsedItemActionsButton')[0]; + fireEvent.click(operationIcons); + const copyIcon = getByText('Copy ID'); + fireEvent.click(copyIcon); + expect(mockNavigatorWrite).toHaveBeenCalledWith('id1'); + }); + it('should be able to update workspace after clicking name', async () => { - const { getAllByTestId } = render(getWrapWorkspaceListInContext()); - const editIcon = getAllByTestId('workspace-list-edit-icon')[0]; + const { getByText, getAllByTestId } = render(getWrapWorkspaceListInContext()); + const operationIcons = getAllByTestId('euiCollapsedItemActionsButton')[0]; + fireEvent.click(operationIcons); + const editIcon = getByText('Edit'); fireEvent.click(editIcon); expect(navigateToWorkspaceDetail).toBeCalled(); }); it('should be able to call delete modal after clicking delete button', async () => { - const { getAllByTestId } = render(getWrapWorkspaceListInContext()); - const deleteIcon = getAllByTestId('workspace-list-delete-icon')[0]; + const { getByText, getAllByTestId } = render(getWrapWorkspaceListInContext()); + const operationIcons = getAllByTestId('euiCollapsedItemActionsButton')[0]; + fireEvent.click(operationIcons); + const deleteIcon = getByText('Delete'); fireEvent.click(deleteIcon); expect(screen.queryByLabelText('mock delete workspace modal')).toBeInTheDocument(); const modalCancelButton = screen.getByLabelText('mock delete workspace modal button'); @@ -132,12 +209,48 @@ describe('WorkspaceList', () => { it('should be able to pagination when clicking pagination button', async () => { const list = [ - { id: 'id1', name: 'name1' }, - { id: 'id2', name: 'name2' }, - { id: 'id3', name: 'name3' }, - { id: 'id4', name: 'name4' }, - { id: 'id5', name: 'name5' }, - { id: 'id6', name: 'name6' }, + { + id: 'id1', + name: 'name1', + features: ['use-case-all'], + description: '', + lastUpdatedTime: '2024-08-06T00:00:00.00Z', + }, + { + id: 'id2', + name: 'name2', + features: ['use-case-observability'], + description: '', + lastUpdatedTime: '2024-08-06T00:00:00.00Z', + }, + { + id: 'id3', + name: 'name3', + features: ['use-case-search'], + description: '', + lastUpdatedTime: '2024-08-06T00:00:00.00Z', + }, + { + id: 'id4', + name: 'name4', + features: ['use-case-all'], + description: '', + lastUpdatedTime: '2024-08-05T00:00:00.00Z', + }, + { + id: 'id5', + name: 'name5', + features: ['use-case-observability'], + description: '', + lastUpdatedTime: '2024-08-06T00:00:00.00Z', + }, + { + id: 'id6', + name: 'name6', + features: ['use-case-search'], + description: '', + lastUpdatedTime: '2024-08-06T00:00:00.00Z', + }, ]; const { getByTestId, getByText, queryByText } = render(getWrapWorkspaceListInContext(list)); expect(getByText('name1')).toBeInTheDocument(); @@ -149,14 +262,12 @@ describe('WorkspaceList', () => { }); it('should display create workspace button for dashboard admin', async () => { - const { getByText } = render(getWrapWorkspaceListInContext([], true)); - - expect(getByText('Create workspace')).toBeInTheDocument(); + const { getAllByText } = render(getWrapWorkspaceListInContext([], true)); + expect(getAllByText('Create workspace')[0]).toBeInTheDocument(); }); it('should hide create workspace button for non dashboard admin', async () => { const { queryByText } = render(getWrapWorkspaceListInContext([], false)); - expect(queryByText('Create workspace')).toBeNull(); }); }); diff --git a/src/plugins/workspace/public/components/workspace_list/index.tsx b/src/plugins/workspace/public/components/workspace_list/index.tsx index 931862b919a7..91fa716890bc 100644 --- a/src/plugins/workspace/public/components/workspace_list/index.tsx +++ b/src/plugins/workspace/public/components/workspace_list/index.tsx @@ -4,19 +4,26 @@ */ import React, { useState, useMemo, useCallback } from 'react'; +import moment from 'moment'; import { EuiPage, EuiPageContent, EuiLink, EuiSmallButton, EuiInMemoryTable, + EuiToolTip, + EuiText, EuiSearchBarProps, + copyToClipboard, + EuiTableSelectionType, + EuiButtonEmpty, + EuiButton, + EuiEmptyPrompt, } from '@elastic/eui'; import useObservable from 'react-use/lib/useObservable'; import { BehaviorSubject, of } from 'rxjs'; import { i18n } from '@osd/i18n'; -import { debounce, DEFAULT_NAV_GROUPS } from '../../../../../core/public'; -import { WorkspaceAttribute } from '../../../../../core/public'; +import { DEFAULT_NAV_GROUPS, WorkspaceAttribute } from '../../../../../core/public'; import { useOpenSearchDashboards } from '../../../../../plugins/opensearch_dashboards_react/public'; import { navigateToWorkspaceDetail } from '../utils/workspace'; @@ -31,6 +38,10 @@ export interface WorkspaceListProps { registeredUseCases$: BehaviorSubject; } +interface WorkspaceAttributeWithUseCaseID extends WorkspaceAttribute { + useCase?: string; +} + export const WorkspaceList = ({ registeredUseCases$ }: WorkspaceListProps) => { const { services: { @@ -38,47 +49,52 @@ export const WorkspaceList = ({ registeredUseCases$ }: WorkspaceListProps) => { application, http, navigationUI: { HeaderControl }, + uiSettings, }, } = useOpenSearchDashboards<{ navigationUI: NavigationPublicPluginStart['ui']; }>(); const registeredUseCases = useObservable(registeredUseCases$); const isDashboardAdmin = application?.capabilities?.dashboards?.isDashboardAdmin; - const initialSortField = 'name'; const initialSortDirection = 'asc'; const workspaceList = useObservable(workspaces?.workspaceList$ ?? of([]), []); - const [queryInput, setQueryInput] = useState(''); + const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 5, pageSizeOptions: [5, 10, 20], }); - const [deletedWorkspace, setDeletedWorkspace] = useState(null); + const [deletedWorkspaces, setDeletedWorkspaces] = useState([]); + const [selection, setSelection] = useState([]); - const handleSwitchWorkspace = useCallback( - (id: string) => { - if (application && http) { - navigateToWorkspaceDetail({ application, http }, id); + const dateFormat = uiSettings?.get('dateFormat'); + + const extractUseCaseFromFeatures = useCallback( + (features: string[]) => { + if (!features || features.length === 0) { + return ''; + } + const useCaseId = getFirstUseCaseOfFeatureConfigs(features); + const usecase = + useCaseId === DEFAULT_NAV_GROUPS.all.id + ? DEFAULT_NAV_GROUPS.all + : registeredUseCases?.find(({ id }) => id === useCaseId); + if (usecase) { + return usecase.title; } }, - [application, http] + [registeredUseCases] ); - const searchResult = useMemo(() => { - if (queryInput) { - const normalizedQuery = queryInput.toLowerCase(); - const result = workspaceList.filter((item) => { - return ( - item.id.toLowerCase().indexOf(normalizedQuery) > -1 || - item.name.toLowerCase().indexOf(normalizedQuery) > -1 - ); - }); - return result; - } - return workspaceList; - }, [workspaceList, queryInput]); - + const newWorkspaceList: WorkspaceAttributeWithUseCaseID[] = useMemo(() => { + return workspaceList.map( + (workspace): WorkspaceAttributeWithUseCaseID => ({ + ...workspace, + useCase: extractUseCaseFromFeatures(workspace.features ?? []), + }) + ); + }, [workspaceList, extractUseCaseFromFeatures]); const workspaceCreateUrl = useMemo(() => { if (!application) { return ''; @@ -92,6 +108,38 @@ export const WorkspaceList = ({ registeredUseCases$ }: WorkspaceListProps) => { return appUrl; }, [application]); + const emptyStateMessage = useMemo(() => { + return ( + + {i18n.translate('workspace.workspaceList.emptyState.title', { + defaultMessage: 'No workspace available', + })} + + } + titleSize="s" + body={i18n.translate('workspace.workspaceList.emptyState.body', { + defaultMessage: 'There are no workspace to display. Create workspace to get started.', + })} + actions={ + isDashboardAdmin && ( + + {i18n.translate('workspace.workspaceList.buttons.createWorkspace', { + defaultMessage: 'Create workspace', + })} + + ) + } + /> + ); + }, [isDashboardAdmin, workspaceCreateUrl]); + const renderCreateWorkspaceButton = () => { const button = ( { ); }; + const handleCopyId = (id: string) => { + copyToClipboard(id); + }; + + const handleSwitchWorkspace = useCallback( + (id: string) => { + if (application && http) { + navigateToWorkspaceDetail({ application, http }, id); + } + }, + [application, http] + ); + + const renderToolsLeft = () => { + if (selection.length === 0) { + return; + } + + const onClick = () => { + const deleteWorkspacesByIds = (workSpaces: WorkspaceAttribute[], ids: string[]) => { + const needToBeDeletedWorkspaceList: WorkspaceAttribute[] = []; + ids.forEach((id) => { + const index = workSpaces.findIndex((workSpace) => workSpace.id === id); + if (index >= 0) { + needToBeDeletedWorkspaceList.push(workSpaces[index]); + } + }); + return needToBeDeletedWorkspaceList; + }; + + setDeletedWorkspaces( + deleteWorkspacesByIds( + newWorkspaceList, + selection.map((item) => item.id) + ) + ); + + setSelection([]); + }; + + return ( + <> + + Delete {selection.length} Workspace + + {deletedWorkspaces && deletedWorkspaces.length > 0 && ( + setDeletedWorkspaces([])} + /> + )} + + ); + }; + + const selectionValue: EuiTableSelectionType = { + onSelectionChange: (deletedSelection) => setSelection(deletedSelection), + }; + + const search: EuiSearchBarProps = { + box: { + incremental: true, + }, + filters: [ + { + type: 'field_value_selection', + field: 'useCase', + name: 'Use Case', + multiSelect: false, + options: Array.from( + new Set(newWorkspaceList.map(({ useCase }) => useCase).filter(Boolean)) + ).map((useCase) => ({ + value: useCase!, + name: useCase!, + })), + }, + ], + toolsLeft: renderToolsLeft(), + }; + const columns = [ { field: 'name', name: 'Name', + width: '25%', sortable: true, render: (name: string, item: WorkspaceAttribute) => ( - handleSwitchWorkspace(item.id)}>{name} + handleSwitchWorkspace(item.id)}> + {name} + ), }, + { - field: 'id', - name: 'ID', - sortable: true, + field: 'useCase', + name: 'Use case', + width: '20%', }, + { field: 'description', name: 'Description', - truncateText: true, + width: '20%', + render: (description: string) => ( + + {/* Here I need to set width mannuly as the tooltip will ineffect the property : truncateText ', */} + + {description} + + + ), }, { - field: 'features', - name: 'Use case', - isExpander: true, - hasActions: true, - render: (features: string[]) => { - if (!features || features.length === 0) { - return ''; - } - const useCaseId = getFirstUseCaseOfFeatureConfigs(features); - const useCase = - useCaseId === DEFAULT_NAV_GROUPS.all.id - ? DEFAULT_NAV_GROUPS.all - : registeredUseCases?.find(({ id }) => id === useCaseId); - if (useCase) { - return useCase.title; - } + field: 'lastUpdatedTime', + name: 'Last updated', + width: '25%', + truncateText: false, + render: (lastUpdatedTime: string) => { + return moment(lastUpdatedTime).format(dateFormat); }, }, + { name: 'Actions', field: '', actions: [ + { + name: 'Copy ID', + type: 'button', + description: 'Copy id', + 'data-test-subj': 'workspace-list-copy-id-icon', + render: ({ id }: WorkspaceAttribute) => { + return ( + handleCopyId(id)} + size="xs" + iconType="copy" + color="text" + > + Copy ID + + ); + }, + }, { name: 'Edit', - icon: 'pencil', type: 'icon', + icon: 'edit', + color: 'danger', description: 'Edit workspace', - onClick: ({ id }: WorkspaceAttribute) => handleSwitchWorkspace(id), 'data-test-subj': 'workspace-list-edit-icon', + onClick: ({ id }: WorkspaceAttribute) => handleSwitchWorkspace(id), + render: ({ id }: WorkspaceAttribute) => { + return ( + handleSwitchWorkspace(id)} + iconType="pencil" + size="xs" + color="text" + > + Edit + + ); + }, }, { name: 'Delete', - icon: 'trash', - type: 'icon', + type: 'button', description: 'Delete workspace', - onClick: (item: WorkspaceAttribute) => setDeletedWorkspace(item), 'data-test-subj': 'workspace-list-delete-icon', + render: (item: WorkspaceAttribute) => { + return ( + { + setDeletedWorkspaces([item]); + }} + size="xs" + iconType="trash" + color="danger" + > + Delete + + ); + }, }, ], }, ]; - const debouncedSetQueryInput = useMemo(() => { - return debounce(setQueryInput, 300); - }, [setQueryInput]); - - const handleSearchInput: EuiSearchBarProps['onChange'] = useCallback( - ({ query }) => { - debouncedSetQueryInput(query?.text ?? ''); - }, - [debouncedSetQueryInput] - ); - - const search: EuiSearchBarProps = { - onChange: handleSearchInput, - box: { - incremental: true, - }, - }; - return ( { hasShadow={false} > setPagination((prev) => { return { ...prev, pageIndex: index, pageSize: size }; @@ -233,12 +395,14 @@ export const WorkspaceList = ({ registeredUseCases$ }: WorkspaceListProps) => { }} isSelectable={true} search={search} + selection={selectionValue} /> - {deletedWorkspace && ( + + {deletedWorkspaces.length > 0 && ( setDeletedWorkspace(null)} + selectedWorkspaces={deletedWorkspaces} + onClose={() => setDeletedWorkspaces([])} /> )}