Skip to content

Commit

Permalink
feat(ui): Make UI errors recoverable. Fixes #3666 (#3674)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexec committed Aug 6, 2020
1 parent 27fea1b commit 6e3c5be
Show file tree
Hide file tree
Showing 33 changed files with 379 additions and 473 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ AUTH_MODE := client
endif
K3D := $(shell if [ "`which kubectl`" != '' ] && [ "`kubectl config current-context`" = "k3s-default" ]; then echo true; else echo false; fi)
LOG_LEVEL := debug
NAMESPACED := true

ALWAYS_OFFLOAD_NODE_STATUS := false
ifeq ($(PROFILE),mysql)
Expand Down Expand Up @@ -371,7 +372,7 @@ endif
grep '127.0.0.1 *minio' /etc/hosts
grep '127.0.0.1 *postgres' /etc/hosts
grep '127.0.0.1 *mysql' /etc/hosts
env SECURE=$(SECURE) ALWAYS_OFFLOAD_NODE_STATUS=$(ALWAYS_OFFLOAD_NODE_STATUS) LOG_LEVEL=$(LOG_LEVEL) VERSION=$(VERSION) AUTH_MODE=$(AUTH_MODE) $(GOPATH)/bin/goreman -set-ports=false -logtime=false start
env SECURE=$(SECURE) ALWAYS_OFFLOAD_NODE_STATUS=$(ALWAYS_OFFLOAD_NODE_STATUS) LOG_LEVEL=$(LOG_LEVEL) VERSION=$(VERSION) AUTH_MODE=$(AUTH_MODE) NAMESPACED=$(NAMESPACED) NAMESPACE=$(KUBE_NAMESPACE) $(GOPATH)/bin/goreman -set-ports=false -logtime=false start

.PHONY: wait
wait:
Expand Down
4 changes: 2 additions & 2 deletions Procfile
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
controller: ALWAYS_OFFLOAD_NODE_STATUS=${ALWAYS_OFFLOAD_NODE_STATUS} OFFLOAD_NODE_STATUS_TTL=30s WORKFLOW_GC_PERIOD=30s UPPERIO_DB_DEBUG=1 ARCHIVED_WORKFLOW_GC_PERIOD=30s ./dist/workflow-controller --executor-image argoproj/argoexec:${VERSION} --namespaced --loglevel ${LOG_LEVEL}
argo-server: UPPERIO_DB_DEBUG=1 ./dist/argo --loglevel ${LOG_LEVEL} server --namespaced --auth-mode ${AUTH_MODE} --secure=$SECURE
controller: ALWAYS_OFFLOAD_NODE_STATUS=${ALWAYS_OFFLOAD_NODE_STATUS} OFFLOAD_NODE_STATUS_TTL=30s WORKFLOW_GC_PERIOD=30s UPPERIO_DB_DEBUG=1 ARCHIVED_WORKFLOW_GC_PERIOD=30s ./dist/workflow-controller --executor-image argoproj/argoexec:${VERSION} --namespaced=${NAMESPACED} --namespace ${NAMESPACE} --loglevel ${LOG_LEVEL}
argo-server: UPPERIO_DB_DEBUG=1 ./dist/argo --loglevel ${LOG_LEVEL} server --namespaced=${NAMESPACED} --namespace ${NAMESPACE} --auth-mode ${AUTH_MODE} --secure=$SECURE
2 changes: 1 addition & 1 deletion manifests/quick-start/sso/dex/dex-cm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ data:
staticClients:
- id: argo-server
redirectURIs:
- https:https://localhost:2746/oauth2/callback
- http:https://localhost:2746/oauth2/callback
name: Argo Server
secret: ZXhhbXBsZS1hcHAtc2VjcmV0
connectors:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ data:
clientSecret:
name: argo-server-sso
key: clientSecret
redirectUrl: https:https://localhost:2746/oauth2/callback
redirectUrl: http:https://localhost:2746/oauth2/callback
kind: ConfigMap
metadata:
name: workflow-controller-configmap
8 changes: 8 additions & 0 deletions test/e2e/malformed/malformed-clusterworkflowtemplate.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: argoproj.io/v1alpha1
kind: ClusterWorkflowTemplate
metadata:
name: malformed
spec:
arguments:
parameters:
someParam: "Hello, world!"
9 changes: 9 additions & 0 deletions test/e2e/malformed/malformed-cronworkflow.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: argoproj.io/v1alpha1
kind: CronWorkflow
metadata:
name: malformed
spec:
workflowSpec:
arguments:
parameters:
someParam: "Hello, world!"
9 changes: 9 additions & 0 deletions test/e2e/malformed/malformed-workflow.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
name: malformed
spec:
entrypoint: no
arguments:
parameters:
someParam: "Hello, world!"
8 changes: 8 additions & 0 deletions test/e2e/malformed/malformed-workflowtemplate.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
name: malformed
spec:
arguments:
parameters:
someParam: "Hello, world!"
106 changes: 74 additions & 32 deletions ui/src/app/app.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {createBrowserHistory} from 'history';
import * as PropTypes from 'prop-types';
import * as React from 'react';
import {Redirect, Route, RouteComponentProps, Router, Switch} from 'react-router';
import {Redirect, Route, Router, Switch} from 'react-router';

import {Layout, NavigationManager, Notifications, NotificationsManager, Popup, PopupManager, PopupProps} from 'argo-ui';
import {uiUrl} from './shared/base';
import {ContextApis, Provider} from './shared/context';

import {NotificationType} from 'argo-ui/src/index';
import {Version} from '../models';
import apidocs from './apidocs';
import archivedWorkflows from './archived-workflows';
Expand All @@ -16,34 +17,21 @@ import help from './help';
import login from './login';
import ErrorBoundary from './shared/components/error-boundary';
import {services} from './shared/services';
import {Utils} from './shared/utils';
import userinfo from './userinfo';
import workflowTemplates from './workflow-templates';
import workflows from './workflows';

const workflowsUrl = uiUrl('workflows');
const workflowTemplatesUrl = uiUrl('workflow-templates');
const clusterWorkflowTemplatesUrl = uiUrl('cluster-workflow-templates');

const cronWorkflowUrl = uiUrl('cron-workflows');
const archivedWorkflowUrl = uiUrl('archived-workflows');
const cronWorkflowsUrl = uiUrl('cron-workflows');
const archivedWorkflowsUrl = uiUrl('archived-workflows');
const helpUrl = uiUrl('help');
const apiDocsUrl = uiUrl('apidocs');
const userInfoUrl = uiUrl('userinfo');
const loginUrl = uiUrl('login');
const timelineUrl = uiUrl('timeline');
const routes: {
[path: string]: {component: React.ComponentType<RouteComponentProps<any>>};
} = {
[workflowsUrl]: {component: workflows.component},
[workflowTemplatesUrl]: {component: workflowTemplates.component},
[clusterWorkflowTemplatesUrl]: {component: clusterWorkflowTemplates.component},
[cronWorkflowUrl]: {component: cronWorkflows.component},
[archivedWorkflowUrl]: {component: archivedWorkflows.component},
[helpUrl]: {component: help.component},
[apiDocsUrl]: {component: apidocs.component},
[userInfoUrl]: {component: userinfo.component},
[loginUrl]: {component: login.component}
};

export const history = createBrowserHistory();

Expand All @@ -65,12 +53,12 @@ const navItems = [
},
{
title: 'Cron Workflows',
path: cronWorkflowUrl,
path: cronWorkflowsUrl,
iconClassName: 'fa fa-clock'
},
{
title: 'Archived Workflows',
path: archivedWorkflowUrl,
path: archivedWorkflowsUrl,
iconClassName: 'fa fa-archive'
},
{
Expand All @@ -90,7 +78,7 @@ const navItems = [
}
];

export class App extends React.Component<{}, {version?: Version; popupProps: PopupProps}> {
export class App extends React.Component<{}, {version?: Version; popupProps: PopupProps; namespace?: string}> {
public static childContextTypes = {
history: PropTypes.object,
apis: PropTypes.object
Expand All @@ -110,7 +98,17 @@ export class App extends React.Component<{}, {version?: Version; popupProps: Pop

public componentDidMount() {
this.popupManager.popupProps.subscribe(popupProps => this.setState({popupProps}));
services.info.getVersion().then(version => this.setState({version}));
services.info
.getVersion()
.then(version => this.setState({version}))
.then(() => services.info.getInfo())
.then(info => this.setState({namespace: info.managedNamespace || Utils.getCurrentNamespace() || ''}))
.catch(error => {
this.notificationsManager.show({
content: 'Failed to load ' + error,
type: NotificationType.Error
});
});
}

public render() {
Expand All @@ -124,24 +122,68 @@ export class App extends React.Component<{}, {version?: Version; popupProps: Pop
<Provider value={providerContext}>
{this.state.popupProps && <Popup {...this.state.popupProps} />}
<Router history={history}>
<Switch>
<Redirect exact={true} path={uiUrl('')} to={workflowsUrl} />
<Redirect from={timelineUrl} to={uiUrl('workflows')} />
<Layout navItems={navItems} version={() => <>{this.state.version ? this.state.version.version : 'unknown'}</>}>
<Notifications notifications={this.notificationsManager.notifications} />
<ErrorBoundary>
<Layout navItems={navItems} version={() => <>{this.state.version ? this.state.version.version : 'unknown'}</>}>
<Notifications notifications={this.notificationsManager.notifications} />
{Object.keys(routes).map(path => {
const route = routes[path];
return <Route key={path} path={path} component={route.component} />;
})}
</Layout>
<Switch>
<Route exact={true} strict={true} path={uiUrl('')}>
<Redirect to={workflowsUrl} />
</Route>
<Route exact={true} strict={true} path={timelineUrl}>
<Redirect to={workflowsUrl} />
</Route>
{this.state.namespace && (
<Route exact={true} strict={true} path={workflowsUrl}>
<Redirect to={this.workflowsUrl} />
</Route>
)}
{this.state.namespace && (
<Route exact={true} strict={true} path={workflowTemplatesUrl}>
<Redirect to={this.workflowTemplatesUrl} />
</Route>
)}
{this.state.namespace && (
<Route exact={true} strict={true} path={cronWorkflowsUrl}>
<Redirect to={this.cronWorkflowsUrl} />
</Route>
)}
{this.state.namespace && (
<Route exact={true} strict={true} path={archivedWorkflowsUrl}>
<Redirect to={this.archivedWorkflowsUrl} />
</Route>
)}
<Route path={workflowsUrl} component={workflows.component} />
<Route path={workflowTemplatesUrl} component={workflowTemplates.component} />
<Route path={clusterWorkflowTemplatesUrl} component={clusterWorkflowTemplates.component} />
<Route path={cronWorkflowsUrl} component={cronWorkflows.component} />
<Route path={archivedWorkflowsUrl} component={archivedWorkflows.component} />
<Route exact={true} strict={true} path={helpUrl} component={help.component} />
<Route exact={true} strict={true} path={apiDocsUrl} component={apidocs.component} />
<Route exact={true} strict={true} path={userInfoUrl} component={userinfo.component} />
<Route exact={true} strict={true} path={loginUrl} component={login.component} />
</Switch>
</ErrorBoundary>
</Switch>
</Layout>
</Router>
</Provider>
);
}

private get archivedWorkflowsUrl() {
return archivedWorkflowsUrl + '/' + this.state.namespace;
}

private get cronWorkflowsUrl() {
return cronWorkflowsUrl + '/' + this.state.namespace;
}
private get workflowTemplatesUrl() {
return workflowTemplatesUrl + '/' + this.state.namespace;
}

private get workflowsUrl() {
return workflowsUrl + '/' + this.state.namespace;
}

public getChildContext() {
return {
history,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {RouteComponentProps} from 'react-router';
import {Link, Workflow} from '../../../../models';
import {uiUrl} from '../../../shared/base';
import {BasePage} from '../../../shared/components/base-page';
import {ErrorNotice} from '../../../shared/components/error-notice';
import {Loading} from '../../../shared/components/loading';
import {ResourceEditor} from '../../../shared/components/resource-editor/resource-editor';
import {services} from '../../../shared/services';
Expand Down Expand Up @@ -67,17 +68,14 @@ export class ArchivedWorkflowDetails extends BasePage<RouteComponentProps<any>,
}

public componentDidMount(): void {
services.archivedWorkflows
.get(this.uid)
.then(workflow => this.setState({workflow}))
services.info
.getInfo()
.then(info => this.setState({links: info.links}))
.then(() => services.archivedWorkflows.get(this.uid).then(workflow => this.setState({workflow})))
.catch(error => this.setState({error}));
services.info.getInfo().then(info => this.setState({links: info.links}));
}

public render() {
if (this.state.error) {
throw this.state.error;
}
const items = [
{
title: 'Resubmit',
Expand Down Expand Up @@ -135,6 +133,9 @@ export class ArchivedWorkflowDetails extends BasePage<RouteComponentProps<any>,
}

private renderArchivedWorkflowDetails() {
if (this.state.error) {
return <ErrorNotice error={this.state.error} style={{margin: 20}} />;
}
if (!this.state.workflow) {
return <Loading />;
}
Expand Down Expand Up @@ -212,12 +213,11 @@ export class ArchivedWorkflowDetails extends BasePage<RouteComponentProps<any>,
},
spec: this.state.workflow.spec
}}
onSubmit={(value: Workflow) => {
onSubmit={(value: Workflow) =>
services.workflows
.create(value, value.metadata.namespace)
.then(workflow => (document.location.href = uiUrl(`workflows/${workflow.metadata.namespace}/${workflow.metadata.name}`)))
.catch(error => this.setState({error}));
}}
}
/>
)}
</SlidingPanel>
Expand Down
Loading

0 comments on commit 6e3c5be

Please sign in to comment.