You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by ma...@apache.org on 2020/06/12 19:51:18 UTC

[airavata-django-portal] 05/05: AIRAVATA-3285 Validation for interactive parameters

This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch AIRAVATA-3285--Interactive-output-view-providers
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit f6a8865cabcdb8d2be998486df484af5071ce49c
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri Jun 12 15:50:43 2020 -0400

    AIRAVATA-3285 Validation for interactive parameters
---
 .../output-displays/OutputDisplayContainer.vue     | 11 +++
 .../InteractiveParameterStepperWidget.vue          | 22 +++++-
 .../InteractiveParameterWidgetContainer.vue        |  2 +
 .../InteractiveParametersPanel.vue                 | 51 ++++++++++----
 .../static/common/js/components/ValidatedForm.vue  | 82 ++++++++++++++++++++++
 .../common/js/components/ValidatedFormGroup.vue    | 43 ++++++++++++
 django_airavata/static/common/js/index.js          |  6 +-
 7 files changed, 200 insertions(+), 17 deletions(-)

diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/OutputDisplayContainer.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/OutputDisplayContainer.vue
index 4cd9384..b7d42f3 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/OutputDisplayContainer.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/OutputDisplayContainer.vue
@@ -19,6 +19,7 @@
       :experiment-output="experimentOutput"
     />
     <interactive-parameters-panel
+      ref="interactiveParametersPanel"
       v-if="viewData && viewData.interactive"
       :parameters="viewData.interactive"
       @input="parametersUpdated"
@@ -134,6 +135,9 @@ export default {
     },
     providerId() {
       return this.currentView["provider-id"];
+    },
+    hasInteractiveParameters() {
+      return this.viewData && this.viewData.interactive;
     }
   },
   methods: {
@@ -147,6 +151,13 @@ export default {
       }
     },
     parametersUpdated(newParams) {
+      if (
+        this.hasInteractiveParameters &&
+        !this.$refs.interactiveParametersPanel.valid
+      ) {
+        // Don't update if we have invalid interactive parameters
+        return;
+      }
       this.loader.load(newParams);
     },
     createLoader() {
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/interactive-parameters/InteractiveParameterStepperWidget.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/interactive-parameters/InteractiveParameterStepperWidget.vue
index a048f1e..d136124 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/interactive-parameters/InteractiveParameterStepperWidget.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/interactive-parameters/InteractiveParameterStepperWidget.vue
@@ -32,20 +32,36 @@ export default {
   },
   data() {
     return {
-      currentValue: parseFloat(this.value)
+      currentValue: parseFloat(this.value),
+      valid: false
     };
   },
   computed: {
     disabled() {
-      return this.currentValue === parseFloat(this.value);
+      return !this.valid || this.currentValue === parseFloat(this.value);
     }
   },
   methods: {
     updateValue(newValue) {
+      if ("max" in this.parameter) {
+        newValue = Math.min(this.parameter.max, newValue);
+      }
+      if ("min" in this.parameter) {
+        newValue = Math.max(this.parameter.min, newValue);
+      }
       this.currentValue = parseFloat(newValue);
+      if (this.$refs.textInput.validity.valid) {
+        this.valid = true;
+        this.$emit("valid");
+      } else {
+        this.valid = false;
+        this.$emit("invalid", this.$refs.textInput.validationMessage);
+      }
     },
     submit() {
-      this.$emit("input", this.currentValue);
+      if (!this.disabled) {
+        this.$emit("input", this.currentValue);
+      }
     },
     enterKeyPressed() {
       if (!this.disabled) {
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/interactive-parameters/InteractiveParameterWidgetContainer.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/interactive-parameters/InteractiveParameterWidgetContainer.vue
index c9d21c1..33ada99 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/interactive-parameters/InteractiveParameterWidgetContainer.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/interactive-parameters/InteractiveParameterWidgetContainer.vue
@@ -4,6 +4,8 @@
     :value="parameter.value"
     :parameter="parameter"
     @input="$emit('input', $event)"
+    @valid="$emit('valid')"
+    @invalid="$emit('invalid', $event)"
   />
 </template>
 
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/interactive-parameters/InteractiveParametersPanel.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/interactive-parameters/InteractiveParametersPanel.vue
index aa2018d..6ad640d 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/interactive-parameters/InteractiveParametersPanel.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/interactive-parameters/InteractiveParametersPanel.vue
@@ -1,24 +1,26 @@
 <template>
   <b-card title="Parameters">
-    <b-form-group
-      v-for="param in parameters"
-      :key="param.name"
-      :label="param.name"
-    >
+    <validated-form ref="validatedForm" :items="formItems">
       <interactive-parameter-widget-container
-        :parameter="param"
-        @input="updated(param, $event)"/>
-    </b-form-group>
+        slot-scope="form"
+        :parameter="form.item"
+        @valid="form.valid"
+        @invalid="form.invalid"
+        @input="updated(form.item, $event)"
+      />
+    </validated-form>
   </b-card>
 </template>
 
 <script>
 import InteractiveParameterWidgetContainer from "./InteractiveParameterWidgetContainer";
+import { components } from "django-airavata-common-ui";
 
 export default {
   name: "interactive-parameters-panel",
   components: {
-    InteractiveParameterWidgetContainer
+    InteractiveParameterWidgetContainer,
+    "validated-form": components.ValidatedForm
   },
   props: {
     parameters: {
@@ -26,16 +28,39 @@ export default {
       required: true
     }
   },
+  computed: {
+    formItems() {
+      return this.localParameters.map(p => {
+        return {
+          key: p.name,
+          label: p.name,
+          item: p
+        };
+      });
+    },
+    valid() {
+      return this.$refs.validatedForm.valid;
+    }
+  },
+  data() {
+    return {
+      localParameters: this.parametersCopy()
+    }
+  },
   methods: {
     updated(param, value) {
-      const params = this.parametersCopy();
-      const i = params.findIndex(x => x.name === param.name);
-      params[i].value = value;
-      this.$emit("input", params);
+      const i = this.localParameters.findIndex(x => x.name === param.name);
+      this.localParameters[i].value = value;
+      this.$emit("input", this.localParameters);
     },
     parametersCopy() {
       return JSON.parse(JSON.stringify(this.parameters));
     }
+  },
+  watch: {
+    parameters() {
+      this.localParameters = this.parametersCopy();
+    }
   }
 };
 </script>
diff --git a/django_airavata/static/common/js/components/ValidatedForm.vue b/django_airavata/static/common/js/components/ValidatedForm.vue
new file mode 100644
index 0000000..a9d7608
--- /dev/null
+++ b/django_airavata/static/common/js/components/ValidatedForm.vue
@@ -0,0 +1,82 @@
+<template>
+  <b-form>
+    <template v-for="item in items">
+      <validated-form-group
+        :label="item.label"
+        :key="item.key"
+        :valid="isValid(item.key)"
+        :feedback-messages="getFeedbackMessages(item.key)"
+      >
+        <slot
+          :item="item.item"
+          :valid="() => setValid(item.key)"
+          :invalid="messages => setInvalid(item.key, messages)"
+        />
+      </validated-form-group>
+    </template>
+  </b-form>
+</template>
+
+<script>
+import ValidatedFormGroup from "./ValidatedFormGroup";
+
+export default {
+  name: "validated-form",
+  props: {
+    items: {
+      type: Array,
+      required: true
+    }
+  },
+  components: {
+    ValidatedFormGroup
+  },
+  data() {
+    return {
+      invalidFormItems: [],
+      feedbackMessages: {}
+    };
+  },
+  computed: {
+    valid() {
+      return this.invalidFormItems.length === 0;
+    }
+  },
+  methods: {
+    setValid(key) {
+      const wasValid = this.valid;
+      if (this.invalidFormItems.includes(key)) {
+        const index = this.invalidFormItems.indexOf(key);
+        this.invalidFormItems.splice(index, 1);
+      }
+      if (!wasValid && this.valid) {
+        this.$emit('valid');
+      }
+    },
+    setInvalid(key, messages) {
+      const wasValid = this.valid;
+      if (!this.invalidFormItems.includes(key)) {
+        this.invalidFormItems.push(key);
+      }
+      if (typeof messages === "string") {
+        this.feedbackMessages[key] = [messages];
+      } else {
+        this.feedbackMessages[key] = messages;
+      }
+      if (wasValid) {
+        this.$emit('invalid');
+      }
+    },
+    isValid(key) {
+      return !this.invalidFormItems.includes(key);
+    },
+    getFeedbackMessages(key) {
+      if (key in this.feedbackMessages) {
+        return this.feedbackMessages[key];
+      } else {
+        return [];
+      }
+    }
+  }
+};
+</script>
diff --git a/django_airavata/static/common/js/components/ValidatedFormGroup.vue b/django_airavata/static/common/js/components/ValidatedFormGroup.vue
new file mode 100644
index 0000000..6cb2861
--- /dev/null
+++ b/django_airavata/static/common/js/components/ValidatedFormGroup.vue
@@ -0,0 +1,43 @@
+<template>
+  <b-form-group :label="label" :state="state" :description="description">
+    <slot></slot>
+    <template slot="invalid-feedback">
+      <ul v-if="feedbackMessages && feedbackMessages.length > 1">
+        <li v-for="feedback in feedbackMessages" :key="feedback">
+          {{ feedback }}
+        </li>
+      </ul>
+      <div v-else-if="feedbackMessages && feedbackMessages.length === 1">
+        {{ feedbackMessages[0] }}
+      </div>
+    </template>
+  </b-form-group>
+</template>
+
+<script>
+export default {
+  name: "validated-form-group",
+  props: {
+    label: {
+      type: String,
+      required: true
+    },
+    description: {
+      type: String
+    },
+    valid: {
+      type: Boolean,
+      required: true
+    },
+    feedbackMessages: {
+      type: Array,
+      required: true
+    }
+  },
+  computed: {
+    state() {
+      return this.valid ? null : "invalid";
+    }
+  }
+};
+</script>
diff --git a/django_airavata/static/common/js/index.js b/django_airavata/static/common/js/index.js
index 25b3519..87ae172 100644
--- a/django_airavata/static/common/js/index.js
+++ b/django_airavata/static/common/js/index.js
@@ -20,6 +20,8 @@ import SidebarFeed from "./components/SidebarFeed.vue";
 import SidebarHeader from "./components/SidebarHeader.vue";
 import UnsavedChangesGuard from "./components/UnsavedChangesGuard.vue";
 import Uppy from "./components/Uppy";
+import ValidatedForm from "./components/ValidatedForm";
+import ValidatedFormGroup from "./components/ValidatedFormGroup";
 
 import GlobalErrorHandler from "./errors/GlobalErrorHandler";
 import ValidationErrors from "./errors/ValidationErrors";
@@ -57,7 +59,9 @@ const components = {
   SidebarFeed,
   SidebarHeader,
   UnsavedChangesGuard,
-  Uppy
+  Uppy,
+  ValidatedForm,
+  ValidatedFormGroup,
 };
 
 const errors = {