Skip to content

Commit

Permalink
WOR-1235 Changes to dashboard for workspace deletion (#4360)
Browse files Browse the repository at this point in the history
  • Loading branch information
blakery authored Oct 19, 2023
1 parent 647784d commit 517a19a
Show file tree
Hide file tree
Showing 13 changed files with 492 additions and 130 deletions.
1 change: 1 addition & 0 deletions src/libs/workspace-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ interface BaseWorkspaceInfo {
attributes?: Record<string, unknown>;
isLocked?: boolean;
state?: WorkspaceState;
errorMessage?: string;
}

export interface AzureWorkspaceInfo extends BaseWorkspaceInfo {
Expand Down
60 changes: 29 additions & 31 deletions src/pages/workspaces/workspace/Dashboard/CloudInformation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,39 +140,37 @@ const GoogleCloudInformation = (props: GoogleCloudInformationProps): ReactNode =
[bucketSize?.usage]
),
]),
h(Fragment, [
div({ style: { paddingBottom: '0.5rem' } }, [
h(
Link,
{
style: { margin: '1rem 0.5rem' },
...newTabLinkProps,
onClick: () => {
Ajax().Metrics.captureEvent(Events.workspaceOpenedBucketInBrowser, {
...extractWorkspaceDetails(workspace),
});
},
href: bucketBrowserUrl(bucketName),
div({ style: { paddingBottom: '0.5rem' } }, [
h(
Link,
{
style: { margin: '1rem 0.5rem' },
...newTabLinkProps,
onClick: () => {
Ajax().Metrics.captureEvent(Events.workspaceOpenedBucketInBrowser, {
...extractWorkspaceDetails(workspace),
});
},
['Open bucket in browser', icon('pop-out', { size: 12, style: { marginLeft: '0.25rem' } })]
),
]),
div({ style: { paddingBottom: '0.5rem' } }, [
h(
Link,
{
style: { margin: '1rem 0.5rem' },
...newTabLinkProps,
onClick: () => {
Ajax().Metrics.captureEvent(Events.workspaceOpenedProjectInConsole, {
...extractWorkspaceDetails(workspace),
});
},
href: `https://console.cloud.google.com/welcome?project=${googleProject}`,
href: bucketBrowserUrl(bucketName),
},
['Open bucket in browser', icon('pop-out', { size: 12, style: { marginLeft: '0.25rem' } })]
),
]),
div({ style: { paddingBottom: '0.5rem' } }, [
h(
Link,
{
style: { margin: '1rem 0.5rem' },
...newTabLinkProps,
onClick: () => {
Ajax().Metrics.captureEvent(Events.workspaceOpenedProjectInConsole, {
...extractWorkspaceDetails(workspace),
});
},
['Open project in Google Cloud Console', icon('pop-out', { size: 12, style: { marginLeft: '0.25rem' } })]
),
]),
href: `https://console.cloud.google.com/welcome?project=${googleProject}`,
},
['Open project in Google Cloud Console', icon('pop-out', { size: 12, style: { marginLeft: '0.25rem' } })]
),
]),
]);
};
Expand Down
5 changes: 4 additions & 1 deletion src/pages/workspaces/workspace/Dashboard/Dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import _ from 'lodash/fp';
import {
CSSProperties,
ForwardedRef,
ForwardRefRenderFunction,
Fragment,
ReactNode,
useCallback,
Expand Down Expand Up @@ -491,7 +492,9 @@ const WorkspaceDashboardComponent = (
]);
};

const WorkspaceDashboard: (props: WorkspaceDashboardProps) => typeof WorkspaceDashboardComponent = _.flow(
const WorkspaceDashboard: (
props: WorkspaceDashboardProps
) => ForwardRefRenderFunction<typeof WorkspaceDashboardComponent, WorkspaceDashboardProps> = _.flow(
forwardRefWithName('WorkspaceDashboard'),
wrapWorkspace({
breadcrumbs: (props) => breadcrumbs.commonPaths.workspaceDashboard(props),
Expand Down
49 changes: 27 additions & 22 deletions src/pages/workspaces/workspace/Dashboard/OwnerNotice.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { cond } from '@terra-ui-packages/core-utils';
import { cond, DEFAULT } from '@terra-ui-packages/core-utils';
import _ from 'lodash/fp';
import { Fragment, ReactNode } from 'react';
import { ReactNode } from 'react';
import { h } from 'react-hyperscript-helpers';
import { Link } from 'src/components/common';
import { InfoBox } from 'src/components/InfoBox';
Expand All @@ -17,36 +17,41 @@ interface OwnerNoticeProps {

export const OwnerNotice = (props: OwnerNoticeProps): ReactNode => {
const { owners, accessLevel, acl } = props;
const notice = cond(
return cond(
// No warning if there are multiple owners.
[_.size(owners) !== 1, () => null],
// If the current user does not own the workspace, then then workspace must be shared.
[
!isOwner(accessLevel),
() =>
h(Fragment, [
'This shared workspace has only one owner. Consider requesting ',
h(Link, { href: `mailto:${owners[0]}` }, [owners[0]]),
' to add another owner to ensure someone is able to manage the workspace in case they lose access to their account.',
]),
h(
InfoBox,
{
icon: 'error-standard',
style: { color: colors.accent() },
},
[
'This shared workspace has only one owner. Consider requesting ',
h(Link, { href: `mailto:${owners[0]}` }, [owners[0]]),
' to add another owner to ensure someone is able to manage the workspace in case they lose access to their account.',
]
),
],
// If the current user is the only owner of the workspace, check if the workspace is shared.
[
_.size(acl) > 1,
() =>
h(Fragment, [
'You are the only owner of this shared workspace. Consider adding another owner to ensure someone is able to manage the workspace in case you lose access to your account.',
]),
]
h(
InfoBox,
{
icon: 'error-standard',
style: { color: colors.accent() },
},
[
'You are the only owner of this shared workspace. Consider adding another owner to ensure someone is able to manage the workspace in case you lose access to your account.',
]
),
],
[DEFAULT, () => null]
);
return !notice
? undefined
: h(
InfoBox,
{
icon: 'error-standard',
style: { color: colors.accent() },
},
[notice]
);
};
15 changes: 10 additions & 5 deletions src/pages/workspaces/workspace/WorkspaceContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
StorageDetails,
useWorkspace,
} from 'src/pages/workspaces/workspace/useWorkspace';
import { WorkspaceDeletingBanner } from 'src/pages/workspaces/workspace/WorkspaceDeletingBanner';
import { WorkspaceTabs } from 'src/pages/workspaces/workspace/WorkspaceTabs';

const TitleBarWarning = (props: PropsWithChildren): ReactNode => {
Expand All @@ -49,6 +50,7 @@ const TitleBarWarning = (props: PropsWithChildren): ReactNode => {
),
style: { backgroundColor: colors.accent(0.35), borderBottom: `1px solid ${colors.accent()}` },
onDismiss: () => {},
hideCloseButton: true,
});
};

Expand Down Expand Up @@ -86,10 +88,10 @@ const GooglePermissionsSpinner = (): ReactNode => {
return h(TitleBarSpinner, warningMessage);
};

interface WorkspaceContainerProps {
interface WorkspaceContainerProps extends PropsWithChildren {
namespace: string;
name: string;
breadcrumbs: ReactNode;
breadcrumbs: ReactNode[];
title: string;
activeTab?: string;
analysesData: AppDetails & CloudEnvironmentDetails;
Expand All @@ -99,7 +101,7 @@ interface WorkspaceContainerProps {
refreshWorkspace: () => void;
}

export const WorkspaceContainer = (props: PropsWithChildren<WorkspaceContainerProps>) => {
export const WorkspaceContainer = (props: WorkspaceContainerProps) => {
const {
namespace,
name,
Expand All @@ -125,7 +127,7 @@ export const WorkspaceContainer = (props: PropsWithChildren<WorkspaceContainerPr
return h(FooterWrapper, [
h(TopBar, { title: 'Workspaces', href: Nav.getLink('workspaces') }, [
div({ style: Style.breadcrumb.breadcrumb }, [
div({ style: Style.noWrapEllipsis }, [breadcrumbs]),
div({ style: Style.noWrapEllipsis }, breadcrumbs),
h2({ style: Style.breadcrumb.textUnderBreadcrumb }, [title || `${namespace}/${name}`]),
]),
div({ style: { flexGrow: 1 } }),
Expand Down Expand Up @@ -164,12 +166,15 @@ export const WorkspaceContainer = (props: PropsWithChildren<WorkspaceContainerPr
setSharingWorkspace,
setShowLockWorkspaceModal,
}),
h(WorkspaceDeletingBanner, { workspace }),
workspaceLoaded && isAzureWorkspace(workspace) && h(AzureWarning),
isGoogleWorkspaceSyncing && h(GooglePermissionsSpinner),
div({ role: 'main', style: Style.elements.pageContentContainer }, [
div({ style: { flex: 1, display: 'flex' } }, [
div({ style: { flex: 1, display: 'flex', flexDirection: 'column' } }, [children]),
workspace &&
workspace?.workspace.state !== 'Deleting' &&
workspace?.workspace.state !== 'DeleteFailed' &&
h(ContextBar, {
workspace,
apps,
Expand Down Expand Up @@ -368,7 +373,7 @@ const useAppPolling = (workspace: Workspace): AppDetails => {
};

interface WrapWorkspaceProps {
breadcrumbs: (props: { name: string; namespace: string }) => ReactNode;
breadcrumbs: (props: { name: string; namespace: string }) => ReactNode[];
activeTab?: string;
title: string;
}
Expand Down
100 changes: 100 additions & 0 deletions src/pages/workspaces/workspace/WorkspaceDeletingBanner.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { fireEvent, screen } from '@testing-library/react';
import { h } from 'react-hyperscript-helpers';
import { WorkspaceWrapper as Workspace } from 'src/libs/workspace-utils';
import { WorkspaceDeletingBanner } from 'src/pages/workspaces/workspace/WorkspaceDeletingBanner';
import { renderWithAppContexts as render } from 'src/testing/test-utils';
import { defaultAzureWorkspace } from 'src/testing/workspace-fixtures';

// Mocking for Nav.getLink
type NavExports = typeof import('src/libs/nav');
jest.mock(
'src/libs/nav',
(): NavExports => ({
...jest.requireActual<NavExports>('src/libs/nav'),
getLink: jest.fn(() => '/'),
})
);

describe('WorkspaceDeletingBanner', () => {
it('shows a message when the workspace is deleting', () => {
// Arrange
const workspace: Workspace = {
...defaultAzureWorkspace,
workspace: {
...defaultAzureWorkspace.workspace,
state: 'Deleting',
},
};
// Act
render(h(WorkspaceDeletingBanner, { workspace }));

// Assert
const message = screen.getByText(
'Workspace deletion in progress. Analyses, Workflow, and Data tools are no longer accessible.'
);
expect(message).not.toBeNull();
});

it('shows a message when the workspace has failed deletion', () => {
// Arrange
const workspace: Workspace = {
...defaultAzureWorkspace,
workspace: {
...defaultAzureWorkspace.workspace,
state: 'DeleteFailed',
},
};
// Act
render(h(WorkspaceDeletingBanner, { workspace }));

// Assert
const message = screen.getByText(
'Error deleting workspace. Analyses, Workflow, and Data tools are no longer accessible.'
);
expect(message).not.toBeNull();
// we didn't set the workspace error message, so there should not be a link for details
const detailsLink = screen.queryByText('See error details.');
expect(detailsLink).toBeNull();
});

// TODO: re-enable when https://broadworkbench.atlassian.net/browse/WOR-1283 is complete
xit('gives a link to display the workspace error message if present', () => {
// Arrange
const workspace: Workspace = {
...defaultAzureWorkspace,
workspace: {
...defaultAzureWorkspace.workspace,
state: 'DeleteFailed',
errorMessage: 'A semi-helpful message!',
},
};
// Act
render(h(WorkspaceDeletingBanner, { workspace }));

// Assert
const detailsLink = screen.getByText('See error details.');
expect(detailsLink).not.toBeNull();
});

// TODO: re-enable when https://broadworkbench.atlassian.net/browse/WOR-1283 is complete
xit('shows the error message in a modal when the details link is clicked', () => {
// Arrange
const workspace: Workspace = {
...defaultAzureWorkspace,
workspace: {
...defaultAzureWorkspace.workspace,
state: 'DeleteFailed',
errorMessage: 'A semi-helpful message!',
},
};
// Act
render(h(WorkspaceDeletingBanner, { workspace }));

// Assert
const detailsLink = screen.getByText('See error details.');
expect(detailsLink).not.toBeNull();
fireEvent.click(detailsLink);
const message = screen.getByText('A semi-helpful message!');
expect(message).not.toBeNull();
});
});
Loading

0 comments on commit 517a19a

Please sign in to comment.