diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 08000926d7aa..484ff309ef4d 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -357,4 +357,5 @@ export { PUBLIC_WORKSPACE_ID, MANAGEMENT_WORKSPACE_ID, WORKSPACE_TYPE, + cleanWorkspaceId, } from '../utils'; diff --git a/src/plugins/workspace/public/components/utils/common.ts b/src/plugins/workspace/public/components/utils/common.ts new file mode 100644 index 000000000000..cadd938e36d9 --- /dev/null +++ b/src/plugins/workspace/public/components/utils/common.ts @@ -0,0 +1,17 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export const debounce = (func: Function, delay: number) => { + let timerId: NodeJS.Timeout; + + return (...args: any) => { + if (!timerId) { + func(...args); + } + clearTimeout(timerId); + + timerId = setTimeout(() => func(...args), delay); + }; +}; diff --git a/src/plugins/workspace/public/components/utils/workspace.ts b/src/plugins/workspace/public/components/utils/workspace.ts index 6be21538838f..a0b49c520b01 100644 --- a/src/plugins/workspace/public/components/utils/workspace.ts +++ b/src/plugins/workspace/public/components/utils/workspace.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { WORKSPACE_OVERVIEW_APP_ID } from '../../../common/constants'; +import { WORKSPACE_OVERVIEW_APP_ID, WORKSPACE_UPDATE_APP_ID } from '../../../common/constants'; import { CoreStart } from '../../../../../core/public'; import { formatUrlWithWorkspaceId } from '../../../../../core/public/utils'; @@ -21,3 +21,16 @@ export const switchWorkspace = ({ application, http }: Core, id: string) => { window.location.href = newUrl; } }; + +export const updateWorkspace = ({ application, http }: Core, id: string) => { + const newUrl = formatUrlWithWorkspaceId( + application.getUrlForApp(WORKSPACE_UPDATE_APP_ID, { + absolute: true, + }), + id, + http.basePath + ); + if (newUrl) { + window.location.href = newUrl; + } +}; diff --git a/src/plugins/workspace/public/components/workspace_list/index.tsx b/src/plugins/workspace/public/components/workspace_list/index.tsx index 5599ae6bcf77..201f1928b8fc 100644 --- a/src/plugins/workspace/public/components/workspace_list/index.tsx +++ b/src/plugins/workspace/public/components/workspace_list/index.tsx @@ -3,45 +3,53 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useState } from 'react'; +import React, { useState, useMemo, useCallback } from 'react'; import { EuiPage, EuiPageBody, EuiPageHeader, EuiPageContent, - EuiBasicTable, EuiLink, - Direction, - CriteriaWithPagination, + EuiButton, + EuiInMemoryTable, + EuiTableSelectionType, + EuiSearchBarProps, } from '@elastic/eui'; import useObservable from 'react-use/lib/useObservable'; -import { useMemo, useCallback } from 'react'; import { of } from 'rxjs'; +import { i18n } from '@osd/i18n'; import { WorkspaceAttribute } from '../../../../../core/public'; import { useOpenSearchDashboards } from '../../../../../plugins/opensearch_dashboards_react/public'; -import { switchWorkspace } from '../utils/workspace'; +import { switchWorkspace, updateWorkspace } from '../utils/workspace'; +import { debounce } from '../utils/common'; + +import { WORKSPACE_CREATE_APP_ID } from '../../../common/constants'; + +import { cleanWorkspaceId } from '../../../../../core/public'; + +const WORKSPACE_LIST_PAGE_DESCRIPTIOIN = i18n.translate('workspace.list.description', { + defaultMessage: + 'Workspace allow you to save and organize library items, such as index patterns, visualizations, dashboards, saved searches, and share them with other OpenSearch Dashboards users. You can control which features are visible in each workspace, and which users and groups have read and write access to the library items in the workspace.', +}); export const WorkspaceList = () => { const { services: { workspaces, application, http }, } = useOpenSearchDashboards(); - const [pageIndex, setPageIndex] = useState(0); - const [pageSize, setPageSize] = useState(5); - const [sortField, setSortField] = useState<'name' | 'id'>('name'); - const [sortDirection, setSortDirection] = useState('asc'); - + 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 pageOfItems = useMemo(() => { - return workspaceList - .sort((a, b) => { - const compare = a[sortField].localeCompare(b[sortField]); - return sortDirection === 'asc' ? compare : -compare; - }) - .slice(pageIndex * pageSize, (pageIndex + 1) * pageSize); - }, [workspaceList, pageIndex, pageSize, sortField, sortDirection]); + // Will be uesed when updating table actions + const [, setSelection] = useState([]); const handleSwitchWorkspace = useCallback( (id: string) => { @@ -52,6 +60,29 @@ export const WorkspaceList = () => { [application, http] ); + const handleUpdateWorkspace = useCallback( + (id: string) => { + if (application && http) { + updateWorkspace({ application, http }, id); + } + }, + [application, http] + ); + + 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 columns = [ { field: 'name', @@ -79,46 +110,95 @@ export const WorkspaceList = () => { isExpander: true, hasActions: true, }, + { + name: 'Actions', + field: '', + actions: [ + { + name: 'Edit', + icon: 'pencil', + type: 'icon', + description: 'edit workspace', + onClick: ({ id }: WorkspaceAttribute) => handleUpdateWorkspace(id), + }, + ], + }, ]; - const onTableChange = ({ page, sort }: CriteriaWithPagination) => { - const { field, direction } = sort!; - const { index, size } = page; + const workspaceCreateUrl = useMemo(() => { + if (!application || !http) { + return ''; + } + + return cleanWorkspaceId( + application.getUrlForApp(WORKSPACE_CREATE_APP_ID, { + absolute: false, + }) + ); + }, [application, http]); + + const debouncedSetQueryInput = useMemo(() => { + return debounce(setQueryInput, 300); + }, [setQueryInput]); + + const handleSearchInput: EuiSearchBarProps['onChange'] = ({ query }) => { + debouncedSetQueryInput(query?.text ?? ''); + }; + + const search: EuiSearchBarProps = { + onChange: handleSearchInput, + box: { + incremental: true, + }, + toolsRight: [ + + Create workspace + , + ], + }; - setPageIndex(index); - setPageSize(size); - setSortField(field as 'name' | 'id'); - setSortDirection(direction); + const selectionValue: EuiTableSelectionType = { + selectable: () => true, + onSelectionChange: (selection) => { + setSelection(selection); + }, }; return ( - + - + setPagination((prev) => { + return { ...prev, pageIndex: index, pageSize: size }; + }) + } + pagination={pagination} sorting={{ sort: { - field: sortField, - direction: sortDirection, + field: initialSortField, + direction: initialSortDirection, }, }} - onChange={onTableChange} + isSelectable={true} + selection={selectionValue} + search={search} /> diff --git a/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx index 4c0868a8b373..05ada930abc4 100644 --- a/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx +++ b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx @@ -30,6 +30,7 @@ import { WORKSPACE_OVERVIEW_APP_ID, } from '../../../common/constants'; import { formatUrlWithWorkspaceId } from '../../../../../core/public/utils'; +import { cleanWorkspaceId } from '../../../../../core/public'; interface Props { getUrlForApp: ApplicationStart['getUrlForApp']; @@ -117,12 +118,10 @@ export const WorkspaceMenu = ({ basePath, getUrlForApp, workspaces, navigateToUr key: length.toString(), onClick: () => { navigateToUrl( - formatUrlWithWorkspaceId( + cleanWorkspaceId( getUrlForApp(WORKSPACE_CREATE_APP_ID, { absolute: false, - }), - currentWorkspace?.id ?? '', - basePath + }) ) ); setPopover(false); @@ -136,12 +135,10 @@ export const WorkspaceMenu = ({ basePath, getUrlForApp, workspaces, navigateToUr key: (length + 1).toString(), onClick: () => { navigateToUrl( - formatUrlWithWorkspaceId( + cleanWorkspaceId( getUrlForApp(WORKSPACE_LIST_APP_ID, { absolute: false, - }), - currentWorkspace?.id ?? '', - basePath + }) ) ); setPopover(false);