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

Allow adding workflow modes when creating a workflow

#63
parent 8868ee06
No related branches found
No related tags found
1 merge request!57Resolve "Support Workflow Modes"
......@@ -9,6 +9,7 @@ import { OpenAPI as S3ProxyOpenAPI } from "@/client/s3proxy";
import { OpenAPI as AuthOpenAPI } from "@/client/auth";
import { OpenAPI as WorkflowOpenAPI } from "@/client/workflow";
import CopyToClipboardIcon from "@/components/CopyToClipboardIcon.vue";
import dayjs from "dayjs";
const store = useAuthStore();
const { cookies } = useCookies();
......@@ -200,6 +201,7 @@ watch(
static-backdrop
modal-i-d="advancedUsageModal"
modal-label="Advanced Usage Modal"
v-if="store.authenticated"
>
<template v-slot:header>
<h3>Advanced Usage</h3>
......@@ -243,12 +245,17 @@ watch(
</tbody>
</table>
<div class="mt-4">
<label for="clowm-jwt" class="form-label">JWT for Services</label>
<label for="clowm-jwt" class="form-label"
>JWT for Services (expires at
{{
dayjs.unix(store.decodedToken!.exp).format("DD.MM.YYYY HH:mm")
}})</label
>
<div class="input-group">
<input
type="text"
readonly
class="form-control"
class="form-control text-truncate"
id="clowm-jwt"
:value="store.token"
aria-describedby="clowm-jwt-copy"
......
......@@ -119,6 +119,15 @@ onMounted(() => {
</div>
<div v-else>
<table class="table table-sm table-hover">
<thead>
<tr>
<th scope="col">Version</th>
<th scope="col">Status</th>
<th scope="col">Updated at</th>
<th scope="col" class="text-align-center">Icon</th>
<th scope="col">Link</th>
</tr>
</thead>
<tbody>
<tr
v-for="version in sortedVersions(props.workflow.versions)"
......@@ -141,7 +150,7 @@ onMounted(() => {
<td>
{{ dayjs.unix(version.created_at).format("D MMMM YYYY") }}
</td>
<td class="w-fit">
<td class="text-align-center">
<img
v-if="version.icon_url != null"
:src="version.icon_url"
......@@ -207,4 +216,7 @@ td > img {
.add-icon-hover:hover {
color: var(--bs-success) !important;
}
.text-align-center {
text-align: center;
}
</style>
......@@ -100,7 +100,7 @@ function checkRepository() {
: undefined,
);
repo
.checkFilesExist(requiredRepositoryFiles, true)
.checkFilesExist(requiredRepositoryFiles([]), true)
.then(() => {
formState.allowUpload = true;
})
......
<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from "vue";
import { Modal, Toast, Collapse, Tooltip } from "bootstrap";
import type { WorkflowIn, WorkflowOut } from "@/client/workflow";
import type {
WorkflowIn,
WorkflowOut,
WorkflowModeOut,
} from "@/client/workflow";
import BootstrapModal from "@/components/modals/BootstrapModal.vue";
import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue";
import { ApiError, WorkflowService } from "@/client/workflow";
......@@ -30,6 +34,7 @@ let createWorkflowModal: Modal | null = null;
let successToast: Toast | null = null;
let privateRepositoryCollapse: Collapse | null = null;
let tokenHelpCollapse: Collapse | null = null;
let workflowModesCollapse: Collapse | null = null;
// HTML Form Elements
// =============================================================================
......@@ -81,6 +86,21 @@ const repositoryCredentials = reactive<{
privateRepo: false,
});
const workflowModes = reactive<{
hasModes: boolean;
modes: WorkflowModeOut[];
}>({
hasModes: false,
modes: [
{
mode_id: crypto.randomUUID(),
name: "",
schema_path: "",
entrypoint: "",
},
],
});
// Computed Properties
// =============================================================================
const gitIcon = computed<string>(() =>
......@@ -99,6 +119,17 @@ watch(
},
);
watch(
() => workflowModes.hasModes,
(show) => {
if (show) {
workflowModesCollapse?.show();
} else {
workflowModesCollapse?.hide();
}
},
);
// Functions
// =============================================================================
function modalClosed() {
......@@ -130,6 +161,15 @@ function createWorkflow() {
) {
workflow.token = repositoryCredentials.token;
}
if (workflowModes.hasModes) {
workflow.modes = workflowModes.modes.map((mode) => {
return {
name: mode.name,
schema_path: mode.schema_path,
entrypoint: mode.entrypoint,
};
});
}
WorkflowService.workflowCreateWorkflow(workflow)
.then((w) => {
emit("workflow-created", w);
......@@ -164,18 +204,21 @@ function resetForm() {
workflow.git_commit_hash = "";
workflow.initial_version = undefined;
workflow.token = undefined;
workflow.modes = [];
workflowModes.modes = [
{
mode_id: crypto.randomUUID(),
name: "",
schema_path: "",
entrypoint: "",
},
];
workflowModes.hasModes = false;
repositoryCredentials.privateRepo = false;
repositoryCredentials.token = "";
privateRepositoryCollapse?.hide();
}
/**
* Watcher function for the file upload in the form.
*/
//function iconChanged() {
// workflow.icon = workflowIconInput.value?.files?.[0].slice();
//}
/**
* Check the workflow repository for the necessary files.
*/
......@@ -194,8 +237,11 @@ function checkRepository() {
? repositoryCredentials.token
: undefined,
);
const requiredFiles = requiredRepositoryFiles(
workflowModes.hasModes ? workflowModes.modes : [],
);
repo
.checkFilesExist(requiredRepositoryFiles, true)
.checkFilesExist(requiredFiles, true)
.then(() => {
formState.allowUpload = true;
})
......@@ -227,6 +273,27 @@ function checkVersionValidity() {
}
}
function addMode() {
if (workflowModes.modes.length < 11) {
workflowModes.modes.push({
mode_id: crypto.randomUUID(),
name: "",
schema_path: "",
entrypoint: "",
});
}
}
function removeMode(index: number) {
if (
workflowModes.modes.length > 1 &&
index > -1 &&
index < (workflowModes.modes.length ?? 0)
) {
workflowModes.modes.splice(index, 1);
}
}
// Lifecycle Events
// =============================================================================
onMounted(() => {
......@@ -238,6 +305,9 @@ onMounted(() => {
tokenHelpCollapse = new Collapse("#tokenHelpCollapse", {
toggle: false,
});
workflowModesCollapse = new Collapse("#workflowModesCollapse", {
toggle: false,
});
new Tooltip("#tooltip-version-" + randomIDSuffix);
new Tooltip("#tooltip-commit-" + randomIDSuffix);
new Tooltip("#tooltip-url-" + randomIDSuffix);
......@@ -430,7 +500,7 @@ onMounted(() => {
aria-controls="#privateRepositoryCollapse"
/>
<label class="form-check-label" for="privateRepositoryCheckbox">
Private Git Repository
Enable private Git Repository
</label>
</div>
<div class="collapse" id="privateRepositoryCollapse">
......@@ -490,6 +560,101 @@ onMounted(() => {
</div>
</div>
</div>
<div class="mb-3">
<div class="form-check fs-5">
<input
class="form-check-input"
type="checkbox"
v-model="workflowModes.hasModes"
id="workflowModesCheckbox"
@change="formState.allowUpload = false"
aria-controls="#workflowModesCollapse"
/>
<label class="form-check-label" for="workflowModesCheckbox">
Enable Workflow Modes
</label>
<button
v-if="workflowModes.hasModes"
class="btn btn-primary float-end"
@click="addMode"
:disabled="workflow.modes!.length >= 10"
>
Add Mode
</button>
</div>
</div>
<div class="collapse" id="workflowModesCollapse">
<TransitionGroup name="list" tag="div">
<div
v-for="(mode, index) in workflowModes.modes"
:key="mode.mode_id"
class="row mb-3"
>
<h6>
<font-awesome-icon
icon="fa-solid fa-minus"
class="text-danger me-1 fs-6 cursor-pointer"
@click="removeMode(index)"
/>
Mode {{ index + 1 }}
</h6>
<div class="col-6">
<label :for="'modeNameInput-' + index" class="form-label"
>Name</label
>
<div class="input-group">
<div class="input-group-text">
<font-awesome-icon icon="fa-solid fa-tag" />
</div>
<input
type="text"
class="form-control"
:id="'modeNameInput-' + index"
maxlength="128"
v-model="mode.name"
:required="workflowModes.hasModes"
/>
</div>
</div>
<div class="col-6 mb-2">
<label :for="'modeEntryInput-' + index" class="form-label"
>Entrypoint</label
>
<div class="input-group">
<div class="input-group-text">
<font-awesome-icon icon="fa-solid fa-tag" />
</div>
<input
type="text"
class="form-control"
:id="'modeEntryInput-' + index"
maxlength="128"
v-model="mode.entrypoint"
:required="workflowModes.hasModes"
/>
</div>
</div>
<label :for="'modeSchemaInput-' + index" class="form-label"
>Schema File</label
>
<div class="input-group">
<div class="input-group-text">
<font-awesome-icon icon="fa-solid fa-file-code" />
</div>
<input
type="text"
class="form-control"
:id="'modeSchemaInput-' + index"
maxlength="128"
pattern=".*\.json$"
v-model="mode.schema_path"
@change="formState.allowUpload = false"
:required="workflowModes.hasModes"
/>
</div>
</div>
</TransitionGroup>
</div>
</form>
</template>
<template v-slot:footer>
......@@ -533,4 +698,26 @@ onMounted(() => {
.hover-info:hover {
color: var(--bs-info) !important;
}
.list-move, /* apply transition to moving elements */
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from {
opacity: 0;
transform: translateX(50%);
}
.list-leave-to {
opacity: 0;
transform: translateX(-50%);
}
/* ensure leaving items are taken out of layout flow so that moving
animations can be calculated correctly. */
.list-leave-active {
position: absolute;
}
</style>
......@@ -149,7 +149,7 @@ function checkRepository() {
formState.workflowToken,
);
repo
.checkFilesExist(requiredRepositoryFiles, true)
.checkFilesExist(requiredRepositoryFiles([]), true)
.then(() => {
formState.allowUpload = true;
})
......
import axios from "axios";
import type { AxiosInstance, AxiosBasicCredentials } from "axios";
export const requiredRepositoryFiles = [
"main.nf",
"CHANGELOG.md",
"README.md",
"nextflow_schema.json",
"docs/usage.md",
"docs/output.md",
];
import type { WorkflowModeOut } from "@/client/workflow";
export function requiredRepositoryFiles(modes: WorkflowModeOut[]) {
const list = [
"main.nf",
"CHANGELOG.md",
"README.md",
"docs/usage.md",
"docs/output.md",
];
if (modes.length > 0) {
list.push(...modes.map((mode) => mode.schema_path));
} else {
list.push("nextflow_schema.json");
}
return list;
}
export function determineGitIcon(repo_url?: string): string {
let gitProvider = "git-alt";
......
import type { WorkflowVersion } from "@/client/workflow";
export function sortedVersions(versions: WorkflowVersion[]): WorkflowVersion[];
export function sortedVersions(
versions: WorkflowVersion[],
desc = true,
......@@ -15,10 +13,6 @@ export function sortedVersions(
return vs;
}
export function latestVersion(
versions: WorkflowVersion[],
): WorkflowVersion | undefined;
export function latestVersion(
versions: WorkflowVersion[],
): WorkflowVersion | undefined {
......
......@@ -46,7 +46,7 @@ function downloadVersionFiles(
workflowState.loading = true;
const repo = GitRepository.buildRepository(repository, commit_hash, token);
Promise.all(
requiredRepositoryFiles.map((file) =>
requiredRepositoryFiles([]).map((file) =>
repo.downloadFile(file).then((response) => {
if (file.includes("README")) {
workflowState.descriptionMarkdown = response.data;
......
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