Skip to content
Snippets Groups Projects
Verified Commit cf520d15 authored by Daniel Göbel's avatar Daniel Göbel
Browse files

Add help to select clowm resource when starting a workflow

#88
parent 42339a48
No related branches found
No related tags found
1 merge request!85Resolve "Add UI for viewing and managing Resources"
......@@ -9,9 +9,11 @@ import { Toast } from "bootstrap";
import { useBucketStore } from "@/stores/buckets";
import { useS3KeyStore } from "@/stores/s3keys";
import BootstrapToast from "@/components/BootstrapToast.vue";
import { useResourceStore } from "@/stores/resources";
const bucketRepository = useBucketStore();
const s3KeyRepository = useS3KeyStore();
const resourceRepository = useResourceStore();
const keyRepository = useS3KeyStore();
// Props
// =============================================================================
......@@ -181,7 +183,8 @@ onMounted(() => {
if (props.schema) updateSchema(props.schema);
bucketRepository.fetchBuckets();
bucketRepository.fetchOwnPermissions();
s3KeyRepository.fetchS3Keys();
keyRepository.fetchS3Keys();
resourceRepository.fetchPublicResources();
errorToast = new Toast("#workflowExecutionErrorToast");
});
</script>
......@@ -230,14 +233,11 @@ onMounted(() => {
<h5 class="card-title">
General Options about the pipeline execution
</h5>
<template v-if="props.allowNotes">
<div v-if="props.allowNotes">
<code class="bg-secondary-subtle p-2 rounded-top">--notes</code>
<div class="input-group">
<span class="input-group-text" id="pipelineNotes">
<font-awesome-icon
class="me-2"
icon="fa-solid fa-sticky-note"
/>
<code>--notes</code>
<font-awesome-icon icon="fa-solid fa-sticky-note" />
</span>
<textarea
class="form-control"
......@@ -248,60 +248,75 @@ onMounted(() => {
<label class="mb-3" for="pipelineNotes"
>Personal notes about the pipeline execution</label
>
</template>
<div class="input-group">
<span class="input-group-text" id="logsS3Path">
<font-awesome-icon class="me-2" icon="fa-solid fa-folder" />
<code>--logs_s3_path</code>
</span>
<parameter-string-input
parameter-name="logs_s3_path"
v-model="formState.logs_s3_path"
:parameter="{
format: 'directory-path',
type: 'string',
}"
/>
</div>
<label class="mb-3" for="logsS3Path">
Directory in bucket where to save Nextflow log and reports
</label>
<div class="input-group">
<span class="input-group-text" id="provenanceS3Path">
<font-awesome-icon class="me-2" icon="fa-solid fa-folder" />
<code>--provenance_s3_path</code>
</span>
<parameter-string-input
parameter-name="provenance_s3_path"
v-model="formState.provenance_s3_path"
:parameter="{
format: 'directory-path',
type: 'string',
}"
/>
<div>
<code class="bg-secondary-subtle p-2 rounded-top"
>--logs_s3_path</code
>
<div class="input-group">
<span class="input-group-text">
<font-awesome-icon icon="fa-solid fa-folder" />
</span>
<parameter-string-input
parameter-name="logs_s3_path"
v-model="formState.logs_s3_path"
:parameter="{
format: 'directory-path',
type: 'string',
}"
remove-advanced
/>
</div>
<label class="mb-3" for="logsS3Path">
Directory in bucket where to save Nextflow log and reports
</label>
</div>
<label class="mb-3" for="provenanceS3Path">
Directory in bucket where to save provenance information about the
workflow execution
</label>
<div class="input-group">
<span class="input-group-text" id="debugS3Path">
<font-awesome-icon class="me-2" icon="fa-solid fa-folder" />
<code>--debug_s3_path</code>
</span>
<parameter-string-input
parameter-name="debug_s3_path"
v-model="formState.debug_s3_path"
:parameter="{
format: 'directory-path',
type: 'string',
}"
/>
<div>
<code class="bg-secondary-subtle p-2 rounded-top"
>--provenance_s3_path</code
>
<div class="input-group">
<span class="input-group-text">
<font-awesome-icon icon="fa-solid fa-folder" />
</span>
<parameter-string-input
parameter-name="provenance_s3_path"
v-model="formState.provenance_s3_path"
:parameter="{
format: 'directory-path',
type: 'string',
}"
remove-advanced
/>
</div>
<label class="mb-3" for="provenanceS3Path">
Directory in bucket where to save provenance information about the
workflow execution
</label>
</div>
<div>
<code class="bg-secondary-subtle p-2 rounded-top"
>--debug_s3_path</code
>
<div class="input-group">
<span class="input-group-text">
<font-awesome-icon icon="fa-solid fa-folder" />
</span>
<parameter-string-input
parameter-name="debug_s3_path"
v-model="formState.debug_s3_path"
:parameter="{
format: 'directory-path',
type: 'string',
}"
remove-advanced
/>
</div>
<label class="mb-3" for="debugS3Path">
Directory in bucket where to save debug information about the
workflow execution
</label>
</div>
<label class="mb-3" for="debugS3Path">
Directory in bucket where to save debug information about the
workflow execution
</label>
</div>
</div>
</form>
......
......@@ -86,14 +86,12 @@ watch(
:key="parameterName"
>
<div :hidden="!showHidden && parameter['hidden']">
<code class="bg-secondary-subtle p-2 rounded-top"
>--{{ parameter["name"] ?? parameterName }}</code
>
<div class="input-group">
<span class="input-group-text" :id="parameterName + '-help'">
<font-awesome-icon
class="me-2"
:icon="parameter['fa_icon']"
v-if="parameter['fa_icon']"
/>
<code>--{{ parameterName }}</code>
<span class="input-group-text" v-if="parameter['fa_icon']">
<font-awesome-icon :icon="parameter['fa_icon']" />
</span>
<parameter-number-input
v-if="
......@@ -145,9 +143,11 @@ watch(
class="input-group-text cursor-pointer px-2"
v-if="parameter['help_text']"
data-bs-toggle="collapse"
:data-bs-target="'#helpCollapse' + parameterName"
:data-bs-target="
'#helpCollapse' + parameterName.replace(/\./g, '')
"
aria-expanded="false"
:aria-controls="'helpCollapse' + parameterName"
:aria-controls="'helpCollapse' + parameterName.replace(/\./g, '')"
>
<font-awesome-icon
class="cursor-pointer"
......@@ -160,7 +160,7 @@ watch(
</label>
<div
class="collapse"
:id="'helpCollapse' + parameterName"
:id="'helpCollapse' + parameterName.replace(/\./g, '')"
v-if="parameter['help_text']"
>
<div class="p-2 pb-0 mx-2 mb-2 mt-1 flex-shrink-1 border rounded">
......
<script setup lang="ts">
import { computed, watch, ref, onMounted, reactive } from "vue";
import { computed, watch, onMounted, reactive } from "vue";
import { useBucketStore } from "@/stores/buckets";
import { useS3ObjectStore } from "@/stores/s3objects";
import { useResourceStore } from "@/stores/resources";
import { Status } from "@/client/resource";
const bucketRepository = useBucketStore();
const s3objectRepository = useS3ObjectStore();
const resourceRepository = useResourceStore();
const randomIDSuffix = Math.random().toString(16).substring(2, 8);
const props = defineProps({
parameter: {
......@@ -25,50 +29,49 @@ const props = defineProps({
helpId: {
type: String,
},
removeAdvanced: {
type: Boolean,
default: false,
},
});
const randomIDSuffix = Math.random().toString(16).substring(2, 8);
const defaultValue = computed<string>(() => props.parameter["default"]);
const emit = defineEmits<{
(e: "update:modelValue", value?: string): void;
}>();
const s3Path = reactive<{
bucket: string | undefined;
key: string | undefined;
bucket: string;
key?: string;
}>({
bucket: undefined,
bucket: "",
key: undefined,
});
watch(defaultValue, (newVal, oldVal) => {
if (newVal != oldVal && newVal != undefined) {
emit("update:modelValue", newVal);
}
const selectedResource = reactive<{
resourceId: string;
resourceVersionIndex: number;
}>({
resourceId: "",
resourceVersionIndex: 0,
});
watch(s3Path, () => {
if (format.value) {
updateValue();
}
const formState = reactive<{
advancedInput: boolean;
stringVal?: string;
}>({
advancedInput: false,
stringVal: undefined,
});
watch(
() => s3Path.bucket,
(newVal, oldVal) => {
if (newVal !== oldVal) {
updateKeysInBucket(newVal);
}
},
);
const defaultValue = computed<string>(() => props.parameter["default"]);
const pattern = computed<string>(() => props.parameter["pattern"]);
const emit = defineEmits<{
(e: "update:modelValue", value: string | undefined): void;
}>();
const stringInput = ref<HTMLInputElement | undefined>(undefined);
const format = computed<string | undefined>(() => props.parameter["format"]);
const dataFormat = computed<string | undefined>(
() => props.parameter["format"],
);
const dataPath = computed<boolean>(() => dataFormat.value != undefined);
const clowmResource = computed<boolean>(
() => props.parameter["clowm-resource"] ?? false,
);
const filesInBucket = computed<string[]>(() =>
(s3objectRepository.objectMapping[s3Path.bucket ?? ""] ?? [])
......@@ -98,7 +101,7 @@ const filesAndFoldersInBucket = computed<string[]>(() =>
);
const keyDataList = computed<string[]>(() => {
switch (format.value) {
switch (dataFormat.value) {
case "file-path":
return filesInBucket.value;
case "directory-path":
......@@ -110,23 +113,63 @@ const keyDataList = computed<string[]>(() => {
}
});
function updateValue() {
if (format.value) {
emit(
"update:modelValue",
!s3Path.bucket && s3Path.key
? undefined
: `s3://${s3Path.bucket}${s3Path.key ? "/" + s3Path.key : ""}`,
);
} else {
emit(
"update:modelValue",
stringInput.value?.value ? stringInput.value?.value : undefined,
);
watch(defaultValue, (newVal, oldVal) => {
if (newVal != oldVal && newVal != undefined) {
emit("update:modelValue", newVal);
}
});
watch(s3Path, () => {
if (dataPath.value && !formState.advancedInput) {
updateStringFromS3();
}
});
watch(
() => s3Path.bucket,
(newVal, oldVal) => {
if (newVal !== oldVal) {
updateKeysInBucket(newVal);
}
},
);
watch(() => formState.stringVal, updateValue);
watch(selectedResource, () => {
if (clowmResource.value && !formState.advancedInput) {
updateStringFromResource();
}
});
watch(
() => formState.advancedInput,
(newVal, oldValue) => {
if (newVal != oldValue) {
if (clowmResource.value) {
updateStringFromResource();
} else if (dataPath.value) {
updateStringFromS3();
}
}
},
);
function updateValue() {
emit("update:modelValue", formState.stringVal);
}
const helpTextPresent = computed<boolean>(() => props.parameter["help_text"]);
function updateStringFromS3() {
formState.stringVal = !s3Path.bucket
? undefined
: `s3://${s3Path.bucket}${s3Path.key ? "/" + s3Path.key : ""}`;
}
function updateStringFromResource() {
formState.stringVal =
resourceRepository.resourceMapping[selectedResource.resourceId]?.versions[
selectedResource.resourceVersionIndex
]?.cluster_path ?? undefined;
}
function updateKeysInBucket(bucketName?: string) {
if (bucketName != null) {
......@@ -138,14 +181,46 @@ function updateKeysInBucket(bucketName?: string) {
}
onMounted(() => {
if (format.value) {
s3Path.key = defaultValue.value;
}
formState.stringVal = defaultValue.value;
});
</script>
<template>
<template v-if="format">
<template v-if="clowmResource && !formState.advancedInput">
<select
class="form-select"
:required="props.required"
v-model="selectedResource.resourceId"
>
<option selected disabled value="">Please select a resource</option>
<option
v-for="resource in resourceRepository.resources"
:key="resource.resource_id"
:value="resource.resource_id"
>
{{ resource.name }}
</option>
</select>
<select
class="form-select"
:required="props.required || selectedResource.resourceId != undefined"
v-model="selectedResource.resourceVersionIndex"
:disabled="selectedResource.resourceId.length === 0"
>
<option disabled>Please select a version</option>
<option
v-for="(version, index) in resourceRepository.resourceMapping[
selectedResource.resourceId
]?.versions ?? []"
:key="version.resource_version_id"
:value="index"
>
{{ version.release }}
{{ version.status === Status.LATEST ? "- Latest" : "" }}
</option>
</select>
</template>
<template v-else-if="dataPath && !formState.advancedInput">
<select
class="form-select"
:required="props.required"
......@@ -162,10 +237,9 @@ onMounted(() => {
</select>
<input
class="form-control"
:class="{ 'rounded-end': !helpTextPresent }"
:list="'datalistOptions2' + randomIDSuffix"
placeholder="Type to search in bucket..."
:required="props.required && format === 'file-path'"
:required="props.required && dataFormat === 'file-path'"
v-model="s3Path.key"
:pattern="pattern"
/>
......@@ -173,17 +247,32 @@ onMounted(() => {
<option v-for="obj in keyDataList" :value="obj" :key="obj" />
</datalist>
</template>
<input
v-else
ref="stringInput"
class="form-control"
type="text"
:value="props.modelValue"
:required="props.required"
:aria-describedby="props.helpId"
:pattern="pattern"
@input="updateValue"
/>
<template v-else>
<input
ref="stringInput"
class="form-control"
type="text"
v-model="formState.stringVal"
:required="props.required"
:aria-describedby="props.helpId"
:pattern="pattern"
/>
</template>
<div v-if="(clowmResource || dataPath) && !props.removeAdvanced">
<input
type="checkbox"
class="btn-check"
:id="'flexCheckDefault' + randomIDSuffix"
autocomplete="off"
v-model="formState.advancedInput"
/>
<label
class="btn btn-outline-secondary rounded-end"
style="border-top-left-radius: 0; border-bottom-left-radius: 0"
:for="'flexCheckDefault' + randomIDSuffix"
>Advanced</label
>
</div>
</template>
<style scoped></style>
......@@ -158,7 +158,8 @@ onMounted(() => {
resourceVersion.resource_version_id
"
>
{{ resourceVersion.release }} - {{ stateToUIMapping[resourceVersion.status] }}
{{ resourceVersion.release }} -
{{ stateToUIMapping[resourceVersion.status] }}
</button>
</h2>
<div
......
......@@ -27,7 +27,9 @@ const resourceVersion = computed<ResourceVersionOut | undefined>(
>
<template #header
>Resource
<b v-if="resourceVersion">{{resource?.name}}@{{ resourceVersion?.release }}</b>
<b v-if="resourceVersion"
>{{ resource?.name }}@{{ resourceVersion?.release }}</b
>
</template>
<template #body>
<h5>Resource</h5>
......
......@@ -135,7 +135,7 @@ onMounted(() => {
chart = new Chart(canvas.value, {
type: "bar",
data: {
labels: [], // Days as lables
labels: [], // Days as labels
datasets: [
{
// Workflow count per day
......
......@@ -6,12 +6,10 @@ import {
type ResourceVersionOut,
Status,
} from "@/client/resource";
import CopyToClipboardIcon from "@/components/CopyToClipboardIcon.vue";
import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue";
import { Tooltip } from "bootstrap";
import ResourceVersionInfoModal from "@/components/resources/ResourceVersionInfoModal.vue";
import BucketDetailModal from "@/components/object-storage/modals/BucketDetailModal.vue";
import {useNameStore} from "@/stores/names";
import { useNameStore } from "@/stores/names";
const resourceRepository = useResourceStore();
const nameRepository = useNameStore();
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment