diff --git a/src/analysis/Environments/DeleteButton.ts b/src/analysis/Environments/DeleteButton.ts index a7172179ed..07de016db9 100644 --- a/src/analysis/Environments/DeleteButton.ts +++ b/src/analysis/Environments/DeleteButton.ts @@ -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; 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'], + [isApp(resource) && !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'] + [makeMenuIcon('trash', undefined), 'Delete'] ); }; diff --git a/src/analysis/Environments/Environments.models.ts b/src/analysis/Environments/Environments.models.ts index 69cdb2afbe..cc052590aa 100644 --- a/src/analysis/Environments/Environments.models.ts +++ b/src/analysis/Environments/Environments.models.ts @@ -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'; @@ -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; diff --git a/src/analysis/Environments/Environments.test.ts b/src/analysis/Environments/Environments.test.ts index 3ecf918799..5251233419 100644 --- a/src/analysis/Environments/Environments.test.ts +++ b/src/analysis/Environments/Environments.test.ts @@ -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'; @@ -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'; @@ -36,10 +37,6 @@ const mockNav: NavLinkProvider = { getUrl: jest.fn().mockReturnValue('/'), navTo: jest.fn(), }; -const mockPermissions: LeoResourcePermissionsProvider = { - canDeleteDisk: jest.fn(), - canPauseResource: jest.fn(), -}; const defaultUseWorkspacesProps = { workspaces: [defaultGoogleWorkspace] as WorkspaceWrapper[], @@ -81,8 +78,16 @@ const getMockLeoDiskProvider = (overrides?: Partial): LeoDiskPr }; const getEnvironmentsProps = (propsOverrides?: Partial): 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, @@ -237,6 +242,7 @@ describe('Environments', () => { ...defaultUseWorkspacesProps, workspaces: [defaultGoogleWorkspace, defaultAzureWorkspace], }); + props.permissions.canDeleteResource = leoResourcePermissions.canDeleteResource; // Act await act(async () => { @@ -524,6 +530,48 @@ describe('Environments', () => { expect(getTextContentForColumn(fourthAppRow, 8)).toBe(makeCompleteDate(azureApp2.auditInfo.dateAccessed)); }); + it('Renders Cromwell apps with disabled delete', async () => { + // 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 + 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 }, diff --git a/src/analysis/Environments/Environments.ts b/src/analysis/Environments/Environments.ts index de975ef277..24371a53aa 100644 --- a/src/analysis/Environments/Environments.ts +++ b/src/analysis/Environments/Environments.ts @@ -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); }, diff --git a/src/analysis/modals/AnalysisModal.ts b/src/analysis/modals/AnalysisModal.ts index 97052f64f1..37999030cb 100644 --- a/src/analysis/modals/AnalysisModal.ts +++ b/src/analysis/modals/AnalysisModal.ts @@ -144,14 +144,11 @@ export const AnalysisModal = withDisplayName('AnalysisModal')( Utils.cond( [doesCloudEnvForToolExist, onSuccess], [ - !doesCloudEnvForToolExist && !!currentRuntime && isResourceDeletable('runtime', currentRuntime), + !doesCloudEnvForToolExist && !!currentRuntime && isResourceDeletable(currentRuntime), () => setViewMode(environmentMode), ], [!doesCloudEnvForToolExist && !currentRuntime, () => setViewMode(environmentMode)], - [ - !doesCloudEnvForToolExist && !!currentRuntime && !isResourceDeletable('runtime', currentRuntime), - onSuccess, - ] + [!doesCloudEnvForToolExist && !!currentRuntime && !isResourceDeletable(currentRuntime), onSuccess] ), ], [environmentMode, onSuccess], @@ -368,9 +365,7 @@ export const AnalysisModal = withDisplayName('AnalysisModal')( : runtimeToolLabels.JupyterLab; const tool = toolLabel ? tools[toolLabel] : undefined; setCurrentToolObj(tool); - currentRuntime && - !isResourceDeletable('runtime', currentRuntime) && - currentRuntimeToolLabel !== toolLabel + currentRuntime && !isResourceDeletable(currentRuntime) && currentRuntimeToolLabel !== toolLabel ? onSuccess() : enterNextViewMode(tool, analysisMode); uploadFiles(files); @@ -494,7 +489,7 @@ export const AnalysisModal = withDisplayName('AnalysisModal')( ]), (isJupyterLab || isRStudio || isJupyter) && currentRuntime && - !isResourceDeletable('runtime', currentRuntime) && + !isResourceDeletable(currentRuntime) && currentRuntimeToolLabel !== toolLabel && div({ style: { backgroundColor: colors.warning(0.1), margin: '0.5rem', padding: '1rem' } }, [ h(WarningTitle, { iconSize: 16 }, [span({ style: { fontWeight: 600 } }, ['Environment Creation'])]), diff --git a/src/analysis/utils/resource-utils.ts b/src/analysis/utils/resource-utils.ts index 133427acfd..ee1fb3d673 100644 --- a/src/analysis/utils/resource-utils.ts +++ b/src/analysis/utils/resource-utils.ts @@ -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'; @@ -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']], + [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; }, ] diff --git a/src/analysis/utils/tool-utils.ts b/src/analysis/utils/tool-utils.ts index 5a14785cba..6681292582 100644 --- a/src/analysis/utils/tool-utils.ts +++ b/src/analysis/utils/tool-utils.ts @@ -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; @@ -48,6 +49,12 @@ export const appToolLabels: Record = { WDS: 'WDS', }; +export const cromwellAppToolLabels: Record = { + CROMWELL: 'CROMWELL', + WORKFLOWS_APP: 'WORKFLOWS_APP', + CROMWELL_RUNNER_APP: 'CROMWELL_RUNNER_APP', +}; + export const appAccessScopes: Record = { USER_PRIVATE: 'USER_PRIVATE', WORKSPACE_SHARED: 'WORKSPACE_SHARED', diff --git a/src/libs/ajax/leonardo/models/disk-models.ts b/src/libs/ajax/leonardo/models/disk-models.ts index f8a1882445..a2a3bc2f2e 100644 --- a/src/libs/ajax/leonardo/models/disk-models.ts +++ b/src/libs/ajax/leonardo/models/disk-models.ts @@ -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; +}; diff --git a/src/pages/EnvironmentsPage/EnvironmentsPage.ts b/src/pages/EnvironmentsPage/EnvironmentsPage.ts index 2e10cc7c6b..6bd45cf749 100644 --- a/src/pages/EnvironmentsPage/EnvironmentsPage.ts +++ b/src/pages/EnvironmentsPage/EnvironmentsPage.ts @@ -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 = { [Property in keyof NavTypes]: (args: NavTypes[Property]) => FnReturn; }; diff --git a/src/pages/EnvironmentsPage/environmentsPermissions.test.ts b/src/pages/EnvironmentsPage/environmentsPermissions.test.ts index edfc8d335a..d090be2621 100644 --- a/src/pages/EnvironmentsPage/environmentsPermissions.test.ts +++ b/src/pages/EnvironmentsPage/environmentsPermissions.test.ts @@ -1,7 +1,8 @@ import { DeepPartial } from '@terra-ui-packages/core-utils'; import { asMockedFn } from '@terra-ui-packages/test-utils'; +import { App } 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 { getTerraUser, TerraUser } from 'src/libs/state'; import { leoResourcePermissions } from './environmentsPermissions'; @@ -75,4 +76,165 @@ describe('environmentsPermissions', () => { // Assert expect(canIDeleteDisk).toBe(false); }); + + it.each([ + { + resource: { + appName: 'terra-app-3f07f6aa-6531-4151-824d-0ab8d0f19cd1', + status: 'RUNNING', + appType: 'GALAXY', + }, + canDeleteApp: true, + }, + { + resource: { + appName: 'terra-app-3f07f6aa-6531-4151-824d-0ab8d0f19cd1', + status: 'CREATING', + appType: 'GALAXY', + }, + canDeleteApp: false, + }, + { + resource: { + appName: 'terra-app-3f07f6aa-6531-4151-824d-0ab8d0f19cd1', + status: 'RUNNING', + appType: 'CROMWELL', + }, + canDeleteApp: false, + }, + { + resource: { + appName: 'terra-app-3f07f6aa-6531-4151-824d-0ab8d0f19cd1', + status: 'RUNNING', + appType: 'WORKFLOWS_APP', + }, + canDeleteApp: false, + }, + { + resource: { + appName: 'terra-app-3f07f6aa-6531-4151-824d-0ab8d0f19cd1', + status: 'RUNNING', + appType: 'CROMWELL_RUNNER_APP', + }, + canDeleteApp: false, + }, + { + resource: { + appName: 'terra-app-3f07f6aa-6531-4151-824d-0ab8d0f19cd1', + status: 'RUNNING', + appType: 'WDS', + }, + canDeleteApp: true, + }, + { + resource: { + appName: 'terra-app-3f07f6aa-6531-4151-824d-0ab8d0f19cd1', + status: 'RUNNING', + appType: 'HAIL_BATCH', + }, + canDeleteApp: true, + }, + ] as { resource: App; canDeleteApp: boolean }[])( + 'returns proper boolean for app deletion', + ({ resource, canDeleteApp }) => { + expect(leoResourcePermissions.canDeleteApp(resource)).toBe(canDeleteApp); + } + ); + + it.each([ + { + resource: { + workspaceId: 'e5795cea-b98f-4b45-a92d-57173e4d1ea4', + runtimeName: 'saturn-eb643cb0-e6f3-451c-88cd-0c79edd2df64', + status: 'Stopped', + runtimeConfig: { + machineType: 'machine', + persistentDiskId: 313, + region: null, + }, + cloudContext: 'AZURE', + }, + canDeleteResource: true, + }, + { + resource: { + workspaceId: 'e5795cea-b98f-4b45-a92d-57173e4d1ea4', + runtimeName: 'saturn-eb643cb0-e6f3-451c-88cd-0c79edd2df64', + status: 'Creating', + runtimeConfig: { + machineType: 'machine', + persistentDiskId: 313, + region: null, + }, + cloudContext: 'AZURE', + }, + canDeleteResource: false, + }, + { + resource: { + workspaceId: 'e5795cea-b98f-4b45-a92d-57173e4d1ea4', + runtimeName: 'saturn-eb643cb0-e6f3-451c-88cd-0c79edd2df64', + status: 'Creating', + diskType: 'pd-standard', + }, + canDeleteResource: false, + }, + { + resource: { + appName: 'terra-app-3f07f6aa-6531-4151-824d-0ab8d0f19cd1', + status: 'RUNNING', + appType: 'GALAXY', + }, + canDeleteResource: true, + }, + { + resource: { + appName: 'terra-app-3f07f6aa-6531-4151-824d-0ab8d0f19cd1', + status: 'RUNNING', + appType: 'CROMWELL', + }, + canDeleteResource: true, + }, + { + resource: { + appName: 'terra-app-3f07f6aa-6531-4151-824d-0ab8d0f19cd1', + status: 'RUNNING', + appType: 'WORKFLOWS_APP', + }, + canDeleteResource: true, + }, + { + resource: { + appName: 'terra-app-3f07f6aa-6531-4151-824d-0ab8d0f19cd1', + status: 'RUNNING', + appType: 'CROMWELL_RUNNER_APP', + }, + canDeleteResource: true, + }, + { + resource: { + appName: 'terra-app-3f07f6aa-6531-4151-824d-0ab8d0f19cd1', + status: 'RUNNING', + appType: 'WDS', + workspaceId: null, + cloudContext: { cloudProvider: 'AZURE', cloudResource: 'string' }, + kubernetesRuntimeConfig: { numNodes: 1, machineType: 'string', autoscalingEnabled: false }, + errors: [], + accessScope: null, + region: 'abc123', + proxyUrls: { + cbas: 'foo.bar', + }, + diskName: null, + auditInfo: { creator: 'foo', createdDate: 'bar', destroyedDate: null, dateAccessed: 'accessed' }, + labels: {}, + }, + canDeleteResource: true, + }, + ] as { resource: App | PersistentDisk | Runtime; canDeleteResource: boolean }[])( + 'returns correct boolean for resource deletion', + ({ resource, canDeleteResource }) => { + expect(leoResourcePermissions.canDeleteResource(resource)).toBe(canDeleteResource); + } + ); }); diff --git a/src/pages/EnvironmentsPage/environmentsPermissions.ts b/src/pages/EnvironmentsPage/environmentsPermissions.ts index 7607f14b2e..6116a6d3ed 100644 --- a/src/pages/EnvironmentsPage/environmentsPermissions.ts +++ b/src/pages/EnvironmentsPage/environmentsPermissions.ts @@ -1,24 +1,29 @@ import { LeoResourcePermissionsProvider } from 'src/analysis/Environments/Environments.models'; -import { getCreatorForCompute } from 'src/analysis/utils/resource-utils'; +import { getCreatorForCompute, isResourceDeletable } from 'src/analysis/utils/resource-utils'; +import { cromwellAppToolLabels } from 'src/analysis/utils/tool-utils'; import { App } 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 { getTerraUser } from 'src/libs/state'; const makePermissionsProvider = (userEmailGetter: () => string): LeoResourcePermissionsProvider => { - const permissionsProvider: LeoResourcePermissionsProvider = { + return { canDeleteDisk: (disk: PersistentDisk) => { const currentUserEmail = userEmailGetter(); - const canDelete = disk.auditInfo.creator === currentUserEmail; - return canDelete; + return disk.auditInfo.creator === currentUserEmail; }, canPauseResource: (resource: App | ListRuntimeItem) => { const currentUserEmail = userEmailGetter(); const creator = getCreatorForCompute(resource); return currentUserEmail === creator; }, + canDeleteApp: (resource: App) => { + return isResourceDeletable(resource) && !Object.keys(cromwellAppToolLabels).includes(resource.appType); + }, + canDeleteResource: (resource: App | PersistentDisk | Runtime) => { + return isResourceDeletable(resource); + }, }; - return permissionsProvider; }; const currentUserEmailGetter = (): string => { diff --git a/src/workspaces/DeleteWorkspaceModal/state/useDeleteWorkspaceState.ts b/src/workspaces/DeleteWorkspaceModal/state/useDeleteWorkspaceState.ts index 29498fece9..059517e487 100644 --- a/src/workspaces/DeleteWorkspaceModal/state/useDeleteWorkspaceState.ts +++ b/src/workspaces/DeleteWorkspaceModal/state/useDeleteWorkspaceState.ts @@ -61,9 +61,9 @@ export const useDeleteWorkspaceState = (hookArgs: DeleteWorkspaceHookArgs): Dele ? await Ajax(signal).Runtimes.listV2WithWorkspace(workspaceInfo.workspaceId) : []; - const [deletableApps, nonDeletableApps] = _.partition((app) => isResourceDeletable('app', app), apps); + const [deletableApps, nonDeletableApps] = _.partition((app) => isResourceDeletable(app), apps); const [deletableRuntimes, nonDeletableRuntimes] = _.partition( - (runtime) => isResourceDeletable('runtime', runtime), + (runtime) => isResourceDeletable(runtime), currentRuntimesList ); return {