Skip to content

Commit

Permalink
feat(ui): Add filters for archived workflows (#2257)
Browse files Browse the repository at this point in the history
  • Loading branch information
whynowy authored Feb 18, 2020
1 parent d30aa33 commit b5c4726
Show file tree
Hide file tree
Showing 2 changed files with 169 additions and 57 deletions.
Original file line number Diff line number Diff line change
@@ -1,52 +1,57 @@
import {Page} from 'argo-ui';
import {Page, SlidingPanel} from 'argo-ui';

import * as classNames from 'classnames';
import {isNaN} from 'formik';
import * as React from 'react';
import {Link, RouteComponentProps} from 'react-router-dom';
import * as models from '../../../../models';
import {Workflow} from '../../../../models';
import {uiUrl} from '../../../shared/base';
import {BasePage} from '../../../shared/components/base-page';
import {Loading} from '../../../shared/components/loading';
import {NamespaceFilter} from '../../../shared/components/namespace-filter';
import {ResourceSubmit} from '../../../shared/components/resource-submit';
import {Timestamp} from '../../../shared/components/timestamp';
import {ZeroState} from '../../../shared/components/zero-state';
import {Consumer} from '../../../shared/context';
import {exampleWorkflow} from '../../../shared/examples';
import {services} from '../../../shared/services';
import {Utils} from '../../../shared/utils';
import {WorkflowFilters} from '../../../workflows/components/workflow-filters/workflow-filters';

interface State {
continue: string;
offset: number;
nextOffset: number;
loading: boolean;
initialized: boolean;
managedNamespace: boolean;
namespace: string;
selectedPhases: string[];
selectedLabels: string[];
workflows?: Workflow[];
error?: Error;
}

export class ArchivedWorkflowList extends BasePage<RouteComponentProps<any>, State> {
private get continue() {
return this.queryParam('continue') || '';
}

private set continue(continueArg: string) {
this.setQueryParams({continue: continueArg});
}

private get namespace() {
return this.state.namespace;
}

private set namespace(namespace: string) {
this.setState({namespace});
history.pushState(null, '', uiUrl('archived-workflows/' + namespace));
this.fetchArchivedWorkflows();
private get wfInput() {
return Utils.tryJsonParse(this.queryParam('new'));
}

constructor(props: RouteComponentProps<any>, context: any) {
super(props, context);
this.state = {continue: '', loading: true, namespace: this.props.match.params.namespace || ''};
this.state = {
loading: true,
offset: this.parseOffset(this.queryParam('continue') || ''),
nextOffset: 0,
initialized: false,
managedNamespace: false,
namespace: this.props.match.params.namespace || '',
selectedPhases: this.queryParams('phase'),
selectedLabels: this.queryParams('label')
};
}

public componentDidMount(): void {
this.fetchArchivedWorkflows();
this.fetchArchivedWorkflows(this.state.namespace, this.state.selectedPhases, this.state.selectedLabels, this.state.offset);
}

public render() {
Expand All @@ -57,38 +62,110 @@ export class ArchivedWorkflowList extends BasePage<RouteComponentProps<any>, Sta
throw this.state.error;
}
return (
<Page
title='Archived Workflows'
toolbar={{
breadcrumbs: [{title: 'Archived Workflows', path: uiUrl('archived-workflow')}],
tools: [
<NamespaceFilter
key='namespace-filter'
value={this.namespace}
onChange={namespace => {
this.namespace = namespace;
}}
/>
]
}}>
<div className='row'>
<div className='columns small-12'>{this.renderWorkflows()}</div>
</div>
</Page>
<Consumer>
{ctx => (
<Page
title='Archived Workflows'
toolbar={{
breadcrumbs: [{title: 'Archived Workflows', path: uiUrl('archived-workflows')}],
actionMenu: {
items: [
{
title: 'Submit New Workflow',
iconClassName: 'fa fa-plus',
action: () => ctx.navigation.goto('.', {new: '{}'})
}
]
},
tools: []
}}>
<div className='row'>
<div className='columns small-12 xlarge-2'>
<div>
<WorkflowFilters
workflows={this.state.workflows}
namespace={this.state.namespace}
phaseItems={Object.values([models.NODE_PHASE.SUCCEEDED, models.NODE_PHASE.FAILED, models.NODE_PHASE.ERROR])}
selectedPhases={this.state.selectedPhases}
selectedLabels={this.state.selectedLabels}
onChange={(namespace, selectedPhases, selectedLabels) => this.changeFilters(namespace, selectedPhases, selectedLabels, 0)}
/>
</div>
</div>
<div className='columns small-12 xlarge-10'>{this.renderWorkflows()}</div>
</div>
<SlidingPanel isShown={!!this.wfInput} onClose={() => ctx.navigation.goto('.', {new: null})}>
<ResourceSubmit<models.Workflow>
resourceName={'Workflow'}
defaultResource={exampleWorkflow(this.state.namespace)}
onSubmit={wfValue => {
return services.workflows
.create(wfValue, wfValue.metadata.namespace || this.state.namespace)
.then(wf => ctx.navigation.goto(uiUrl(`workflows/${wf.metadata.namespace}/${wf.metadata.name}`)));
}}
/>
</SlidingPanel>
</Page>
)}
</Consumer>
);
}

private fetchArchivedWorkflows(): void {
services.info
.get()
.then(info => {
if (info.managedNamespace && info.managedNamespace !== this.namespace) {
this.namespace = info.managedNamespace;
private parseOffset(str: string) {
if (isNaN(str)) {
return 0;
}
const result = parseInt(str, 10);
return result >= 0 ? result : 0;
}

private changeFilters(namespace: string, selectedPhases: string[], selectedLabels: string[], offset: number) {
const params = new URLSearchParams();
selectedPhases.forEach(phase => {
params.append('phase', phase);
});
selectedLabels.forEach(label => {
params.append('label', label);
});
if (offset > 0) {
params.append('continue', offset.toString());
}
let url = 'archived-workflows/' + namespace;
if (selectedPhases.length > 0 || selectedLabels.length > 0 || offset > 0) {
url += '?' + params.toString();
}
history.pushState(null, '', uiUrl(url));
this.fetchArchivedWorkflows(namespace, selectedPhases, selectedLabels, offset && offset >= 0 ? offset : 0);
}

private fetchArchivedWorkflows(namespace: string, selectedPhases: string[], selectedLabels: string[], offset: number): void {
let archivedWorkflowList;
let newNamespace = namespace;
if (!this.state.initialized) {
archivedWorkflowList = services.info.get().then(info => {
if (info.managedNamespace) {
newNamespace = info.managedNamespace;
}
return services.archivedWorkflows.list(this.namespace, this.continue);
})
this.setState({initialized: true, managedNamespace: info.managedNamespace ? true : false});
return services.archivedWorkflows.list(newNamespace, selectedPhases, selectedLabels, offset);
});
} else {
if (this.state.managedNamespace) {
newNamespace = this.state.namespace;
}
archivedWorkflowList = services.archivedWorkflows.list(newNamespace, selectedPhases, selectedLabels, offset);
}
archivedWorkflowList
.then(list => {
this.setState({workflows: list.items || [], continue: list.metadata.continue || '', loading: false});
this.setState({
namespace: newNamespace,
workflows: list.items || [],
selectedPhases,
selectedLabels,
offset,
nextOffset: this.parseOffset(list.metadata.continue || ''),
loading: false
});
})
.catch(error => this.setState({error, loading: false}));
}
Expand Down Expand Up @@ -129,14 +206,22 @@ export class ArchivedWorkflowList extends BasePage<RouteComponentProps<any>, Sta
))}
</div>
<p>
{this.continue !== '' && (
<button className='argo-button argo-button--base-o' onClick={() => (this.continue = '')}>
{this.state.offset !== 0 && (
<button
className='argo-button argo-button--base-o'
onClick={() => {
this.changeFilters(this.state.namespace, this.state.selectedPhases, this.state.selectedLabels, 0);
}}>
<i className='fa fa-chevron-left' /> Start
</button>
)}
{this.state.continue !== '' && (
<button className='argo-button argo-button--base-o' onClick={() => (this.continue = this.state.continue)}>
Next: {this.state.continue} <i className='fa fa-chevron-right' />
{this.state.nextOffset !== 0 && (
<button
className='argo-button argo-button--base-o'
onClick={() => {
this.changeFilters(this.state.namespace, this.state.selectedPhases, this.state.selectedLabels, this.state.nextOffset);
}}>
Next: {this.state.nextOffset} <i className='fa fa-chevron-right' />
</button>
)}
</p>
Expand Down
35 changes: 31 additions & 4 deletions ui/src/app/shared/services/archived-workflows-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ import * as models from '../../../models';
import requests from './requests';

export class ArchivedWorkflowsService {
public list(namespace: string, continueArg: string) {
return requests
.get(`api/v1/archived-workflows?listOptions.fieldSelector=metadata.namespace=${namespace}&listOptions.continue=${continueArg}`)
.then(res => res.body as models.WorkflowList);
public list(namespace: string, phases: string[], labels: string[], offset: number) {
return requests.get(`api/v1/archived-workflows?${this.queryParams({namespace, phases, labels, offset}).join('&')}`).then(res => res.body as models.WorkflowList);
}

public get(uid: string) {
Expand All @@ -15,4 +13,33 @@ export class ArchivedWorkflowsService {
public delete(uid: string) {
return requests.delete(`api/v1/archived-workflows/${uid}`);
}

private queryParams(filter: {namespace?: string; phases?: Array<string>; labels?: Array<string>; offset?: number}) {
const queryParams: string[] = [];
if (filter.namespace) {
queryParams.push(`listOptions.fieldSelector=metadata.namespace=${filter.namespace}`);
}
const labelSelector = this.labelSelectorParams(filter.phases, filter.labels);
if (labelSelector.length > 0) {
queryParams.push(`listOptions.labelSelector=${labelSelector}`);
}
if (filter.offset) {
queryParams.push(`listOptions.continue=${filter.offset}`);
}
return queryParams;
}

private labelSelectorParams(phases?: Array<string>, labels?: Array<string>) {
let labelSelector = '';
if (phases && phases.length > 0) {
labelSelector = `workflows.argoproj.io/phase in (${phases.join(',')})`;
}
if (labels && labels.length > 0) {
if (labelSelector.length > 0) {
labelSelector += ',';
}
labelSelector += labels.join(',');
}
return labelSelector;
}
}

0 comments on commit b5c4726

Please sign in to comment.