From 4cfe079eb300cd6f9e0f96aa3748e89d5e909133 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20G=C3=B6bel?= <dgoebel@techfak.uni-bielefeld.de>
Date: Wed, 20 Mar 2024 14:45:55 +0100
Subject: [PATCH] Allow switch between raw and parsed parameters

#105
---
 .../form-mode/ParameterFileInput.vue          | 27 +++++++---
 .../form-mode/ParameterGroupForm.vue          |  1 +
 .../form-mode/ParameterInput.vue              | 18 +++++--
 .../form-mode/ParameterResourceInput.vue      | 50 ++++++++++++++++---
 .../form-mode/ParameterStringInput.vue        | 24 ++++++++-
 .../workflows/WorkflowWithVersionsCard.vue    |  5 +-
 src/views/workflows/StartWorkflowView.vue     |  1 -
 7 files changed, 102 insertions(+), 24 deletions(-)

diff --git a/src/components/parameter-schema/form-mode/ParameterFileInput.vue b/src/components/parameter-schema/form-mode/ParameterFileInput.vue
index 260ff12..26f9508 100644
--- a/src/components/parameter-schema/form-mode/ParameterFileInput.vue
+++ b/src/components/parameter-schema/form-mode/ParameterFileInput.vue
@@ -5,7 +5,7 @@ import { useBucketStore } from "@/stores/buckets";
 import type { SizeModifierType } from "@/types/PropTypes";
 
 const model = defineModel<string | undefined>({ required: true });
-const s3Regex = /s3:\/\/(\S*)\/(\S*)/g;
+const s3Regex = /s3:\/\/([^\s/]*)(\/\S*)?/g;
 
 const props = defineProps({
   parameter: {
@@ -23,8 +23,13 @@ const props = defineProps({
     type: String as PropType<SizeModifierType>,
   },
   border: Boolean,
+  allowRaw: Boolean,
 });
 
+const emit = defineEmits<{
+  (e: "switch-to-raw"): void;
+}>();
+
 const s3ObjectRepository = useS3ObjectStore();
 const bucketRepository = useBucketStore();
 const randomIDSuffix = Math.random().toString(16).substring(2, 8);
@@ -45,7 +50,7 @@ const inputDynamicClass = computed<string[]>(() => {
   if (props.sizeModifier) {
     cssClasses.push(`form-control-${props.sizeModifier}`);
   }
-  if (!helpTextPresent.value) {
+  if (!helpTextPresent.value && !props.allowRaw) {
     cssClasses.push("rounded-end");
   }
   return cssClasses;
@@ -61,7 +66,7 @@ watch(model, (newVal, oldVal) => {
 });
 
 function parseModel(val?: string) {
-  if (val == undefined) {
+  if (val == undefined || val.length === 0) {
     s3Path.bucket = "";
     s3Path.key = undefined;
     return;
@@ -69,12 +74,14 @@ function parseModel(val?: string) {
   const match = s3Regex.exec(val ?? "");
   if (match) {
     s3Path.bucket = match[1];
-    s3Path.key = match[2];
+    s3Path.key = match[2]?.slice(1);
     if (bucketRepository.bucketMapping[s3Path.bucket] == undefined) {
-      console.log("Missing bucket");
+      // Missing bucket
+      emit("switch-to-raw");
     }
   } else {
-    console.log("Not S3 Path");
+    // Not S3 Path
+    emit("switch-to-raw");
   }
 }
 
@@ -186,6 +193,14 @@ onMounted(() => {
   <datalist :id="'keys-options-' + randomIDSuffix">
     <option v-for="obj in keyDataList" :value="obj" :key="obj" />
   </datalist>
+  <button
+    v-if="allowRaw"
+    type="button"
+    class="btn btn-outline-secondary"
+    @click="emit('switch-to-raw')"
+  >
+    Advanced
+  </button>
 </template>
 
 <style scoped></style>
diff --git a/src/components/parameter-schema/form-mode/ParameterGroupForm.vue b/src/components/parameter-schema/form-mode/ParameterGroupForm.vue
index aba2788..346aced 100644
--- a/src/components/parameter-schema/form-mode/ParameterGroupForm.vue
+++ b/src/components/parameter-schema/form-mode/ParameterGroupForm.vue
@@ -108,6 +108,7 @@ function parameterId(parameterName: string): string {
               :required="parameterRequired(parameterGroup, parameterName)"
               border
               :resource-parameter="resourceParameters?.includes(parameterName)"
+              :allow-raw="showOptional"
             />
             <span
               class="input-group-text cursor-pointer px-2 border border-secondary"
diff --git a/src/components/parameter-schema/form-mode/ParameterInput.vue b/src/components/parameter-schema/form-mode/ParameterInput.vue
index ca67de0..0f6d110 100644
--- a/src/components/parameter-schema/form-mode/ParameterInput.vue
+++ b/src/components/parameter-schema/form-mode/ParameterInput.vue
@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { computed } from "vue";
+import { computed, ref } from "vue";
 import ParameterResourceInput from "@/components/parameter-schema/form-mode/ParameterResourceInput.vue";
 import ParameterNumberInput from "@/components/parameter-schema/form-mode/ParameterNumberInput.vue";
 import ParameterBooleanInput from "@/components/parameter-schema/form-mode/ParameterBooleanInput.vue";
@@ -15,15 +15,17 @@ const model = defineModel<
   required: true,
 });
 
+const rawInput = ref<boolean>(false);
+
 const props = defineProps<{
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   parameter: Record<string, any>;
   required?: boolean;
   resourceParameter?: boolean;
-  advanced?: boolean;
   rawModel?: boolean;
   sizeModifier?: SizeModifierType;
   border?: boolean;
+  allowRaw?: boolean;
 }>();
 
 const parameterType = computed<string>(
@@ -56,22 +58,25 @@ const parameterType = computed<string>(
   />
   <template v-else>
     <parameter-resource-input
-      v-if="resourceParameter && !advanced"
+      v-if="resourceParameter && !rawInput"
       :parameter="parameter"
       :required="required"
       v-model="model as ResourcePath_Input"
       :rawModel="rawModel"
       :size-modifier="sizeModifier"
       :border="border"
+      @switch-to-raw="rawInput = true"
+      :allow-raw="allowRaw"
     />
     <parameter-file-input
-      v-else-if="parameter['format'] && !advanced"
-      class="border border-secondary"
+      v-else-if="parameter['format'] && !rawInput"
       :required="required"
       :parameter="parameter"
       v-model="model as string"
       :size-modifier="sizeModifier"
       :border="border"
+      @switch-to-raw="rawInput = true"
+      :allow-raw="allowRaw"
     />
     <parameter-string-input
       v-else
@@ -80,6 +85,9 @@ const parameterType = computed<string>(
       v-model="model as string"
       :size-modifier="sizeModifier"
       :border="border"
+      :resource-parameter="resourceParameter"
+      :allow-switch="allowRaw"
+      @disable-raw="rawInput = false"
     />
   </template>
 </template>
diff --git a/src/components/parameter-schema/form-mode/ParameterResourceInput.vue b/src/components/parameter-schema/form-mode/ParameterResourceInput.vue
index 8e4a5ca..8166de0 100644
--- a/src/components/parameter-schema/form-mode/ParameterResourceInput.vue
+++ b/src/components/parameter-schema/form-mode/ParameterResourceInput.vue
@@ -18,6 +18,11 @@ const props = defineProps<{
   rawModel?: boolean;
   sizeModifier?: SizeModifierType;
   border?: boolean;
+  allowRaw?: boolean;
+}>();
+
+const emit = defineEmits<{
+  (e: "switch-to-raw"): void;
 }>();
 
 const resourceRepository = useResourceStore();
@@ -29,6 +34,8 @@ const resource = reactive<ResourcePath_Input>({
   suffix: undefined,
 });
 
+const helpTextPresent = computed<boolean>(() => props.parameter["help_text"]);
+
 const baseDynamicClass = computed<string[]>(() =>
   props.border ? ["border", "border-secondary"] : [],
 );
@@ -45,6 +52,9 @@ const inputDynamicClass = computed<string[]>(() => {
   if (props.sizeModifier) {
     cssClasses.push(`form-control-${props.sizeModifier}`);
   }
+  if (!helpTextPresent.value && !props.allowRaw) {
+    cssClasses.push("rounded-end");
+  }
   return cssClasses;
 });
 
@@ -67,7 +77,7 @@ watch(model, (newVal, oldVal) => {
 });
 
 function parseModel(val?: string | ResourcePath_Input) {
-  if (val == undefined) {
+  if (val == undefined || (typeof val === "string" && val.length === 0)) {
     Object.assign(resource, {
       resource_id: "",
       resource_version_id: "",
@@ -76,9 +86,13 @@ function parseModel(val?: string | ResourcePath_Input) {
   } else if (typeof val === "string") {
     const match = resourceRegex.exec(val);
     if (match) {
-      resource.resource_id = hexToUUID(match[1]);
-      resource.suffix = match[3];
-      resource.resource_version_id =
+      const tempResource: ResourcePath_Input = {
+        resource_id: "",
+        resource_version_id: "",
+      };
+      tempResource.resource_id = hexToUUID(match[1]);
+      tempResource.suffix = match[3];
+      tempResource.resource_version_id =
         match[2].length === 32
           ? hexToUUID(match[2])
           : resourceRepository.getLatestVersion(resource.resource_id);
@@ -87,12 +101,26 @@ function parseModel(val?: string | ResourcePath_Input) {
         resourceRepository.versionMapping[resource.resource_version_id] ==
           undefined
       ) {
-        console.log("Missing resource");
+        // Missing resource
+        emit("switch-to-raw");
+        return;
       }
+      Object.assign(resource, tempResource);
     } else {
-      console.log("Not resource Path");
+      // Not resource path
+      emit("switch-to-raw");
     }
   } else {
+    if (
+      (val.resource_id.length > 0 &&
+        resourceRepository.resourceMapping[val.resource_id] == undefined) ||
+      (val.resource_version_id.length > 0 &&
+        resourceRepository.versionMapping[val.resource_version_id] == undefined)
+    ) {
+      // Missing resource
+      emit("switch-to-raw");
+      return;
+    }
     Object.assign(resource, JSON.parse(JSON.stringify(val)));
   }
 }
@@ -178,7 +206,7 @@ onMounted(() => {
   </select>
   <input
     type="text"
-    class="form-control rounded-end"
+    class="form-control"
     :class="inputDynamicClass"
     placeholder="/optional/path/in/resource/..."
     minlength="2"
@@ -196,6 +224,14 @@ onMounted(() => {
       :key="file"
     />
   </datalist>
+  <button
+    v-if="allowRaw"
+    type="button"
+    class="btn btn-outline-secondary"
+    @click="emit('switch-to-raw')"
+  >
+    Advanced
+  </button>
 </template>
 
 <style scoped></style>
diff --git a/src/components/parameter-schema/form-mode/ParameterStringInput.vue b/src/components/parameter-schema/form-mode/ParameterStringInput.vue
index 85e6eee..3871923 100644
--- a/src/components/parameter-schema/form-mode/ParameterStringInput.vue
+++ b/src/components/parameter-schema/form-mode/ParameterStringInput.vue
@@ -8,7 +8,7 @@ const props = defineProps({
     type: Object,
     required: true,
     validator(value: Record<string, never>) {
-      return "string" === value["type"] && value["enum"];
+      return "string" === value["type"];
     },
   },
   required: Boolean,
@@ -19,6 +19,18 @@ const props = defineProps({
     type: String as PropType<SizeModifierType>,
   },
   border: Boolean,
+  resourceParameter: Boolean,
+  allowSwitch: Boolean,
+});
+
+const emit = defineEmits<{
+  (e: "disable-raw"): void;
+}>();
+
+const fileOrResource = computed<boolean>(() => {
+  return (
+    (props.resourceParameter || props.parameter["format"]) && props.allowSwitch
+  );
 });
 
 const dynamicCssClass = computed<string[]>(() => {
@@ -26,7 +38,7 @@ const dynamicCssClass = computed<string[]>(() => {
   if (props.sizeModifier) {
     cssClasses.push(`form-control-${props.sizeModifier}`);
   }
-  if (!helpTextPresent.value) {
+  if (!helpTextPresent.value && !fileOrResource.value) {
     cssClasses.push("rounded-end");
   }
   if (props.border) {
@@ -51,6 +63,14 @@ const helpTextPresent = computed<boolean>(() => props.parameter["help_text"]);
     :aria-describedby="props.helpId"
     :pattern="pattern"
   />
+  <button
+    v-if="fileOrResource"
+    type="button"
+    class="btn btn-secondary"
+    @click="emit('disable-raw')"
+  >
+    Advanced
+  </button>
 </template>
 
 <style scoped></style>
diff --git a/src/components/workflows/WorkflowWithVersionsCard.vue b/src/components/workflows/WorkflowWithVersionsCard.vue
index 1e8e19e..d69290f 100644
--- a/src/components/workflows/WorkflowWithVersionsCard.vue
+++ b/src/components/workflows/WorkflowWithVersionsCard.vue
@@ -128,7 +128,7 @@ onMounted(() => {
         <div class="btn-group">
           <button
             type="button"
-            class="btn btn-success"
+            class="btn btn-outline-success"
             :class="{ disabled: props.loading }"
             @click="emit('workflow-update-click', props.workflow)"
             data-bs-toggle="modal"
@@ -138,8 +138,7 @@ onMounted(() => {
           </button>
           <button
             type="button"
-            class="btn btn-success dropdown-toggle dropdown-toggle-split"
-            style="filter: brightness(85%)"
+            class="btn btn-outline-success dropdown-toggle dropdown-toggle-split"
             data-bs-toggle="dropdown"
             aria-expanded="false"
           >
diff --git a/src/views/workflows/StartWorkflowView.vue b/src/views/workflows/StartWorkflowView.vue
index e6fde26..99f1f6d 100644
--- a/src/views/workflows/StartWorkflowView.vue
+++ b/src/views/workflows/StartWorkflowView.vue
@@ -128,7 +128,6 @@ onMounted(() => {
     </template>
   </bootstrap-toast>
   <parameter-schema-form-component
-    :workflow-version-id="versionId"
     :schema="versionState.parameterSchema"
     :loading="versionState.loading"
     allow-notes
-- 
GitLab