diff --git a/changelogs/fragments/8592.yml b/changelogs/fragments/8592.yml new file mode 100644 index 000000000000..fd4fae271865 --- /dev/null +++ b/changelogs/fragments/8592.yml @@ -0,0 +1,2 @@ +fix: +- Workspace selector style alignment ([#8592](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/8592)) \ No newline at end of file diff --git a/src/plugins/workspace/public/components/workspace_menu/workspace_menu.test.tsx b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.test.tsx index 71a7632d6f08..f70d5efb23a0 100644 --- a/src/plugins/workspace/public/components/workspace_menu/workspace_menu.test.tsx +++ b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.test.tsx @@ -83,7 +83,7 @@ describe('', () => { const selectButton = screen.getByTestId('workspace-select-button'); fireEvent.click(selectButton); - expect(screen.getByText(`viewed ${moment(1234567890).fromNow()}`)).toBeInTheDocument(); + expect(screen.getByText(`Viewed ${moment(1234567890).fromNow()}`)).toBeInTheDocument(); }); it('should be able to display empty state when the workspace list is empty', () => { diff --git a/src/plugins/workspace/public/components/workspace_picker_content/workspace_picker_content.tsx b/src/plugins/workspace/public/components/workspace_picker_content/workspace_picker_content.tsx index 2bbb2c613173..cf8ce3324233 100644 --- a/src/plugins/workspace/public/components/workspace_picker_content/workspace_picker_content.tsx +++ b/src/plugins/workspace/public/components/workspace_picker_content/workspace_picker_content.tsx @@ -27,18 +27,6 @@ import { WorkspaceUseCase } from '../../types'; import { validateWorkspaceColor } from '../../../common/utils'; import { getFirstUseCaseOfFeatureConfigs, getUseCaseUrl } from '../../utils'; -function sortBy(getkey: (item: T) => number | undefined) { - return (a: T, b: T): number => { - const aValue = getkey(a); - const bValue = getkey(b); - - if (aValue === undefined) return 1; - if (bValue === undefined) return -1; - - return bValue - aValue; - }; -} - const searchFieldPlaceholder = i18n.translate('workspace.menu.search.placeholder', { defaultMessage: 'Search workspace name', }); @@ -48,7 +36,7 @@ const getValidWorkspaceColor = (color?: string) => interface UpdatedWorkspaceObject extends WorkspaceObject { accessTimeStamp?: number; - accessTime?: string; + accessTimeDescription?: string; } interface Props { coreStart: CoreStart; @@ -57,6 +45,21 @@ interface Props { isInTwoLines?: boolean; } +const sortByRecentVisitedAndAlphabetical = ( + ws1: UpdatedWorkspaceObject, + ws2: UpdatedWorkspaceObject +) => { + // First, sort by accessTimeStamp in descending order (if both have timestamps) + if (ws1?.accessTimeStamp && ws2?.accessTimeStamp) { + return ws2.accessTimeStamp - ws1.accessTimeStamp; + } + // If one has a timestamp and the other does not, prioritize the one with the timestamp + if (ws1.accessTimeStamp) return -1; + if (ws2.accessTimeStamp) return 1; + // If neither has a timestamp, sort alphabetically by name + return ws1.name.localeCompare(ws2.name); +}; + export const WorkspacePickerContent = ({ coreStart, registeredUseCases$, @@ -72,18 +75,23 @@ export const WorkspacePickerContent = ({ const recentWorkspaces = recentWorkspaceManager.getRecentWorkspaces(); const updatedList = workspaceList.map((workspace) => { const recentWorkspace = recentWorkspaces.find((recent) => recent.id === workspace.id); - - if (recentWorkspace) { - return { - ...workspace, - accessTimeStamp: recentWorkspace.timestamp, - accessTime: `viewed ${moment(recentWorkspace.timestamp).fromNow()}`, - }; - } - return workspace as UpdatedWorkspaceObject; + return { + ...workspace, + accessTimeStamp: recentWorkspace?.timestamp, + accessTimeDescription: recentWorkspace + ? i18n.translate('workspace.picker.accessTime.description', { + defaultMessage: 'Viewed {timeLabel}', + values: { + timeLabel: moment(recentWorkspace.timestamp).fromNow(), + }, + }) + : i18n.translate('workspace.picker.accessTime.not.visited', { + defaultMessage: 'Not visited recently', + }), + }; }); - return updatedList.sort(sortBy((workspace) => workspace.accessTimeStamp)); + return updatedList.sort(sortByRecentVisitedAndAlphabetical); }, [workspaceList]); const queryFromList = ({ list, query }: { list: UpdatedWorkspaceObject[]; query: string }) => { @@ -186,7 +194,7 @@ export const WorkspacePickerContent = ({ - {workspace.accessTime} + {workspace.accessTimeDescription} @@ -211,7 +219,7 @@ export const WorkspacePickerContent = ({ - {workspace.accessTime} + {workspace.accessTimeDescription} diff --git a/src/plugins/workspace/public/components/workspace_selector/workspace_selector.scss b/src/plugins/workspace/public/components/workspace_selector/workspace_selector.scss index 685d823b30c6..9ac8ef1fa86e 100644 --- a/src/plugins/workspace/public/components/workspace_selector/workspace_selector.scss +++ b/src/plugins/workspace/public/components/workspace_selector/workspace_selector.scss @@ -7,3 +7,32 @@ transition: none !important; transform: none !important; } + +/* Trying to show the border label: + * The following style is referenced from data souce selector + * see file src/plugins/data_source_management/public/components/popover_button/popover_button.scss +*/ +.workspaceSelectorPopoverButtonContainer { + position: relative; + background-color: $euiSideNavBackgroundColor; + border: 1px solid $euiColorMediumShade; + border-radius: 4px; + + &::before { + content: attr(data-label); + position: absolute; + top: -0.2rem; + left: $euiSizeS; + font-size: 0.4rem; + line-height: 0.4rem; + padding: 0 $euiSizeXS; + + /* update + * Delete the line-gradient and set the background color to euiSideNavBackgroundColor directly + */ + background-color: $euiSideNavBackgroundColor; + color: $euiTextColor; + z-index: 0; + text-transform: uppercase; + } +} diff --git a/src/plugins/workspace/public/components/workspace_selector/workspace_selector.test.tsx b/src/plugins/workspace/public/components/workspace_selector/workspace_selector.test.tsx index 5b8b05a6506a..6f0b4136698d 100644 --- a/src/plugins/workspace/public/components/workspace_selector/workspace_selector.test.tsx +++ b/src/plugins/workspace/public/components/workspace_selector/workspace_selector.test.tsx @@ -10,13 +10,8 @@ import { WorkspaceSelector } from './workspace_selector'; import { coreMock } from '../../../../../core/public/mocks'; import { CoreStart, DEFAULT_NAV_GROUPS, WorkspaceObject } from '../../../../../core/public'; import { BehaviorSubject } from 'rxjs'; -import { IntlProvider } from 'react-intl'; import { recentWorkspaceManager } from '../../recent_workspace_manager'; -jest.mock('@osd/i18n', () => ({ - i18n: { - translate: (id: string, { defaultMessage }: { defaultMessage: string }) => defaultMessage, - }, -})); +import { I18nProvider } from '@osd/i18n/react'; describe('', () => { let coreStartMock: CoreStart; const navigateToApp = jest.fn(); @@ -52,9 +47,9 @@ describe('', () => { const WorkspaceSelectorCreatorComponent = () => { return ( - + - + ); }; @@ -68,6 +63,7 @@ describe('', () => { expect(screen.getByTestId('workspace-selector-current-title')).toBeInTheDocument(); expect(screen.getByTestId('workspace-selector-current-name')).toBeInTheDocument(); }); + it('should display a list of workspaces in the dropdown', () => { jest .spyOn(recentWorkspaceManager, 'getRecentWorkspaces') @@ -82,11 +78,11 @@ describe('', () => { const selectButton = screen.getByTestId('workspace-selector-button'); fireEvent.click(selectButton); - expect(screen.getByTestId('workspace-menu-item-workspace-1')).toBeInTheDocument(); - expect(screen.getByTestId('workspace-menu-item-workspace-2')).toBeInTheDocument(); + expect(screen.getByText('workspace 1')).toBeInTheDocument(); + expect(screen.getByText('workspace 2')).toBeInTheDocument(); }); - it('should display viewed xx ago for recent workspaces', () => { + it('should display viewed xx ago for recent workspaces, and Not visited recently for never-visited workspace', () => { jest .spyOn(recentWorkspaceManager, 'getRecentWorkspaces') .mockReturnValue([{ id: 'workspace-1', timestamp: 1234567890 }]); @@ -95,13 +91,12 @@ describe('', () => { { id: 'workspace-1', name: 'workspace 1', features: [] }, { id: 'workspace-2', name: 'workspace 2', features: [] }, ]); - render(); - const selectButton = screen.getByTestId('workspace-selector-button'); fireEvent.click(selectButton); - expect(screen.getByText(`viewed ${moment(1234567890).fromNow()}`)).toBeInTheDocument(); + expect(screen.getByText(`Viewed ${moment(1234567890).fromNow()}`)).toBeInTheDocument(); + expect(screen.getByText('Not visited recently')).toBeInTheDocument(); }); it('should be able to display empty state when the workspace list is empty', () => { @@ -127,8 +122,8 @@ describe('', () => { const searchInput = screen.getByRole('searchbox'); fireEvent.change(searchInput, { target: { value: 'works' } }); - expect(screen.getByTestId('workspace-menu-item-workspace-1')).toBeInTheDocument(); - expect(screen.queryByText('workspace-menu-item-workspace-1')).not.toBeInTheDocument(); + expect(screen.getByText('workspace 1')).toBeInTheDocument(); + expect(screen.queryByText('test 2')).not.toBeInTheDocument(); }); it('should be able to display empty state when seach is not found', () => { diff --git a/src/plugins/workspace/public/components/workspace_selector/workspace_selector.tsx b/src/plugins/workspace/public/components/workspace_selector/workspace_selector.tsx index f0c0243703f5..5374f3f2aea6 100644 --- a/src/plugins/workspace/public/components/workspace_selector/workspace_selector.tsx +++ b/src/plugins/workspace/public/components/workspace_selector/workspace_selector.tsx @@ -64,36 +64,21 @@ export const WorkspaceSelector = ({ coreStart, registeredUseCases$ }: Props) => const closePopover = () => { setPopover(false); }; + const button = currentWorkspace ? ( - - + - - {i18n.translate('workspace.left.nav.selector.label', { - defaultMessage: 'WORKSPACE', - })} - - - - + - + color={getValidWorkspaceColor(currentWorkspace.color)} /> - - - + + +

{currentWorkspace.name}

- +
-
+ ) : ( Select a Workspace );