Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WM-2427] Remove ability for Cromwell appTypes to be deleted #4592

Merged
merged 29 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
01837c7
filter out cromwell app types; update test
kpierre13 Jan 16, 2024
f21115f
cleanup
kpierre13 Jan 16, 2024
f94f52e
Commenting out azure tests to be used again in the future
kpierre13 Jan 16, 2024
cc9aa48
refactor
kpierre13 Jan 16, 2024
4268574
excluding cromwell app types
kpierre13 Jan 19, 2024
d553483
Merge branch 'dev' into wm-2427_remove_cromwell_app_instances_kp
kpierre13 Jan 22, 2024
b50e128
using environmentPermissions logic
kpierre13 Jan 23, 2024
8dabb33
add a test; revert other tests
kpierre13 Jan 23, 2024
dcdc6a4
change type of resource
kpierre13 Jan 23, 2024
fcb1fbc
cast to access appType
kpierre13 Jan 23, 2024
5e09b16
update tests
kpierre13 Jan 23, 2024
f870837
wip
kpierre13 Jan 24, 2024
6d16f23
new permission and state provider
kpierre13 Jan 29, 2024
5d35d62
cleanup
kpierre13 Jan 29, 2024
58937b1
Merge branch 'dev' into wm-2427_remove_cromwell_app_instances_kp
kpierre13 Jan 29, 2024
268eae7
test cleanup
kpierre13 Jan 29, 2024
fcf557a
test cleanup
kpierre13 Jan 30, 2024
9814dee
test cleanup
kpierre13 Jan 30, 2024
27fead3
cleanup
kpierre13 Jan 30, 2024
8d14b12
tests
kpierre13 Jan 30, 2024
0b70825
tests
kpierre13 Jan 30, 2024
967a632
renaming PR comment
kpierre13 Feb 1, 2024
2466b5a
stronger type safety in test
kpierre13 Feb 5, 2024
30e520a
Merge branch 'dev' into wm-2427_remove_cromwell_app_instances_kp
kpierre13 Feb 5, 2024
c6b70e1
Merge branch 'dev' into wm-2427_remove_cromwell_app_instances_kp
kpierre13 Feb 6, 2024
b68ef1b
cleanup
kpierre13 Feb 6, 2024
60641fc
PR comments
kpierre13 Feb 7, 2024
9125f5b
CI failures
kpierre13 Feb 7, 2024
b3988d1
Merge branch 'dev' into wm-2427_remove_cromwell_app_instances_kp
kpierre13 Feb 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 19 additions & 11 deletions src/analysis/Environments/DeleteButton.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,44 @@
import _ from 'lodash/fp';
import { ReactNode } from 'react';
import { h } from 'react-hyperscript-helpers';
import { isResourceDeletable } from 'src/analysis/utils/resource-utils';
import { getDisplayRuntimeStatus } from 'src/analysis/utils/runtime-utils';
import { Link } from 'src/components/common';
import { makeMenuIcon } from 'src/components/PopupTrigger';
import { isApp } from 'src/libs/ajax/leonardo/models/app-models';
import { LeoRuntimeStatus } from 'src/libs/ajax/leonardo/models/runtime-models';
import * as Utils from 'src/libs/utils';

import { DecoratedComputeResource } from './Environments.models';
import { DecoratedComputeResource, LeoResourcePermissionsProvider } from './Environments.models';

type DeletePermissionsProvider = Pick<LeoResourcePermissionsProvider, 'canDeleteApp' | 'canDeleteResource'>;

export interface DeleteButtonProps {
resource: DecoratedComputeResource;
permissions: DeletePermissionsProvider;
onClick: (resource: DecoratedComputeResource) => void;
}

export const DeleteButton = (props: DeleteButtonProps): ReactNode => {
const { resource, onClick } = props;
const resourceType = isApp(resource) ? 'app' : 'runtime';
const isDeletable = isResourceDeletable(resourceType, resource);
const { resource, permissions, onClick } = props;
const isDeletable = isApp(resource) ? permissions.canDeleteApp(resource) : permissions.canDeleteResource(resource);

return h(
Link,
{
disabled: !isDeletable,
tooltip: isDeletable
? 'Delete cloud environment'
: `Cannot delete a cloud environment while in status ${_.upperCase(
getDisplayRuntimeStatus(resource.status as LeoRuntimeStatus)
)}.`,
tooltip: Utils.cond(
[isDeletable, () => 'Delete cloud environment'],
[!permissions.canDeleteApp(resource), () => 'Deleting not yet supported'],
[
Utils.DEFAULT,
() =>
`Cannot delete a cloud environment while in status ${_.upperCase(
getDisplayRuntimeStatus(resource.status as LeoRuntimeStatus)
)}.`,
]
),
onClick: () => onClick(resource),
},
[makeMenuIcon('trash'), 'Delete']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this undefined do?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment, nothing. TS was unhappy here because it expected a props arg, but there wasn't one. I added the undefined to make this function happy.

[makeMenuIcon('trash', undefined), 'Delete']
);
};
4 changes: 3 additions & 1 deletion src/analysis/Environments/Environments.models.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { App, ListAppItem } from 'src/libs/ajax/leonardo/models/app-models';
import { PersistentDisk } from 'src/libs/ajax/leonardo/models/disk-models';
import { ListRuntimeItem } from 'src/libs/ajax/leonardo/models/runtime-models';
import { ListRuntimeItem, Runtime } from 'src/libs/ajax/leonardo/models/runtime-models';
import { LeoDiskProvider } from 'src/libs/ajax/leonardo/providers/LeoDiskProvider';
import { LeoRuntimeProvider } from 'src/libs/ajax/leonardo/providers/LeoRuntimeProvider';
import { WorkspaceInfo } from 'src/workspaces/utils';
Expand All @@ -20,6 +20,8 @@ export type DecoratedResource = DecoratedComputeResource | DiskWithWorkspace;
export interface LeoResourcePermissionsProvider {
canDeleteDisk: (disk: PersistentDisk) => boolean;
canPauseResource: (resource: App | ListRuntimeItem) => boolean;
canDeleteApp: (resource: App) => boolean;
canDeleteResource: (resource: App | PersistentDisk | Runtime) => boolean;
}

export type DeleteRuntimeProvider = Pick<LeoRuntimeProvider, 'delete'>;
Expand Down
58 changes: 53 additions & 5 deletions src/analysis/Environments/Environments.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NavLinkProvider } from '@terra-ui-packages/core-utils';
import { act, fireEvent, getAllByRole, screen } from '@testing-library/react';
import { act, fireEvent, getAllByRole, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import _ from 'lodash/fp';
import { h } from 'react-hyperscript-helpers';
Expand All @@ -21,6 +21,7 @@ import { LeoAppProvider } from 'src/libs/ajax/leonardo/providers/LeoAppProvider'
import { LeoDiskProvider } from 'src/libs/ajax/leonardo/providers/LeoDiskProvider';
import { LeoRuntimeProvider } from 'src/libs/ajax/leonardo/providers/LeoRuntimeProvider';
import { makeCompleteDate } from 'src/libs/utils';
import { leoResourcePermissions } from 'src/pages/EnvironmentsPage/environmentsPermissions';
import { asMockedFn, renderWithAppContexts as render } from 'src/testing/test-utils';
import { defaultAzureWorkspace, defaultGoogleWorkspace } from 'src/testing/workspace-fixtures';
import { WorkspaceWrapper } from 'src/workspaces/utils';
Expand All @@ -36,10 +37,6 @@ const mockNav: NavLinkProvider<EnvironmentNavActions> = {
getUrl: jest.fn().mockReturnValue('/'),
navTo: jest.fn(),
};
const mockPermissions: LeoResourcePermissionsProvider = {
canDeleteDisk: jest.fn(),
canPauseResource: jest.fn(),
};

const defaultUseWorkspacesProps = {
workspaces: [defaultGoogleWorkspace] as WorkspaceWrapper[],
Expand Down Expand Up @@ -81,8 +78,16 @@ const getMockLeoDiskProvider = (overrides?: Partial<LeoDiskProvider>): LeoDiskPr
};

const getEnvironmentsProps = (propsOverrides?: Partial<EnvironmentsProps>): EnvironmentsProps => {
const mockPermissions: LeoResourcePermissionsProvider = {
canDeleteDisk: jest.fn(),
canPauseResource: jest.fn(),
canDeleteApp: jest.fn(),
canDeleteResource: jest.fn(),
};
asMockedFn(mockPermissions.canDeleteDisk).mockReturnValue(true);
asMockedFn(mockPermissions.canPauseResource).mockReturnValue(true);
asMockedFn(mockPermissions.canDeleteApp).mockReturnValue(true);
asMockedFn(mockPermissions.canDeleteResource).mockReturnValue(true);

const defaultProps: EnvironmentsProps = {
nav: mockNav,
Expand Down Expand Up @@ -237,6 +242,7 @@ describe('Environments', () => {
...defaultUseWorkspacesProps,
workspaces: [defaultGoogleWorkspace, defaultAzureWorkspace],
});
props.permissions.canDeleteResource = leoResourcePermissions.canDeleteResource;

// Act
await act(async () => {
Expand Down Expand Up @@ -524,6 +530,48 @@ describe('Environments', () => {
expect(getTextContentForColumn(fourthAppRow, 8)).toBe(makeCompleteDate(azureApp2.auditInfo.dateAccessed));
});

it('Renders Cromwell apps with disabled delete', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for adding a detailed test!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed! this is a great looking test.

// Arrange
const props = getEnvironmentsProps();

const googleApp1 = generateTestAppWithGoogleWorkspace({}, defaultGoogleWorkspace);
const azureApp1 = generateTestAppWithAzureWorkspace({ appType: appToolLabels.CROMWELL }, defaultAzureWorkspace);
const azureWorkspace2 = generateAzureWorkspace();
const azureApp2 = generateTestAppWithAzureWorkspace({ appType: appToolLabels.WORKFLOWS_APP }, azureWorkspace2);
asMockedFn(props.leoAppData.listWithoutProject).mockResolvedValue([googleApp1, azureApp1, azureApp2]);
asMockedFn(props.useWorkspaces).mockReturnValue({
...defaultUseWorkspacesProps,
workspaces: [defaultGoogleWorkspace, defaultAzureWorkspace, azureWorkspace2],
});
props.permissions = leoResourcePermissions;

// Act
await act(async () => {
render(h(Environments, props));
});

// Assert
expect(screen.getAllByRole('row')).toHaveLength(6);

const tableRows: HTMLElement[] = screen.getAllByRole('row').slice(1); // skip header row
kpierre13 marked this conversation as resolved.
Show resolved Hide resolved
const firstAppRow: HTMLElement = tableRows[0];
const actionColumnButton1 = within(firstAppRow).getByRole('button', { name: 'Delete' });
expect(actionColumnButton1).not.toHaveAttribute('disabled');

const secondAppRow: HTMLElement = tableRows[1];
const actionColumnButton2 = within(secondAppRow).getByRole('button', { name: 'Delete' });
expect(actionColumnButton2).toHaveAttribute('disabled');

const thirdAppRow: HTMLElement = tableRows[2];
const actionColumnButton3 = within(thirdAppRow).getByRole('button', { name: 'Delete' });
expect(actionColumnButton3).toHaveAttribute('disabled');

// Check for tooltip, and that it's only present for azure apps
const notSupportedTooltip = screen.getAllByText('Deleting not yet supported');
expect(notSupportedTooltip).toBeInTheDocument;
expect(notSupportedTooltip.length).toBe(2);
});

it.each([
{ app: generateTestAppWithGoogleWorkspace({}, defaultGoogleWorkspace), workspace: defaultGoogleWorkspace },
{ app: generateTestAppWithAzureWorkspace({}, defaultAzureWorkspace), workspace: defaultAzureWorkspace },
Expand Down
1 change: 1 addition & 0 deletions src/analysis/Environments/Environments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,7 @@ export const Environments = (props: EnvironmentsProps): ReactNode => {
h(PauseButton, { cloudEnvironment, permissions, pauseComputeAndRefresh }),
h(DeleteButton, {
resource: cloudEnvironment,
permissions,
onClick: (resource) => {
isApp(resource) ? deleteAppModal.open(resource) : setDeleteRuntimeId(resource.id);
},
Expand Down
17 changes: 7 additions & 10 deletions src/analysis/utils/resource-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import _ from 'lodash/fp';
import { getAppStatusForDisplay } from 'src/analysis/utils/app-utils';
import { getDisplayRuntimeStatus } from 'src/analysis/utils/runtime-utils';
import { App, isApp } from 'src/libs/ajax/leonardo/models/app-models';
import { PersistentDisk } from 'src/libs/ajax/leonardo/models/disk-models';
import { isPersistentDisk, PersistentDisk } from 'src/libs/ajax/leonardo/models/disk-models';
import { isRuntime, Runtime, runtimeStatuses } from 'src/libs/ajax/leonardo/models/runtime-models';
import * as Utils from 'src/libs/utils';

Expand All @@ -13,20 +13,17 @@ import * as Utils from 'src/libs/utils';
* https://github.com/DataBiosphere/leonardo/blob/e60c71a9e78b53196c2848cd22a752e22a2cf6f5/core/src/main/scala/org/broadinstitute/dsde/workbench/leonardo/diskModels.scala
*/
// TODO: stop using resourceType here when all types are defined....
export const isResourceDeletable = (resourceType, resource: App | PersistentDisk | Runtime) =>
export const isResourceDeletable = (resource: App | PersistentDisk | Runtime) =>
_.includes(
_.lowerCase(resource?.status),
Utils.switchCase(
resourceType,
['runtime', () => ['unknown', 'running', 'updating', 'error', 'stopping', 'stopped', 'starting']],
['app', () => ['unspecified', 'running', 'error']],
['disk', () => ['failed', 'ready']],
Utils.cond(
[isApp(resource), () => ['unspecified', 'running', 'error']],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for cleaning this up! You can remove the TODO on line 15 now!

[isRuntime(resource), () => ['unknown', 'running', 'updating', 'error', 'stopping', 'stopped', 'starting']],
[isPersistentDisk(resource), () => ['failed', 'ready']],
[
Utils.DEFAULT,
() => {
console.error(
`Cannot determine deletability; resource type ${resourceType} must be one of runtime, app or disk.`
);
console.error('Cannot determine deletability; resource type must be one of runtime, app or disk.');
return undefined;
},
]
Expand Down
7 changes: 7 additions & 0 deletions src/analysis/utils/tool-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { CloudProvider, cloudProviderTypes } from 'src/workspaces/utils';

export type RuntimeToolLabel = 'Jupyter' | 'RStudio' | 'JupyterLab';
export type AppToolLabel = 'GALAXY' | 'CROMWELL' | 'HAIL_BATCH' | 'WDS' | 'WORKFLOWS_APP' | 'CROMWELL_RUNNER_APP';
export type CromwellAppToolLabel = 'CROMWELL' | 'WORKFLOWS_APP' | 'CROMWELL_RUNNER_APP';
export type AppAccessScope = 'USER_PRIVATE' | 'WORKSPACE_SHARED';
export type LaunchableToolLabel = 'spark' | 'terminal' | 'RStudio' | 'JupyterLab';
export type ToolLabel = RuntimeToolLabel | AppToolLabel;
Expand Down Expand Up @@ -48,6 +49,12 @@ export const appToolLabels: Record<AppToolLabel, AppToolLabel> = {
WDS: 'WDS',
};

export const cromwellAppToolLabels: Record<CromwellAppToolLabel, CromwellAppToolLabel> = {
CROMWELL: 'CROMWELL',
WORKFLOWS_APP: 'WORKFLOWS_APP',
CROMWELL_RUNNER_APP: 'CROMWELL_RUNNER_APP',
};

export const appAccessScopes: Record<AppAccessScope, AppAccessScope> = {
USER_PRIVATE: 'USER_PRIVATE',
WORKSPACE_SHARED: 'WORKSPACE_SHARED',
Expand Down
5 changes: 5 additions & 0 deletions src/libs/ajax/leonardo/models/disk-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,8 @@ export interface AzurePdSelectOption {
}

export const azureDiskSizes: number[] = [32, 64, 128, 256, 512, 1024, 2048, 4095];

export const isPersistentDisk = (obj: any): obj is PersistentDisk => {
const castDisk = obj as PersistentDisk;
return castDisk && castDisk.diskType !== undefined;
};
3 changes: 1 addition & 2 deletions src/pages/EnvironmentsPage/EnvironmentsPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ import { leoDiskProvider } from 'src/libs/ajax/leonardo/providers/LeoDiskProvide
import { leoRuntimeProvider } from 'src/libs/ajax/leonardo/providers/LeoRuntimeProvider';
import { useMetricsEvent } from 'src/libs/ajax/metrics/useMetrics';
import { terraNavKey, TerraNavLinkProvider, terraNavLinkProvider } from 'src/libs/nav';
import { leoResourcePermissions } from 'src/pages/EnvironmentsPage/environmentsPermissions';
import { useWorkspaces } from 'src/workspaces/common/state/useWorkspaces';

import { leoResourcePermissions } from './environmentsPermissions';

type NavMap<NavTypes, FnReturn> = {
[Property in keyof NavTypes]: (args: NavTypes[Property]) => FnReturn;
};
Expand Down
Loading
Loading