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

Add page for starting a workflow

#39
parent 65e9b158
No related branches found
No related tags found
2 merge requests!84Remove development branch,!41Resolve "Create Start Workflow Execution Page"
...@@ -3,6 +3,10 @@ body { ...@@ -3,6 +3,10 @@ body {
background: #181818; background: #181818;
} }
.fs-0 {
font-size: 3.5rem;
}
.top-toast { .top-toast {
top: 4rem; top: 4rem;
} }
......
...@@ -14,7 +14,6 @@ import { Toast } from "bootstrap"; ...@@ -14,7 +14,6 @@ import { Toast } from "bootstrap";
const props = defineProps({ const props = defineProps({
schema: { schema: {
type: Object, type: Object,
required: true,
}, },
workflowVersionId: { workflowVersionId: {
type: String, type: String,
...@@ -66,7 +65,7 @@ const formState = reactive<{ ...@@ -66,7 +65,7 @@ const formState = reactive<{
// Computed Properties // Computed Properties
// ============================================================================= // =============================================================================
const parameterGroups = computed<Record<string, never>>( const parameterGroups = computed<Record<string, never>>(
() => props.schema["definitions"] () => props.schema?.["definitions"]
); );
// Create a list with the names of all parameter groups // Create a list with the names of all parameter groups
...@@ -94,7 +93,9 @@ const navParameterGroups = computed<ParameterGroup[]>(() => ...@@ -94,7 +93,9 @@ const navParameterGroups = computed<ParameterGroup[]>(() =>
watch( watch(
() => props.schema, () => props.schema,
(newValue) => { (newValue) => {
updateSchema(newValue); if (newValue) {
updateSchema(newValue);
}
} }
); );
...@@ -102,7 +103,7 @@ watch( ...@@ -102,7 +103,7 @@ watch(
// ============================================================================= // =============================================================================
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/ban-ts-comment, @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/ban-ts-comment, @typescript-eslint/no-unused-vars */
function updateSchema(schema: Record<string, any>) { function updateSchema(schema: Record<string, any>) {
validateSchema = schemaCompiler.compile(props.schema); validateSchema = schemaCompiler.compile(schema);
const b = Object.keys(schema["definitions"]).map((groupName) => [ const b = Object.keys(schema["definitions"]).map((groupName) => [
groupName, groupName,
Object.fromEntries( Object.fromEntries(
...@@ -122,6 +123,7 @@ function updateSchema(schema: Record<string, any>) { ...@@ -122,6 +123,7 @@ function updateSchema(schema: Record<string, any>) {
function startWorkflow() { function startWorkflow() {
formState.validated = true; formState.validated = true;
formState.errorType = undefined;
if (launchForm.value?.checkValidity()) { if (launchForm.value?.checkValidity()) {
const realInput = Object.values(formState.formInput).reduce((acc, val) => { const realInput = Object.values(formState.formInput).reduce((acc, val) => {
return { ...acc, ...val }; return { ...acc, ...val };
...@@ -132,7 +134,6 @@ function startWorkflow() { ...@@ -132,7 +134,6 @@ function startWorkflow() {
console.error(validateSchema.errors); console.error(validateSchema.errors);
errorToast?.show(); errorToast?.show();
} else { } else {
formState.errorType = undefined;
formState.loading = true; formState.loading = true;
WorkflowExecutionService.workflowExecutionStartWorkflow({ WorkflowExecutionService.workflowExecutionStartWorkflow({
workflow_version_id: props.workflowVersionId, workflow_version_id: props.workflowVersionId,
...@@ -154,13 +155,16 @@ function startWorkflow() { ...@@ -154,13 +155,16 @@ function startWorkflow() {
formState.loading = false; formState.loading = false;
}); });
} }
} else {
formState.errorType = "form";
errorToast?.show();
} }
} }
// Lifecycle Events // Lifecycle Events
// ============================================================================= // =============================================================================
onMounted(() => { onMounted(() => {
updateSchema(props.schema); if (props.schema) updateSchema(props.schema);
errorToast = new Toast("#workflowExecutionErrorToast"); errorToast = new Toast("#workflowExecutionErrorToast");
}); });
</script> </script>
...@@ -175,17 +179,22 @@ onMounted(() => { ...@@ -175,17 +179,22 @@ onMounted(() => {
data-bs-autohide="true" data-bs-autohide="true"
id="workflowExecutionErrorToast" id="workflowExecutionErrorToast"
> >
<div class="d-flex p-2"> <div class="d-flex p-2 justify-content-between align-items-center">
<div v-if="formState.errorType === 'limit'" class="toast-body"> <div class="toast-body">
You have too many active workflow executions to start a new one <template v-if="formState.errorType === 'limit'">
</div> You have too many active workflow executions to start a new one.
<div v-else> </template>
There was an error with starting the workflow execution. Look in the <template v-else-if="formState.errorType === 'form'">
console for more information Some inputs are not valid.
</template>
<template v-else>
There was an error with starting the workflow execution. Look in the
console for more information.
</template>
</div> </div>
<button <button
type="button" type="button"
class="btn-close btn-close-white m-auto" class="btn-close btn-close-white"
data-bs-dismiss="toast" data-bs-dismiss="toast"
aria-label="Close" aria-label="Close"
></button> ></button>
...@@ -194,10 +203,13 @@ onMounted(() => { ...@@ -194,10 +203,13 @@ onMounted(() => {
</div> </div>
<div class="row mb-5 align-items-start"> <div class="row mb-5 align-items-start">
<form <form
v-if="props.schema"
class="col-9" class="col-9"
id="launchWorkflowForm" id="launchWorkflowForm"
ref="launchForm" ref="launchForm"
:class="{ 'was-validated': formState.validated }" :class="{ 'was-validated': formState.validated }"
@submit.prevent="startWorkflow"
novalidate
> >
<div class="card bg-dark mb-3"> <div class="card bg-dark mb-3">
<h2 class="card-header" id="pipelineGeneralOptions"> <h2 class="card-header" id="pipelineGeneralOptions">
...@@ -218,7 +230,7 @@ onMounted(() => { ...@@ -218,7 +230,7 @@ onMounted(() => {
</span> </span>
<textarea <textarea
class="form-control" class="form-control"
rows="2" rows="1"
v-model="formState.pipelineNotes" v-model="formState.pipelineNotes"
/> />
</div> </div>
...@@ -260,6 +272,27 @@ onMounted(() => { ...@@ -260,6 +272,27 @@ onMounted(() => {
/> />
</template> </template>
</form> </form>
<!-- Loading card -->
<div v-else class="col-9">
<div class="card bg-dark mb-3">
<h2 class="card-header placeholder-glow">
<span class="placeholder col-6"></span>
</h2>
<div class="card-body">
<h5 class="card-title placeholder-glow">
<span class="placeholder col-5"> </span>
</h5>
<template v-for="n in 4" :key="n">
<div class="placeholder-glow fs-5">
<span class="placeholder w-100"> </span>
</div>
<div class="mb-3 placeholder-glow">
<span class="placeholder col-3"> </span>
</div>
</template>
</div>
</div>
</div>
<div <div
class="col-3 sticky-top bg-dark rounded-1 px-0" class="col-3 sticky-top bg-dark rounded-1 px-0"
style="top: 70px !important; max-height: calc(100vh - 150px)" style="top: 70px !important; max-height: calc(100vh - 150px)"
...@@ -268,9 +301,8 @@ onMounted(() => { ...@@ -268,9 +301,8 @@ onMounted(() => {
<button <button
type="submit" type="submit"
form="launchWorkflowForm" form="launchWorkflowForm"
@click.prevent="startWorkflow"
class="btn btn-success w-50 mx-2" class="btn btn-success w-50 mx-2"
:disabled="formState.loading" :disabled="formState.loading || !props.schema"
> >
<font-awesome-icon icon="fa-solid fa-rocket" class="me-2" /> <font-awesome-icon icon="fa-solid fa-rocket" class="me-2" />
Launch Launch
...@@ -286,7 +318,7 @@ onMounted(() => { ...@@ -286,7 +318,7 @@ onMounted(() => {
</router-link> </router-link>
</div> </div>
<nav class="h-100"> <nav class="h-100">
<nav class="nav"> <nav v-if="props.schema" class="nav">
<ul class="ps-0"> <ul class="ps-0">
<li class="nav-link"> <li class="nav-link">
<a href="#pipelineGeneralOptions" <a href="#pipelineGeneralOptions"
...@@ -309,6 +341,14 @@ onMounted(() => { ...@@ -309,6 +341,14 @@ onMounted(() => {
</li> </li>
</ul> </ul>
</nav> </nav>
<!-- Loading nav links -->
<div v-else class="placeholder-glow ps-3 pt-3">
<span
v-for="n in 5"
:key="n"
class="placeholder col-8 mt-2 mb-3"
></span>
</div>
</nav> </nav>
</div> </div>
</div> </div>
......
...@@ -46,7 +46,7 @@ const router = createRouter({ ...@@ -46,7 +46,7 @@ const router = createRouter({
meta: { requiresReviewerRole: true }, meta: { requiresReviewerRole: true },
}, },
{ {
path: "workflows/:workflowId/", path: "workflows/:workflowId",
name: "workflow", name: "workflow",
component: () => import("../views/workflows/WorkflowView.vue"), component: () => import("../views/workflows/WorkflowView.vue"),
props: true, props: true,
...@@ -62,6 +62,13 @@ const router = createRouter({ ...@@ -62,6 +62,13 @@ const router = createRouter({
activeTab: route.query.tab ?? "description", activeTab: route.query.tab ?? "description",
}), }),
}, },
{
path: "version/:versionId/start",
name: "workflow-start",
component: () =>
import("../views/workflows/StartWorkflowView.vue"),
props: true,
},
], ],
}, },
], ],
......
<script setup lang="ts">
import ParameterSchemaFormComponent from "@/components/parameter-schema/ParameterSchemaFormComponent.vue";
import type { WorkflowVersionFull } from "@/client/workflow";
import { WorkflowVersionService } from "@/client/workflow";
import axios from "axios";
import { onMounted, ref, reactive } from "vue";
import type { JSONSchemaType } from "ajv";
const props = defineProps<{
versionId: string;
workflowId: string;
}>();
const parameterSchema = ref(undefined);
const versionState = reactive<{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
parameterSchema?: JSONSchemaType<any>;
workflowVersion?: WorkflowVersionFull;
}>({
parameterSchema: undefined,
workflowVersion: undefined,
});
function downloadVersion() {
WorkflowVersionService.workflowVersionGetWorkflowVersion(
props.versionId,
props.workflowId
)
.then((version) => {
versionState.workflowVersion = version;
return version;
})
.then(downloadVersionFiles);
}
function downloadVersionFiles(version: WorkflowVersionFull) {
axios.get(version.parameter_schema_url).then((response) => {
parameterSchema.value = response.data;
});
}
onMounted(() => {
downloadVersion();
});
</script>
<template>
<parameter-schema-form-component
:workflow-version-id="versionId"
:schema="parameterSchema"
/>
</template>
<style scoped></style>
...@@ -57,7 +57,11 @@ watch( ...@@ -57,7 +57,11 @@ watch(
watch( watch(
() => workflowState.activeVersionId, () => workflowState.activeVersionId,
(newVersionId, oldVersionId) => { (newVersionId, oldVersionId) => {
if (newVersionId !== oldVersionId) { if (
newVersionId &&
newVersionId !== oldVersionId &&
route.name !== "workflow-start"
) {
router.push({ router.push({
name: "workflow-version", name: "workflow-version",
params: { versionId: newVersionId }, params: { versionId: newVersionId },
...@@ -101,7 +105,7 @@ function updateWorkflow(workflowId: string) { ...@@ -101,7 +105,7 @@ function updateWorkflow(workflowId: string) {
WorkflowService.workflowGetWorkflow(workflowId) WorkflowService.workflowGetWorkflow(workflowId)
.then((workflow) => { .then((workflow) => {
workflowState.workflow = workflow; workflowState.workflow = workflow;
if (!workflowState.initialOpen || route.params.versionId == null) { if (!workflowState.initialOpen || !route.params.versionId) {
workflowState.activeVersionId = workflowState.activeVersionId =
workflow.versions[workflow.versions.length - 1].git_commit_hash; workflow.versions[workflow.versions.length - 1].git_commit_hash;
} else { } else {
...@@ -145,7 +149,10 @@ onMounted(() => { ...@@ -145,7 +149,10 @@ onMounted(() => {
</div> </div>
<div v-else-if="workflowState.workflow != null"> <div v-else-if="workflowState.workflow != null">
<div class="d-flex justify-content-between align-items-center"> <div class="d-flex justify-content-between align-items-center">
<span class="fs-0 w-fit">{{ workflowState.workflow.name }}</span> <div class="fs-0 w-fit text-light">
{{ workflowState.workflow.name }}
<span v-if="activeVersionString">@{{ activeVersionString }}</span>
</div>
<a :href="workflowState.workflow.repository_url" target="_blank"> <a :href="workflowState.workflow.repository_url" target="_blank">
<img <img
v-if="activeVersionIcon != null" v-if="activeVersionIcon != null"
...@@ -155,70 +162,80 @@ onMounted(() => { ...@@ -155,70 +162,80 @@ onMounted(() => {
/></a> /></a>
</div> </div>
<p class="fs-4 mb-5 mt-3">{{ workflowState.workflow.short_description }}</p> <p class="fs-4 mb-5 mt-3">{{ workflowState.workflow.short_description }}</p>
<div <template v-if="route.name !== 'workflow-start'">
v-if="!versionLaunchable"
class="alert alert-warning w-fit mx-auto"
role="alert"
>
This version can not be used.
<router-link
v-if="latestVersion != null"
class="alert-link"
:to="{
name: 'workflow-version',
params: {
versionId: latestVersion.git_commit_hash,
},
query: { tab: route.query.tab },
}"
>Try the latest version {{ latestVersion?.version }}.</router-link
>
</div>
<div class="row align-items-center">
<a
role="button"
class="btn btn-success btn-lg w-fit mx-auto"
:class="{ disabled: !versionLaunchable }"
href="#"
>
<font-awesome-icon icon="fa-solid fa-rocket" class="me-2" />
<span class="align-middle">Launch {{ activeVersionString }}</span>
</a>
<div <div
v-if="latestVersion != null" v-if="!versionLaunchable"
class="input-group w-fit position-absolute end-0" class="alert alert-warning w-fit mx-auto"
role="alert"
> >
<span class="input-group-text px-2" id="workflow-version-wrapping" This version can not be used.
><font-awesome-icon icon="fa-solid fa-tags" class="text-secondary" <router-link
/></span> v-if="latestVersion != null"
<select class="alert-link"
class="form-select form-select-sm" :to="{
aria-label="Workflow version selection" name: 'workflow-version',
aria-describedby="workflow-version-wrapping" params: {
v-model="workflowState.activeVersionId" versionId: latestVersion.git_commit_hash,
},
query: { tab: route.query.tab },
}"
>Try the latest version {{ latestVersion?.version }}.</router-link
>
</div>
<div class="row align-items-center">
<router-link
role="button"
class="btn btn-success btn-lg w-fit mx-auto"
:class="{ disabled: !versionLaunchable }"
:to="{
name: 'workflow-start',
params: {
versionId: props.versionId,
workflowId: props.workflowId,
},
}"
>
<font-awesome-icon icon="fa-solid fa-rocket" class="me-2" />
<span class="align-middle">Launch {{ activeVersionString }}</span>
</router-link>
<div
v-if="latestVersion != null"
class="input-group w-fit position-absolute end-0"
> >
<option <span class="input-group-text px-2" id="workflow-version-wrapping"
v-for="version in sortedVersions(workflowState.workflow?.versions)" ><font-awesome-icon icon="fa-solid fa-tags" class="text-secondary"
:key="version.git_commit_hash" /></span>
:value="version.git_commit_hash" <select
class="form-select form-select-sm"
aria-label="Workflow version selection"
aria-describedby="workflow-version-wrapping"
v-model="workflowState.activeVersionId"
> >
{{ version.version }} <option
</option> v-for="version in sortedVersions(
</select> workflowState.workflow?.versions
)"
:key="version.git_commit_hash"
:value="version.git_commit_hash"
>
{{ version.version }}
</option>
</select>
</div>
</div> </div>
</div> <div class="row w-100 mb-4 mt-2 mx-0">
<div class="row w-100 mb-4 mt-2 mx-0"> <a
<a :href="workflowState.workflow.repository_url"
:href="workflowState.workflow.repository_url" target="_blank"
target="_blank" class="text-secondary text-decoration-none mx-auto w-fit p-0"
class="text-secondary text-decoration-none mx-auto w-fit p-0" >
> <font-awesome-icon :icon="gitIcon" class="me-1" />
<font-awesome-icon :icon="gitIcon" class="me-1" /> <span class="align-middle">
<span class="align-middle"> {{ workflowState.workflow.repository_url }}</span
{{ workflowState.workflow.repository_url }}</span ></a
></a >
> </div>
</div> </template>
</div> </div>
<router-view v-if="workflowState.loading || workflowState.workflow != null" /> <router-view v-if="workflowState.loading || workflowState.workflow != null" />
<div v-else class="text-center fs-1 mt-5"> <div v-else class="text-center fs-1 mt-5">
...@@ -235,10 +252,6 @@ onMounted(() => { ...@@ -235,10 +252,6 @@ onMounted(() => {
</template> </template>
<style scoped> <style scoped>
.fs-0 {
font-size: 4em;
}
.icon:hover { .icon:hover {
opacity: 0.8; opacity: 0.8;
} }
......
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