Skip to content

Commit

Permalink
fix(ui): Fix multiple UI issues (#3573)
Browse files Browse the repository at this point in the history
  • Loading branch information
simster7 authored and alexec committed Jul 28, 2020
1 parent e94cf8a commit 5f4dec7
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,56 +9,62 @@ export type OperationDisabled = {
export type WorkflowOperationName = 'RETRY' | 'RESUBMIT' | 'SUSPEND' | 'RESUME' | 'STOP' | 'TERMINATE' | 'DELETE';

export interface WorkflowOperation {
title: string;
action: () => Promise<any>;
title: WorkflowOperationName;
action: WorkflowOperationAction;
iconClassName: string;
disabled: (wf: Workflow) => boolean;
}

export const WorkflowOperations = {
export type WorkflowOperationAction = (wf: Workflow) => Promise<any>;

export interface WorkflowOperations {
[name: string]: WorkflowOperation;
}

export const WorkflowOperationsMap: WorkflowOperations = {
RETRY: {
title: 'RETRY',
iconClassName: 'fa fa-undo',
disabled: (wf: Workflow) => {
const workflowPhase: NodePhase = wf && wf.status ? wf.status.phase : undefined;
return workflowPhase === undefined || !(workflowPhase === 'Failed' || workflowPhase === 'Error');
},
action: services.workflows.retry
action: (wf: Workflow) => services.workflows.retry(wf.metadata.name, wf.metadata.namespace)
},
RESUBMIT: {
title: 'RESUBMIT',
iconClassName: 'fa fa-plus-circle',
disabled: () => false,
action: services.workflows.resubmit
action: (wf: Workflow) => services.workflows.resubmit(wf.metadata.name, wf.metadata.namespace)
},
SUSPEND: {
title: 'SUSPEND',
iconClassName: 'fa fa-pause',
disabled: (wf: Workflow) => !Utils.isWorkflowRunning(wf) || Utils.isWorkflowSuspended(wf),
action: services.workflows.suspend
action: (wf: Workflow) => services.workflows.suspend(wf.metadata.name, wf.metadata.namespace)
},
RESUME: {
title: 'RESUME',
iconClassName: 'fa fa-play',
disabled: (wf: Workflow) => !Utils.isWorkflowSuspended(wf),
action: services.workflows.resume
action: (wf: Workflow) => services.workflows.resume(wf.metadata.name, wf.metadata.namespace)
},
STOP: {
title: 'STOP',
iconClassName: 'fa fa-stop-circle',
disabled: (wf: Workflow) => !Utils.isWorkflowSuspended(wf),
action: services.workflows.stop
disabled: (wf: Workflow) => !Utils.isWorkflowRunning(wf),
action: (wf: Workflow) => services.workflows.stop(wf.metadata.name, wf.metadata.namespace)
},
TERMINATE: {
title: 'TERMINATE',
iconClassName: 'fa fa-times-circle',
disabled: (wf: Workflow) => !Utils.isWorkflowSuspended(wf),
action: services.workflows.terminate
disabled: (wf: Workflow) => !Utils.isWorkflowRunning(wf),
action: (wf: Workflow) => services.workflows.terminate(wf.metadata.name, wf.metadata.namespace)
},
DELETE: {
title: 'DELETE',
iconClassName: 'fa fa-trash',
disabled: () => false,
action: services.workflows.delete
action: (wf: Workflow) => services.workflows.delete(wf.metadata.name, wf.metadata.namespace)
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {CostOptimisationNudge} from '../../../shared/components/cost-optimisatio
import {Loading} from '../../../shared/components/loading';
import {hasWarningConditionBadge} from '../../../shared/conditions-panel';
import {Consumer, ContextApis} from '../../../shared/context';
import * as Operations from '../../../shared/workflow-operations';
import * as Operations from '../../../shared/workflow-operations-map';
import {WorkflowOperationAction, WorkflowOperationName, WorkflowOperations} from '../../../shared/workflow-operations-map';
import {WorkflowParametersPanel} from '../workflow-parameters-panel';
import {WorkflowResourcePanel} from './workflow-resource-panel';

Expand Down Expand Up @@ -177,12 +178,18 @@ export class WorkflowDetails extends React.Component<RouteComponentProps<any>, W
);
}

private performAction(action: (name: string, namespace: string) => Promise<any>, title: string, redirect: string, ctx: ContextApis): void {
private performAction(action: WorkflowOperationAction, title: WorkflowOperationName, ctx: ContextApis): void {
if (!confirm(`Are you sure you want to ${title.toLowerCase()} this workflow?`)) {
return;
}
action(this.props.match.params.name, this.props.match.params.namespace)
.then(() => ctx.navigation.goto(uiUrl(redirect)))
action(this.state.workflow)
.then(wf => {
if (title === 'DELETE') {
ctx.navigation.goto(uiUrl(``));
} else {
ctx.navigation.goto(uiUrl(`workflows/${wf.metadata.namespace}/${wf.metadata.name}`));
}
})
.catch(() => {
this.appContext.apis.notifications.show({
content: `Unable to ${title} workflow`,
Expand All @@ -192,14 +199,14 @@ export class WorkflowDetails extends React.Component<RouteComponentProps<any>, W
}

private getItems(workflowPhase: NodePhase, ctx: any) {
const actions: any = Operations.WorkflowOperations;
const items = Object.keys(actions).map(actionName => {
const action = actions[actionName];
const workflowOperationsMap: WorkflowOperations = Operations.WorkflowOperationsMap;
const items = Object.keys(workflowOperationsMap).map(actionName => {
const workflowOperation = workflowOperationsMap[actionName];
return {
title: action.title.charAt(0).toUpperCase() + action.title.slice(1),
iconClassName: action.iconClassName,
disabled: action.disabled(this.state.workflow),
action: () => this.performAction(action.action, action.title, ``, ctx)
title: workflowOperation.title.charAt(0).toUpperCase() + workflowOperation.title.slice(1),
iconClassName: workflowOperation.iconClassName,
disabled: workflowOperation.disabled(this.state.workflow),
action: () => this.performAction(workflowOperation.action, workflowOperation.title, ctx)
};
});

Expand Down
26 changes: 14 additions & 12 deletions ui/src/app/workflows/components/workflows-list/workflows-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {Query} from '../../../shared/components/query';
import {ZeroState} from '../../../shared/components/zero-state';
import {exampleWorkflow} from '../../../shared/examples';
import {Utils} from '../../../shared/utils';
import * as Actions from '../../../shared/workflow-operations';
import * as Actions from '../../../shared/workflow-operations-map';

import {CostOptimisationNudge} from '../../../shared/components/cost-optimisation-nudge';
import {PaginationPanel} from '../../../shared/components/pagination-panel';
Expand All @@ -35,7 +35,7 @@ interface State {
namespace: string;
selectedPhases: string[];
selectedLabels: string[];
selectedWorkflows: {[index: string]: models.Workflow};
selectedWorkflows: Map<string, models.Workflow>;
workflows?: Workflow[];
error?: Error;
batchActionDisabled: Actions.OperationDisabled;
Expand Down Expand Up @@ -95,18 +95,18 @@ export class WorkflowsList extends BasePage<RouteComponentProps<any>, State> {
namespace: this.props.match.params.namespace || Utils.getCurrentNamespace() || '',
selectedPhases: this.queryParams('phase').length > 0 ? this.queryParams('phase') : savedOptions.selectedPhases,
selectedLabels: this.queryParams('label').length > 0 ? this.queryParams('label') : savedOptions.selectedLabels,
selectedWorkflows: {},
selectedWorkflows: new Map<string, models.Workflow>(),
batchActionDisabled: {...allBatchActionsEnabled}
};
}

public componentDidMount(): void {
this.fetchWorkflows(this.state.namespace, this.state.selectedPhases, this.state.selectedLabels, this.state.pagination);
this.setState({selectedWorkflows: {}});
this.setState({selectedWorkflows: new Map<string, models.Workflow>()});
}

public componentWillUnmount(): void {
this.setState({selectedWorkflows: {}});
this.setState({selectedWorkflows: new Map<string, models.Workflow>()});
if (this.subscription) {
this.subscription.unsubscribe();
}
Expand Down Expand Up @@ -139,8 +139,9 @@ export class WorkflowsList extends BasePage<RouteComponentProps<any>, State> {
}}>
<WorkflowsToolbar
selectedWorkflows={this.state.selectedWorkflows}
clearSelection={() => this.setState({selectedWorkflows: new Map<string, models.Workflow>()})}
loadWorkflows={() => {
this.setState({selectedWorkflows: {}});
this.setState({selectedWorkflows: new Map<string, models.Workflow>()});
this.fetchWorkflows(this.state.namespace, this.state.selectedPhases, this.state.selectedLabels, {limit: this.state.pagination.limit});
}}
isDisabled={this.state.batchActionDisabled}
Expand Down Expand Up @@ -212,7 +213,7 @@ export class WorkflowsList extends BasePage<RouteComponentProps<any>, State> {
namespace: newNamespace,
selectedPhases,
selectedLabels,
selectedWorkflows: {}
selectedWorkflows: new Map<string, models.Workflow>()
});
Utils.setCurrentNamespace(newNamespace);
return wfList.metadata.resourceVersion;
Expand Down Expand Up @@ -327,6 +328,7 @@ export class WorkflowsList extends BasePage<RouteComponentProps<any>, State> {
<WorkflowsRow
workflow={wf}
key={wf.metadata.uid}
checked={this.state.selectedWorkflows.has(wf.metadata.uid)}
onChange={key => {
const value = `${key}=${wf.metadata.labels[key]}`;
let newTags: string[] = [];
Expand All @@ -344,12 +346,12 @@ export class WorkflowsList extends BasePage<RouteComponentProps<any>, State> {
const currentlySelected = this.state.selectedWorkflows;
if (!(wfUID in currentlySelected)) {
this.updateBatchActionsDisabled(subWf, false);
currentlySelected[wfUID] = subWf;
currentlySelected.set(wfUID, subWf);
} else {
this.updateBatchActionsDisabled(subWf, true);
delete currentlySelected[wfUID];
currentlySelected.delete(wfUID);
}
this.setState({selectedWorkflows: {...currentlySelected}});
this.setState({selectedWorkflows: new Map<string, models.Workflow>(currentlySelected)});
}}
/>
);
Expand All @@ -365,15 +367,15 @@ export class WorkflowsList extends BasePage<RouteComponentProps<any>, State> {

private updateBatchActionsDisabled(wf: Workflow, deselect: boolean): void {
const currentlyDisabled: any = this.state.batchActionDisabled;
const actions: any = Actions.WorkflowOperations;
const actions: any = Actions.WorkflowOperationsMap;
const nowDisabled: any = {...allBatchActionsEnabled};
for (const action of Object.keys(currentlyDisabled)) {
if (deselect) {
for (const wfUID of Object.keys(this.state.selectedWorkflows)) {
if (wfUID === wf.metadata.uid) {
continue;
}
nowDisabled[action] = actions[action].disabled(this.state.selectedWorkflows[wfUID]) || nowDisabled[action];
nowDisabled[action] = actions[action].disabled(this.state.selectedWorkflows.get(wfUID)) || nowDisabled[action];
}
} else {
nowDisabled[action] = actions[action].disabled(wf) || currentlyDisabled[action];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,18 @@ interface WorkflowsRowProps {
workflow: Workflow;
onChange: (key: string) => void;
select: (wf: Workflow) => void;
checked: boolean;
}

interface WorkflowRowState {
hideDrawer: boolean;
selected: boolean;
}

export class WorkflowsRow extends React.Component<WorkflowsRowProps, WorkflowRowState> {
constructor(props: WorkflowsRowProps) {
super(props);
this.state = {
hideDrawer: true,
selected: false
hideDrawer: true
};
}

Expand All @@ -37,12 +36,11 @@ export class WorkflowsRow extends React.Component<WorkflowsRowProps, WorkflowRow
<input
type='checkbox'
className='workflows-list__status--checkbox'
checked={this.state.selected}
checked={this.props.checked}
onClick={e => {
e.stopPropagation();
}}
onChange={e => {
this.setState({selected: !this.state.selected});
this.props.select(this.props.workflow);
}}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@ import * as PropTypes from 'prop-types';
import * as React from 'react';
import {Workflow} from '../../../../models';
import {AppContext, Consumer} from '../../../shared/context';
import * as Actions from '../../../shared/workflow-operations';
import {WorkflowOperation} from '../../../shared/workflow-operations';
import * as Actions from '../../../shared/workflow-operations-map';
import {WorkflowOperation, WorkflowOperationAction} from '../../../shared/workflow-operations-map';

require('./workflows-toolbar.scss');

interface WorkflowsToolbarProps {
selectedWorkflows: {[index: string]: Workflow};
selectedWorkflows: Map<string, Workflow>;
loadWorkflows: () => void;
isDisabled: Actions.OperationDisabled;
clearSelection: () => void;
}

interface WorkflowGroupAction extends WorkflowOperation {
groupIsDisabled: boolean;
className: string;
groupAction: () => Promise<any>;
}

export class WorkflowsToolbar extends React.Component<WorkflowsToolbarProps, {}> {
Expand Down Expand Up @@ -46,41 +48,42 @@ export class WorkflowsToolbar extends React.Component<WorkflowsToolbarProps, {}>
}

private getNumberSelected(): number {
return Object.keys(this.props.selectedWorkflows).length;
return this.props.selectedWorkflows.size;
}

private performActionOnSelectedWorkflows(ctx: any, title: string, action: (name: string, namespace: string) => Promise<any>): Promise<any> {
private performActionOnSelectedWorkflows(ctx: any, title: string, action: WorkflowOperationAction): Promise<any> {
if (!confirm(`Are you sure you want to ${title.toLowerCase()} all selected workflows?`)) {
return;
}
const promises = [];
for (const wfUID of Object.keys(this.props.selectedWorkflows)) {
const wf = this.props.selectedWorkflows[wfUID];
const promises: Promise<any>[] = [];
this.props.selectedWorkflows.forEach((wf: Workflow) => {
promises.push(
action(wf.metadata.name, wf.metadata.namespace).catch(() => {
action(wf).catch(() => {
this.props.loadWorkflows();
this.appContext.apis.notifications.show({
content: `Unable to ${title} workflow`,
type: NotificationType.Error
});
})
);
}
});
return Promise.all(promises);
}

private renderActions(ctx: any): JSX.Element[] {
const actionButtons = [];
const actions: any = Actions.WorkflowOperations;
const actions: any = Actions.WorkflowOperationsMap;
const disabled: any = this.props.isDisabled;
const groupActions: WorkflowGroupAction[] = Object.keys(actions).map(actionName => {
const action = actions[actionName];
return {
title: action.title,
iconClassName: action.iconClassName,
groupIsDisabled: disabled[actionName],
action: () => {
action,
groupAction: () => {
return this.performActionOnSelectedWorkflows(ctx, action.title, action.action).then(() => {
this.props.clearSelection();
this.appContext.apis.notifications.show({
content: `Performed '${action.title}' on selected workflows.`,
type: NotificationType.Success
Expand All @@ -90,17 +93,19 @@ export class WorkflowsToolbar extends React.Component<WorkflowsToolbarProps, {}>
},
className: action.title,
disabled: () => false
};
} as WorkflowGroupAction;
});
for (const action of groupActions) {
for (const groupAction of groupActions) {
actionButtons.push(
<button
key={action.title}
onClick={action.action}
className={`workflows-toolbar__actions--${action.className} workflows-toolbar__actions--action`}
disabled={this.getNumberSelected() === 0 || action.groupIsDisabled}>
<i className={action.iconClassName} />
&nbsp;{action.title}
key={groupAction.title}
onClick={() => {
groupAction.groupAction().catch();
}}
className={`workflows-toolbar__actions--${groupAction.className} workflows-toolbar__actions--action`}
disabled={this.getNumberSelected() === 0 || groupAction.groupIsDisabled}>
<i className={groupAction.iconClassName} />
&nbsp;{groupAction.title}
</button>
);
}
Expand Down

0 comments on commit 5f4dec7

Please sign in to comment.