From 7257de1722318226e7e621054e7009eed5400285 Mon Sep 17 00:00:00 2001 From: Alexandru Mahmoud Date: Mon, 14 Jun 2021 16:57:02 -0400 Subject: [PATCH 001/218] Temporary fix for keycloak OIDC --- lib/galaxy/authnz/custos_authnz.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/galaxy/authnz/custos_authnz.py b/lib/galaxy/authnz/custos_authnz.py index f2a22191dd3..e5eef15b196 100644 --- a/lib/galaxy/authnz/custos_authnz.py +++ b/lib/galaxy/authnz/custos_authnz.py @@ -87,6 +87,7 @@ class CustosAuthnz(IdentityProvider): else: userinfo = self._get_userinfo(oauth2_session) email = userinfo['email'] + username = userinfo.get('preferred_username', self._generate_username(trans, email)) user_id = userinfo['sub'] # Create or update custos_authnz_token record @@ -110,9 +111,13 @@ class CustosAuthnz(IdentityProvider): message = "There already exists a user with email %s. To associate this external login, you must first be logged in as that existing account." % email log.exception(message) raise exceptions.AuthenticationFailed(message) - else: - login_redirect_url = login_redirect_url + 'root/login?confirm=true&custos_token=' + json.dumps(token) + elif self.config['provider'] == 'custos': + login_redirect_url = f"{login_redirect_url}root/login?confirm=true&custos_token={json.dumps(token)}" return login_redirect_url, None + else: + user = trans.app.user_manager.create(email=email, username=username) + if trans.app.config.user_activation_on: + trans.app.user_manager.send_activation_email(trans, email, username) custos_authnz_token = CustosAuthnzToken(user=user, external_user_id=user_id, @@ -130,7 +135,7 @@ class CustosAuthnz(IdentityProvider): custos_authnz_token.refresh_expiration_time = refresh_expiration_time trans.sa_session.add(custos_authnz_token) trans.sa_session.flush() - return login_redirect_url, custos_authnz_token.user + return "/", custos_authnz_token.user def create_user(self, token, trans, login_redirect_url): token_dict = json.loads(token) From 3c88705c4bc1f6231999b436cbd9dd0c734fb586 Mon Sep 17 00:00:00 2001 From: Nuwan Goonasekera <2070605+nuwang@users.noreply.github.com> Date: Thu, 22 Jul 2021 15:53:15 +0530 Subject: [PATCH 002/218] Some refactoring of interactive tool handling in k8s --- lib/galaxy/jobs/runners/kubernetes.py | 93 ++++++++++++++++----------- 1 file changed, 57 insertions(+), 36 deletions(-) diff --git a/lib/galaxy/jobs/runners/kubernetes.py b/lib/galaxy/jobs/runners/kubernetes.py index 9e0c089a2ba..ca8561354b9 100644 --- a/lib/galaxy/jobs/runners/kubernetes.py +++ b/lib/galaxy/jobs/runners/kubernetes.py @@ -150,36 +150,24 @@ class KubernetesJobRunner(AsynchronousJobRunner): log.exception(f"({job_wrapper.get_id_tag()}) failure writing job script") return - # Construction of the Kubernetes Job object follows: http://kubernetes.io/docs/user-guide/persistent-volumes/ + # Construction of Kubernetes objects follow: https://kubernetes.io/docs/concepts/workloads/controllers/job/ + if self.__is_interactive_tool(ajs): + try: + self.__configure_interactive_tool_services(ajs) + except HTTPError: + log.exception("Kubernetes failed to create interactive tool services, HTTP exception encountered") + ajs.runner_state = JobState.runner_states.UNKNOWN_ERROR + ajs.fail_message = "Kubernetes failed to create interactive tool services." + self.mark_as_failed(ajs) + return + k8s_job_prefix = self.__produce_k8s_job_prefix() - guest_ports = ajs.job_wrapper.guest_ports - ports_dict = {} - for guest_port in guest_ports: - ports_dict[str(guest_port)] = dict(host='manual', port=guest_port, protocol="https") - eps = None - if ajs.job_wrapper.guest_ports: - k8s_job_name = self.__get_k8s_job_name(k8s_job_prefix, ajs.job_wrapper) - log.debug(f'Configuring entry points and deploying service/ingress for job with ID {ajs.job_id}') - k8s_service_obj = service_object_dict( - self.runner_params, - k8s_job_name, - self.__get_k8s_service_spec(ajs) - ) - eps = self.app.interactivetool_manager.configure_entry_points(ajs.job_wrapper.get_job(), ports_dict) - k8s_ingress_obj = ingress_object_dict( - self.runner_params, - k8s_job_name, - self.__get_k8s_ingress_spec(ajs, eps) - ) - service = Service(self._pykube_api, k8s_service_obj) - service.create() - ingress = Ingress(self._pykube_api, k8s_ingress_obj) - ingress.create() k8s_job_obj = job_object_dict( self.runner_params, k8s_job_prefix, - self.__get_k8s_job_spec(ajs, eps) + self.__get_k8s_job_spec(ajs) ) + job = Job(self._pykube_api, k8s_job_obj) try: job.create() @@ -203,6 +191,37 @@ class KubernetesJobRunner(AsynchronousJobRunner): job_wrapper.set_external_id(job_id) self.monitor_queue.put(ajs) + def __is_interactive_tool(self, ajs): + return bool(ajs.job_wrapper.guest_ports) + + def __configure_interactive_tool_services(self, ajs): + # Configure interactive tool entry points first + guest_ports = ajs.job_wrapper.guest_ports + ports_dict = {} + for guest_port in guest_ports: + ports_dict[str(guest_port)] = dict(host='manual', port=guest_port, protocol="https") + self.app.interactivetool_manager.configure_entry_points(ajs.job_wrapper.get_job(), ports_dict) + + # Configure additional k8s service and ingress for interactive tool + k8s_job_prefix = self.__produce_k8s_job_prefix() + k8s_job_name = self.__get_k8s_job_name(k8s_job_prefix, ajs.job_wrapper) + log.debug(f'Configuring entry points and deploying service/ingress for job with ID {ajs.job_id}') + k8s_service_obj = service_object_dict( + self.runner_params, + k8s_job_name, + self.__get_k8s_service_spec(ajs) + ) + + k8s_ingress_obj = ingress_object_dict( + self.runner_params, + k8s_job_name, + self.__get_k8s_ingress_spec(ajs) + ) + service = Service(self._pykube_api, k8s_service_obj) + service.create() + ingress = Ingress(self._pykube_api, k8s_ingress_obj) + ingress.create() + def __get_overridable_params(self, job_wrapper, param_key): dest_params = self.__get_destination_params(job_wrapper) return dest_params.get(param_key, self.runner_params[param_key]) @@ -265,10 +284,10 @@ class KubernetesJobRunner(AsynchronousJobRunner): instance_id = self._galaxy_instance_id or '' return produce_k8s_job_prefix(app_prefix='gxy', instance_id=instance_id) - def __get_k8s_job_spec(self, ajs, eps=None): + def __get_k8s_job_spec(self, ajs): """Creates the k8s Job spec. For a Job spec, the only requirement is to have a .spec.template. If the job hangs around unlimited it will be ended after k8s wall time limit, which sets activeDeadlineSeconds""" - k8s_job_spec = {"template": self.__get_k8s_job_spec_template(ajs, eps), + k8s_job_spec = {"template": self.__get_k8s_job_spec_template(ajs), "activeDeadlineSeconds": int(self.runner_params['k8s_walltime_limit'])} job_ttl = self.runner_params["k8s_job_ttl_secs_after_finished"] if self.runner_params["k8s_cleanup_job"] != "never" and job_ttl is not None: @@ -289,7 +308,7 @@ class KubernetesJobRunner(AsynchronousJobRunner): label_val += 'x' return label_val - def __get_k8s_job_spec_template(self, ajs, eps=None): + def __get_k8s_job_spec_template(self, ajs): """The k8s spec template is nothing but a Pod spec, except that it is nested and does not have an apiversion nor kind. In addition to required fields for a Pod, a pod template in a job must specify appropriate labels (see pod selector) and an appropriate restart policy.""" @@ -314,7 +333,7 @@ class KubernetesJobRunner(AsynchronousJobRunner): "spec": { "volumes": self.runner_params['k8s_mountable_volumes'], "restartPolicy": self.__get_k8s_restart_policy(ajs.job_wrapper), - "containers": self.__get_k8s_containers(ajs, eps), + "containers": self.__get_k8s_containers(ajs), "priorityClassName": self.runner_params['k8s_pod_priority_class'], "tolerations": yaml.safe_load(self.runner_params['k8s_tolerations'] or "[]"), "affinity": yaml.safe_load(self.__get_overridable_params(ajs.job_wrapper, @@ -363,19 +382,20 @@ class KubernetesJobRunner(AsynchronousJobRunner): } return k8s_spec_template - def __get_k8s_ingress_spec(self, ajs, eps=None): + def __get_k8s_ingress_spec(self, ajs): """The k8s spec template is nothing but a Ingress spec, except that it is nested and does not have an apiversion nor kind.""" guest_ports = ajs.job_wrapper.guest_ports if len(guest_ports) > 0: entry_points = [] - for entry_point in eps.get('configured', []): + configured_eps = [ep for ep in ajs.job_wrapper.get_job().interactivetool_entry_points if ep.configured] + for entry_point in configured_eps: # sending in self.app as `trans` since it's only used for `.security` so seems to work entry_point_path = self.app.interactivetool_manager.get_entry_point_path(self.app, entry_point) if '?' in entry_point_path: # Removing all the parameters from the ingress path, but they will still be in the database # so the link that the user clicks on will still have them - log.warn("IT urls including parameters (eg: /myit?mykey=myvalue) are only experimentally supported on K8S") + log.warning("IT urls including parameters (eg: /myit?mykey=myvalue) are only experimentally supported on K8S") entry_point_path = entry_point_path.split('?')[0] entry_point_domain = f'{self.app.config.interactivetools_proxy_host}' if entry_point.requires_domain: @@ -432,7 +452,7 @@ class KubernetesJobRunner(AsynchronousJobRunner): """The default Kubernetes restart policy for Jobs""" return "Never" - def __get_k8s_containers(self, ajs, eps=None): + def __get_k8s_containers(self, ajs): """Fills in all required for setting up the docker containers to be used, including setting a pull policy if this has been set. $GALAXY_VIRTUAL_ENV is set to None to avoid the galaxy virtualenv inside the tool container. @@ -480,14 +500,15 @@ class KubernetesJobRunner(AsynchronousJobRunner): extra_envs = yaml.safe_load(self.__get_overridable_params(ajs.job_wrapper, 'k8s_extra_job_envs') or "{}") for key in extra_envs: envs.append({'name': key, 'value': extra_envs[key]}) - if eps: - for entry_point in eps.get('configured', []): + if self.__is_interactive_tool(ajs): + configured_eps = [ep for ep in ajs.job_wrapper.get_job().interactivetool_entry_points if ep.configured] + for entry_point in configured_eps: # sending in self.app as `trans` since it's only used for `.security` so seems to work entry_point_path = self.app.interactivetool_manager.get_entry_point_path(self.app, entry_point) if '?' in entry_point_path: # Removing all the parameters from the ingress path, but they will still be in the database # so the link that the user clicks on will still have them - log.warn("IT urls including parameters (eg: /myit?mykey=myvalue) are only experimentally supported on K8S") + log.warning("IT urls including parameters (eg: /myit?mykey=myvalue) are only experimentally supported on K8S") entry_point_path = entry_point_path.split('?')[0] entry_point_domain = f'{self.app.config.interactivetools_proxy_host}' if entry_point.requires_domain: From 08dc137ccb126bddda3226f7909de882bf72a698 Mon Sep 17 00:00:00 2001 From: guerler Date: Wed, 16 Jun 2021 13:11:42 -0400 Subject: [PATCH 003/218] Add workflow parameters to workflow run component --- .../Workflow/Run/WorkflowRunForm.vue | 23 ++++++++++++++++++- client/src/mvc/tool/tool-form-composite.js | 2 +- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/client/src/components/Workflow/Run/WorkflowRunForm.vue b/client/src/components/Workflow/Run/WorkflowRunForm.vue index c645ed2cae3..9f81a779394 100644 --- a/client/src/components/Workflow/Run/WorkflowRunForm.vue +++ b/client/src/components/Workflow/Run/WorkflowRunForm.vue @@ -1,11 +1,24 @@ diff --git a/client/src/mvc/tool/tool-form-composite.js b/client/src/mvc/tool/tool-form-composite.js index abff56d190d..57043fcfb8d 100644 --- a/client/src/mvc/tool/tool-form-composite.js +++ b/client/src/mvc/tool/tool-form-composite.js @@ -98,7 +98,7 @@ var View = Backbone.View.extend({ }, ], }); - this._append(this.$el, this.history_form.$el); + //this._append(this.$el, this.history_form.$el); }, /** Render Workflow Options */ From a37fef2aa9ebfa4faf07afd19cc1af48a4ca3f9a Mon Sep 17 00:00:00 2001 From: guerler Date: Wed, 16 Jun 2021 15:52:22 -0400 Subject: [PATCH 006/218] Add initial components for data and tool steps to workflow run component --- .../Workflow/Run/WorkflowRunDefaultStep.vue | 57 ++++++++++++++++++ .../Workflow/Run/WorkflowRunForm.vue | 12 ++++ .../Workflow/Run/WorkflowRunToolStep.vue | 60 +++++++++++++++++++ client/src/mvc/tool/tool-form-composite.js | 8 +-- 4 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 client/src/components/Workflow/Run/WorkflowRunDefaultStep.vue create mode 100644 client/src/components/Workflow/Run/WorkflowRunToolStep.vue diff --git a/client/src/components/Workflow/Run/WorkflowRunDefaultStep.vue b/client/src/components/Workflow/Run/WorkflowRunDefaultStep.vue new file mode 100644 index 00000000000..0d878cafb7a --- /dev/null +++ b/client/src/components/Workflow/Run/WorkflowRunDefaultStep.vue @@ -0,0 +1,57 @@ + + + diff --git a/client/src/components/Workflow/Run/WorkflowRunForm.vue b/client/src/components/Workflow/Run/WorkflowRunForm.vue index 06624d69446..bd04fc61cb5 100644 --- a/client/src/components/Workflow/Run/WorkflowRunForm.vue +++ b/client/src/components/Workflow/Run/WorkflowRunForm.vue @@ -21,6 +21,14 @@
+
+
+ +
+
+ +
+
@@ -29,11 +37,15 @@ import { getGalaxyInstance } from "app"; import ToolFormComposite from "mvc/tool/tool-form-composite"; import Form from "components/Form/Form"; import FormCard from "components/Form/FormCard"; +import WorkflowRunDefaultStep from "./WorkflowRunDefaultStep"; +import WorkflowRunToolStep from "./WorkflowRunToolStep"; export default { components: { Form, FormCard, + WorkflowRunDefaultStep, + WorkflowRunToolStep, }, props: { model: { diff --git a/client/src/components/Workflow/Run/WorkflowRunToolStep.vue b/client/src/components/Workflow/Run/WorkflowRunToolStep.vue new file mode 100644 index 00000000000..09195fa97ac --- /dev/null +++ b/client/src/components/Workflow/Run/WorkflowRunToolStep.vue @@ -0,0 +1,60 @@ + + + diff --git a/client/src/mvc/tool/tool-form-composite.js b/client/src/mvc/tool/tool-form-composite.js index 57043fcfb8d..aee5bede902 100644 --- a/client/src/mvc/tool/tool-form-composite.js +++ b/client/src/mvc/tool/tool-form-composite.js @@ -37,13 +37,13 @@ var View = Backbone.View.extend({ render: function () { this.deferred.reset(); - this._renderParameters(); + /*this._renderParameters(); this._renderHistory(); this._renderUseCachedJob(); this._renderResourceParameters(); _.each(this.steps, (step) => { this._renderStep(step); - }); + });*/ }, /** Render workflow parameters */ @@ -62,7 +62,7 @@ var View = Backbone.View.extend({ }); }, }); - //this._append(this.$el.empty(), this.wp_form.$el); + this._append(this.$el.empty(), this.wp_form.$el); } }, @@ -98,7 +98,7 @@ var View = Backbone.View.extend({ }, ], }); - //this._append(this.$el, this.history_form.$el); + this._append(this.$el, this.history_form.$el); }, /** Render Workflow Options */ From 62a1475d6a4e0e30c874b681ab5c9543617612ee Mon Sep 17 00:00:00 2001 From: guerler Date: Thu, 17 Jun 2021 03:26:07 -0400 Subject: [PATCH 007/218] Add correct tool form title to workflow steps --- client/src/components/Tool/ToolCard.vue | 6 +--- .../Workflow/Run/WorkflowRunToolStep.vue | 28 ++++--------------- 2 files changed, 7 insertions(+), 27 deletions(-) diff --git a/client/src/components/Tool/ToolCard.vue b/client/src/components/Tool/ToolCard.vue index 850cfd2050a..8c276fb16cd 100644 --- a/client/src/components/Tool/ToolCard.vue +++ b/client/src/components/Tool/ToolCard.vue @@ -143,10 +143,6 @@ export default { type: String, required: false, }, - sustainVersion: { - type: Boolean, - default: false, - }, options: { type: Object, required: true, @@ -179,7 +175,7 @@ export default { return !!this.user.email && !this.isFavorite; }, showVersions() { - return !this.sustainVersion && this.versions && this.versions.length > 1; + return this.versions && this.versions.length > 1; }, availableVersions() { const versions = this.versions.slice(); diff --git a/client/src/components/Workflow/Run/WorkflowRunToolStep.vue b/client/src/components/Workflow/Run/WorkflowRunToolStep.vue index 09195fa97ac..1d5d7448120 100644 --- a/client/src/components/Workflow/Run/WorkflowRunToolStep.vue +++ b/client/src/components/Workflow/Run/WorkflowRunToolStep.vue @@ -1,8 +1,8 @@ From 8cc89b1159ddd72e8ad5760d818edcc05558ce73 Mon Sep 17 00:00:00 2001 From: guerler Date: Thu, 17 Jun 2021 03:42:53 -0400 Subject: [PATCH 009/218] Add corresponding icons to workflow steps --- .../src/components/Workflow/Run/WorkflowRunDefaultStep.vue | 6 +++++- client/src/components/Workflow/Run/WorkflowRunToolStep.vue | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/client/src/components/Workflow/Run/WorkflowRunDefaultStep.vue b/client/src/components/Workflow/Run/WorkflowRunDefaultStep.vue index 0d878cafb7a..224f0eaf702 100644 --- a/client/src/components/Workflow/Run/WorkflowRunDefaultStep.vue +++ b/client/src/components/Workflow/Run/WorkflowRunDefaultStep.vue @@ -1,6 +1,6 @@ From 9ebe3925fef802b44e8dee49c5b834859f30e6c2 Mon Sep 17 00:00:00 2001 From: guerler Date: Tue, 22 Jun 2021 20:33:24 -0400 Subject: [PATCH 017/218] Handle connected input change triggers to tool step states --- client/src/components/Form/FormDisplay.vue | 3 --- .../Workflow/Run/WorkflowRunForm.vue | 10 ++++++--- .../Workflow/Run/WorkflowRunToolStep.vue | 22 +++++++++++++++++-- .../src/components/Workflow/Run/services.js | 19 ++++++++++++++++ 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/client/src/components/Form/FormDisplay.vue b/client/src/components/Form/FormDisplay.vue index aa54f1d9d4e..bbb5b056cef 100644 --- a/client/src/components/Form/FormDisplay.vue +++ b/client/src/components/Form/FormDisplay.vue @@ -157,10 +157,7 @@ export default { new_value = { values: [] }; _.each(input.step_linked, (source_step) => { if (isDataStep(source_step)) { - console.log(this.replaceData); - console.log(source_step.index); var value = this.replaceData[source_step.index].input; - console.log(value); if (value) { _.each(value.values, (v) => { new_value.values.push(v); diff --git a/client/src/components/Workflow/Run/WorkflowRunForm.vue b/client/src/components/Workflow/Run/WorkflowRunForm.vue index c34516453c7..e3ec4a3a42f 100644 --- a/client/src/components/Workflow/Run/WorkflowRunForm.vue +++ b/client/src/components/Workflow/Run/WorkflowRunForm.vue @@ -26,9 +26,9 @@ :model="step" :replaceData="stepData" :replaceParams="wpData" - @onChange="onStepInputs" + @onChange="onToolStepInputs" /> - + @@ -131,8 +131,12 @@ export default { }, }, methods: { - onStepInputs(stepId, data) { + onDefaultStepInputs(stepId, data) { Vue.set(this.stepData, stepId, data); + this.stepData = Object.assign({}, this.stepData); + }, + onToolStepInputs(stepId, data) { + this.stepData[stepId] = data; }, onWpInputs(data) { this.wpData = data; diff --git a/client/src/components/Workflow/Run/WorkflowRunToolStep.vue b/client/src/components/Workflow/Run/WorkflowRunToolStep.vue index f294e5f83db..817d2a1967a 100644 --- a/client/src/components/Workflow/Run/WorkflowRunToolStep.vue +++ b/client/src/components/Workflow/Run/WorkflowRunToolStep.vue @@ -2,7 +2,13 @@
@@ -11,6 +17,7 @@ diff --git a/client/src/components/Workflow/Run/WorkflowRun.vue b/client/src/components/Workflow/Run/WorkflowRun.vue index c7d1ee31425..d90fb2242e9 100644 --- a/client/src/components/Workflow/Run/WorkflowRun.vue +++ b/client/src/components/Workflow/Run/WorkflowRun.vue @@ -7,11 +7,7 @@ - +
@@ -29,41 +25,15 @@ Workflow submission failed: {{ submissionError }}
- -
- Workflow: {{ workflowName }} - - -
- - +
@@ -71,7 +41,6 @@ diff --git a/client/src/components/Workflow/Run/WorkflowRunForm.vue b/client/src/components/Workflow/Run/WorkflowRunForm.vue index 0acdaaf1962..d58f78f4a3f 100644 --- a/client/src/components/Workflow/Run/WorkflowRunForm.vue +++ b/client/src/components/Workflow/Run/WorkflowRunForm.vue @@ -29,10 +29,18 @@ v-if="step.step_type == 'tool'" :model="step" :step-data="stepData" + :step-scroll-to="stepScrollTo" :wp-data="wpData" @onChange="onToolStepInputs" + @onValidation="onValidation" + /> + - @@ -62,6 +70,8 @@ export default { data() { return { stepData: {}, + stepValidation: {}, + stepScrollTo: {}, wpData: {}, historyData: {}, cacheData: {}, @@ -153,7 +163,20 @@ export default { onCacheInputs(data) { this.cacheData = data; }, + onValidation(stepId, validation) { + this.stepValidation[stepId] = validation; + }, onExecute() { + for (const [stepId, stepValidation] of Object.entries(this.stepValidation)) { + if (stepValidation) { + const validation = stepValidation.slice(); + this.stepScrollTo = { + stepId, + validation, + }; + return; + } + } const Galaxy = getGalaxyInstance(); const job_def = { new_history_name: this.historyData["new_history|name"] ? this.historyData["new_history|name"] : null, @@ -170,6 +193,7 @@ export default { // so that inputs can be batched. batch: true, }; + var validated = true; /*for (var i in this.forms) { var form = this.forms[i]; diff --git a/client/src/components/Workflow/Run/WorkflowRunToolStep.vue b/client/src/components/Workflow/Run/WorkflowRunToolStep.vue index 26fc1cc45af..1b023e50446 100644 --- a/client/src/components/Workflow/Run/WorkflowRunToolStep.vue +++ b/client/src/components/Workflow/Run/WorkflowRunToolStep.vue @@ -7,7 +7,9 @@ :inputs="model.inputs" :form-config="formConfig" :replace-params="replaceParams" + :validation-scroll-to="validationScrollTo" @onChange="onChange" + @onValidation="onValidation" /> @@ -41,6 +43,10 @@ export default { type: Object, default: null, }, + stepScrollTo: { + type: Object, + required: true, + }, }, data() { return { @@ -48,9 +54,15 @@ export default { formData: {}, replaceParams: {}, errorText: null, + validationScrollTo: [], }; }, watch: { + stepScrollTo() { + if (this.stepScrollTo && this.stepScrollTo.stepId == this.model.index) { + this.validationScrollTo = this.stepScrollTo.validation; + } + }, stepData() { this.onReplaceParams(); }, @@ -122,6 +134,9 @@ export default { this.formData = data; this.$emit("onChange", this.model.index, data); }, + onValidation(validation) { + this.$emit("onValidation", this.model.index, validation); + }, }, }; diff --git a/client/src/style/scss/ui.scss b/client/src/style/scss/ui.scss index a752574eb50..3099d91631e 100644 --- a/client/src/style/scss/ui.scss +++ b/client/src/style/scss/ui.scss @@ -195,6 +195,7 @@ $ui-margin-horizontal-large: $margin-v * 2; background: $portlet-bg-color; border-color: transparent; .portlet-title { + @extend .my-1; .portlet-title-text.collapsible { cursor: pointer; text-decoration: underline; From a63874b870d471cfd022555fbff06324e074ba79 Mon Sep 17 00:00:00 2001 From: guerler Date: Mon, 19 Jul 2021 16:02:33 -0400 Subject: [PATCH 035/218] Add workflow run submission protocol to workflow component --- .../Workflow/Run/WorkflowRunForm.vue | 133 ++++++------------ 1 file changed, 42 insertions(+), 91 deletions(-) diff --git a/client/src/components/Workflow/Run/WorkflowRunForm.vue b/client/src/components/Workflow/Run/WorkflowRunForm.vue index d58f78f4a3f..2ca72019c74 100644 --- a/client/src/components/Workflow/Run/WorkflowRunForm.vue +++ b/client/src/components/Workflow/Run/WorkflowRunForm.vue @@ -2,7 +2,13 @@
Workflow: {{ model.name }} - +