-
Notifications
You must be signed in to change notification settings - Fork 21
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do I understand correctly that the only way to delete cromwell app types on either cloud is through deleting the workspace?
That's my understanding. I'm not totally clear about the fine details, but since reconnecting to a previously running app state is not supported, manually deleting from the clusters page throws the associated workspace into a weird state. |
leoAppData.listWithoutProject(listArgs, { signal }), | ||
// Excluding cromwell/workflows app types for now, as trying to delete these app types puts associated workspace in an error state | ||
( | ||
await leoAppData.listWithoutProject(listArgs, { signal }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... I don't particularly like this approach, it is going the opposite direction we are trying to in terms of UI architecture; adding business logic to visual components. Furthermore, just because a resource is not deletable does not necessarily mean we don't want to show it on this page.
I'd advise waiting until this PR is merged, and extending environmentPermissions
with the already existing function isResourceDeletable
.
At the very least, if you need to move this forward ASAP, I'd recommend leveraging isResourceDeletable
to centralize the logic. I believe the delete buttons already leverage this function, so adding the logic there should be sufficient.
cc @msilva-broad , so you know I'm advising someone extend work you have in progress
@@ -22,5 +22,9 @@ export interface LeoResourcePermissionsProvider { | |||
canPauseResource: (resource: App | ListRuntimeItem) => boolean; | |||
} | |||
|
|||
export interface CanDeleteProvider { | |||
canBeDeleted: (resource: App) => boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this can live in the LeoResourcePermissionProvider
and shouldn't need its own interface.
@@ -22,5 +22,9 @@ export interface LeoResourcePermissionsProvider { | |||
canPauseResource: (resource: App | ListRuntimeItem) => boolean; | |||
} | |||
|
|||
export interface CanDeleteProvider { | |||
canBeDeleted: (resource: App) => boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd also call it canDeleteApp
to be consistent with other interface method names
@@ -524,6 +524,51 @@ describe('Environments', () => { | |||
expect(getTextContentForColumn(fourthAppRow, 8)).toBe(makeCompleteDate(azureApp2.auditInfo.dateAccessed)); | |||
}); | |||
|
|||
it('Renders Cromwell apps with disabled delete', async () => { |
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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.
@@ -21,6 +22,14 @@ const makePermissionsProvider = (userEmailGetter: () => string): LeoResourcePerm | |||
return permissionsProvider; | |||
}; | |||
|
|||
export const makeCromwellAppsNotDeletable = (): CanDeleteProvider => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should move to the above makePermissionsProvider
once you consolidate CanDeleteProvider
into LeoResourcePermissionsProvider
@@ -18,19 +21,28 @@ export interface DeleteButtonProps { | |||
export const DeleteButton = (props: DeleteButtonProps): ReactNode => { | |||
const { resource, onClick } = props; | |||
const resourceType = isApp(resource) ? 'app' : 'runtime'; | |||
const isDeletable = isResourceDeletable(resourceType, resource); | |||
// Casting resource to type App to access the appType property | |||
const castToApp = resource as App; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't need to cast.
It sounds like we want to function canBeDeleted
to take resource: DecoratedComputeResource
and check isResourceDeletable
and the logic you have in !Object.keys(cromwellAppToolLabels).includes(resource.appType);
. It will need to do something like the following
if (isApp(resource)) { // access appType here }
else { //unimplemented exception, other resources, aka runtimes, not implemented yet }
Bonus points if you can consolidate it with the canDeleteDisk
function but you'd need to re-wire the type here from DecoratedComputeResource
to Resource
.
Sorry if this is a bit confusing, definitely down to pair and help you get this through. You've got all the pieces in place here though, a great second iteration! tl;dr, we should not need to cast in the DeleteButton, the permissions provider should handle the guts of isDeletable
and can take the resource directly and figure it out from there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I see what you're saying. Let me take a stab at this implementation, and I'll reach out if I get stuck. Thank you for all of the feedback!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, just a quick clarification, are you advocating for calling isResourceDeletable
from within canBeDeleted
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left that intentionally vague 😅 . I think the permissionsProvider
ultimately was designed to do one thing only, tell you if you have permissions to delete something (which relates to IAM logic).
I think we are encountering the need for another provider, one that can tell you whether a resource is in a state (business logic or otherwise) that allows a specific action.
In the scope of your PR, I could see you doing any of the following, as long as we don't straddle between multiple approaches:
- extending the
permissionsProvider
with thisisResourceDeletable
functionality (seems the least desirable) - extending the utility function
isResourceDeletable
to include your cromwell-specific logic - creating a new provider that the delete button consumes that wraps the permissionsProvider and includes the utility function (isResourceDeletable) logic + your new cromwell app logic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry if I'm a bit scattered here, but trying to move towards an ideal architecture with old code through incremental improvements is a bit tricky 😅 . I'd vote for the third above, but I understand if you would rather go with something along the lines of #2 to not expand the scope of your PR too much
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No worries! I don't think it'll be too much of a lift to create a new wrapper provider, so I'll do my best to try and implement option #3 here. I'll reach out in Slack if things start to get a little wonky in my understanding.
const resourceType = isApp(resource) ? 'app' : 'runtime'; | ||
const isDeletable = isResourceDeletable(resourceType, resource); | ||
const isDeletable = | ||
resourceType === 'app' ? permissions.canDeleteApp(resource) : permissions.canDeleteResource(resourceType, resource); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should use isApp
here instead of a string to determine typing and enforce type safety. This will let you change canDeleteApp
to actually take an App
as well.
See the TODO in isResourceDeletable
in the ts
file, we shouldn't need to pass resourceType
to canDeleteResource
, we can ask the type of the resource
directly.
onClick: (resource: DecoratedComputeResource) => void; | ||
} | ||
|
||
export const DeleteButton = (props: DeleteButtonProps): ReactNode => { | ||
const { resource, onClick } = props; | ||
const { resource, permissions, onClick } = props; | ||
const resourceType = isApp(resource) ? 'app' : 'runtime'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this can be deleted
}, | ||
canPauseResource: (resource: App | ListRuntimeItem) => { | ||
const currentUserEmail = userEmailGetter(); | ||
const creator = getCreatorForCompute(resource); | ||
return currentUserEmail === creator; | ||
}, | ||
canDeleteApp: (resource: DecoratedComputeResource) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should take an App
?
} | ||
}, | ||
canDeleteResource: (resourceType, resource: App | PersistentDisk | Runtime) => { | ||
return isResourceDeletable(resourceType, resource); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See previous comment, we should not be passing resourceType
around, the resources have types!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks mostly there! Just a bit of cleanup needed (with inline comments)
resourceType
should not be used anywhere, its a code smell to be passing around a string for the type when we can inspect the resource itself with questions like isApp
.
Additionally, canDeleteApp
should take an App
['app', () => ['unspecified', 'running', 'error']], | ||
['disk', () => ['failed', 'ready']], | ||
Utils.cond( | ||
[isApp(resource), () => ['unspecified', 'running', 'error']], |
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, thanks for cleaning this up! One small nit about removing a TODO you addressed.
Eventually, I would like to see canDeleteResource
call canDeleteApp
so the call site doesn't need to choose which function to call, but I don't feel super strongly about doing that this iteration since you've already left the code in a much better place than when you started
I agree with your point. I think the goal is for |
Quality Gate passedKudos, no new issues were introduced! 0 New issues |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Workspaces changes look good!
Jira Ticket: https://broadworkbench.atlassian.net/browse/[WM-2427]
Summary of changes:
What
Why
Testing strategy
Before:
After: