From 1fdba5506bdc3f73d089d454f51ad8c246b4c542 Mon Sep 17 00:00:00 2001 From: Oleksii Orel Date: Tue, 22 Oct 2024 16:26:41 +0300 Subject: [PATCH] fix: workspace actions Signed-off-by: Oleksii Orel --- .../RecentItem/WorkspaceActions/index.tsx | 3 -- .../WorkspaceActions/Dropdown/index.tsx | 21 +++++++-- .../contexts/WorkspaceActions/Provider.tsx | 18 ++++++- .../__tests__/Provider.spec.tsx | 47 +++++++++++++++---- .../helpers/__tests__/location.spec.ts | 12 ++++- .../src/services/helpers/location.ts | 8 ++++ .../src/services/helpers/types.ts | 1 + 7 files changed, 91 insertions(+), 19 deletions(-) diff --git a/packages/dashboard-frontend/src/Layout/Navigation/RecentItem/WorkspaceActions/index.tsx b/packages/dashboard-frontend/src/Layout/Navigation/RecentItem/WorkspaceActions/index.tsx index b6f65be6b..f331784c9 100644 --- a/packages/dashboard-frontend/src/Layout/Navigation/RecentItem/WorkspaceActions/index.tsx +++ b/packages/dashboard-frontend/src/Layout/Navigation/RecentItem/WorkspaceActions/index.tsx @@ -42,9 +42,6 @@ export class RecentItemWorkspaceActions extends React.PureComponent { context={context} isPlain menuAppendTo={menuAppendTo} - onAction={async () => { - // no-op - }} position="right" toggle="kebab-toggle" workspace={item.workspace} diff --git a/packages/dashboard-frontend/src/contexts/WorkspaceActions/Dropdown/index.tsx b/packages/dashboard-frontend/src/contexts/WorkspaceActions/Dropdown/index.tsx index bbf2d4c3b..2ccb747fe 100644 --- a/packages/dashboard-frontend/src/contexts/WorkspaceActions/Dropdown/index.tsx +++ b/packages/dashboard-frontend/src/contexts/WorkspaceActions/Dropdown/index.tsx @@ -59,10 +59,14 @@ export class WorkspaceActionsDropdown extends React.PureComponent }; } + private get hasKebabToggle(): boolean { + return this.props.toggle === 'kebab-toggle'; + } + private buildToggle(): React.ReactElement { - const { isDisabled = false, toggle, workspace } = this.props; + const { isDisabled = false, workspace } = this.props; - if (toggle === 'kebab-toggle') { + if (this.hasKebabToggle) { return ( ); }; - return [ + const items = [ getItem(WorkspaceAction.OPEN_IDE, isTerminating), - getItem(WorkspaceAction.START_DEBUG_AND_OPEN_LOGS, isTerminating || isStopped === false), - getItem(WorkspaceAction.START_IN_BACKGROUND, isTerminating || isStopped === false), + getItem(WorkspaceAction.START_DEBUG_AND_OPEN_LOGS, isTerminating || !isStopped), + getItem(WorkspaceAction.START_IN_BACKGROUND, isTerminating || !isStopped), getItem(WorkspaceAction.RESTART_WORKSPACE, isTerminating || isStopped), getItem(WorkspaceAction.STOP_WORKSPACE, isTerminating || isStopped), getItem(WorkspaceAction.DELETE_WORKSPACE, isTerminating), ]; + + // The 'Workspace Details' action is available only with kebab-toggle because this actions widget is used on the workspace details page without kebab-toggle + if (this.hasKebabToggle) { + items.push(getItem(WorkspaceAction.WORKSPACE_DETAILS, false)); + } + + return items; } render(): React.ReactElement { diff --git a/packages/dashboard-frontend/src/contexts/WorkspaceActions/Provider.tsx b/packages/dashboard-frontend/src/contexts/WorkspaceActions/Provider.tsx index 48bc17526..476a1db21 100644 --- a/packages/dashboard-frontend/src/contexts/WorkspaceActions/Provider.tsx +++ b/packages/dashboard-frontend/src/contexts/WorkspaceActions/Provider.tsx @@ -17,7 +17,11 @@ import { Location } from 'react-router-dom'; import { WorkspaceActionsDeleteConfirmation } from '@/contexts/WorkspaceActions/DeleteConfirmation'; import { lazyInject } from '@/inversify.config'; -import { buildIdeLoaderLocation, toHref } from '@/services/helpers/location'; +import { + buildIdeLoaderLocation, + buildWorkspaceDetailsLocation, + toHref, +} from '@/services/helpers/location'; import { LoaderTab, WorkspaceAction } from '@/services/helpers/types'; import { TabManager } from '@/services/tabManager'; import { Workspace } from '@/services/workspace-adapter'; @@ -67,6 +71,14 @@ class WorkspaceActionsProvider extends React.Component { this.tabManager.open(link); } + /** + * replace the current tab with the given location + */ + private replaceLocation(location: Location): void { + const link = toHref(location); + this.tabManager.replace(link); + } + private async deleteWorkspace(workspace: Workspace): Promise { if (this.deleting.has(workspace.uid)) { console.warn(`Workspace "${workspace.name}" is being deleted.`); @@ -114,6 +126,10 @@ class WorkspaceActionsProvider extends React.Component { this.handleLocation(buildIdeLoaderLocation(workspace)); break; } + case WorkspaceAction.WORKSPACE_DETAILS: { + this.replaceLocation(buildWorkspaceDetailsLocation(workspace)); + break; + } case WorkspaceAction.START_DEBUG_AND_OPEN_LOGS: { await this.props.startWorkspace(workspace, { 'debug-workspace-start': true, diff --git a/packages/dashboard-frontend/src/contexts/WorkspaceActions/__tests__/Provider.spec.tsx b/packages/dashboard-frontend/src/contexts/WorkspaceActions/__tests__/Provider.spec.tsx index 32afe970e..210f03b32 100644 --- a/packages/dashboard-frontend/src/contexts/WorkspaceActions/__tests__/Provider.spec.tsx +++ b/packages/dashboard-frontend/src/contexts/WorkspaceActions/__tests__/Provider.spec.tsx @@ -22,8 +22,10 @@ import { WorkspaceActionsConsumer, } from '@/contexts/WorkspaceActions'; import WorkspaceActionsProvider from '@/contexts/WorkspaceActions/Provider'; +import { container } from '@/inversify.config'; import getComponentRenderer, { screen } from '@/services/__mocks__/getComponentRenderer'; import { WorkspaceAction } from '@/services/helpers/types'; +import { TabManager } from '@/services/tabManager'; import { AppThunk } from '@/store'; import { DevWorkspaceBuilder } from '@/store/__mocks__/devWorkspaceBuilder'; import { FakeStoreBuilder } from '@/store/__mocks__/storeBuilder'; @@ -67,6 +69,10 @@ const wantDelete = ['1234', '5678'] as WantDelete; const mockHandleAction: jest.Mock = jest.fn(); +const mockWindowReplace = jest.fn(); + +const mockTabManagerOpen = jest.fn(); + describe('WorkspaceActionsProvider', () => { let store: Store; let user: UserEvent; @@ -92,6 +98,18 @@ describe('WorkspaceActionsProvider', () => { jest.useFakeTimers(); user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + + class MockTabManager extends TabManager { + public open(url: string): void { + mockTabManagerOpen(url); + } + public replace(url: string): void { + mockWindowReplace(url); + } + } + + container.snapshot(); + container.rebind(TabManager).to(MockTabManager).inSingletonScope(); }); afterEach(() => { @@ -138,8 +156,6 @@ describe('WorkspaceActionsProvider', () => { }); describe('handle actions', () => { - window.open = jest.fn(); - describe('delete workspace', () => { test('succeeded (with debouncing)', async () => { console.warn = jest.fn(); @@ -209,10 +225,24 @@ describe('WorkspaceActionsProvider', () => { // make sure all timers are executed await jest.advanceTimersByTimeAsync(1000); - expect(window.open).toHaveBeenCalledTimes(1); - expect(window.open).toHaveBeenCalledWith( - expect.stringContaining('/ide/user-che/wksp-1234'), - expect.stringContaining('/ide/user-che/wksp-1234'), + expect(mockTabManagerOpen).toHaveBeenCalledTimes(1); + expect(mockTabManagerOpen).toHaveBeenCalledWith('http://localhost/#/ide/user-che/wksp-1234'); + }); + + test('open workspace details', async () => { + renderComponent(store, WorkspaceAction.WORKSPACE_DETAILS, '1234'); + + // get and click start button + const handleActionBtn = screen.getByTestId('test-component-handle-action'); + + await user.click(handleActionBtn); + + // make sure all timers are executed + await jest.advanceTimersByTimeAsync(1000); + + expect(mockWindowReplace).toHaveBeenCalledTimes(1); + expect(mockWindowReplace).toHaveBeenCalledWith( + 'http://localhost/#/workspace/user-che/wksp-1234', ); }); @@ -235,9 +265,8 @@ describe('WorkspaceActionsProvider', () => { { 'debug-workspace-start': true }, ); - expect(window.open).toHaveBeenCalledWith( - expect.stringContaining('/ide/user-che/wksp-5678?tab=Logs'), - expect.stringContaining('/ide/user-che/wksp-5678?tab=Logs'), + expect(mockTabManagerOpen).toHaveBeenCalledWith( + 'http://localhost/#/ide/user-che/wksp-5678?tab=Logs', ); }); diff --git a/packages/dashboard-frontend/src/services/helpers/__tests__/location.spec.ts b/packages/dashboard-frontend/src/services/helpers/__tests__/location.spec.ts index 28141f07d..f9451d6ba 100644 --- a/packages/dashboard-frontend/src/services/helpers/__tests__/location.spec.ts +++ b/packages/dashboard-frontend/src/services/helpers/__tests__/location.spec.ts @@ -12,7 +12,9 @@ import { Location } from 'react-router-dom'; -import { toHref } from '@/services/helpers/location'; +import { buildWorkspaceDetailsLocation, toHref } from '@/services/helpers/location'; +import { constructWorkspace } from '@/services/workspace-adapter'; +import { DevWorkspaceBuilder } from '@/store/__mocks__/devWorkspaceBuilder'; describe('location', () => { test('toHref', () => { @@ -25,4 +27,12 @@ describe('location', () => { }; expect(toHref(location)).toEqual(`${window.location.origin}/#/foo?bar=baz`); }); + + test('buildWorkspaceDetailsLocation', () => { + const devWorkspaceBuilder = new DevWorkspaceBuilder() + .withName('wksp') + .withNamespace('che-user'); + const workspace = constructWorkspace(devWorkspaceBuilder.build()); + expect(buildWorkspaceDetailsLocation(workspace).pathname).toEqual(`/workspace/che-user/wksp`); + }); }); diff --git a/packages/dashboard-frontend/src/services/helpers/location.ts b/packages/dashboard-frontend/src/services/helpers/location.ts index 9e0ce2261..5477e3bf3 100644 --- a/packages/dashboard-frontend/src/services/helpers/location.ts +++ b/packages/dashboard-frontend/src/services/helpers/location.ts @@ -43,6 +43,14 @@ export function buildWorkspacesLocation(): Location { return _buildLocationObject(ROUTE.WORKSPACES); } +export function buildWorkspaceDetailsLocation(workspace: Workspace): Location { + const path = ROUTE.WORKSPACE_DETAILS.replace(':namespace', workspace.namespace).replace( + ':workspaceName', + workspace.name, + ); + return _buildLocationObject(path); +} + export function buildUserPreferencesLocation(tab?: UserPreferencesTab): Location { let pathAndQuery: string; if (!tab) { diff --git a/packages/dashboard-frontend/src/services/helpers/types.ts b/packages/dashboard-frontend/src/services/helpers/types.ts index 98da6d8b4..9fd39affe 100644 --- a/packages/dashboard-frontend/src/services/helpers/types.ts +++ b/packages/dashboard-frontend/src/services/helpers/types.ts @@ -99,6 +99,7 @@ export enum WorkspaceAction { START_DEBUG_AND_OPEN_LOGS = 'Open in Debug mode', START_IN_BACKGROUND = 'Start in background', STOP_WORKSPACE = 'Stop Workspace', + WORKSPACE_DETAILS = 'Workspace Details', } export enum UserPreferencesTab {