Repository: argoproj/argo
Pull request #3358 (2020-06-30 21:47:54)

feat(ui): Add Swagger UI
Checklist:

* [x] Either (a) I've created an [enhancement proposal](https://github.com/argoproj/argo/issues/new/choose) and discussed it with the community, (b) this is a bug fix, or (c) this is a chore.
* [x] The title of the PR is (a) [conventional](https://www.conventionalcommits.org/en/v1.0.0/), (b) states what changed, and (c) suffixes the related issues number. E.g. `"fix(controller): Updates such and such. Fixes #1234"`.  
* [x] I've signed the CLA.
* [x] I have written unit and/or e2e tests for my change. PRs without these are unlikely to be merged. N/A
* [x] My builds are green. Try syncing with master if they are not. 
* [x] My organization is added to [USERS.md](https://github.com/argoproj/argo/blob/master/USERS.md).

Demo on YouTube:

https://youtu.be/8c3zyPRYh8Q


alexec: This feature is orthogonal and can be merged whenever.

Patch:
diff --git a/Dockerfile b/Dockerfile
index 5aa9d8551..4121221c9 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -73,10 +73,11 @@ COPY --from=builder /usr/local/bin/docker /usr/local/bin/
 
 FROM node:14.0.0 as argo-ui
 
-ADD ["ui", "."]
+ADD ["ui", "ui"]
+ADD ["api", "api"]
 
-RUN yarn install
-RUN yarn build
+RUN yarn --cwd ui install
+RUN yarn --cwd ui build
 
 ####################################################################################################
 # Argo Build stage which performs the actual build of Argo binaries
@@ -91,26 +92,23 @@ WORKDIR /go/src/github.com/argoproj/argo
 COPY . .
 # check we can use Git
 RUN git rev-parse HEAD
+
+# controller image
+RUN make dist/workflow-controller-linux-${IMAGE_ARCH}
+RUN ["sh", "-c", "./dist/workflow-controller-linux-${IMAGE_ARCH} version | grep clean"]
+
+# executor image
+RUN make dist/argoexec-linux-${IMAGE_ARCH}
+RUN ["sh", "-c", "./dist/argoexec-linux-${IMAGE_ARCH} version | grep clean"]
+
+# cli image
 RUN mkdir -p ui/dist
-COPY --from=argo-ui dist/app ui/dist/app
+COPY --from=argo-ui ui/dist/app ui/dist/app
 # stop make from trying to re-build this without yarn installed
 RUN touch ui/dist/node_modules.marker
 RUN touch ui/dist/app/index.html
-# fail the build if we are "dirty" prior to build
-RUN git diff --exit-code
-# order is important, must build the CLI first, as building can make the build dirty
-RUN make \
-    argo-server.crt \
-    argo-server.key \
-    dist/argo-linux-${IMAGE_ARCH} \
-    dist/workflow-controller-linux-${IMAGE_ARCH} \
-    dist/argoexec-linux-${IMAGE_ARCH}
-# double check "dirty"
-RUN git diff --exit-code
-# triple check "dirty"
-RUN ["sh", "-c", "./dist/workflow-controller-linux-${IMAGE_ARCH} version | grep clean"]
-# we can't check the argo cli, it must have a Kubernetes cluster to work
-RUN ["sh", "-c", "./dist/argoexec-linux-${IMAGE_ARCH} version | grep clean"]
+RUN make argo-server.crt argo-server.key dist/argo-linux-${IMAGE_ARCH}
+RUN ["sh", "-c", "./dist/argo-linux-${IMAGE_ARCH} version | grep clean"]
 
 ####################################################################################################
 # argoexec
diff --git a/USERS.md b/USERS.md
index fcbe8d0aa..4d764e333 100644
--- a/USERS.md
+++ b/USERS.md
@@ -74,3 +74,4 @@ Currently, the following organizations are **officially** using Argo Workflows:
 1. [Threekit](https://www.threekit.com/)
 1. [Tiger Analytics](https://www.tigeranalytics.com/)
 1. [Wavefront](https://www.wavefront.com/)
+1. [Wellcome Trust](https://wellcome.ac.uk/)
diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json
index 8530a3f6f..abb593c79 100644
--- a/api/openapi-spec/swagger.json
+++ b/api/openapi-spec/swagger.json
@@ -6,7 +6,8 @@
     "application/json"
   ],
   "schemes": [
-    "http"
+    "http",
+    "https"
   ],
   "swagger": "2.0",
   "info": {
@@ -14,7 +15,6 @@
     "title": "Argo",
     "version": "latest"
   },
-  "host": "localhost:2746",
   "paths": {
     "/api/v1/archived-workflows": {
       "get": {
@@ -2520,14 +2520,7 @@
       }
     },
     "io.argoproj.workflow.v1alpha1.Item": {
-      "description": "Item expands a single workflow step into multiple parallel steps The value of Item can be a map, string, bool, or number",
-      "type": [
-        "string",
-        "number",
-        "boolean",
-        "array",
-        "object"
-      ]
+      "description": "Item expands a single workflow step into multiple parallel steps The value of Item can be a map, string, bool, or number"
     },
     "io.argoproj.workflow.v1alpha1.Link": {
       "description": "A link to another app.",
diff --git a/cmd/argo/commands/list.go b/cmd/argo/commands/list.go
index 90f28dddc..3fdf9bd61 100644
--- a/cmd/argo/commands/list.go
+++ b/cmd/argo/commands/list.go
@@ -114,15 +114,23 @@ func listWorkflows(ctx context.Context, serviceClient workflowpkg.WorkflowServic
 		Filter(func(wf wfv1.Workflow) bool {
 			return strings.HasPrefix(wf.ObjectMeta.Name, flags.prefix)
 		})
-	if flags.createdSince != "" {
-		t, err := argotime.ParseSince(flags.createdSince)
+	if flags.createdSince != "" && flags.finishedAfter != "" {
+		startTime, err := argotime.ParseSince(flags.createdSince)
 		errors.CheckError(err)
-		workflows = workflows.Filter(wfv1.WorkflowCreatedAfter(*t))
-	}
-	if flags.finishedAfter != "" {
-		t, err := argotime.ParseSince(flags.finishedAfter)
+		endTime, err := argotime.ParseSince(flags.finishedAfter)
 		errors.CheckError(err)
-		workflows = workflows.Filter(wfv1.WorkflowFinishedBefore(*t))
+		workflows = workflows.Filter(wfv1.WorkflowRanBetween(*startTime, *endTime))
+	} else {
+		if flags.createdSince != "" {
+			t, err := argotime.ParseSince(flags.createdSince)
+			errors.CheckError(err)
+			workflows = workflows.Filter(wfv1.WorkflowCreatedAfter(*t))
+		}
+		if flags.finishedAfter != "" {
+			t, err := argotime.ParseSince(flags.finishedAfter)
+			errors.CheckError(err)
+			workflows = workflows.Filter(wfv1.WorkflowFinishedBefore(*t))
+		}
 	}
 	sort.Sort(workflows)
 	return workflows, nil
diff --git a/cmd/argo/commands/wait.go b/cmd/argo/commands/wait.go
index 9a4535e08..04992f127 100644
--- a/cmd/argo/commands/wait.go
+++ b/cmd/argo/commands/wait.go
@@ -91,8 +91,12 @@ func waitOnOne(serviceClient workflowpkg.WorkflowServiceClient, ctx context.Cont
 			log.Debug("Re-establishing workflow watch")
 			stream, err = serviceClient.WatchWorkflows(ctx, req)
 			errors.CheckError(err)
+			continue
 		}
 		errors.CheckError(err)
+		if event == nil {
+			continue
+		}
 		wf := event.Object
 		if !wf.Status.FinishedAt.IsZero() {
 			if !quiet {
diff --git a/cmd/argo/commands/watch.go b/cmd/argo/commands/watch.go
index c9331260d..bca703368 100644
--- a/cmd/argo/commands/watch.go
+++ b/cmd/argo/commands/watch.go
@@ -72,8 +72,12 @@ func watchWorkflow(ctx context.Context, serviceClient workflowpkg.WorkflowServic
 				log.Debug("Re-establishing workflow watch")
 				stream, err = serviceClient.WatchWorkflows(ctx, req)
 				errors.CheckError(err)
+				continue
 			}
 			errors.CheckError(err)
+			if event == nil {
+				continue
+			}
 			wfChan <- event.Object
 		}
 	}()
diff --git a/docs/cost-optimisation.md b/docs/cost-optimisation.md
index e61249909..2f5cbe38b 100644
--- a/docs/cost-optimisation.md
+++ b/docs/cost-optimisation.md
@@ -119,4 +119,4 @@ executorResources:
     memory: 512Mi
 ```
 
-The correct values depend on the size of artifacts your workflows download. For artifacts > 10GB, memory usage maybe large - [#1322](https://github.com/argoproj/argo/issues/1322).
+The correct values depend on the size of artifacts your workflows download. For artifacts > 10GB, memory usage may be large - [#1322](https://github.com/argoproj/argo/issues/1322).
diff --git a/docs/cron-workflows.md b/docs/cron-workflows.md
index 582da11c7..d693b98df 100644
--- a/docs/cron-workflows.md
+++ b/docs/cron-workflows.md
@@ -44,7 +44,7 @@ The resuling `Workflow` name will be a generated name based on the `CronWorkflow
 |          Option Name         |      Default Value     | Description                                                                                                                                                                                                                            |
 |:----------------------------:|:----------------------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
 |          `schedule`          | None, must be provided | Schedule at which the `Workflow` will be run. E.g. `5 4 * * * `                                                                                                                                                                        |
-|          `timezone`          |    Machine timezone    | Timezone during which the Workflow will be run. E.g. `America/Los_Angeles`                                                                                                                                                             |
+|          `timezone`          |    Machine timezone    | Timezone (the name of the timezone as listed in the [tz database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)) during which the Workflow will be run. E.g. `America/Los_Angeles`                                                                                                                                                             |
 |           `suspend`          |         `false`        | If `true` Workflow scheduling will not occur. Can be set from the CLI, GitOps, or directly                                                                                                                                             |
 |      `concurrencyPolicy`     |         `Allow`        | Policy that determines what to do if multiple `Workflows` are scheduled at the same time. Available options: `Allow`: allow all, `Replace`: remove all old before scheduling a new, `Forbid`: do not allow any new while there are old |
 | `startingDeadlineSeconds`    |           `0`          | Number of seconds after the last successful run during which a missed `Workflow` will be run                                                                                                                                           |
diff --git a/docs/fields.md b/docs/fields.md
index 8bc735c7d..ff2247725 100644
--- a/docs/fields.md
+++ b/docs/fields.md
@@ -32,6 +32,10 @@ Workflow is the definition of a workflow resource
 
 - [`mixed-cluster-namespaced-wftmpl-steps.yaml`](../examples/cluster-workflow-template/mixed-cluster-namespaced-wftmpl-steps.yaml)
 
+- [`workflow-template-ref-with-entrypoint-arg-passing.yaml`](../examples/cluster-workflow-template/workflow-template-ref-with-entrypoint-arg-passing.yaml)
+
+- [`workflow-template-ref.yaml`](../examples/cluster-workflow-template/workflow-template-ref.yaml)
+
 - [`coinflip-recursive.yaml`](../examples/coinflip-recursive.yaml)
 
 - [`coinflip.yaml`](../examples/coinflip.yaml)
@@ -331,6 +335,10 @@ WorkflowSpec is the specification of a Workflow.
 
 - [`mixed-cluster-namespaced-wftmpl-steps.yaml`](../examples/cluster-workflow-template/mixed-cluster-namespaced-wftmpl-steps.yaml)
 
+- [`workflow-template-ref-with-entrypoint-arg-passing.yaml`](../examples/cluster-workflow-template/workflow-template-ref-with-entrypoint-arg-passing.yaml)
+
+- [`workflow-template-ref.yaml`](../examples/cluster-workflow-template/workflow-template-ref.yaml)
+
 - [`coinflip-recursive.yaml`](../examples/coinflip-recursive.yaml)
 
 - [`coinflip.yaml`](../examples/coinflip.yaml)
@@ -643,6 +651,10 @@ CronWorkflowSpec is the specification of a CronWorkflow
 
 - [`mixed-cluster-namespaced-wftmpl-steps.yaml`](../examples/cluster-workflow-template/mixed-cluster-namespaced-wftmpl-steps.yaml)
 
+- [`workflow-template-ref-with-entrypoint-arg-passing.yaml`](../examples/cluster-workflow-template/workflow-template-ref-with-entrypoint-arg-passing.yaml)
+
+- [`workflow-template-ref.yaml`](../examples/cluster-workflow-template/workflow-template-ref.yaml)
+
 - [`coinflip-recursive.yaml`](../examples/coinflip-recursive.yaml)
 
 - [`coinflip.yaml`](../examples/coinflip.yaml)
@@ -920,6 +932,10 @@ WorkflowTemplateSpec is a spec of WorkflowTemplate.
 
 - [`mixed-cluster-namespaced-wftmpl-steps.yaml`](../examples/cluster-workflow-template/mixed-cluster-namespaced-wftmpl-steps.yaml)
 
+- [`workflow-template-ref-with-entrypoint-arg-passing.yaml`](../examples/cluster-workflow-template/workflow-template-ref-with-entrypoint-arg-passing.yaml)
+
+- [`workflow-template-ref.yaml`](../examples/cluster-workflow-template/workflow-template-ref.yaml)
+
 - [`coinflip-recursive.yaml`](../examples/coinflip-recursive.yaml)
 
 - [`coinflip.yaml`](../examples/coinflip.yaml)
@@ -1207,6 +1223,8 @@ Arguments to a template
 
 - [`mixed-cluster-namespaced-wftmpl-steps.yaml`](../examples/cluster-workflow-template/mixed-cluster-namespaced-wftmpl-steps.yaml)
 
+- [`workflow-template-ref-with-entrypoint-arg-passing.yaml`](../examples/cluster-workflow-template/workflow-template-ref-with-entrypoint-arg-passing.yaml)
+
 - [`conditionals.yaml`](../examples/conditionals.yaml)
 
 - [`cron-backfill.yaml`](../examples/cron-backfill.yaml)
@@ -1684,6 +1702,10 @@ WorkflowTemplateRef is a reference to a WorkflowTemplate resource.
 <summary>Examples with this field (click to open)</summary>
 <br>
 
+- [`workflow-template-ref-with-entrypoint-arg-passing.yaml`](../examples/cluster-workflow-template/workflow-template-ref-with-entrypoint-arg-passing.yaml)
+
+- [`workflow-template-ref.yaml`](../examples/cluster-workflow-template/workflow-template-ref.yaml)
+
 - [`cron-backfill.yaml`](../examples/cron-backfill.yaml)
 
 - [`workflow-template-ref-with-entrypoint-arg-passing.yaml`](../examples/workflow-template/workflow-template-ref-with-entrypoint-arg-passing.yaml)
@@ -1898,6 +1920,8 @@ Parameter indicate a passed string parameter to a service template with an optio
 
 - [`mixed-cluster-namespaced-wftmpl-steps.yaml`](../examples/cluster-workflow-template/mixed-cluster-namespaced-wftmpl-steps.yaml)
 
+- [`workflow-template-ref-with-entrypoint-arg-passing.yaml`](../examples/cluster-workflow-template/workflow-template-ref-with-entrypoint-arg-passing.yaml)
+
 - [`conditionals.yaml`](../examples/conditionals.yaml)
 
 - [`cron-backfill.yaml`](../examples/cron-backfill.yaml)
@@ -2311,6 +2335,10 @@ Pod metdata
 
 - [`mixed-cluster-namespaced-wftmpl-steps.yaml`](../examples/cluster-workflow-template/mixed-cluster-namespaced-wftmpl-steps.yaml)
 
+- [`workflow-template-ref-with-entrypoint-arg-passing.yaml`](../examples/cluster-workflow-template/workflow-template-ref-with-entrypoint-arg-passing.yaml)
+
+- [`workflow-template-ref.yaml`](../examples/cluster-workflow-template/workflow-template-ref.yaml)
+
 - [`coinflip-recursive.yaml`](../examples/coinflip-recursive.yaml)
 
 - [`coinflip.yaml`](../examples/coinflip.yaml)
@@ -3438,6 +3466,10 @@ ObjectMeta is metadata that all persisted resources must have, which includes al
 
 - [`mixed-cluster-namespaced-wftmpl-steps.yaml`](../examples/cluster-workflow-template/mixed-cluster-namespaced-wftmpl-steps.yaml)
 
+- [`workflow-template-ref-with-entrypoint-arg-passing.yaml`](../examples/cluster-workflow-template/workflow-template-ref-with-entrypoint-arg-passing.yaml)
+
+- [`workflow-template-ref.yaml`](../examples/cluster-workflow-template/workflow-template-ref.yaml)
+
 - [`coinflip-recursive.yaml`](../examples/coinflip-recursive.yaml)
 
 - [`coinflip.yaml`](../examples/coinflip.yaml)
@@ -4544,6 +4576,10 @@ PersistentVolumeClaimSpec describes the common attributes of storage devices and
 
 - [`mixed-cluster-namespaced-wftmpl-steps.yaml`](../examples/cluster-workflow-template/mixed-cluster-namespaced-wftmpl-steps.yaml)
 
+- [`workflow-template-ref-with-entrypoint-arg-passing.yaml`](../examples/cluster-workflow-template/workflow-template-ref-with-entrypoint-arg-passing.yaml)
+
+- [`workflow-template-ref.yaml`](../examples/cluster-workflow-template/workflow-template-ref.yaml)
+
 - [`coinflip-recursive.yaml`](../examples/coinflip-recursive.yaml)
 
 - [`coinflip.yaml`](../examples/coinflip.yaml)
@@ -5516,6 +5552,10 @@ ListMeta describes metadata that synthetic resources must have, including lists
 
 - [`mixed-cluster-namespaced-wftmpl-steps.yaml`](../examples/cluster-workflow-template/mixed-cluster-namespaced-wftmpl-steps.yaml)
 
+- [`workflow-template-ref-with-entrypoint-arg-passing.yaml`](../examples/cluster-workflow-template/workflow-template-ref-with-entrypoint-arg-passing.yaml)
+
+- [`workflow-template-ref.yaml`](../examples/cluster-workflow-template/workflow-template-ref.yaml)
+
 - [`coinflip-recursive.yaml`](../examples/coinflip-recursive.yaml)
 
 - [`coinflip.yaml`](../examples/coinflip.yaml)
diff --git a/docs/workflow-controller-configmap.yaml b/docs/workflow-controller-configmap.yaml
index 0353426b7..8e4ff1b31 100644
--- a/docs/workflow-controller-configmap.yaml
+++ b/docs/workflow-controller-configmap.yaml
@@ -86,6 +86,9 @@ data:
         secretKeySecret:
           name: my-s3-credentials
           key: secretKey
+        # If this is set to true, argo workflows will use AWS SDK default credentials provider chain. This will allow things like
+        # IRSA and any of the authentication methods that the golang SDK uses in it's default chain.
+        useSDKCreds: false
 
     # Specifies the container runtime interface to use (default: docker)
     # must be one of: docker, kubelet, k8sapi, pns
diff --git a/examples/cluster-workflow-template/workflow-template-ref-with-entrypoint-arg-passing.yaml b/examples/cluster-workflow-template/workflow-template-ref-with-entrypoint-arg-passing.yaml
new file mode 100644
index 000000000..5755b6f3b
--- /dev/null
+++ b/examples/cluster-workflow-template/workflow-template-ref-with-entrypoint-arg-passing.yaml
@@ -0,0 +1,15 @@
+apiVersion: argoproj.io/v1alpha1
+kind: Workflow
+metadata:
+  generateName: cluster-workflow-template-hello-world-
+spec:
+  entrypoint: whalesay-template
+  arguments:
+    parameters:
+      - name: message
+        value: "hello world"
+  workflowTemplateRef:
+    name: cluster-workflow-template-whalesay-template
+    clusterScope: true
+
+
diff --git a/examples/cluster-workflow-template/workflow-template-ref.yaml b/examples/cluster-workflow-template/workflow-template-ref.yaml
new file mode 100644
index 000000000..ab54992ad
--- /dev/null
+++ b/examples/cluster-workflow-template/workflow-template-ref.yaml
@@ -0,0 +1,9 @@
+apiVersion: argoproj.io/v1alpha1
+kind: Workflow
+metadata:
+  generateName: cluster-workflow-template-hello-world-
+spec:
+  workflowTemplateRef:
+    name: cluster-workflow-template-submittable
+    clusterScope: true
+
diff --git a/examples/global-outputs.yaml b/examples/global-outputs.yaml
index 9c2d96801..83633ebfc 100644
--- a/examples/global-outputs.yaml
+++ b/examples/global-outputs.yaml
@@ -37,7 +37,7 @@ spec:
 
   # Once exported, global outputs are referenceable in later parts of the workflow.
   # In this example, the consume-globals template is invoked after the generate-globals step, as
-  # well as in the onExit handler. and can reference the globals, my-global-param and my-global-art.
+  # well as in the onExit handler, and can reference the globals, my-global-param and my-global-art.
   - name: consume-globals
     steps:
     - - name: consume-global-param
@@ -50,10 +50,14 @@ spec:
             from: "{{workflow.outputs.artifacts.my-global-art}}"
 
   - name: consume-global-param
+    inputs:
+      parameters:
+        - name: param
+          value: "{{workflow.outputs.parameters.my-global-param}}"
     container:
       image: alpine:3.7
       command: [sh, -c]
-      args: ["echo {{workflow.outputs.parameters.my-global-param}}"]
+      args: ["echo {{inputs.parameters.param}}"]
 
   - name: consume-global-art
     inputs:
diff --git a/pkg/apiclient/_.primary.swagger.json b/pkg/apiclient/_.primary.swagger.json
index 68306a4ed..5b8b170a3 100644
--- a/pkg/apiclient/_.primary.swagger.json
+++ b/pkg/apiclient/_.primary.swagger.json
@@ -6,9 +6,9 @@
     "application/json"
   ],
   "swagger": "2.0",
-  "host": "localhost:2746",
   "schemes": [
-    "http"
+    "http",
+    "https"
   ],
   "info": {
     "title": "Argo",
diff --git a/pkg/apis/workflow/v1alpha1/item.go b/pkg/apis/workflow/v1alpha1/item.go
index 184d42411..1a357abdb 100644
--- a/pkg/apis/workflow/v1alpha1/item.go
+++ b/pkg/apis/workflow/v1alpha1/item.go
@@ -89,7 +89,7 @@ func (i *Item) DeepCopyInto(out *Item) {
 }
 
 func (i Item) OpenAPISchemaType() []string {
-	return []string{"string", "number", "boolean", "array", "object"}
+	return []string{}
 }
 
 func (i Item) OpenAPISchemaFormat() string { return "" }
diff --git a/pkg/apis/workflow/v1alpha1/workflow_types.go b/pkg/apis/workflow/v1alpha1/workflow_types.go
index 62d0fe2fd..f2d2a2b48 100644
--- a/pkg/apis/workflow/v1alpha1/workflow_types.go
+++ b/pkg/apis/workflow/v1alpha1/workflow_types.go
@@ -129,6 +129,11 @@ var (
 			return !wf.Status.FinishedAt.IsZero() && wf.Status.FinishedAt.Time.Before(t)
 		}
 	}
+	WorkflowRanBetween = func(startTime time.Time, endTime time.Time) WorkflowPredicate {
+		return func(wf Workflow) bool {
+			return wf.ObjectMeta.CreationTimestamp.After(startTime) && !wf.Status.FinishedAt.IsZero() && wf.Status.FinishedAt.Time.Before(endTime)
+		}
+	}
 )
 
 // WorkflowList is list of Workflow resources
diff --git a/server/auth/sso/sso.go b/server/auth/sso/sso.go
index 39e83cb35..81e6a3967 100644
--- a/server/auth/sso/sso.go
+++ b/server/auth/sso/sso.go
@@ -20,7 +20,6 @@ import (
 const Prefix = "Bearer id_token:"
 
 type claims struct {
-	Groups []string `json:"groups"`
 }
 
 type Interface interface {
@@ -115,7 +114,7 @@ func newSso(
 		ClientSecret: string(clientSecret),
 		RedirectURL:  c.RedirectURL,
 		Endpoint:     provider.Endpoint(),
-		Scopes:       []string{oidc.ScopeOpenID, "groups"},
+		Scopes:       []string{oidc.ScopeOpenID},
 	}
 	idTokenVerifier := provider.Verifier(&oidc.Config{ClientID: config.ClientID})
 	log.WithFields(log.Fields{"redirectUrl": config.RedirectURL, "issuer": c.Issuer, "clientId": c.ClientID}).Info("SSO configuration")
diff --git a/ui/package.json b/ui/package.json
index 77b31dda1..22fde758f 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -31,7 +31,8 @@
         "react-router-dom": "^4.2.2",
         "rxjs": "^5.5.6",
         "superagent": "^3.8.2",
-        "superagent-promise": "^1.1.0"
+        "superagent-promise": "^1.1.0",
+        "swagger-ui-react": "^3.28.0"
     },
     "devDependencies": {
         "@types/classnames": "^2.2.3",
@@ -44,6 +45,7 @@
         "@types/react-dom": "^16.8.2",
         "@types/react-router-dom": "^4.2.3",
         "@types/superagent": "^3.5.7",
+        "@types/swagger-ui-react": "^3.23.2",
         "@types/ws": "^4.0.0",
         "copy-webpack-plugin": "^4.3.1",
         "copyfiles": "^1.2.0",
diff --git a/ui/src/app/apidocs/components/apiDocs.tsx b/ui/src/app/apidocs/components/apiDocs.tsx
new file mode 100644
index 000000000..fee870588
--- /dev/null
+++ b/ui/src/app/apidocs/components/apiDocs.tsx
@@ -0,0 +1,6 @@
+import * as React from 'react';
+import SwaggerUI from 'swagger-ui-react';
+import 'swagger-ui-react/swagger-ui.css';
+import {uiUrl} from '../../shared/base';
+
+export const ApiDocs = () => <SwaggerUI url={uiUrl('assets/openapi-spec/swagger.json')} defaultModelExpandDepth={0} />;
diff --git a/ui/src/app/apidocs/index.tsx b/ui/src/app/apidocs/index.tsx
new file mode 100644
index 000000000..c505ba801
--- /dev/null
+++ b/ui/src/app/apidocs/index.tsx
@@ -0,0 +1,5 @@
+import {ApiDocs} from './components/apiDocs';
+
+export default {
+    component: ApiDocs
+};
diff --git a/ui/src/app/app.tsx b/ui/src/app/app.tsx
index 94a6e9955..55ab9e886 100644
--- a/ui/src/app/app.tsx
+++ b/ui/src/app/app.tsx
@@ -8,6 +8,7 @@ import {uiUrl} from './shared/base';
 import {ContextApis, Provider} from './shared/context';
 
 import {Version} from '../models';
+import apidocs from './apidocs';
 import archivedWorkflows from './archived-workflows';
 import clusterWorkflowTemplates from './cluster-workflow-templates';
 import cronWorkflows from './cron-workflows';
@@ -25,6 +26,7 @@ const clusterWorkflowTemplatesUrl = uiUrl('cluster-workflow-templates');
 const cronWorkflowUrl = uiUrl('cron-workflows');
 const archivedWorkflowUrl = uiUrl('archived-workflows');
 const helpUrl = uiUrl('help');
+const apiDocsUrl = uiUrl('apidocs');
 const loginUrl = uiUrl('login');
 const timelineUrl = uiUrl('timeline');
 const routes: {
@@ -36,6 +38,7 @@ const routes: {
     [cronWorkflowUrl]: {component: cronWorkflows.component},
     [archivedWorkflowUrl]: {component: archivedWorkflows.component},
     [helpUrl]: {component: help.component},
+    [apiDocsUrl]: {component: apidocs.component},
     [loginUrl]: {component: login.component}
 };
 
@@ -72,6 +75,11 @@ const navItems = [
         path: loginUrl,
         iconClassName: 'fa fa-shield-alt'
     },
+    {
+        title: 'API Docs',
+        path: apiDocsUrl,
+        iconClassName: 'fa fa-code'
+    },
     {
         title: 'Help',
         path: helpUrl,
diff --git a/ui/src/app/help/components/help.scss b/ui/src/app/help/components/help.scss
index b2bda18e7..833114292 100644
--- a/ui/src/app/help/components/help.scss
+++ b/ui/src/app/help/components/help.scss
@@ -52,6 +52,7 @@
         i {
             width: 30px;
             font-size: 24px;
+            text-align: center;
         }
     }
 }
diff --git a/ui/src/app/help/components/help.tsx b/ui/src/app/help/components/help.tsx
index c376aa319..ee77bacec 100644
--- a/ui/src/app/help/components/help.tsx
+++ b/ui/src/app/help/components/help.tsx
@@ -1,5 +1,6 @@
 import {Page} from 'argo-ui';
 import * as React from 'react';
+import {uiUrl} from '../../shared/base';
 
 require('./help.scss');
 
@@ -10,9 +11,11 @@ export const Help = () => (
                 <div className='help-box'>
                     <div className='help-box__ico help-box__ico--manual' />
                     <h3>Documentation</h3>
-                    <a href='https://argoproj.github.io/' target='_blank' className='help-box__link'>
-                        {' '}
-                        Argo Project
+                    <a href='https://argoproj.github.io/argo' target='_blank' className='help-box__link'>
+                        Online Help
+                    </a>
+                    <a className='help-box__link' target='_blank' href={uiUrl('/apidocs')}>
+                        API Docs
                     </a>
                 </div>
             </div>
@@ -20,11 +23,8 @@ export const Help = () => (
                 <div className='help-box'>
                     <div className='help-box__ico help-box__ico--email' />
                     <h3>Contact</h3>
-                    <a className='help-box__link' target='_blank' href='https://groups.google.com/forum/#!forum/argoproj'>
-                        Argo Community
-                    </a>
                     <a className='help-box__link' target='_blank' href='https://argoproj.slack.com'>
-                        Slack Channel
+                        Slack
                     </a>
                 </div>
             </div>
diff --git a/ui/src/app/workflows/components/workflow-dag/workflow-dag.tsx b/ui/src/app/workflows/components/workflow-dag/workflow-dag.tsx
index 904116063..a933552d4 100644
--- a/ui/src/app/workflows/components/workflow-dag/workflow-dag.tsx
+++ b/ui/src/app/workflows/components/workflow-dag/workflow-dag.tsx
@@ -321,21 +321,23 @@ export class WorkflowDag extends React.Component<WorkflowDagProps, WorkflowDagRe
         // `h` and `v` move the arrow heads to next to the node, otherwise they would be behind it
         const h = this.state.horizontal ? this.nodeSize / 2 : 0;
         const v = !this.state.horizontal ? this.nodeSize / 2 : 0;
-        this.graph.edges = edges.map(e => ({
-            v: e.v,
-            w: e.w,
-            points: [
-                {
-                    // for hidden nodes, we want to size them zero
-                    x: this.graph.nodes.get(e.v).x + (this.hiddenNode(e.v) ? 0 : h),
-                    y: this.graph.nodes.get(e.v).y + (this.hiddenNode(e.v) ? 0 : v)
-                },
-                {
-                    x: this.graph.nodes.get(e.w).x - (this.hiddenNode(e.w) ? 0 : h),
-                    y: this.graph.nodes.get(e.w).y - (this.hiddenNode(e.w) ? 0 : v)
-                }
-            ]
-        }));
+        this.graph.edges = edges
+            .filter(e => this.graph.nodes.has(e.v) && this.graph.nodes.has(e.w))
+            .map(e => ({
+                v: e.v,
+                w: e.w,
+                points: [
+                    {
+                        // for hidden nodes, we want to size them zero
+                        x: this.graph.nodes.get(e.v).x + (this.hiddenNode(e.v) ? 0 : h),
+                        y: this.graph.nodes.get(e.v).y + (this.hiddenNode(e.v) ? 0 : v)
+                    },
+                    {
+                        x: this.graph.nodes.get(e.w).x - (this.hiddenNode(e.w) ? 0 : h),
+                        y: this.graph.nodes.get(e.w).y - (this.hiddenNode(e.w) ? 0 : v)
+                    }
+                ]
+            }));
     }
 
     private selectNode(nodeId: string) {
diff --git a/ui/src/app/workflows/components/workflow-details/workflow-details.tsx b/ui/src/app/workflows/components/workflow-details/workflow-details.tsx
index 89812739a..ec2713aae 100644
--- a/ui/src/app/workflows/components/workflow-details/workflow-details.tsx
+++ b/ui/src/app/workflows/components/workflow-details/workflow-details.tsx
@@ -177,14 +177,10 @@ export class WorkflowDetails extends React.Component<RouteComponentProps<any>, W
         );
     }
 
-    private confirmAction(title: string): void {
+    private performAction(action: (name: string, namespace: string) => Promise<any>, title: string, redirect: string, ctx: ContextApis): void {
         if (!confirm(`Are you sure you want to ${title.toLowerCase()} this workflow?`)) {
             return;
         }
-    }
-
-    private performAction(action: (name: string, namespace: string) => Promise<any>, title: string, redirect: string, ctx: ContextApis): void {
-        this.confirmAction(title);
         action(this.props.match.params.name, this.props.match.params.namespace)
             .then(() => ctx.navigation.goto(uiUrl(redirect)))
             .catch(() => {
diff --git a/ui/src/app/workflows/components/workflow-logs-viewer/workflow-logs-viewer.tsx b/ui/src/app/workflows/components/workflow-logs-viewer/workflow-logs-viewer.tsx
index e0998ceb1..cf55ded21 100644
--- a/ui/src/app/workflows/components/workflow-logs-viewer/workflow-logs-viewer.tsx
+++ b/ui/src/app/workflows/components/workflow-logs-viewer/workflow-logs-viewer.tsx
@@ -40,7 +40,7 @@ export class WorkflowLogsViewer extends React.Component<WorkflowLogsViewerProps,
                 <h3>Logs</h3>
                 {this.props.archived && (
                     <p>
-                        <i className='fa fa-exclamation-triangle' /> Logs for archived workflows maybe overwritten by a more recent workflow with the same name.
+                        <i className='fa fa-exclamation-triangle' /> Logs for archived workflows may be overwritten by a more recent workflow with the same name.
                     </p>
                 )}
                 <p>
diff --git a/ui/src/app/workflows/components/workflow-node-info/workflow-node-info.tsx b/ui/src/app/workflows/components/workflow-node-info/workflow-node-info.tsx
index 0ed07bccf..831cd1713 100644
--- a/ui/src/app/workflows/components/workflow-node-info/workflow-node-info.tsx
+++ b/ui/src/app/workflows/components/workflow-node-info/workflow-node-info.tsx
@@ -234,7 +234,7 @@ export const WorkflowNodeArtifacts = (props: Props) => {
             )}
             {artifacts.length > 0 && props.archived && (
                 <p>
-                    <i className='fa fa-exclamation-triangle' /> Artifacts for archived workflows maybe be overwritten by a more recent workflow with the same name.
+                    <i className='fa fa-exclamation-triangle' /> Artifacts for archived workflows may be overwritten by a more recent workflow with the same name.
                 </p>
             )}
             {artifacts.map(artifact => (
diff --git a/ui/src/app/workflows/components/workflows-toolbar/workflows-toolbar.tsx b/ui/src/app/workflows/components/workflows-toolbar/workflows-toolbar.tsx
index ef394521f..ff7ddc932 100644
--- a/ui/src/app/workflows/components/workflows-toolbar/workflows-toolbar.tsx
+++ b/ui/src/app/workflows/components/workflows-toolbar/workflows-toolbar.tsx
@@ -50,7 +50,9 @@ export class WorkflowsToolbar extends React.Component<WorkflowsToolbarProps, {}>
     }
 
     private performActionOnSelectedWorkflows(ctx: any, title: string, action: (name: string, namespace: string) => Promise<any>): Promise<any> {
-        this.confirmAction(title);
+        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];
@@ -67,13 +69,6 @@ export class WorkflowsToolbar extends React.Component<WorkflowsToolbarProps, {}>
         return Promise.all(promises);
     }
 
-    private confirmAction(title: string): void {
-        if (!confirm(`Are you sure you want to ${title.toLowerCase()} all selected workflows?`)) {
-            return;
-        }
-        return;
-    }
-
     private renderActions(ctx: any): JSX.Element[] {
         const actionButtons = [];
         const actions: any = Actions.WorkflowOperations;
diff --git a/ui/src/models/workflows.ts b/ui/src/models/workflows.ts
index 0290ce52a..b1cc472c8 100644
--- a/ui/src/models/workflows.ts
+++ b/ui/src/models/workflows.ts
@@ -22,22 +22,10 @@ export interface Arguments {
  * Artifact indicates an artifact to place at a specified path
  */
 export interface Artifact {
-    /**
-     * Artifactory contains artifactory artifact location details
-     */
-    artifactory?: ArtifactoryArtifact;
     /**
      * From allows an artifact to reference an artifact from a previous step
      */
     from?: string;
-    /**
-     * Git contains git artifact location details
-     */
-    git?: GitArtifact;
-    /**
-     * HTTP contains HTTP artifact location details
-     */
-    http?: HTTPArtifact;
     /**
      * mode bits to use on this file, must be a value between 0 and 0777 set when loading input artifacts.
      */
@@ -50,115 +38,6 @@ export interface Artifact {
      * Path is the container path to the artifact
      */
     path?: string;
-    /**
-     * Raw contains raw artifact location details
-     */
-    raw?: RawArtifact;
-    /**
-     * S3 contains S3 artifact location details
-     */
-    s3?: S3Artifact;
-    /**
-     * OSS contains Alibaba Cloud OSS artifact location details
-     */
-    oss?: OSSArtifact;
-}
-
-/**
- * ArtifactLocation describes a location for a single or multiple artifacts.
- * It is used as single artifact in the context of inputs/outputs (e.g. outputs.artifacts.artname).
- * It is also used to describe the location of multiple artifacts such as the archive location of a single workflow step,
- * which the executor will use as a default location to store its files.
- */
-export interface ArtifactLocation {
-    /**
-     * Artifactory contains artifactory artifact location details
-     */
-    artifactory?: ArtifactoryArtifact;
-    /**
-     * Git contains git artifact location details
-     */
-    git?: GitArtifact;
-    /**
-     * HTTP contains HTTP artifact location details
-     */
-    http?: HTTPArtifact;
-    /**
-     * Raw contains raw artifact location details
-     */
-    raw?: RawArtifact;
-    /**
-     * S3 contains S3 artifact location details
-     */
-    s3?: S3Artifact;
-    /**
-     * OSS contains Alibaba Cloud OSS artifact location details
-     */
-    oss?: OSSArtifact;
-}
-
-/**
- * ArtifactoryArtifact is the location of an artifactory artifact
- */
-export interface ArtifactoryArtifact {
-    /**
-     * PasswordSecret is the secret selector to the repository password
-     */
-    passwordSecret?: kubernetes.SecretKeySelector;
-    /**
-     * URL of the artifact
-     */
-    url: string;
-    /**
-     * UsernameSecret is the secret selector to the repository username
-     */
-    usernameSecret?: kubernetes.SecretKeySelector;
-}
-
-/**
- * ArtifactoryAuth describes the secret selectors required for authenticating to artifactory
- */
-export interface ArtifactoryAuth {
-    /**
-     * PasswordSecret is the secret selector to the repository password
-     */
-    passwordSecret?: kubernetes.SecretKeySelector;
-    /**
-     * UsernameSecret is the secret selector to the repository username
-     */
-    usernameSecret?: kubernetes.SecretKeySelector;
-}
-
-/**
- * GitArtifact is the location of an git artifact
- */
-export interface GitArtifact {
-    /**
-     * PasswordSecret is the secret selector to the repository password
-     */
-    passwordSecret?: kubernetes.SecretKeySelector;
-    /**
-     * Repo is the git repository
-     */
-    repo: string;
-    /**
-     * Revision is the git commit, tag, branch to checkout
-     */
-    revision?: string;
-    /**
-     * UsernameSecret is the secret selector to the repository username
-     */
-    usernameSecret?: kubernetes.SecretKeySelector;
-}
-
-/**
- * HTTPArtifact allows an file served on HTTP to be placed as an input artifact in a container
- */
-export interface HTTPArtifact {
-    /**
-     * URL of the artifact
-     */
-    url: string;
 }
 
 /**
@@ -219,16 +98,6 @@ export interface Parameter {
     valueFrom?: ValueFrom;
 }
 
-/**
- * RawArtifact allows raw string content to be placed as an artifact in a container
- */
-export interface RawArtifact {
-    /**
-     * Data is the string contents of the artifact
-     */
-    data: string;
-}
-
 /**
  * ResourceTemplate is a template subtype to manipulate kubernetes resources
  */
@@ -261,118 +130,6 @@ export interface RetryStrategy {
     limit?: number;
 }
 
-/**
- * S3Artifact is the location of an S3 artifact
- */
-export interface S3Artifact {
-    /**
-     * AccessKeySecret is the secret selector to the bucket's access key
-     */
-    accessKeySecret: kubernetes.SecretKeySelector;
-    /**
-     * Bucket is the name of the bucket
-     */
-    bucket: string;
-    /**
-     * Endpoint is the hostname of the bucket endpoint
-     */
-    endpoint: string;
-    /**
-     * Insecure will connect to the service with TLS
-     */
-    insecure?: boolean;
-    /**
-     * Key is the key in the bucket where the artifact resides
-     */
-    key: string;
-    /**
-     * Region contains the optional bucket region
-     */
-    region?: string;
-    /**
-     * SecretKeySecret is the secret selector to the bucket's secret key
-     */
-    secretKeySecret: kubernetes.SecretKeySelector;
-}
-
-/**
- * S3Bucket contains the access information required for interfacing with an S3 bucket
- */
-export interface S3Bucket {
-    /**
-     * AccessKeySecret is the secret selector to the bucket's access key
-     */
-    accessKeySecret: kubernetes.SecretKeySelector;
-    /**
-     * Bucket is the name of the bucket
-     */
-    bucket: string;
-    /**
-     * Endpoint is the hostname of the bucket endpoint
-     */
-    endpoint: string;
-    /**
-     * Insecure will connect to the service with TLS
-     */
-    insecure?: boolean;
-    /**
-     * Region contains the optional bucket region
-     */
-    region?: string;
-    /**
-     * SecretKeySecret is the secret selector to the bucket's secret key
-     */
-    secretKeySecret: kubernetes.SecretKeySelector;
-}
-
-/**
- * OSSArtifact is the location of an Alibaba Cloud OSS artifact
- */
-export interface OSSArtifact {
-    /**
-     * AccessKeySecret is the secret selector to the bucket's access key
-     */
-    accessKeySecret: kubernetes.SecretKeySelector;
-    /**
-     * Bucket is the name of the bucket
-     */
-    bucket: string;
-    /**
-     * Endpoint is the hostname of the bucket endpoint
-     */
-    endpoint: string;
-    /**
-     * Key is the key in the bucket where the artifact resides
-     */
-    key: string;
-    /**
-     * SecretKeySecret is the secret selector to the bucket's secret key
-     */
-    secretKeySecret: kubernetes.SecretKeySelector;
-}
-
-/**
- * OSSBucket contains the access information required for interfacing with an OSS bucket
- */
-export interface OSSBucket {
-    /**
-     * AccessKeySecret is the secret selector to the bucket's access key
-     */
-    accessKeySecret: kubernetes.SecretKeySelector;
-    /**
-     * Bucket is the name of the bucket
-     */
-    bucket: string;
-    /**
-     * Endpoint is the hostname of the bucket endpoint
-     */
-    endpoint: string;
-    /**
-     * SecretKeySecret is the secret selector to the bucket's secret key
-     */
-    secretKeySecret: kubernetes.SecretKeySelector;
-}
-
 /**
  * Script is a template subtype to enable scripting through code steps
  */
@@ -541,12 +298,6 @@ export interface Template {
      * Affinity sets the pod's scheduling constraints Overrides the affinity set at the workflow level (if any)
      */
     affinity?: kubernetes.Affinity;
-    /**
-     * Location in which all files related to the step will be stored (logs, artifacts, etc...).
-     * Can be overridden by individual items in Outputs.
-     * If omitted, will use the default artifact repository location configured in the controller, appended with the <workflowname>/<nodename> in the key.
-     */
-    archiveLocation?: ArtifactLocation;
     /**
      * Container is the main container image to run in the pod
      */
diff --git a/ui/yarn.lock b/ui/yarn.lock
index d86cf8e3c..fa170830e 100644
--- a/ui/yarn.lock
+++ b/ui/yarn.lock
@@ -92,6 +92,22 @@
   resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.7.7.tgz#1b886595419cf92d811316d5b715a53ff38b4937"
   integrity sha512-WtTZMZAZLbeymhkd/sEaPD8IQyGAhmuTuvTzLiCFM7iXiVdY0gc0IaI+cW0fh1BnSMbJSzXX6/fHllgHKwHhXw==
 
+"@babel/runtime-corejs2@=7.10.2":
+  version "7.10.2"
+  resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.10.2.tgz#532f20b839684615f97e9f700f630e995efed426"
+  integrity sha512-ZLwsFnNm3WpIARU1aLFtufjMHsmEnc8TjtrfAjmbgMbeoyR+LuQoyESoNdTfeDhL6IdY12SpeycXMgSgl8XGXA==
+  dependencies:
+    core-js "^2.6.5"
+    regenerator-runtime "^0.13.4"
+
+"@babel/runtime-corejs2@^7.5.5":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.10.4.tgz#5d48ee239624d511c88208da86c27a161ee01cf7"
+  integrity sha512-9sArmpZDQsnR1yyAcU51DxQrntWxt0LUKjPp3pIyo7kVLfaqKt8muppcT87QmFkXV5H50qXAF8JWOjk0jaXRYA==
+  dependencies:
+    core-js "^2.6.5"
+    regenerator-runtime "^0.13.4"
+
 "@babel/runtime@^7.1.2", "@babel/runtime@^7.4.2":
   version "7.4.3"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.3.tgz#79888e452034223ad9609187a0ad1fe0d2ad4bdc"
@@ -132,11 +148,28 @@
     lodash "^4.17.13"
     to-fast-properties "^2.0.0"
 
+"@braintree/sanitize-url@^4.0.0":
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-4.0.1.tgz#16e719eba72693f95989c05f2b3b424f2aabc5b0"
+  integrity sha512-THIQtVN491hY2rTZ/h5boj0CwDKrZQIUnfaj6RbpGwRJKfmEIgNpgcCJC3KZl1Vux9bQmdE2P4Q7cYUoSUQ4MA==
+
 "@fortawesome/fontawesome-free@^5.12.0", "@fortawesome/fontawesome-free@^5.8.1":
   version "5.12.0"
   resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.12.0.tgz#8ceb9f09edfb85ea18a6c7bf098f6f5dd5ffd62b"
   integrity sha512-vKDJUuE2GAdBERaQWmmtsciAMzjwNrROXA5KTGSZvayAsmuTGjam5z6QNqNPCwDfVljLWuov1nEC3mEQf/n6fQ==
 
+"@kyleshockey/object-assign-deep@^0.4.2":
+  version "0.4.2"
+  resolved "https://registry.yarnpkg.com/@kyleshockey/object-assign-deep/-/object-assign-deep-0.4.2.tgz#84900f0eefc372798f4751b5262830b8208922ec"
+  integrity sha1-hJAPDu/DcnmPR1G1JigwuCCJIuw=
+
+"@kyleshockey/xml@^1.0.2":
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/@kyleshockey/xml/-/xml-1.0.2.tgz#81fad3d7c33da2ba2639db095db3db24c2921f70"
+  integrity sha512-iMo32MPLcI9cPxs3YL5kmKxKgDmkSZDCFEqIT5eRk7d/Ll8r4X3SwGYSigzALd6+RHWlFEmjL1QyaQ15xDZFlw==
+  dependencies:
+    stream "^0.0.2"
+
 "@tippy.js/react@^2.1.2":
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/@tippy.js/react/-/react-2.1.2.tgz#2f51a206d3b90ee2423574bf256c76e1cf2b3e53"
@@ -255,7 +288,7 @@
     "@types/history" "*"
     "@types/react" "*"
 
-"@types/react@*", "@types/react@16.8.5", "@types/react@^16.8.5", "@types/react@^16.x":
+"@types/react@*", "@types/react@16.4.6", "@types/react@16.8.5", "@types/react@^16.8.5", "@types/react@^16.x":
   version "16.8.5"
   resolved "https://registry.yarnpkg.com/@types/react/-/react-16.8.5.tgz#03b9a6597bc20f6eaaed43f377a160f7e41c2b90"
   integrity sha512-8LRySaaSJVLNZb2dbOGvGmzn88cbAfrgDpuWy+6lLgQ0OJFgHHvyuaCX4/7ikqJlpmCPf4uazJAZcfTQRdJqdQ==
@@ -278,6 +311,13 @@
     "@types/cookiejar" "*"
     "@types/node" "*"
 
+"@types/swagger-ui-react@^3.23.2":
+  version "3.23.2"
+  resolved "https://registry.yarnpkg.com/@types/swagger-ui-react/-/swagger-ui-react-3.23.2.tgz#153eb466785763b514459a300772a43257a3c16b"
+  integrity sha512-EMD/EHdGQ6rBjfKE05x2syyw4kMZiA0qRIlv0q9EpQWYWWBhHwffSR0ljSHCy+pbX8zdOct08w8hVA0V0SAo0Q==
+  dependencies:
+    "@types/react" "*"
+
 "@types/ws@^4.0.0":
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/@types/ws/-/ws-4.0.2.tgz#b29037627dd7ba31ec49a4f1584840422efb856f"
@@ -615,7 +655,7 @@ are-we-there-yet@~1.1.2:
     xterm "2.4.0"
     yamljs "^0.3.0"
 
-argparse@^1.0.6, argparse@^1.0.7:
+argparse@^1.0.10, argparse@^1.0.6, argparse@^1.0.7:
   version "1.0.10"
   resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
   integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
@@ -664,6 +704,11 @@ arrify@^1.0.0, arrify@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
 
+asap@~2.0.3:
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
+  integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
+
 asn1.js@^4.0.0:
   version "4.10.1"
   resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0"
@@ -730,6 +775,13 @@ atob@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.1.tgz#ae2d5a729477f289d60dd7f96a6314a22dd6c22a"
 
+autolinker@^3.11.0:
+  version "3.14.1"
+  resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-3.14.1.tgz#6ae4b812b6eaf42d4d68138b9e67757cbf2bc1e4"
+  integrity sha512-yvsRHIaY51EYDml6MGlbqyJGfl4n7zezGYf+R7gvM8c5LNpRGc4SISkvgAswSS8SWxk/OrGCylKV9mJyVstz7w==
+  dependencies:
+    tslib "^1.9.3"
+
 aws-sign2@~0.6.0:
   version "0.6.0"
   resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
@@ -771,6 +823,11 @@ base64-js@^1.0.2:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3"
 
+base64-js@^1.2.0:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
+  integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
+
 base@^0.11.1:
   version "0.11.2"
   resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
@@ -953,6 +1010,11 @@ browserify-zlib@^0.2.0:
   dependencies:
     pako "~1.0.5"
 
+btoa@=1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/btoa/-/btoa-1.2.1.tgz#01a9909f8b2c93f6bf680ba26131eb30f7fa3d73"
+  integrity sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==
+
 buffer-from@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.0.tgz#87fcaa3a298358e0ade6e442cfce840740d1ad04"
@@ -965,6 +1027,14 @@ buffer-xor@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
 
+buffer@=5.6.0:
+  version "5.6.0"
+  resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786"
+  integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==
+  dependencies:
+    base64-js "^1.0.2"
+    ieee754 "^1.1.4"
+
 buffer@^4.3.0:
   version "4.9.1"
   resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298"
@@ -1378,6 +1448,11 @@ cookie@0.4.0:
   resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
   integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
 
+cookie@=0.4.1:
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
+  integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
+
 cookiejar@^2.1.0:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c"
@@ -1397,6 +1472,13 @@ copy-descriptor@^0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
 
+copy-to-clipboard@^3:
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae"
+  integrity sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==
+  dependencies:
+    toggle-selection "^1.0.6"
+
 copy-webpack-plugin@^4.3.1:
   version "4.5.2"
   resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.5.2.tgz#d53444a8fea2912d806e78937390ddd7e632ee5c"
@@ -1421,10 +1503,20 @@ copyfiles@^1.2.0:
     noms "0.0.0"
     through2 "^2.0.1"
 
+core-js@^1.0.0:
+  version "1.2.7"
+  resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
+  integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=
+
 core-js@^2.4.0:
   version "2.5.7"
   resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e"
 
+core-js@^2.6.11, core-js@^2.6.5:
+  version "2.6.11"
+  resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c"
+  integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==
+
 core-util-is@1.0.2, core-util-is@~1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -1457,6 +1549,15 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
     safe-buffer "^5.0.1"
     sha.js "^2.4.8"
 
+create-react-class@^15.5.1:
+  version "15.6.3"
+  resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.3.tgz#2d73237fb3f970ae6ebe011a9e66f46dbca80036"
+  integrity sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==
+  dependencies:
+    fbjs "^0.8.9"
+    loose-envify "^1.3.1"
+    object-assign "^4.1.1"
+
 create-react-context@^0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.3.0.tgz#546dede9dc422def0d3fc2fe03afe0bc0f4f7d8c"
@@ -1465,6 +1566,13 @@ create-react-context@^0.3.0:
     gud "^1.0.0"
     warning "^4.0.3"
 
+cross-fetch@=3.0.5:
+  version "3.0.5"
+  resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.5.tgz#2739d2981892e7ab488a7ad03b92df2816e03f4c"
+  integrity sha512-FFLcLtraisj5eteosnX1gf01qYDCOc4fDy0+euOt8Kn9YBY2NtXL/pCoYPavw24NIQkQqm5ZOLsGD5Zzj0gyew==
+  dependencies:
+    node-fetch "2.6.0"
+
 cross-spawn@6.0.5, cross-spawn@^6.0.0:
   version "6.0.5"
   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@@ -1518,6 +1626,11 @@ css-what@2.1:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd"
 
+css.escape@1.5.1:
+  version "1.5.1"
+  resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb"
+  integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=
+
 csstype@^2.2.0:
   version "2.6.8"
   resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.8.tgz#0fb6fc2417ffd2816a418c9336da74d7f07db431"
@@ -1537,6 +1650,14 @@ cyclist@~0.2.2:
   version "0.2.2"
   resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
 
+d@1, d@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a"
+  integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==
+  dependencies:
+    es5-ext "^0.10.50"
+    type "^1.0.1"
+
 dagre@^0.8.2:
   version "0.8.2"
   resolved "https://registry.yarnpkg.com/dagre/-/dagre-0.8.2.tgz#755b79f4d5499d63cf74c3368fb08add93eceafe"
@@ -1613,7 +1734,7 @@ deep-equal@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
 
-deep-extend@^0.6.0:
+deep-extend@0.6.0, deep-extend@=0.6.0, deep-extend@^0.6.0:
   version "0.6.0"
   resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
 
@@ -1789,6 +1910,11 @@ domhandler@2.1:
   dependencies:
     domelementtype "1"
 
+dompurify@^2.0.7:
+  version "2.0.12"
+  resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.0.12.tgz#284a2b041e1c60b8e72d7b4d2fadad36141254ae"
+  integrity sha512-Fl8KseK1imyhErHypFPA8qpq9gPzlsJ/EukA6yk9o0gX23p1TzC+rh9LqNg1qvErRTc0UNMYlKxEGSfSh43NDg==
+
 domutils@1.1:
   version "1.1.6"
   resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.1.6.tgz#bddc3de099b9a2efacc51c623f28f416ecc57485"
@@ -1833,6 +1959,11 @@ elliptic@^6.0.0:
     minimalistic-assert "^1.0.0"
     minimalistic-crypto-utils "^1.0.0"
 
+emitter-component@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/emitter-component/-/emitter-component-1.1.1.tgz#065e2dbed6959bf470679edabeaf7981d1003ab6"
+  integrity sha1-Bl4tvtaVm/RwZ57avq95gdEAOrY=
+
 emoji-regex@^7.0.1:
   version "7.0.3"
   resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
@@ -1851,6 +1982,13 @@ encodeurl@~1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
 
+encoding@^0.1.11:
+  version "0.1.12"
+  resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
+  integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=
+  dependencies:
+    iconv-lite "~0.4.13"
+
 end-of-stream@^1.0.0, end-of-stream@^1.1.0:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
@@ -1906,6 +2044,42 @@ es-to-primitive@^1.1.1:
     is-date-object "^1.0.1"
     is-symbol "^1.0.1"
 
+es5-ext@^0.10.35, es5-ext@^0.10.45, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46:
+  version "0.10.53"
+  resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1"
+  integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==
+  dependencies:
+    es6-iterator "~2.0.3"
+    es6-symbol "~3.1.3"
+    next-tick "~1.0.0"
+
+es6-iterator@^2.0.3, es6-iterator@~2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
+  integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c=
+  dependencies:
+    d "1"
+    es5-ext "^0.10.35"
+    es6-symbol "^3.1.1"
+
+es6-symbol@^3.1.1, es6-symbol@~3.1.3:
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18"
+  integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==
+  dependencies:
+    d "^1.0.1"
+    ext "^1.1.2"
+
+es6-weak-map@^2.0.2:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53"
+  integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==
+  dependencies:
+    d "1"
+    es5-ext "^0.10.46"
+    es6-iterator "^2.0.3"
+    es6-symbol "^3.1.1"
+
 escape-html@~1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
@@ -1952,6 +2126,14 @@ etag@~1.8.1:
   version "1.8.1"
   resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
 
+event-emitter@^0.3.5:
+  version "0.3.5"
+  resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39"
+  integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=
+  dependencies:
+    d "1"
+    es5-ext "~0.10.14"
+
 eventemitter3@^3.0.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163"
@@ -2045,6 +2227,13 @@ express@^4.17.1:
     utils-merge "1.0.1"
     vary "~1.1.2"
 
+ext@^1.1.2:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244"
+  integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==
+  dependencies:
+    type "^2.0.0"
+
 extend-shallow@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
@@ -2097,6 +2286,13 @@ fast-diff@^1.1.1:
   resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
   integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
 
+fast-json-patch@=2.2.1:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/fast-json-patch/-/fast-json-patch-2.2.1.tgz#18150d36c9ab65c7209e7d4eb113f4f8eaabe6d9"
+  integrity sha512-4j5uBaTnsYAV5ebkidvxiLUYOwjQ+JSFljeqfTxCrH9bDmlCQaOJFS84oDJ2rAXZq2yskmk3ORfoP9DCwqFNig==
+  dependencies:
+    fast-deep-equal "^2.0.1"
+
 fast-json-stable-stringify@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
@@ -2114,6 +2310,19 @@ faye-websocket@~0.11.1:
   dependencies:
     websocket-driver ">=0.5.1"
 
+fbjs@^0.8.9:
+  version "0.8.17"
+  resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd"
+  integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=
+  dependencies:
+    core-js "^1.0.0"
+    isomorphic-fetch "^2.1.1"
+    loose-envify "^1.0.0"
+    object-assign "^4.1.0"
+    promise "^7.1.1"
+    setimmediate "^1.0.5"
+    ua-parser-js "^0.7.18"
+
 figgy-pudding@^3.5.1:
   version "3.5.1"
   resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790"
@@ -2247,6 +2456,15 @@ form-data@^2.3.1:
     combined-stream "1.0.6"
     mime-types "^2.1.12"
 
+form-data@^2.3.2:
+  version "2.5.1"
+  resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4"
+  integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==
+  dependencies:
+    asynckit "^0.4.0"
+    combined-stream "^1.0.6"
+    mime-types "^2.1.12"
+
 form-data@~2.1.1:
   version "2.1.4"
   resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1"
@@ -2831,7 +3049,7 @@ https-browserify@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
 
-iconv-lite@0.4.24:
+iconv-lite@0.4.24, iconv-lite@~0.4.13:
   version "0.4.24"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
   integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
@@ -2844,6 +3062,11 @@ iconv-lite@^0.4.4:
   dependencies:
     safer-buffer ">= 2.1.2 < 3"
 
+ieee754@^1.1.13:
+  version "1.1.13"
+  resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
+  integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
+
 ieee754@^1.1.4:
   version "1.1.12"
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
@@ -2862,6 +3085,11 @@ ignore@^3.3.5:
   version "3.3.10"
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043"
 
+immutable@^3.8.1, immutable@^3.x.x:
+  version "3.8.2"
+  resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3"
+  integrity sha1-wkOZUUVbs5kT2vKBN28VMOEErfM=
+
 import-local@2.0.0, import-local@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d"
@@ -3028,6 +3256,14 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2:
     is-data-descriptor "^1.0.0"
     kind-of "^6.0.2"
 
+is-dom@^1.0.9:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/is-dom/-/is-dom-1.1.0.tgz#af1fced292742443bb59ca3f76ab5e80907b4e8a"
+  integrity sha512-u82f6mvhYxRPKpw8V1N0W8ce1xXwOrQtgGcxl6UCL5zBmZu3is/18K0rR7uFCnMDuAsS/3W54mGL4vsaFUQlEQ==
+  dependencies:
+    is-object "^1.0.1"
+    is-window "^1.0.2"
+
 is-extendable@^0.1.0, is-extendable@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
@@ -3082,6 +3318,11 @@ is-number@^7.0.0:
   resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
   integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
 
+is-object@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470"
+  integrity sha1-iVJojF7C/9awPsyF52ngKQMINHA=
+
 is-path-cwd@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.1.0.tgz#2e0c7e463ff5b7a0eb60852d851a6809347a124c"
@@ -3108,13 +3349,18 @@ is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
   dependencies:
     isobject "^3.0.1"
 
+is-promise@^2.1:
+  version "2.2.2"
+  resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1"
+  integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==
+
 is-regex@^1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491"
   dependencies:
     has "^1.0.1"
 
-is-stream@^1.1.0:
+is-stream@^1.0.1, is-stream@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
 
@@ -3130,6 +3376,11 @@ is-utf8@^0.2.0:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
 
+is-window@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/is-window/-/is-window-1.0.2.tgz#2c896ca53db97de45d3c33133a65d8c9f563480d"
+  integrity sha1-LIlspT25feRdPDMTOmXYyfVjSA0=
+
 is-windows@^1.0.1, is-windows@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
@@ -3161,6 +3412,21 @@ isobject@^3.0.0, isobject@^3.0.1:
   resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
   integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
 
+isomorphic-fetch@^2.1.1:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
+  integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=
+  dependencies:
+    node-fetch "^1.0.1"
+    whatwg-fetch ">=0.10.0"
+
+isomorphic-form-data@=2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/isomorphic-form-data/-/isomorphic-form-data-2.0.0.tgz#9f6adf1c4c61ae3aefd8f110ab60fb9b143d6cec"
+  integrity sha512-TYgVnXWeESVmQSg4GLVbalmQ+B4NPi/H4eWxqALKj63KsUrcu301YDjBqaOw3h+cbak7Na4Xyps3BiptHtxTfg==
+  dependencies:
+    form-data "^2.3.2"
+
 isstream@~0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
@@ -3179,6 +3445,11 @@ js-base64@^2.1.8:
   version "2.4.8"
   resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.8.tgz#57a9b130888f956834aa40c5b165ba59c758f033"
 
+js-file-download@^0.4.1:
+  version "0.4.12"
+  resolved "https://registry.yarnpkg.com/js-file-download/-/js-file-download-0.4.12.tgz#10c70ef362559a5b23cdbdc3bd6f399c3d91d821"
+  integrity sha512-rML+NkoD08p5Dllpjo0ffy4jRHeY6Zsapvr/W86N7E0yuzAO6qa5X9+xog6zQNlH102J7IXljNY2FtS6Lj3ucg==
+
 "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -3188,6 +3459,14 @@ js-tokens@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
 
+js-yaml@=3.14.0:
+  version "3.14.0"
+  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482"
+  integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==
+  dependencies:
+    argparse "^1.0.7"
+    esprima "^4.0.0"
+
 js-yaml@^3.13.1:
   version "3.13.1"
   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
@@ -3400,7 +3679,7 @@ lodash-es@^4.17.14, lodash-es@^4.17.5, lodash-es@^4.2.1:
   resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
   integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==
 
-lodash.debounce@^4.0.8:
+lodash.debounce@^4, lodash.debounce@^4.0.8:
   version "4.0.8"
   resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
 
@@ -3408,6 +3687,11 @@ lodash.tail@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664"
 
+lodash@=4.17.15, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15:
+  version "4.17.15"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
+  integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
+
 lodash@^4.0.0, lodash@^4.11.1, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.6.1, lodash@~4.17.10:
   version "4.17.10"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
@@ -3417,11 +3701,6 @@ lodash@^4.17.11:
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
   integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
 
-lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15:
-  version "4.17.15"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
-  integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
-
 loglevel@^1.6.3:
   version "1.6.3"
   resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.3.tgz#77f2eb64be55a404c9fd04ad16d57c1d6d6b1280"
@@ -3463,6 +3742,13 @@ lru-cache@^5.1.1:
   dependencies:
     yallist "^3.0.2"
 
+lru-queue@0.1:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3"
+  integrity sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=
+  dependencies:
+    es5-ext "~0.10.2"
+
 ltcdr@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/ltcdr/-/ltcdr-2.2.1.tgz#5ab87ad1d4c1dab8e8c08bbf037ee0c1902287cf"
@@ -3531,6 +3817,20 @@ mem@^4.0.0:
     mimic-fn "^2.0.0"
     p-is-promise "^2.0.0"
 
+memoizee@^0.4.12:
+  version "0.4.14"
+  resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.14.tgz#07a00f204699f9a95c2d9e77218271c7cd610d57"
+  integrity sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==
+  dependencies:
+    d "1"
+    es5-ext "^0.10.45"
+    es6-weak-map "^2.0.2"
+    event-emitter "^0.3.5"
+    is-promise "^2.1"
+    lru-queue "0.1"
+    next-tick "1"
+    timers-ext "^0.1.5"
+
 memory-fs@^0.4.0, memory-fs@^0.4.1, memory-fs@~0.4.1:
   version "0.4.1"
   resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
@@ -3853,6 +4153,16 @@ neo-async@^2.5.0:
   version "2.5.1"
   resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.1.tgz#acb909e327b1e87ec9ef15f41b8a269512ad41ee"
 
+next-tick@1:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb"
+  integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==
+
+next-tick@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
+  integrity sha1-yobR/ogoFpsBICCOPchCS524NCw=
+
 nice-try@^1.0.4:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
@@ -3864,6 +4174,19 @@ no-case@^2.2.0:
   dependencies:
     lower-case "^1.1.1"
 
+node-fetch@2.6.0:
+  version "2.6.0"
+  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
+  integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
+
+node-fetch@^1.0.1:
+  version "1.7.3"
+  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
+  integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==
+  dependencies:
+    encoding "^0.1.11"
+    is-stream "^1.0.1"
+
 node-forge@0.7.5:
   version "0.7.5"
   resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.5.tgz#6c152c345ce11c52f465c2abd957e8639cd674df"
@@ -4477,7 +4800,14 @@ promise-inflight@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
 
-prop-types@^15.5.10, prop-types@^15.7.2:
+promise@^7.1.1:
+  version "7.3.1"
+  resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
+  integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==
+  dependencies:
+    asap "~2.0.3"
+
+prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.7.2:
   version "15.7.2"
   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
   integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -4568,6 +4898,11 @@ qs@6.7.0:
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
   integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
 
+qs@=6.9.4:
+  version "6.9.4"
+  resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687"
+  integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==
+
 qs@^6.5.1, qs@~6.5.2:
   version "6.5.2"
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
@@ -4576,6 +4911,11 @@ qs@~6.4.0:
   version "6.4.0"
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
 
+querystring-browser@=1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/querystring-browser/-/querystring-browser-1.0.4.tgz#f2e35881840a819bc7b1bf597faf0979e6622dc6"
+  integrity sha1-8uNYgYQKgZvHsb9Zf68JeeZiLcY=
+
 querystring-es3@^0.2.0:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
@@ -4589,12 +4929,26 @@ querystringify@^2.1.1:
   resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e"
   integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==
 
+raf@^3.1.0:
+  version "3.4.1"
+  resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
+  integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==
+  dependencies:
+    performance-now "^2.1.0"
+
 randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80"
   dependencies:
     safe-buffer "^5.1.0"
 
+randombytes@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
+  integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
+  dependencies:
+    safe-buffer "^5.1.0"
+
 randomfill@^1.0.3:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458"
@@ -4638,6 +4992,14 @@ react-autocomplete@^1.8.1:
     dom-scroll-into-view "1.0.1"
     prop-types "^15.5.10"
 
+react-copy-to-clipboard@5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.1.tgz#8eae107bb400be73132ed3b6a7b4fb156090208e"
+  integrity sha512-ELKq31/E3zjFs5rDWNCfFL4NvNFQvGRoJdAKReD/rUPA+xxiLPQmZBZBvy2vgH7V0GE9isIQpT9WXbwIVErYdA==
+  dependencies:
+    copy-to-clipboard "^3"
+    prop-types "^15.5.8"
+
 react-datepicker@^2.14.0:
   version "2.14.1"
   resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-2.14.1.tgz#83463beb85235a575475955f554290a95f89c65b"
@@ -4649,6 +5011,14 @@ react-datepicker@^2.14.0:
     react-onclickoutside "^6.9.0"
     react-popper "^1.3.4"
 
+react-debounce-input@^3.2.0:
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/react-debounce-input/-/react-debounce-input-3.2.2.tgz#d2cc99c1ce47fae89037965f5699edc1b0317197"
+  integrity sha512-RIBu68Cq/gImKz/2h1cE042REDqyqj3D+7SJ3lnnIpJX0ht9D9PfH7KAnL+SgDz6hvKa9pZS2CnAxlkrLmnQlg==
+  dependencies:
+    lodash.debounce "^4"
+    prop-types "^15.7.2"
+
 react-deep-force-update@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/react-deep-force-update/-/react-deep-force-update-2.1.1.tgz#8ea4263cd6455a050b37445b3f08fd839d86e909"
@@ -4701,6 +5071,27 @@ react-hot-loader@^3.1.3:
     redbox-react "^1.3.6"
     source-map "^0.6.1"
 
+react-immutable-proptypes@2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/react-immutable-proptypes/-/react-immutable-proptypes-2.1.0.tgz#023d6f39bb15c97c071e9e60d00d136eac5fa0b4"
+  integrity sha1-Aj1vObsVyXwHHp5g0A0TbqxfoLQ=
+
+react-immutable-pure-component@^1.1.1:
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/react-immutable-pure-component/-/react-immutable-pure-component-1.2.3.tgz#fa33638df68cfe9f73ccbee1d5861c17f3053f86"
+  integrity sha512-kNy2A/fDrSuR8TKwB+4ynmItmp1vgF87tWxxfmadwDYo2J3ANipHqTjDIBvJvJ7libvuh76jIbvmK0krjtKH1g==
+  optionalDependencies:
+    "@types/react" "16.4.6"
+
+react-inspector@^2.3.0:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-2.3.1.tgz#f0eb7f520669b545b441af9d38ec6d706e5f649c"
+  integrity sha512-tUUK7t3KWgZEIUktOYko5Ic/oYwvjEvQUFAGC1UeMeDaQ5za2yZFtItJa2RTwBJB//NxPr000WQK6sEbqC6y0Q==
+  dependencies:
+    babel-runtime "^6.26.0"
+    is-dom "^1.0.9"
+    prop-types "^15.6.1"
+
 react-is@^16.7.0, react-is@^16.8.1:
   version "16.12.0"
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c"
@@ -4724,6 +5115,15 @@ react-monaco-editor@^0.36.0:
     monaco-editor "*"
     prop-types "^15.7.2"
 
+react-motion@^0.5.2:
+  version "0.5.2"
+  resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.5.2.tgz#0dd3a69e411316567927917c6626551ba0607316"
+  integrity sha512-9q3YAvHoUiWlP3cK0v+w1N5Z23HXMj4IF4YuvjvWegWqNPfLXsOBE/V7UvQGpXxHFKRQQcNcVQE31g9SB/6qgQ==
+  dependencies:
+    performance-now "^0.2.0"
+    prop-types "^15.5.8"
+    raf "^3.1.0"
+
 react-onclickoutside@^6.9.0:
   version "6.9.0"
   resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.9.0.tgz#a54bc317ae8cf6131a5d78acea55a11067f37a1f"
@@ -4748,6 +5148,18 @@ react-proxy@^3.0.0-alpha.0:
   dependencies:
     lodash "^4.6.1"
 
+react-redux@^4.x.x:
+  version "4.4.10"
+  resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-4.4.10.tgz#ad57bd1db00c2d0aa7db992b360ce63dd0b80ec5"
+  integrity sha512-tjL0Bmpkj75Td0k+lXlF8Fc8a9GuXFv/3ahUOCXExWs/jhsKiQeTffdH0j5byejCGCRL4tvGFYlrwBF1X/Aujg==
+  dependencies:
+    create-react-class "^15.5.1"
+    hoist-non-react-statics "^3.3.0"
+    invariant "^2.0.0"
+    lodash "^4.17.11"
+    loose-envify "^1.4.0"
+    prop-types "^15.7.2"
+
 react-redux@^5.0.6:
   version "5.0.7"
   resolved "https://registry.npmjs.org/react-redux/-/react-redux-5.0.7.tgz#0dc1076d9afb4670f993ffaef44b8f8c1155a4c8"
@@ -4919,6 +5331,13 @@ redent@^1.0.0:
     indent-string "^2.1.0"
     strip-indent "^1.0.1"
 
+redux-immutable@3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/redux-immutable/-/redux-immutable-3.1.0.tgz#cafbd686e0711261119b9c28960935dc47a49d0a"
+  integrity sha1-yvvWhuBxEmERm5wolgk13EeknQo=
+  dependencies:
+    immutable "^3.8.1"
+
 redux-logger@^3.0.6:
   version "3.0.6"
   resolved "https://registry.yarnpkg.com/redux-logger/-/redux-logger-3.0.6.tgz#f7555966f3098f3c88604c449cf0baf5778274bf"
@@ -4929,7 +5348,7 @@ redux-thunk@^2.2.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
 
-redux@^3.7.2:
+redux@^3.7.2, redux@^3.x.x:
   version "3.7.2"
   resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b"
   dependencies:
@@ -4947,6 +5366,11 @@ regenerator-runtime@^0.13.2:
   resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz#32e59c9a6fb9b1a4aff09b4930ca2d4477343447"
   integrity sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==
 
+regenerator-runtime@^0.13.4:
+  version "0.13.5"
+  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697"
+  integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==
+
 regex-not@^1.0.0, regex-not@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"
@@ -4964,6 +5388,14 @@ relateurl@0.2.x:
   version "0.2.7"
   resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
 
+remarkable@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/remarkable/-/remarkable-2.0.1.tgz#280ae6627384dfb13d98ee3995627ca550a12f31"
+  integrity sha512-YJyMcOH5lrR+kZdmB0aJJ4+93bEojRZ1HGDn9Eagu6ibg7aVZhc3OWbbShRid+Q5eAfsEqWxpe+g5W5nYNfNiA==
+  dependencies:
+    argparse "^1.0.10"
+    autolinker "^3.11.0"
+
 remove-trailing-separator@^1.0.1:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
@@ -5062,6 +5494,11 @@ requires-port@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
 
+reselect@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"
+  integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==
+
 resolve-cwd@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
@@ -5267,6 +5704,11 @@ send@0.17.1:
     range-parser "~1.2.1"
     statuses "~1.5.0"
 
+serialize-error@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-2.1.0.tgz#50b679d5635cdf84667bdc8e59af4e5b81d5f60a"
+  integrity sha1-ULZ51WNc34Rme9yOWa9OW4HV9go=
+
 serialize-javascript@^1.4.0:
   version "1.5.0"
   resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.5.0.tgz#1aa336162c88a890ddad5384baebc93a655161fe"
@@ -5325,7 +5767,7 @@ set-value@^2.0.0:
     is-plain-object "^2.0.3"
     split-string "^3.0.1"
 
-setimmediate@^1.0.4:
+setimmediate@^1.0.4, setimmediate@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
 
@@ -5338,7 +5780,7 @@ setprototypeof@1.1.1:
   resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
   integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
 
-sha.js@^2.4.0, sha.js@^2.4.8:
+sha.js@^2.4.0, sha.js@^2.4.11, sha.js@^2.4.8:
   version "2.4.11"
   resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
   dependencies:
@@ -5626,6 +6068,13 @@ stream-shift@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
 
+stream@^0.0.2:
+  version "0.0.2"
+  resolved "https://registry.yarnpkg.com/stream/-/stream-0.0.2.tgz#7f5363f057f6592c5595f00bc80a27f5cec1f0ef"
+  integrity sha1-f1Nj8Ff2WSxVlfALyAon9c7B8O8=
+  dependencies:
+    emitter-component "^1.1.1"
+
 string-width@^1.0.1, string-width@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
@@ -5798,6 +6247,67 @@ svgpath@^2.1.5:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/svgpath/-/svgpath-2.2.1.tgz#0834bb67c89a76472b2bd06cc101fa7b517b222c"
 
+swagger-client@=3.10.9:
+  version "3.10.9"
+  resolved "https://registry.yarnpkg.com/swagger-client/-/swagger-client-3.10.9.tgz#685cd332f1ed365ea49e9938e00f5c353a39739b"
+  integrity sha512-tPnhVQ0AC7wM89jfYsP/LlNvmzArr2Uf0NdQ7N69sHrS+7fibB/i75fOIG1kH4bb78D70Z1hwqszEvAOQaZ0Og==
+  dependencies:
+    "@babel/runtime-corejs2" "=7.10.2"
+    btoa "=1.2.1"
+    buffer "=5.6.0"
+    cookie "=0.4.1"
+    cross-fetch "=3.0.5"
+    deep-extend "=0.6.0"
+    fast-json-patch "=2.2.1"
+    isomorphic-form-data "=2.0.0"
+    js-yaml "=3.14.0"
+    lodash "=4.17.15"
+    qs "=6.9.4"
+    querystring-browser "=1.0.4"
+    traverse "=0.6.6"
+    url "=0.11.0"
+
+swagger-ui-react@^3.28.0:
+  version "3.28.0"
+  resolved "https://registry.yarnpkg.com/swagger-ui-react/-/swagger-ui-react-3.28.0.tgz#33b303874f1f9fd37e880d23b9238ea4c4892ab1"
+  integrity sha512-GEYJ3qajUI+th5jP1pebaSsOJDmkJsRp6AtntUvSjCVeUSbOp1OEq2IQoWqpGeXr54V5Epk2OUFZilXS7FsDOw==
+  dependencies:
+    "@babel/runtime-corejs2" "^7.5.5"
+    "@braintree/sanitize-url" "^4.0.0"
+    "@kyleshockey/object-assign-deep" "^0.4.2"
+    "@kyleshockey/xml" "^1.0.2"
+    base64-js "^1.2.0"
+    classnames "^2.2.6"
+    core-js "^2.6.11"
+    css.escape "1.5.1"
+    deep-extend "0.6.0"
+    dompurify "^2.0.7"
+    ieee754 "^1.1.13"
+    immutable "^3.x.x"
+    js-file-download "^0.4.1"
+    js-yaml "^3.13.1"
+    lodash "^4.17.15"
+    memoizee "^0.4.12"
+    prop-types "^15.7.2"
+    randombytes "^2.1.0"
+    react-copy-to-clipboard "5.0.1"
+    react-debounce-input "^3.2.0"
+    react-immutable-proptypes "2.1.0"
+    react-immutable-pure-component "^1.1.1"
+    react-inspector "^2.3.0"
+    react-motion "^0.5.2"
+    react-redux "^4.x.x"
+    redux "^3.x.x"
+    redux-immutable "3.1.0"
+    remarkable "^2.0.1"
+    reselect "^4.0.0"
+    serialize-error "^2.1.0"
+    sha.js "^2.4.11"
+    swagger-client "=3.10.9"
+    url-parse "^1.4.7"
+    xml-but-prettier "^1.0.1"
+    zenscroll "^4.0.2"
+
 symbol-observable@1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4"
@@ -5873,6 +6383,14 @@ timers-browserify@^2.0.4:
   dependencies:
     setimmediate "^1.0.4"
 
+timers-ext@^0.1.5:
+  version "0.1.7"
+  resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6"
+  integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==
+  dependencies:
+    es5-ext "~0.10.46"
+    next-tick "1"
+
 tiny-warning@^1.0.2:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
@@ -5923,6 +6441,11 @@ to-regex@^3.0.1, to-regex@^3.0.2:
     regex-not "^1.0.2"
     safe-regex "^1.1.0"
 
+toggle-selection@^1.0.6:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
+  integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI=
+
 toidentifier@1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
@@ -5946,6 +6469,11 @@ tough-cookie@~2.4.3:
     psl "^1.1.24"
     punycode "^1.4.1"
 
+traverse@=0.6.6:
+  version "0.6.6"
+  resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137"
+  integrity sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=
+
 trim-newlines@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
@@ -6011,6 +6539,11 @@ tslib@^1.8.0, tslib@^1.8.1:
   version "1.9.3"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
 
+tslib@^1.9.3:
+  version "1.13.0"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
+  integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
+
 tslint-config-prettier@^1.18.0:
   version "1.18.0"
   resolved "https://registry.yarnpkg.com/tslint-config-prettier/-/tslint-config-prettier-1.18.0.tgz#75f140bde947d35d8f0d238e0ebf809d64592c37"
@@ -6100,6 +6633,16 @@ type-is@~1.6.17, type-is@~1.6.18:
     media-typer "0.3.0"
     mime-types "~2.1.24"
 
+type@^1.0.1:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0"
+  integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==
+
+type@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/type/-/type-2.0.0.tgz#5f16ff6ef2eb44f260494dae271033b29c09a9c3"
+  integrity sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==
+
 typed-styles@^0.0.7:
   version "0.0.7"
   resolved "https://registry.yarnpkg.com/typed-styles/-/typed-styles-0.0.7.tgz#93392a008794c4595119ff62dde6809dbc40a3d9"
@@ -6118,6 +6661,11 @@ typescript@^3.9.2:
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.2.tgz#64e9c8e9be6ea583c54607677dd4680a1cf35db9"
   integrity sha512-q2ktq4n/uLuNNShyayit+DTobV2ApPEo/6so68JaD5ojvc/6GClBipedB9zNWYxRSAlZXAe405Rlijzl6qDiSw==
 
+ua-parser-js@^0.7.18:
+  version "0.7.21"
+  resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777"
+  integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==
+
 uglify-js@3.4.x:
   version "3.4.5"
   resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.5.tgz#650889c0766cf0f6fd5346cea09cd212f544be69"
@@ -6208,7 +6756,7 @@ url-join@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/url-join/-/url-join-1.1.0.tgz#741c6c2f4596c4830d6718460920d0c92202dc78"
 
-url-parse@^1.4.3:
+url-parse@^1.4.3, url-parse@^1.4.7:
   version "1.4.7"
   resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278"
   integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==
@@ -6216,7 +6764,7 @@ url-parse@^1.4.3:
     querystringify "^2.1.1"
     requires-port "^1.0.0"
 
-url@^0.11.0:
+url@=0.11.0, url@^0.11.0:
   version "0.11.0"
   resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
   dependencies:
@@ -6503,6 +7051,11 @@ what-input@^4.1.3:
   version "4.3.1"
   resolved "https://registry.yarnpkg.com/what-input/-/what-input-4.3.1.tgz#b8ea7554ba1d9171887c4c6addf28185fec3d31d"
 
+whatwg-fetch@>=0.10.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.1.0.tgz#49d630cdfa308dba7f2819d49d09364f540dbcc6"
+  integrity sha512-pgmbsVWKpH9GxLXZmtdowDIqtb/rvPyjjQv3z9wLcmgWKFHilKnZD3ldgrOlwJoPGOUluQsRPWd52yVkPfmI1A==
+
 which-module@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
@@ -6570,6 +7123,13 @@ ws@^4.0.0:
     async-limiter "~1.0.0"
     safe-buffer "~5.1.0"
 
+xml-but-prettier@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/xml-but-prettier/-/xml-but-prettier-1.0.1.tgz#f5a33267ed42ccd4e355c62557a5e39b01fb40f3"
+  integrity sha1-9aMyZ+1CzNTjVcYlV6XjmwH7QPM=
+  dependencies:
+    repeat-string "^1.5.2"
+
 xmldom@~0.1.22:
   version "0.1.27"
   resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9"
@@ -6693,3 +7253,8 @@ yargs@~3.10.0:
 yn@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/yn/-/yn-2.0.0.tgz#e5adabc8acf408f6385fc76495684c88e6af689a"
+
+zenscroll@^4.0.2:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/zenscroll/-/zenscroll-4.0.2.tgz#e8d5774d1c0738a47bcfa8729f3712e2deddeb25"
+  integrity sha1-6NV3TRwHOKR7z6hynzcS4t7d6yU=
diff --git a/util/logs/workflow-logger.go b/util/logs/workflow-logger.go
index 7471e1919..49ab4dfaa 100644
--- a/util/logs/workflow-logger.go
+++ b/util/logs/workflow-logger.go
@@ -65,7 +65,10 @@ func WorkflowLogs(ctx context.Context, wfClient versioned.Interface, kubeClient
 	var wg sync.WaitGroup
 	// A non-blocking channel for log entries to go down.
 	unsortedEntries := make(chan logEntry, 128)
-	logOptions := req.GetLogOptions().DeepCopy()
+	logOptions := req.GetLogOptions()
+	if logOptions == nil {
+		logOptions = &corev1.PodLogOptions{}
+	}
 	logOptions.Timestamps = true
 
 	// this func start a stream if one is not already running
diff --git a/workflow/controller/config.go b/workflow/controller/config.go
index 14d756d04..4ebd64775 100644
--- a/workflow/controller/config.go
+++ b/workflow/controller/config.go
@@ -74,7 +74,6 @@ func (wfc *WorkflowController) updateConfig(config config.Config) error {
 		log.Info("Persistence configuration disabled")
 	}
 	wfc.hydrator = hydrator.New(wfc.offloadNodeStatusRepo)
-	wfc.throttler.SetParallelism(config.Parallelism)
 	return nil
 }
 
diff --git a/workflow/controller/controller.go b/workflow/controller/controller.go
index 8a0d8ecb1..29c308f30 100644
--- a/workflow/controller/controller.go
+++ b/workflow/controller/controller.go
@@ -80,7 +80,7 @@ type WorkflowController struct {
 	offloadNodeStatusRepo sqldb.OffloadNodeStatusRepo
 	hydrator              hydrator.Interface
 	wfArchive             sqldb.WorkflowArchive
-	metrics               metrics.Metrics
+	metrics               *metrics.Metrics
 	eventRecorderManager  EventRecorderManager
 	archiveLabelSelector  labels.Selector
 }
@@ -103,17 +103,22 @@ func NewWorkflowController(restConfig *rest.Config, kubeclientset kubernetes.Int
 		cliExecutorImage:           executorImage,
 		cliExecutorImagePullPolicy: executorImagePullPolicy,
 		containerRuntimeExecutor:   containerRuntimeExecutor,
-		wfQueue:                    workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
-		podQueue:                   workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
 		configController:           config.NewController(namespace, configMap, kubeclientset),
 		completedPods:              make(chan string, 512),
 		gcPods:                     make(chan string, 512),
 		eventRecorderManager:       newEventRecorderManager(kubeclientset),
 	}
-	wfc.throttler = NewThrottler(0, wfc.wfQueue)
+
 	wfc.UpdateConfig()
 
 	wfc.metrics = metrics.New(wfc.getMetricsServerConfig())
+
+	workqueue.SetProvider(wfc.metrics)
+	wfc.wfQueue = workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "workflow_queue")
+	wfc.throttler = NewThrottler(0, wfc.wfQueue)
+	wfc.throttler.SetParallelism(wfc.getParallelism())
+	wfc.podQueue = workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "pod_queue")
+
 	return &wfc
 }
 
@@ -127,7 +132,7 @@ func (wfc *WorkflowController) runTTLController(ctx context.Context) {
 }
 
 func (wfc *WorkflowController) runCronController(ctx context.Context) {
-	cronController := cron.NewCronController(wfc.wfclientset, wfc.restConfig, wfc.namespace, wfc.GetManagedNamespace(), wfc.Config.InstanceID, &wfc.metrics)
+	cronController := cron.NewCronController(wfc.wfclientset, wfc.restConfig, wfc.namespace, wfc.GetManagedNamespace(), wfc.Config.InstanceID, wfc.metrics)
 	cronController.Run(ctx)
 }
 
@@ -587,13 +592,22 @@ func (wfc *WorkflowController) addWorkflowInformerHandlers() {
 	)
 	wfc.wfInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
 		AddFunc: func(obj interface{}) {
-			wfc.metrics.WorkflowAdded(getWfPhase(obj))
+			key, err := cache.MetaNamespaceKeyFunc(obj)
+			if err == nil {
+				wfc.metrics.WorkflowAdded(key, getWfPhase(obj))
+			}
 		},
 		UpdateFunc: func(old, new interface{}) {
-			wfc.metrics.WorkflowUpdated(getWfPhase(old), getWfPhase(new))
+			key, err := cache.MetaNamespaceKeyFunc(new)
+			if err == nil {
+				wfc.metrics.WorkflowUpdated(key, getWfPhase(old), getWfPhase(new))
+			}
 		},
 		DeleteFunc: func(obj interface{}) {
-			wfc.metrics.WorkflowDeleted(getWfPhase(obj))
+			key, err := cache.MetaNamespaceKeyFunc(obj)
+			if err == nil {
+				wfc.metrics.WorkflowDeleted(key, getWfPhase(obj))
+			}
 		},
 	})
 }
@@ -712,6 +726,10 @@ func (wfc *WorkflowController) GetContainerRuntimeExecutor() string {
 	return wfc.Config.ContainerRuntimeExecutor
 }
 
+func (wfc *WorkflowController) getParallelism() int {
+	return wfc.Config.Parallelism
+}
+
 func (wfc *WorkflowController) getMetricsServerConfig() (metrics.ServerConfig, metrics.ServerConfig) {
 	// Metrics config
 	path := wfc.Config.MetricsConfig.Path
diff --git a/workflow/controller/dag.go b/workflow/controller/dag.go
index ac286b2c7..efb3a19e2 100644
--- a/workflow/controller/dag.go
+++ b/workflow/controller/dag.go
@@ -366,11 +366,12 @@ func (woc *wfOperationCtx) executeDAGTask(dagCtx *dagContext, taskName string) {
 			connectDependencies(nodeName)
 			return
 		}
+		// Recurse into all of this node's dependencies
+		for _, dep := range taskDependencies {
+			woc.executeDAGTask(dagCtx, dep)
+		}
 		if !proceed {
-			// This node's dependencies are not completed yet, recurse into them, then return
-			for _, dep := range taskDependencies {
-				woc.executeDAGTask(dagCtx, dep)
-			}
+			// This node's dependencies are not completed yet, return
 			return
 		}
 		if !execute {
diff --git a/workflow/controller/operator.go b/workflow/controller/operator.go
index fcd3fe29b..3f6ab5f3d 100644
--- a/workflow/controller/operator.go
+++ b/workflow/controller/operator.go
@@ -780,7 +780,7 @@ func (woc *wfOperationCtx) podReconciliation() error {
 					woc.onNodeComplete(&node)
 				}
 			}
-			if !node.FailedOrError() {
+			if node.Succeeded() {
 				woc.succeededPods[pod.ObjectMeta.Name] = true
 			}
 		}
diff --git a/workflow/cron/controller.go b/workflow/cron/controller.go
index 19999203d..57b7a35e9 100644
--- a/workflow/cron/controller.go
+++ b/workflow/cron/controller.go
@@ -68,8 +68,8 @@ func NewCronController(
 		restConfig:         restConfig,
 		nameEntryIDMap:     make(map[string]cron.EntryID),
 		nameEntryIDMapLock: &sync.Mutex{},
-		wfQueue:            workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
-		cronWfQueue:        workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
+		wfQueue:            workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "wf_cron_queue"),
+		cronWfQueue:        workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "cron_wf_queue"),
 		metrics:            metrics,
 	}
 }
diff --git a/workflow/metrics/metrics.go b/workflow/metrics/metrics.go
index 92343dd31..4775ae1f9 100644
--- a/workflow/metrics/metrics.go
+++ b/workflow/metrics/metrics.go
@@ -2,9 +2,11 @@ package metrics
 
 import (
 	"fmt"
+	"sync"
 	"time"
 
 	"github.com/prometheus/client_golang/prometheus"
+	"k8s.io/client-go/util/workqueue"
 
 	"github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
 )
@@ -34,30 +36,36 @@ type metric struct {
 }
 
 type Metrics struct {
+	// Ensures mutual exclusion in workflows map
+	workflowsMutex  sync.Mutex
 	metricsConfig   ServerConfig
 	telemetryConfig ServerConfig
 
 	workflowsProcessed prometheus.Counter
 	workflowsByPhase   map[v1alpha1.NodePhase]prometheus.Gauge
+	workflows          map[string]bool
 	operationDurations prometheus.Histogram
 	errors             map[ErrorCause]prometheus.Counter
 	customMetrics      map[string]metric
+	workqueueMetrics   map[string]prometheus.Metric
 
 	// Used to quickly check if a metric desc is already used by the system
 	defaultMetricDescs map[string]bool
 }
 
-var _ prometheus.Collector = Metrics{}
+var _ prometheus.Collector = &Metrics{}
 
-func New(metricsConfig, telemetryConfig ServerConfig) Metrics {
-	metrics := Metrics{
+func New(metricsConfig, telemetryConfig ServerConfig) *Metrics {
+	metrics := &Metrics{
 		metricsConfig:      metricsConfig,
 		telemetryConfig:    telemetryConfig,
 		workflowsProcessed: newCounter("workflows_processed_count", "Number of workflow updates processed", nil),
 		workflowsByPhase:   getWorkflowPhaseGauges(),
+		workflows:          make(map[string]bool),
 		operationDurations: newHistogram("operation_duration_seconds", "Histogram of durations of operations", nil, []float64{0.1, 0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0, 2.5, 3.0}),
 		errors:             getErrorCounters(),
 		customMetrics:      make(map[string]metric),
+		workqueueMetrics:   make(map[string]prometheus.Metric),
 		defaultMetricDescs: make(map[string]bool),
 	}
 
@@ -68,7 +76,7 @@ func New(metricsConfig, telemetryConfig ServerConfig) Metrics {
 	return metrics
 }
 
-func (m Metrics) allMetrics() []prometheus.Metric {
+func (m *Metrics) allMetrics() []prometheus.Metric {
 	allMetrics := []prometheus.Metric{
 		m.workflowsProcessed,
 		m.operationDurations,
@@ -79,6 +87,9 @@ func (m Metrics) allMetrics() []prometheus.Metric {
 	for _, metric := range m.errors {
 		allMetrics = append(allMetrics, metric)
 	}
+	for _, metric := range m.workqueueMetrics {
+		allMetrics = append(allMetrics, metric)
+	}
 	for _, metric := range m.customMetrics {
 		allMetrics = append(allMetrics, metric.metric)
 	}
@@ -86,33 +97,53 @@ func (m Metrics) allMetrics() []prometheus.Metric {
 	return allMetrics
 }
 
-func (m Metrics) WorkflowAdded(phase v1alpha1.NodePhase) {
+func (m *Metrics) WorkflowAdded(key string, phase v1alpha1.NodePhase) {
+	m.workflowsMutex.Lock()
+	defer m.workflowsMutex.Unlock()
+
+	if m.workflows[key] {
+		return
+	}
+	m.workflows[key] = true
 	if _, ok := m.workflowsByPhase[phase]; ok {
 		m.workflowsByPhase[phase].Inc()
 	}
 }
 
-func (m Metrics) WorkflowUpdated(fromPhase, toPhase v1alpha1.NodePhase) {
-	m.WorkflowDeleted(fromPhase)
-	m.WorkflowAdded(toPhase)
+func (m *Metrics) WorkflowUpdated(key string, fromPhase, toPhase v1alpha1.NodePhase) {
+	m.workflowsMutex.Lock()
+	hasKey := m.workflows[key]
+	m.workflowsMutex.Unlock()
+	if fromPhase == toPhase || !hasKey {
+		return
+	}
+	m.WorkflowDeleted(key, fromPhase)
+	m.WorkflowAdded(key, toPhase)
 }
 
-func (m Metrics) WorkflowDeleted(phase v1alpha1.NodePhase) {
+func (m *Metrics) WorkflowDeleted(key string, phase v1alpha1.NodePhase) {
+	m.workflowsMutex.Lock()
+	defer m.workflowsMutex.Unlock()
+
+	if !m.workflows[key] {
+		return
+	}
+	delete(m.workflows, key)
 	if _, ok := m.workflowsByPhase[phase]; ok {
 		m.workflowsByPhase[phase].Dec()
 	}
 }
 
-func (m Metrics) OperationCompleted(durationSeconds float64) {
+func (m *Metrics) OperationCompleted(durationSeconds float64) {
 	m.operationDurations.Observe(durationSeconds)
 }
 
-func (m Metrics) GetCustomMetric(key string) prometheus.Metric {
+func (m *Metrics) GetCustomMetric(key string) prometheus.Metric {
 	// It's okay to return nil metrics in this function
 	return m.customMetrics[key].metric
 }
 
-func (m Metrics) UpsertCustomMetric(key string, newMetric prometheus.Metric) error {
+func (m *Metrics) UpsertCustomMetric(key string, newMetric prometheus.Metric) error {
 	if _, inUse := m.defaultMetricDescs[newMetric.Desc().String()]; inUse {
 		return fmt.Errorf("metric '%s' is already in use by the system, please use a different name", newMetric.Desc())
 	}
@@ -127,10 +158,54 @@ const (
 	ErrorCauseCronWorkflowSubmissionError ErrorCause = "CronWorkflowSubmissionError"
 )
 
-func (m Metrics) OperationPanic() {
+func (m *Metrics) OperationPanic() {
 	m.errors[ErrorCauseOperationPanic].Inc()
 }
 
-func (m Metrics) CronWorkflowSubmissionError() {
+func (m *Metrics) CronWorkflowSubmissionError() {
 	m.errors[ErrorCauseCronWorkflowSubmissionError].Inc()
 }
+
+// Act as a metrics provider for a workflow queue
+var _ workqueue.MetricsProvider = &Metrics{}
+
+func (m *Metrics) NewDepthMetric(name string) workqueue.GaugeMetric {
+	key := fmt.Sprintf("%s-depth", name)
+	if _, ok := m.workqueueMetrics[key]; !ok {
+		m.workqueueMetrics[key] = newGauge("queue_depth_count", "Depth of the queue", map[string]string{"queue_name": name})
+	}
+	return m.workqueueMetrics[key].(prometheus.Gauge)
+}
+
+func (m *Metrics) NewAddsMetric(name string) workqueue.CounterMetric {
+	key := fmt.Sprintf("%s-adds", name)
+	if _, ok := m.workqueueMetrics[key]; !ok {
+		m.workqueueMetrics[key] = newCounter("queue_adds_count", "Adds to the queue", map[string]string{"queue_name": name})
+	}
+	return m.workqueueMetrics[key].(prometheus.Counter)
+}
+
+func (m *Metrics) NewLatencyMetric(name string) workqueue.HistogramMetric {
+	key := fmt.Sprintf("%s-latency", name)
+	if _, ok := m.workqueueMetrics[key]; !ok {
+		m.workqueueMetrics[key] = newHistogram("queue_latency", "Time objects spend waiting in the queue", map[string]string{"queue_name": name}, []float64{1.0, 5.0, 20.0, 60.0, 180.0})
+	}
+	return m.workqueueMetrics[key].(prometheus.Histogram)
+}
+
+// These metrics are not relevant to be exposed
+type noopMetric struct{}
+
+func (noopMetric) Inc()            {}
+func (noopMetric) Dec()            {}
+func (noopMetric) Set(float64)     {}
+func (noopMetric) Observe(float64) {}
+
+func (m *Metrics) NewRetriesMetric(name string) workqueue.CounterMetric        { return noopMetric{} }
+func (m *Metrics) NewWorkDurationMetric(name string) workqueue.HistogramMetric { return noopMetric{} }
+func (m *Metrics) NewUnfinishedWorkSecondsMetric(name string) workqueue.SettableGaugeMetric {
+	return noopMetric{}
+}
+func (m *Metrics) NewLongestRunningProcessorSecondsMetric(name string) workqueue.SettableGaugeMetric {
+	return noopMetric{}
+}
diff --git a/workflow/metrics/server.go b/workflow/metrics/server.go
index dcc62730d..eb4285623 100644
--- a/workflow/metrics/server.go
+++ b/workflow/metrics/server.go
@@ -13,7 +13,7 @@ import (
 )
 
 // RunServer starts a metrics server
-func (m Metrics) RunServer(ctx context.Context) {
+func (m *Metrics) RunServer(ctx context.Context) {
 	if !m.metricsConfig.Enabled {
 		// If metrics aren't enabled, return
 		return
@@ -66,19 +66,19 @@ func runServer(config ServerConfig, registry *prometheus.Registry, ctx context.C
 	}
 }
 
-func (m Metrics) Describe(ch chan<- *prometheus.Desc) {
+func (m *Metrics) Describe(ch chan<- *prometheus.Desc) {
 	for _, metric := range m.allMetrics() {
 		ch <- metric.Desc()
 	}
 }
 
-func (m Metrics) Collect(ch chan<- prometheus.Metric) {
+func (m *Metrics) Collect(ch chan<- prometheus.Metric) {
 	for _, metric := range m.allMetrics() {
 		ch <- metric
 	}
 }
 
-func (m Metrics) garbageCollector(ctx context.Context) {
+func (m *Metrics) garbageCollector(ctx context.Context) {
 	if m.metricsConfig.TTL == 0 {
 		return
 	}
diff --git a/workflow/ttlcontroller/ttlcontroller.go b/workflow/ttlcontroller/ttlcontroller.go
index eb18f7f4b..6374aad5f 100644
--- a/workflow/ttlcontroller/ttlcontroller.go
+++ b/workflow/ttlcontroller/ttlcontroller.go
@@ -37,7 +37,7 @@ func NewController(wfClientset wfclientset.Interface, wfInformer cache.SharedInd
 	controller := &Controller{
 		wfclientset:  wfClientset,
 		wfInformer:   wfInformer,
-		workqueue:    workqueue.NewNamedDelayingQueue("workflow-ttl"),
+		workqueue:    workqueue.NewDelayingQueue(),
 		resyncPeriod: workflowTTLResyncPeriod,
 		clock:        clock.RealClock{},
 	}
diff --git a/workflow/util/util.go b/workflow/util/util.go
index 85795b544..b5f0a6104 100644
--- a/workflow/util/util.go
+++ b/workflow/util/util.go
@@ -211,7 +211,7 @@ func ApplySubmitOpts(wf *wfv1.Workflow, opts *wfv1.SubmitOpts) error {
 	if opts.Labels != "" {
 		passedLabels, err := cmdutil.ParseLabels(opts.Labels)
 		if err != nil {
-			return fmt.Errorf("Expected labels of the form: NAME1=VALUE2,NAME2=VALUE2. Received: %s", opts.Labels)
+			return fmt.Errorf("expected labels of the form: NAME1=VALUE2,NAME2=VALUE2. Received: %s: %w", opts.Labels, err)
 		}
 		for k, v := range passedLabels {
 			wfLabels[k] = v
@@ -223,14 +223,11 @@ func ApplySubmitOpts(wf *wfv1.Workflow, opts *wfv1.SubmitOpts) error {
 		passedParams := make(map[string]bool)
 		for _, paramStr := range opts.Parameters {
 			parts := strings.SplitN(paramStr, "=", 2)
-			if len(parts) == 1 {
-				return fmt.Errorf("Expected parameter of the form: NAME=VALUE. Received: %s", paramStr)
-			}
-			intOrString := intstr.Parse(parts[2])
-			param := wfv1.Parameter{
-				Name:  parts[0],
-				Value: &intOrString,
+			if len(parts) != 2 {
+				return fmt.Errorf("expected parameter of the form: NAME=VALUE. Received: %s", paramStr)
 			}
+			intOrString := intstr.Parse(parts[1])
+			param := wfv1.Parameter{Name: parts[0], Value: &intOrString}
 			newParams = append(newParams, param)
 			passedParams[param.Name] = true
 		}
@@ -265,10 +262,7 @@ func ApplySubmitOpts(wf *wfv1.Workflow, opts *wfv1.SubmitOpts) error {
 					value = string(v)
 				}
 				intOrString := intstr.Parse(value)
-				param := wfv1.Parameter{
-					Name:  k,
-					Value: &intOrString,
-				}
+				param := wfv1.Parameter{Name: k, Value: &intOrString}
 				if _, ok := passedParams[param.Name]; ok {
 					// this parameter was overridden via command line
 					continue


Test patch:
diff --git a/api/openapi-spec/swagger_test.go b/api/openapi-spec/swagger_test.go
index a2badbca2..def067123 100644
--- a/api/openapi-spec/swagger_test.go
+++ b/api/openapi-spec/swagger_test.go
@@ -49,10 +49,6 @@ func TestSwagger(t *testing.T) {
 		definition := definitions["io.argoproj.workflow.v1alpha1.Workflow"].(obj)
 		assert.NotContains(t, definition["required"], "status")
 	})
-	t.Run("io.argoproj.workflow.v1alpha1.Item", func(t *testing.T) {
-		definition := definitions["io.argoproj.workflow.v1alpha1.Item"].(obj)
-		assert.ElementsMatch(t, []string{"string", "number", "boolean", "array", "object"}, definition["type"])
-	})
 	t.Run("io.argoproj.workflow.v1alpha1.Parameter", func(t *testing.T) {
 		definition := definitions["io.argoproj.workflow.v1alpha1.Parameter"].(obj)
 		properties := definition["properties"].(obj)
diff --git a/pkg/apis/workflow/v1alpha1/item_test.go b/pkg/apis/workflow/v1alpha1/item_test.go
index 36bf89113..b94e0a37d 100644
--- a/pkg/apis/workflow/v1alpha1/item_test.go
+++ b/pkg/apis/workflow/v1alpha1/item_test.go
@@ -64,4 +64,4 @@ func TestItem_GetStrVal(t *testing.T) {
 	assert.NoError(t, err)
 	val := item.GetStrVal()
 	assert.Equal(t, "foo", val)
-}
\ No newline at end of file
+}
diff --git a/pkg/apis/workflow/v1alpha1/workflow_types_test.go b/pkg/apis/workflow/v1alpha1/workflow_types_test.go
index 986ca9796..d79eff2be 100644
--- a/pkg/apis/workflow/v1alpha1/workflow_types_test.go
+++ b/pkg/apis/workflow/v1alpha1/workflow_types_test.go
@@ -47,6 +47,32 @@ func TestWorkflowFinishedBefore(t *testing.T) {
 	assert.True(t, WorkflowFinishedBefore(t1)(Workflow{Status: WorkflowStatus{FinishedAt: metav1.Time{Time: t0}}}))
 }
 
+func TestWorkflowHappenedBetween(t *testing.T) {
+	t0 := time.Time{}
+	t1 := t0.Add(time.Second)
+	t2 := t1.Add(time.Second)
+	t3 := t2.Add(time.Second)
+	assert.False(t, WorkflowRanBetween(t0, t3)(Workflow{}))
+	assert.False(t, WorkflowRanBetween(t0, t1)(Workflow{
+		ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Time{Time: t0}},
+		Status:     WorkflowStatus{FinishedAt: metav1.Time{Time: t1}}}))
+	assert.False(t, WorkflowRanBetween(t1, t2)(Workflow{
+		ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Time{Time: t0}},
+		Status:     WorkflowStatus{FinishedAt: metav1.Time{Time: t1}}}))
+	assert.False(t, WorkflowRanBetween(t2, t3)(Workflow{
+		ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Time{Time: t0}},
+		Status:     WorkflowStatus{FinishedAt: metav1.Time{Time: t1}}}))
+	assert.False(t, WorkflowRanBetween(t0, t1)(Workflow{
+		ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Time{Time: t1}},
+		Status:     WorkflowStatus{FinishedAt: metav1.Time{Time: t2}}}))
+	assert.False(t, WorkflowRanBetween(t2, t3)(Workflow{
+		ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Time{Time: t1}},
+		Status:     WorkflowStatus{FinishedAt: metav1.Time{Time: t2}}}))
+	assert.True(t, WorkflowRanBetween(t0, t3)(Workflow{
+		ObjectMeta: metav1.ObjectMeta{CreationTimestamp: metav1.Time{Time: t1}},
+		Status:     WorkflowStatus{FinishedAt: metav1.Time{Time: t2}}}))
+}
+
 func TestArtifactLocation_HasLocation(t *testing.T) {
 	assert.False(t, (&ArtifactLocation{}).HasLocation())
 	assert.False(t, (&ArtifactLocation{ArchiveLogs: pointer.BoolPtr(true)}).HasLocation())
diff --git a/workflow/controller/dag_test.go b/workflow/controller/dag_test.go
index 7ec11902e..9cb21e162 100644
--- a/workflow/controller/dag_test.go
+++ b/workflow/controller/dag_test.go
@@ -1643,3 +1643,130 @@ func TestOnExitDAGPhase(t *testing.T) {
 
 	assert.Equal(t, wfv1.NodeRunning, woc.wf.Status.Phase)
 }
+
+var testOnExitNonLeaf = `
+apiVersion: argoproj.io/v1alpha1
+kind: Workflow
+metadata:
+  name: exit-handler-bug-example
+spec:
+  arguments: {}
+  entrypoint: dag
+  templates:
+  - arguments: {}
+    dag:
+      tasks:
+      - arguments: {}
+        name: step-2
+        onExit: on-exit
+        template: step-template
+      - arguments: {}
+        dependencies:
+        - step-2
+        name: step-3
+        onExit: on-exit
+        template: step-template
+    inputs: {}
+    metadata: {}
+    name: dag
+    outputs: {}
+  - arguments: {}
+    container:
+      args:
+      - echo exit-handler-step-{{pod.name}}
+      command:
+      - sh
+      - -c
+      image: alpine:latest
+      name: ""
+      resources: {}
+    inputs: {}
+    metadata: {}
+    name: on-exit
+    outputs: {}
+  - arguments: {}
+    container:
+      args:
+      - echo step {{pod.name}}
+      command:
+      - sh
+      - -c
+      image: alpine:latest
+      name: ""
+      resources: {}
+    inputs: {}
+    metadata: {}
+    name: step-template
+    outputs: {}
+status:
+  nodes:
+    exit-handler-bug-example:
+      children:
+      - exit-handler-bug-example-3054913383
+      displayName: exit-handler-bug-example
+      finishedAt: "2020-07-07T16:15:54Z"
+      id: exit-handler-bug-example
+      name: exit-handler-bug-example
+      outboundNodes:
+      - exit-handler-bug-example-3038135764
+      phase: Running
+      startedAt: "2020-07-07T16:15:33Z"
+      templateName: dag
+      templateScope: local/exit-handler-bug-example
+      type: DAG
+    exit-handler-bug-example-3054913383:
+      boundaryID: exit-handler-bug-example
+      displayName: step-2
+      finishedAt: "2020-07-07T16:15:37Z"
+      hostNodeName: minikube
+      id: exit-handler-bug-example-3054913383
+      name: exit-handler-bug-example.step-2
+      outputs:
+        artifacts:
+        - archiveLogs: true
+          name: main-logs
+          s3:
+            accessKeySecret:
+              key: accesskey
+              name: my-minio-cred
+            bucket: my-bucket
+            endpoint: minio:9000
+            insecure: true
+            key: exit-handler-bug-example/exit-handler-bug-example-3054913383/main.log
+            secretKeySecret:
+              key: secretkey
+              name: my-minio-cred
+        exitCode: "0"
+      phase: Succeeded
+      resourcesDuration:
+        cpu: 4
+        memory: 2
+      startedAt: "2020-07-07T16:15:33Z"
+      templateName: step-template
+      templateScope: local/exit-handler-bug-example
+      type: Pod
+  phase: Running
+  startedAt: "2020-07-07T16:15:33Z"
+`
+
+func TestOnExitNonLeaf(t *testing.T) {
+	cancel, controller := newController()
+	defer cancel()
+	wfcset := controller.wfclientset.ArgoprojV1alpha1().Workflows("")
+
+	wf := unmarshalWF(testOnExitNonLeaf)
+	wf, err := wfcset.Create(wf)
+	assert.NoError(t, err)
+	woc := newWorkflowOperationCtx(wf, controller)
+
+	woc.operate()
+	retryNode := woc.getNodeByName("step-2.onExit")
+	if assert.NotNil(t, retryNode) {
+		assert.Equal(t, wfv1.NodePending, retryNode.Phase)
+	}
+	retryNode = woc.getNodeByName("exit-handler-bug-example.step-3")
+	if assert.NotNil(t, retryNode) {
+		assert.Equal(t, wfv1.NodePending, retryNode.Phase)
+	}
+	assert.Equal(t, wfv1.NodeRunning, woc.wf.Status.Phase)
+}
diff --git a/workflow/cron/operator_test.go b/workflow/cron/operator_test.go
index 31ed71cb0..b27640e69 100644
--- a/workflow/cron/operator_test.go
+++ b/workflow/cron/operator_test.go
@@ -176,7 +176,7 @@ func TestCronWorkflowConditionSubmissionError(t *testing.T) {
 		wfLister:    &fakeLister{},
 		cronWf:      &cronWf,
 		log:         logrus.WithFields(logrus.Fields{}),
-		metrics:     &testMetrics,
+		metrics:     testMetrics,
 	}
 	woc.Run()
 
@@ -232,7 +232,7 @@ func TestSpecError(t *testing.T) {
 		wfLister:    &fakeLister{},
 		cronWf:      &cronWf,
 		log:         logrus.WithFields(logrus.Fields{}),
-		metrics:     &testMetrics,
+		metrics:     testMetrics,
 	}
 
 	err = woc.validateCronWorkflow()
@@ -261,7 +261,7 @@ func TestReapplyUpdate(t *testing.T) {
 		origCronWf:  cronWf.DeepCopy(),
 		name:        cronWf.Name,
 		log:         logrus.WithFields(logrus.Fields{}),
-		metrics:     &testMetrics,
+		metrics:     testMetrics,
 	}
 
 	cronWf.Spec.Schedule = "1 * * * *"
diff --git a/workflow/metrics/metrics_test.go b/workflow/metrics/metrics_test.go
index ac9101d6e..b7e60ff38 100644
--- a/workflow/metrics/metrics_test.go
+++ b/workflow/metrics/metrics_test.go
@@ -7,6 +7,7 @@ import (
 
 	dto "github.com/prometheus/client_model/go"
 	"github.com/stretchr/testify/assert"
+	"k8s.io/client-go/util/workqueue"
 
 	"github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
 )
@@ -47,14 +48,21 @@ func TestMetrics(t *testing.T) {
 	}
 	m := New(config, config)
 
-	m.WorkflowAdded(v1alpha1.NodeRunning)
+	m.WorkflowAdded("wf", v1alpha1.NodeRunning)
 	var metric dto.Metric
 	err := m.workflowsByPhase[v1alpha1.NodeRunning].Write(&metric)
 	if assert.NoError(t, err) {
 		assert.Equal(t, float64(1), *metric.Gauge.Value)
 	}
 
-	m.WorkflowUpdated(v1alpha1.NodeRunning, v1alpha1.NodeSucceeded)
+	// Test that we don't double add
+	m.WorkflowAdded("wf", v1alpha1.NodeRunning)
+	err = m.workflowsByPhase[v1alpha1.NodeRunning].Write(&metric)
+	if assert.NoError(t, err) {
+		assert.Equal(t, float64(1), *metric.Gauge.Value)
+	}
+
+	m.WorkflowUpdated("wf", v1alpha1.NodeRunning, v1alpha1.NodeSucceeded)
 	err = m.workflowsByPhase[v1alpha1.NodeRunning].Write(&metric)
 	if assert.NoError(t, err) {
 		assert.Equal(t, float64(0), *metric.Gauge.Value)
@@ -64,11 +72,29 @@ func TestMetrics(t *testing.T) {
 		assert.Equal(t, float64(1), *metric.Gauge.Value)
 	}
 
-	m.WorkflowDeleted(v1alpha1.NodeSucceeded)
+	m.WorkflowDeleted("wf", v1alpha1.NodeSucceeded)
+	err = m.workflowsByPhase[v1alpha1.NodeRunning].Write(&metric)
+	if assert.NoError(t, err) {
+		assert.Equal(t, float64(0), *metric.Gauge.Value)
+	}
+
+	// Test that we don't double delete
+	m.WorkflowDeleted("wf", v1alpha1.NodeSucceeded)
+	err = m.workflowsByPhase[v1alpha1.NodeRunning].Write(&metric)
+	if assert.NoError(t, err) {
+		assert.Equal(t, float64(0), *metric.Gauge.Value)
+	}
+
+	// Test that we don't update workflows that we're not tracking
+	m.WorkflowUpdated("does-not-exist", v1alpha1.NodeRunning, v1alpha1.NodeSucceeded)
 	err = m.workflowsByPhase[v1alpha1.NodeRunning].Write(&metric)
 	if assert.NoError(t, err) {
 		assert.Equal(t, float64(0), *metric.Gauge.Value)
 	}
+	err = m.workflowsByPhase[v1alpha1.NodeSucceeded].Write(&metric)
+	if assert.NoError(t, err) {
+		assert.Equal(t, float64(0), *metric.Gauge.Value)
+	}
 
 	m.OperationCompleted(0.05)
 	err = m.operationDurations.Write(&metric)
@@ -126,3 +152,29 @@ func TestMetricGC(t *testing.T) {
 
 	assert.Len(t, m.customMetrics, 0)
 }
+
+func TestWorkflowQueueMetrics(t *testing.T) {
+	config := ServerConfig{
+		Enabled: true,
+		Path:    DefaultMetricsServerPath,
+		Port:    DefaultMetricsServerPort,
+		TTL:     1 * time.Second,
+	}
+	m := New(config, config)
+	workqueue.SetProvider(m)
+	wfQueue := workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "workflow_queue")
+	assert.NotNil(t, m.workqueueMetrics["workflow_queue-depth"])
+	assert.NotNil(t, m.workqueueMetrics["workflow_queue-adds"])
+	assert.NotNil(t, m.workqueueMetrics["workflow_queue-latency"])
+
+	wfQueue.Add("hello")
+
+	if assert.NotNil(t, m.workqueueMetrics["workflow_queue-adds"]) {
+		var metric dto.Metric
+		err := m.workqueueMetrics["workflow_queue-adds"].Write(&metric)
+		if assert.NoError(t, err) {
+			assert.Equal(t, 1.0, *metric.Counter.Value)
+		}
+
+	}
+}
diff --git a/workflow/ttlcontroller/ttlcontroller_test.go b/workflow/ttlcontroller/ttlcontroller_test.go
index a4c515459..7951c97bc 100644
--- a/workflow/ttlcontroller/ttlcontroller_test.go
+++ b/workflow/ttlcontroller/ttlcontroller_test.go
@@ -348,7 +348,7 @@ func newTTLController() *Controller {
 		wfInformer:   wfInformer,
 		resyncPeriod: workflowTTLResyncPeriod,
 		clock:        clock,
-		workqueue:    workqueue.NewNamedDelayingQueue("workflow-ttl"),
+		workqueue:    workqueue.NewDelayingQueue(),
 	}
 }
 
diff --git a/workflow/util/util_test.go b/workflow/util/util_test.go
index 4a6b8abaf..4bdb1d824 100644
--- a/workflow/util/util_test.go
+++ b/workflow/util/util_test.go
@@ -10,6 +10,7 @@ import (
 	"github.com/stretchr/testify/assert"
 	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
 	"sigs.k8s.io/yaml"
 
 	wfv1 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
@@ -325,3 +326,62 @@ func TestStopWorkflowByNodeName(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, wfv1.NodeFailed, wf.Status.Nodes.FindByDisplayName("approve").Phase)
 }
+
+func TestApplySubmitOpts(t *testing.T) {
+	t.Run("Nil", func(t *testing.T) {
+		assert.NoError(t, ApplySubmitOpts(&wfv1.Workflow{}, nil))
+	})
+	t.Run("InvalidLabels", func(t *testing.T) {
+		assert.Error(t, ApplySubmitOpts(&wfv1.Workflow{}, &wfv1.SubmitOpts{Labels: "a"}))
+	})
+	t.Run("Labels", func(t *testing.T) {
+		wf := &wfv1.Workflow{}
+		err := ApplySubmitOpts(wf, &wfv1.SubmitOpts{Labels: "a=1,b=1"})
+		assert.NoError(t, err)
+		assert.Len(t, wf.GetLabels(), 2)
+	})
+	t.Run("MergeLabels", func(t *testing.T) {
+		wf := &wfv1.Workflow{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"a": "0", "b": "0"}}}
+		err := ApplySubmitOpts(wf, &wfv1.SubmitOpts{Labels: "a=1"})
+		assert.NoError(t, err)
+		if assert.Len(t, wf.GetLabels(), 2) {
+			assert.Equal(t, "1", wf.GetLabels()["a"])
+			assert.Equal(t, "0", wf.GetLabels()["b"])
+		}
+	})
+	t.Run("InvalidParameters", func(t *testing.T) {
+		assert.Error(t, ApplySubmitOpts(&wfv1.Workflow{}, &wfv1.SubmitOpts{Parameters: []string{"a"}}))
+	})
+	t.Run("Parameters", func(t *testing.T) {
+		intOrString := intstr.Parse("0")
+		wf := &wfv1.Workflow{
+			Spec: wfv1.WorkflowSpec{
+				Arguments: wfv1.Arguments{
+					Parameters: []wfv1.Parameter{{Name: "a", Value: &intOrString}},
+				},
+			},
+		}
+		err := ApplySubmitOpts(wf, &wfv1.SubmitOpts{Parameters: []string{"a=1"}})
+		assert.NoError(t, err)
+		parameters := wf.Spec.Arguments.Parameters
+		if assert.Len(t, parameters, 1) {
+			assert.Equal(t, "a", parameters[0].Name)
+			assert.Equal(t, "1", parameters[0].Value.String())
+		}
+	})
+	t.Run("ParameterFile", func(t *testing.T) {
+		wf := &wfv1.Workflow{}
+		file, err := ioutil.TempFile("", "")
+		assert.NoError(t, err)
+		defer func() { _ = os.Remove(file.Name()) }()
+		err = ioutil.WriteFile(file.Name(), []byte(`a: 1`), 0644)
+		assert.NoError(t, err)
+		err = ApplySubmitOpts(wf, &wfv1.SubmitOpts{ParameterFile: file.Name()})
+		assert.NoError(t, err)
+		parameters := wf.Spec.Arguments.Parameters
+		if assert.Len(t, parameters, 1) {
+			assert.Equal(t, "a", parameters[0].Name)
+			assert.Equal(t, "1", parameters[0].Value.String())
+		}
+	})
+}