-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
feat(ui): Make UI errors recoverable. Fixes #3666 #3674
Conversation
@@ -15,7 +15,7 @@ interface Props<T> { | |||
value: T; | |||
readonly?: boolean; | |||
editing?: boolean; | |||
onSubmit?: (value: T) => void; | |||
onSubmit?: (value: T) => Promise<any>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
small change, big impact
import {InputFilter} from './input-filter'; | ||
|
||
interface Props { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this component has always been a big buggy - so now it is dumb
return Observable.create((observer: Observer<any>) => { | ||
const eventSource = new EventSource(url); | ||
let opened = false; | ||
eventSource.onopen = () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is canonical way to do this, I hope is more robust, certainly surfaces errors correctly
@@ -15,7 +15,7 @@ data: | |||
staticClients: | |||
- id: argo-server | |||
redirectURIs: | |||
- https://localhost:2746/oauth2/callback | |||
- http://localhost:2746/oauth2/callback |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fix
@@ -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} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
run in non-namespaced mode in dev
@@ -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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔑 This is one of the key changes in this PR. This allows us to know what managed namespace we're running in almost immediately. We use this to create redirects that mean that child components no longer need to know about namespaces.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great change
.then(version => this.setState({version})) | ||
.then(() => services.info.getInfo()) | ||
.then(info => this.setState({namespace: info.managedNamespace || Utils.getCurrentNamespace() || ''})) | ||
.catch(error => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I make an exception here - report this error in a popup. We still need to be able to get to the login page on 401.
<Redirect from={timelineUrl} to={uiUrl('workflows')} /> | ||
<Layout navItems={navItems} version={() => <>{this.state.version ? this.state.version.version : 'unknown'}</>}> | ||
<Notifications notifications={this.notificationsManager.notifications} /> | ||
|
||
<ErrorBoundary> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<Route exact={true} strict={true} path={timelineUrl}> | ||
<Redirect to={workflowsUrl} /> | ||
</Route> | ||
{this.state.namespace && ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these are the redirects, it seems you should use exact
and strict
if you can
@@ -20,9 +20,6 @@ import {ArchivedWorkflowFilters} from '../archived-workflow-filters/archived-wor | |||
|
|||
interface State { | |||
pagination: Pagination; | |||
loading: boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we don't need these anymore, infer from other fields
@@ -62,42 +56,32 @@ export class ArchivedWorkflowList extends BasePage<RouteComponentProps<any>, Sta | |||
} | |||
|
|||
public render() { | |||
if (this.state.loading) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move error shims lower in the dom, so more the page remains responsive, you'll see this several times in this PR
} | ||
|
||
private saveHistory() { | ||
this.url = uiUrl('archived-workflows/' + this.state.namespace + '?' + this.filterParams.toString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you'll see saveHistory
several times as a new pattern for - saving history
loading: false | ||
}); | ||
Utils.setCurrentNamespace(newNamespace); | ||
this.saveHistory |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
part of the saveHistory
pattern is to invoke it after only on successful state update, again - you'll see this as a repeating pattern
services.clusterWorkflowTemplate | ||
.get(this.name) | ||
.then(template => this.setState({template})) | ||
.then(() => services.info.getInfo()) | ||
.then(info => this.setState({namespace: info.managedNamespace || Utils.getCurrentNamespace() || 'default'})) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cluster templates an the one type where we cannot infer the namespace from the URL
.then(clusterWorkflowTemplate => props.onChange(clusterWorkflowTemplate)) | ||
.catch(err => props.onError(err)); | ||
}} | ||
onSubmit={(value: WorkflowTemplate) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
onSubmit
now requires a promise which we display in the UI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{props.error.message} | ||
export const ErrorNotice = (props: {style?: CSSProperties; error: Error & {response?: {body: {message?: string}}}}) => ( | ||
<Notice style={props.style}> | ||
<PhaseIcon value='Error' /> {props.error.message || 'Unknown error. Open your browser error console for more information.'} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there are rare cases where we don't get info in the error - ask the user to look in the console
@@ -135,6 +133,9 @@ export class ArchivedWorkflowDetails extends BasePage<RouteComponentProps<any>, | |||
} | |||
|
|||
private renderArchivedWorkflowDetails() { | |||
if (this.state.error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -212,12 +213,11 @@ export class ArchivedWorkflowDetails extends BasePage<RouteComponentProps<any>, | |||
}, | |||
spec: this.state.workflow.spec | |||
}} | |||
onSubmit={(value: Workflow) => { | |||
onSubmit={(value: Workflow) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | ||
|
||
private renderWorkflows() { | ||
if (this.state.error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -102,10 +99,13 @@ export class ClusterWorkflowTemplateDetails extends BasePage<RouteComponentProps | |||
} | |||
|
|||
private renderClusterWorkflowTemplate() { | |||
if (this.state.error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -90,12 +88,13 @@ export class CronWorkflowDetails extends BasePage<RouteComponentProps<any>, Stat | |||
} | |||
|
|||
private renderCronWorkflow() { | |||
if (this.state.error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | ||
|
||
private renderCronWorkflows() { | ||
if (this.state.error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -72,12 +70,11 @@ export const CronWorkflowSummaryPanel = (props: Props) => { | |||
// magic - we get the latest from the server and then apply the changes from the rendered version to this | |||
const original = props.cronWorkflow; | |||
const patch = jsonMergePatch.generate(original, value) || {}; | |||
services.cronWorkflows | |||
return services.cronWorkflows |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return ( | ||
<Page title='User Info' toolbar={{breadcrumbs: [{title: 'User Info'}]}}> | ||
<div className='argo-container'> | ||
{this.state.error && <ErrorNotice error={this.state.error} />} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -101,10 +99,13 @@ export class WorkflowTemplateDetails extends BasePage<RouteComponentProps<any>, | |||
} | |||
|
|||
private renderWorkflowTemplate() { | |||
if (this.state.error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | ||
|
||
private renderTemplates() { | ||
if (this.state.error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -34,12 +33,11 @@ export const WorkflowTemplateSummaryPanel = (props: Props) => { | |||
<div className='white-box__details'> | |||
<ResourceEditor | |||
value={props.template} | |||
onSubmit={(value: WorkflowTemplate) => { | |||
onSubmit={(value: WorkflowTemplate) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -290,10 +282,12 @@ export class WorkflowsList extends BasePage<RouteComponentProps<any>, State> { | |||
} | |||
|
|||
private renderWorkflows() { | |||
if (this.state.error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The screenshots look great!
@@ -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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great change
Checklist:
"fix(controller): Updates such and such. Fixes #1234"
.I have written unit and/or e2e tests for my change. PRs without these are unlikely to be merged.N/AI recommend the reviewer and I sit down and go over the changes.
Goals
The goal of this PR is to make the UI usable under error scenarios. Typically if an error occurs, we throw it and a global
<ErrorBoundary/>
render a generic "ops!" page. The user must then reload. This looses the user's context as well as any data entered into forms - very annoying.Errors are now more localised in the interface. E.g. an error listing workflows just shows the error and keeps the page usable. The user can then change their search parameters. If the error was a 403, this would most useful.
Changes
<App/>
. This simplifies many component.malformed-*.yaml
manifests to aid testing with malformed manifests.throw this.state.error
and replaced with<ErrorNotice/>
so that the UI remains usable on error.<ResourceEditor/>
now displays submission error in a way so that you can edit and retry.saveHistory
functions andthis.url =
.Testing Notes
I'm not expecting the reviewers to do this - this is for my notes.
Does the UI stay usable?
Can we deal with really bad errors?
yarn start
and turn off Argo Server so that you get gateway errors, try all pages.Does login still work?
make AUTH_MODE=client start
and verify that you are redirect to login page.Does namespace preference still work?
make NAMESPACED=false start
clear local storage and verify no redirection occurs (you should just list the at cluster scope).Known Issues