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

Conversation

kpierre13
Copy link
Contributor

@kpierre13 kpierre13 commented Jan 16, 2024

Jira Ticket: https://broadworkbench.atlassian.net/browse/[WM-2427]

Summary of changes:

What

  • Removing Cromwell app types from being displayed on the Clusters page

Why

  • Currently, deleting a Cromwell app type will put a workspace into an error state. To avoid users from deleting these apps and erroring their workspace, we want to stop a user from being able to delete, but still show them information regarding their apps.

Testing strategy

  • Unit tests

Before:
image

After:
image

@kpierre13 kpierre13 marked this pull request as ready for review January 17, 2024 15:20
@kpierre13 kpierre13 requested a review from a team as a code owner January 17, 2024 15:20
Copy link
Contributor

@LizBaldo LizBaldo left a 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?

src/analysis/Environments/Environments.ts Outdated Show resolved Hide resolved
@kpierre13
Copy link
Contributor Author

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 })
Copy link
Contributor

@jdcanas jdcanas Jan 19, 2024

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

@kpierre13 kpierre13 changed the title [WM-2427] Remove Cromwell app types from being displayed to users [WM-2427] Remove ability for Cromwell appTypes to be deleted Jan 23, 2024
@@ -22,5 +22,9 @@ export interface LeoResourcePermissionsProvider {
canPauseResource: (resource: App | ListRuntimeItem) => boolean;
}

export interface CanDeleteProvider {
canBeDeleted: (resource: App) => boolean;
Copy link
Contributor

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;
Copy link
Contributor

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 () => {
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.

@@ -21,6 +22,14 @@ const makePermissionsProvider = (userEmailGetter: () => string): LeoResourcePerm
return permissionsProvider;
};

export const makeCromwellAppsNotDeletable = (): CanDeleteProvider => {
Copy link
Contributor

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;
Copy link
Contributor

@jdcanas jdcanas Jan 23, 2024

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.

Copy link
Contributor Author

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!

Copy link
Contributor Author

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?

Copy link
Contributor

@jdcanas jdcanas Jan 23, 2024

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 this isResourceDeletable 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

Copy link
Contributor

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

Copy link
Contributor Author

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);
Copy link
Contributor

@jdcanas jdcanas Feb 6, 2024

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';
Copy link
Contributor

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) => {
Copy link
Contributor

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);
Copy link
Contributor

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!

Copy link
Contributor

@jdcanas jdcanas left a 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']],
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!

Copy link
Contributor

@jdcanas jdcanas left a 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

@kpierre13 kpierre13 requested a review from a team as a code owner February 7, 2024 18:38
@kpierre13
Copy link
Contributor Author

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 canDeleteApp to be temporary. Once there is handling of deleting Cromwell-type apps, canDeleteResource should be the only decider for all resource types.

Copy link

sonarcloud bot commented Feb 7, 2024

Copy link
Member

@marctalbott marctalbott left a 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!

@kpierre13 kpierre13 added this pull request to the merge queue Feb 7, 2024
Merged via the queue into dev with commit dc3d28b Feb 7, 2024
7 checks passed
@kpierre13 kpierre13 deleted the wm-2427_remove_cromwell_app_instances_kp branch February 7, 2024 20:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants