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

Action menu: sort items, show in resource details page #11008

Merged
merged 15 commits into from
Nov 8, 2022
Merged
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {DataLoader, Tab, Tabs} from 'argo-ui';
import {DataLoader, DropDown, Tab, Tabs} from 'argo-ui';
import * as React from 'react';
import {useState} from 'react';
import {EventsList, YamlEditor} from '../../../shared/components';
Expand Down Expand Up @@ -309,9 +309,21 @@ export const ResourceDetails = (props: ResourceDetailsProps) => {
className='argo-button argo-button--base'>
<i className='fa fa-sync-alt' /> SYNC
</button>
<button onClick={() => AppUtils.deletePopup(appContext, selectedNode, application)} className='argo-button argo-button--base'>
<button
onClick={() => AppUtils.deletePopup(appContext, selectedNode, application)}
style={{marginRight: '5px'}}
className='argo-button argo-button--base'>
<i className='fa fa-trash' /> DELETE
</button>
<DropDown
isMenu={true}
anchor={() => (
<button className='argo-button argo-button--light argo-button--lg argo-button--short'>
<i className='fa fa-ellipsis-v' />
</button>
)}>
{() => AppUtils.renderResourceActionMenu(selectedNode, application, tree, {apis: appContext})}
</DropDown>
</div>
<Tabs
navTransparent={true}
Expand Down
90 changes: 61 additions & 29 deletions ui/src/app/applications/components/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {DataLoader, FormField, MenuItem, NotificationType, Tooltip} from 'argo-ui';
import {models, DataLoader, FormField, MenuItem, NotificationType, Tooltip} from 'argo-ui';
import {ActionButton} from 'argo-ui/v2';
import * as classNames from 'classnames';
import * as React from 'react';
Expand Down Expand Up @@ -397,6 +397,37 @@ export const deletePopup = async (ctx: ContextApis, resource: ResourceTreeNode,
);
};

function getResourceActionsMenuItems(resource: ResourceTreeNode, metadata: models.Metadata, appContext: AppContext): Observable<ActionMenuItem[]> {
return services.applications
.getResourceActions(metadata.name, metadata.namespace, resource)
.then(actions => {
return actions.map(
action =>
({
title: action.name,
disabled: !!action.disabled,
action: async () => {
try {
const confirmed = await appContext.apis.popup.confirm(
`Execute '${action.name}' action?`,
`Are you sure you want to execute '${action.name}' action?`
);
if (confirmed) {
await services.applications.runResourceAction(metadata.name, metadata.namespace, resource, action.name);
}
} catch (e) {
appContext.apis.notifications.show({
content: <ErrorNotification title='Unable to execute resource action' e={e} />,
type: NotificationType.Error
});
}
}
} as MenuItem)
);
})
.catch(() => [] as MenuItem[]);
}

function getActionItems(
resource: ResourceTreeNode,
application: appModels.Application,
Expand Down Expand Up @@ -460,34 +491,8 @@ function getActionItems(
})
.catch(() => [] as MenuItem[]);

const resourceActions = services.applications
.getResourceActions(application.metadata.name, application.metadata.namespace, resource)
.then(actions => {
return actions.map(
action =>
({
title: action.name,
disabled: !!action.disabled,
action: async () => {
try {
const confirmed = await appContext.apis.popup.confirm(
`Execute '${action.name}' action?`,
`Are you sure you want to execute '${action.name}' action?`
);
if (confirmed) {
await services.applications.runResourceAction(application.metadata.name, application.metadata.namespace, resource, action.name);
}
} catch (e) {
appContext.apis.notifications.show({
content: <ErrorNotification title='Unable to execute resource action' e={e} />,
type: NotificationType.Error
});
}
}
} as MenuItem)
);
})
.catch(() => [] as MenuItem[]);
const resourceActions = getResourceActionsMenuItems(resource, application, appContext);

return combineLatest(
from([items]), // this resolves immediately
concat([[] as MenuItem[]], resourceActions), // this resolves at first to [] and then whatever the API returns
Expand Down Expand Up @@ -534,6 +539,33 @@ export function renderResourceMenu(
);
}

export function renderResourceActionMenu(resource: ResourceTreeNode, application: appModels.Application, tree: appModels.ApplicationTree, appContext: AppContext): React.ReactNode {
const menuItems = getResourceActionsMenuItems(resource, application.metadata, appContext);

return (
<DataLoader load={() => menuItems}>
{items => (
<ul>
{items.map((item, i) => (
<li
className={classNames('application-details__action-menu', {disabled: item.disabled})}
key={i}
onClick={e => {
e.stopPropagation();
if (!item.disabled) {
item.action();
document.body.click();
}
}}>
{item.iconClassName && <i className={item.iconClassName} />} {item.title}
</li>
))}
</ul>
)}
</DataLoader>
);
}

export function renderResourceButtons(
resource: ResourceTreeNode,
application: appModels.Application,
Expand Down
6 changes: 5 additions & 1 deletion ui/src/app/shared/services/applications-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,11 @@ export class ApplicationsService {
kind: resource.kind,
group: resource.group
})
.then(res => (res.body.actions as models.ResourceAction[]) || []);
.then(res => {
const actions = (res.body.actions as models.ResourceAction[]) || [];
actions.sort((actionA, actionB) => actionA.name.localeCompare(actionB.name));
return actions;
});
}

public runResourceAction(name: string, appNamspace: string, resource: models.ResourceNode, action: string): Promise<models.ResourceAction[]> {
Expand Down