-
Daniel Göbel authoredDaniel Göbel authored
ParameterModal.vue 11.71 KiB
<script setup lang="ts">
import BootstrapModal from "@/components/modals/BootstrapModal.vue";
import { computed, onMounted, reactive, watch } from "vue";
import { useWorkflowExecutionStore } from "@/stores/workflowExecutions";
import type { RouteLocationRaw, RouteParamsRaw } from "vue-router";
import { Modal } from "bootstrap";
import { useRouter } from "vue-router";
import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue";
import type { WorkflowExecutionOut, WorkflowVersion } from "@/client/workflow";
import { useNameStore } from "@/stores/names";
import { useWorkflowStore } from "@/stores/workflows";
import { createDownloadUrl } from "@/utils/DownloadJson";
import type { WorkflowParameters } from "@/types/WorkflowParameters";
const nameRepository = useNameStore();
const executionRepository = useWorkflowExecutionStore();
const workflowRepository = useWorkflowStore();
const router = useRouter();
let parameterModal: Modal | null = null;
const props = defineProps<{
modalID: string;
executionId?: string;
}>();
const parameterState = reactive<{
loading: boolean;
error: boolean;
}>({
loading: false,
error: false,
});
const execution = computed<WorkflowExecutionOut | undefined>(() => {
if (props.executionId == undefined) {
return undefined;
}
return executionRepository.executionMapping[props.executionId] ?? undefined;
});
const workflowVersion = computed<WorkflowVersion | undefined>(
() =>
workflowRepository.versionMapping[
execution.value?.workflow_version_id ?? ""
],
);
watch(execution, (newVal, oldVal) => {
if (
newVal != undefined &&
newVal.execution_id != oldVal?.execution_id &&
newVal.workflow_id != undefined &&
workflowRepository.workflowMapping[newVal.workflow_id] == undefined
) {
workflowRepository.fetchWorkflow(newVal.workflow_id);
}
});
const logs_s3_path = computed<string | undefined>(
() => execution.value?.logs_s3_path ?? undefined,
);
const debug_s3_path = computed<string | undefined>(
() => execution.value?.debug_s3_path ?? undefined,
);
const provenance_s3_path = computed<string | undefined>(
() => execution.value?.provenance_s3_path ?? undefined,
);
const notes = computed<string | undefined>(
() => execution.value?.notes ?? undefined,
);
const parameters = computed<WorkflowParameters | undefined>(() => {
if (props.executionId == undefined) {
return undefined;
}
return executionRepository.parameters[props.executionId] ?? undefined;
});
const parameterDownloadUrl = computed<string | undefined>(() => {
if (parameters.value == undefined) {
return undefined;
}
return createDownloadUrl(
JSON.stringify(parameters.value, null, 2),
"application/json",
);
});
const workflowName = computed<string>(() => {
if (props.executionId) {
const execution = executionRepository.executionMapping[props.executionId];
if (
execution?.workflow_id != undefined &&
execution?.workflow_version_id != undefined
) {
return (
nameRepository.getName(execution.workflow_id) +
"@" +
nameRepository.getName(execution.workflow_version_id)
);
}
}
return "";
});
function fetchWorkflowExecutionParameters(executionId?: string) {
parameterState.error = false;
if (executionId != undefined) {
parameterState.loading = true;
executionRepository
.fetchExecutionParameters(executionId)
.catch(() => {
parameterState.error = true;
})
.finally(() => {
parameterState.loading = false;
});
}
}
function handleLinkClick(route: RouteLocationRaw) {
parameterModal?.hide();
router.push(route);
}
function saveParameters() {
if (parameters.value != undefined) {
executionRepository.pushTemporaryParameters(parameters.value, {
logs_s3_path: logs_s3_path?.value?.split("/")?.slice(0, -1)?.join("/"),
debug_s3_path: debug_s3_path?.value?.split("/")?.slice(0, -1)?.join("/"),
provenance_s3_path: provenance_s3_path?.value
?.split("/")
?.slice(0, -1)
?.join("/"),
notes: notes.value,
});
}
}
function getS3LinkParameters(s3String: string): RouteParamsRaw {
const pathComponents = s3String.slice(5).split("/");
const s3File =
pathComponents.length > 1 &&
pathComponents[pathComponents.length - 1].includes(".");
return {
bucketName: pathComponents[0],
subFolders: pathComponents.slice(1, s3File ? -1 : undefined),
};
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function isBucketLink(value: any): boolean {
if (typeof value === "string") {
return value.startsWith("s3://");
}
return false;
}
watch(
() => props.executionId,
(newId, oldId) => {
if (newId != oldId) {
fetchWorkflowExecutionParameters(newId);
}
},
);
onMounted(() => {
fetchWorkflowExecutionParameters(props.executionId);
parameterModal = Modal.getOrCreateInstance("#" + props.modalID);
});
</script>
<template>
<bootstrap-modal
:modalId="modalID"
:static-backdrop="false"
modal-label="Workflow Execution Parameters Modal"
size-modifier="lg"
>
<template v-slot:header
>Workflow Execution Parameters
<b>
{{ workflowName }}
</b>
</template>
<template v-slot:body>
<div v-if="parameterState.error" class="text-center fs-4 mt-5">
<font-awesome-icon
icon="fa-solid fa-magnifying-glass"
class="mb-3 fs-0"
style="color: var(--bs-secondary)"
/>
<p>
Workflow Execution <i>{{ props.executionId }}</i> not found
</p>
</div>
<table v-else-if="props.executionId" class="table table-hover">
<caption class="placeholder-glow">
<span v-if="parameterState.loading" class="placeholder col-1"></span>
<template v-else>
{{
Object.keys(executionRepository.parameters[props.executionId])
.length
}}
</template>
Parameters
</caption>
<tbody>
<template v-if="parameterState.loading">
<tr v-for="n in 6" :key="n">
<th scope="row" style="width: 20%" class="placeholder-glow">
<div class="placeholder col-12"></div>
</th>
<td class="placeholder-glow">
<div class="placeholder col-8"></div>
</td>
</tr>
</template>
<template v-else>
<tr
v-if="logs_s3_path"
:class="{
'border-bottom-thick': !debug_s3_path && !provenance_s3_path,
}"
>
<th scope="row" style="width: 10%" class="text-end">
<b>logs</b>
</th>
<td>
<router-link
:to="{
name: 'bucket',
params: getS3LinkParameters(logs_s3_path),
}"
@click.prevent="
handleLinkClick({
name: 'bucket',
params: getS3LinkParameters(logs_s3_path),
})
"
>{{ logs_s3_path }}
</router-link>
</td>
</tr>
<tr
v-if="provenance_s3_path"
:class="{
'border-bottom-thick': !debug_s3_path,
}"
>
<th scope="row" style="width: 10%" class="text-end">
<b>provenance</b>
</th>
<td>
<router-link
:to="{
name: 'bucket',
params: getS3LinkParameters(provenance_s3_path),
}"
@click.prevent="
handleLinkClick({
name: 'bucket',
params: getS3LinkParameters(provenance_s3_path),
})
"
>{{ provenance_s3_path }}
</router-link>
</td>
</tr>
<tr v-if="debug_s3_path" class="border-bottom-thick">
<th scope="row" style="width: 10%" class="text-end">
<b>debug</b>
</th>
<td>
<router-link
:to="{
name: 'bucket',
params: getS3LinkParameters(debug_s3_path),
}"
@click.prevent="
handleLinkClick({
name: 'bucket',
params: getS3LinkParameters(debug_s3_path),
})
"
>{{ debug_s3_path }}
</router-link>
</td>
</tr>
<tr v-for="(value, name) in parameters" :key="name">
<th scope="row" style="width: 10%" class="text-end">
<b>{{ name }}</b>
</th>
<td>
<router-link
v-if="typeof value === 'string' && isBucketLink(value)"
:to="{
name: 'bucket',
params: getS3LinkParameters(value),
}"
@click.prevent="
handleLinkClick({
name: 'bucket',
params: getS3LinkParameters(value),
})
"
>{{ value }}
</router-link>
<a
v-else-if="
typeof value === 'string' && value.startsWith('http')
"
:href="value"
target="_blank"
>{{ value }}
<font-awesome-icon
icon="fa-solid fa-arrow-up-right-from-square"
class="ms-1"
/>
</a>
<template v-else>{{ value }}</template>
</td>
</tr>
</template>
</tbody>
</table>
</template>
<template v-slot:footer>
<router-link
v-if="workflowVersion"
class="btn btn-primary"
:class="{
disabled: parameterState.loading || parameterState.error,
}"
@click.prevent="
saveParameters();
handleLinkClick({
name: 'workflow-start',
params: {
versionId: workflowVersion.workflow_version_id,
workflowId: workflowVersion.workflow_id,
},
query: {
workflowModeId: execution?.mode_id,
},
});
"
:to="{
name: 'workflow-start',
params: {
versionId: workflowVersion.workflow_version_id,
workflowId: workflowVersion.workflow_id,
},
query: {
workflowModeId: execution?.mode_id,
},
}"
>
Rerun Execution
<font-awesome-icon icon="fa-solid fa-arrow-rotate-right" class="me2" />
</router-link>
<a
class="btn btn-primary"
role="button"
:class="{
disabled: parameterState.loading || parameterState.error,
}"
:href="parameterDownloadUrl"
:download="
'parameters-' + props.executionId?.replace(/-/g, '') + '.json'
"
:tabindex="parameterState.loading || parameterState.error ? -1 : 0"
:aria-disabled="parameterState.loading || parameterState.error"
>
Download Parameters
<font-awesome-icon icon="fa-solid fa-download" class="ms-1" />
</a>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
Close
</button>
</template>
</bootstrap-modal>
</template>
<style scoped>
.border-bottom-thick {
border-bottom: 3px var(--bs-border-style) var(--bs-border-color) !important;
}
</style>