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

Add button to delete a parameter extension

parent 32eaac16
No related branches found
No related tags found
1 merge request!102Resolve "Add UI for parameter translation layer"
Pipeline #49342 failed
<script setup lang="ts">
import { onBeforeMount } from "vue";
import { onBeforeMount, onMounted } from "vue";
import { useCookies } from "vue3-cookies";
import { useAuthStore } from "@/stores/users";
import { useRoute, useRouter } from "vue-router";
......@@ -12,12 +12,20 @@ import axios from "axios";
import { useNameStore } from "@/stores/names";
import AppHeader from "@/components/AppHeader.vue";
import AppFooter from "@/components/AppFooter.vue";
import { useResourceStore } from "@/stores/resources";
import { useWorkflowStore } from "@/stores/workflows";
import { useBucketStore } from "@/stores/buckets";
import {useS3KeyStore} from "@/stores/s3keys";
const { cookies } = useCookies();
const store = useAuthStore();
const router = useRouter();
const route = useRoute();
const userRepository = useAuthStore();
const nameRepository = useNameStore();
const resourceRepository = useResourceStore();
const workflowRepository = useWorkflowStore();
const bucketRepository = useBucketStore();
const s3KeyRepository = useS3KeyStore();
onBeforeMount(() => {
S3ProxyOpenAPI.BASE = environment.S3PROXY_API_BASE_URL;
......@@ -31,7 +39,7 @@ onBeforeMount(() => {
(err.response.status === 400 || err.response.status === 403) &&
err.response.data.detail?.includes("JWT")
) {
store.logout();
userRepository.logout();
cookies.remove("bearer");
router.push({
name: "login",
......@@ -46,15 +54,15 @@ onBeforeMount(() => {
return Promise.reject(err);
},
);
store.setToken(cookies.get("bearer"));
userRepository.setToken(cookies.get("bearer"));
router.afterEach((to, from) => {
window._paq.push(["setReferrerUrl", from.path]);
window._paq.push(["deleteCustomVariables", "page"]);
window._paq.push(["deleteCustomDimension", 1]);
window._paq.push(["setCustomUrl", to.path]);
window._paq.push(["setDocumentTitle", to.name]);
if (store.currentUID.length > 0) {
window._paq.push(["setUserId", store.currentUID]);
if (userRepository.currentUID.length > 0) {
window._paq.push(["setUserId", userRepository.currentUID]);
}
window._paq.push(["trackPageView"]);
window._paq.push(["enableLinkTracking"]);
......@@ -62,7 +70,7 @@ onBeforeMount(() => {
router.beforeEach(async (to) => {
// make sure the user is authenticated
if (
!store.authenticated &&
!userRepository.authenticated &&
// ❗️ Avoid an infinite redirect
to.name !== "login"
) {
......@@ -73,20 +81,20 @@ onBeforeMount(() => {
};
} else if (
to.meta.requiresDeveloperRole &&
!(store.workflowDev || store.admin)
!(userRepository.workflowDev || userRepository.admin)
) {
return { name: "dashboard" };
} else if (
to.meta.requiresReviewerRole &&
!(store.rewiewer || store.admin)
!(userRepository.rewiewer || userRepository.admin)
) {
return { name: "dashboard" };
} else if (
to.meta.requiresMaintainerRole &&
!(store.resourceMaintainer || store.admin)
!(userRepository.resourceMaintainer || userRepository.admin)
) {
return { name: "dashboard" };
} else if (to.meta.adminRole && !store.admin) {
} else if (to.meta.adminRole && !userRepository.admin) {
return { name: "dashboard" };
} else if (to.name !== "login" && to.query.return_path) {
// return to original path after login
......@@ -95,6 +103,24 @@ onBeforeMount(() => {
});
nameRepository.loadNameMapping();
});
onMounted(() => {
if (userRepository.authenticated) {
resourceRepository.fetchPublicResources();
workflowRepository.fetchWorkflows();
bucketRepository.fetchBuckets();
s3KeyRepository.fetchS3Keys();
if (!userRepository.foreignUser) {
bucketRepository.fetchOwnPermissions();
}
if (userRepository.workflowDev || userRepository.admin) {
workflowRepository.fetchOwnWorkflows();
}
if (userRepository.resourceMaintainer || userRepository.admin) {
resourceRepository.fetchOwnResources();
}
}
});
</script>
<template>
......
......@@ -142,7 +142,7 @@ export class WorkflowVersionService {
});
}
/**
* Deprecate a workflow version
* Update parameter extension of workflow version
* Update the parameter extension of a workflow version.
*
*
......@@ -176,6 +176,37 @@ export class WorkflowVersionService {
},
});
}
/**
* Delete parameter extension of workflow version
* Delete the parameter extension of a workflow version.
*
*
* Permission `workflow:update` required.
* @param wid ID of a workflow
* @param gitCommitHash Git commit git_commit_hash of specific version.
* @returns void
* @throws ApiError
*/
public static workflowVersionDeleteWorkflowVersionParameterExtension(
wid: string,
gitCommitHash: string,
): CancelablePromise<void> {
return __request(OpenAPI, {
method: 'DELETE',
url: '/workflows/{wid}/versions/{git_commit_hash}/parameter-extension',
path: {
'wid': wid,
'git_commit_hash': gitCommitHash,
},
errors: {
400: `Error decoding JWT Token`,
401: `Not authenticated`,
403: `Not authorized`,
404: `Entity not Found`,
422: `Validation Error`,
},
});
}
/**
* Fetch documentation for a workflow version
* Get the documentation for a specific workflow version.
......
......@@ -555,8 +555,7 @@ export const useWorkflowStore = defineStore({
(version) => version.workflow_version_id == version_id,
);
if (versionIndex1 > -1) {
this.workflowMapping[workflow_id].versions[versionIndex1].icon_url =
null;
this.workflowMapping[workflow_id].versions[versionIndex1] = version;
}
}
......@@ -572,11 +571,52 @@ export const useWorkflowStore = defineStore({
if (versionIndex2 > -1) {
this.comprehensiveWorkflowMapping[workflow_id].versions[
versionIndex2
].icon_url = null;
] = version;
}
}
return version;
});
},
deleteWorkflowExtension(
workflow_id: string,
version_id: string,
): Promise<void> {
return WorkflowVersionService.workflowVersionDeleteWorkflowVersionParameterExtension(
workflow_id,
version_id,
).then(() => {
// Update version in workflowMapping
if (this.workflowMapping[workflow_id] == undefined) {
this.fetchWorkflow(workflow_id, false);
} else {
const versionIndex1 = this.workflowMapping[
workflow_id
].versions.findIndex(
(version) => version.workflow_version_id == version_id,
);
if (versionIndex1 > -1) {
this.workflowMapping[workflow_id].versions[
versionIndex1
].parameter_extension = undefined;
}
}
// Update version in comprehensiveWorkflowMapping
if (this.comprehensiveWorkflowMapping[workflow_id] == undefined) {
this.fetchWorkflow(workflow_id, true);
} else {
const versionIndex2 = this.comprehensiveWorkflowMapping[
workflow_id
].versions.findIndex(
(version) => version.workflow_version_id == version_id,
);
if (versionIndex2 > -1) {
this.comprehensiveWorkflowMapping[workflow_id].versions[
versionIndex2
].parameter_extension = undefined;
}
}
});
},
},
});
......@@ -12,6 +12,7 @@ import { useResourceStore } from "@/stores/resources";
import ParameterInput from "@/components/parameter-schema/form-mode/ParameterInput.vue";
import BootstrapToast from "@/components/BootstrapToast.vue";
import { Toast } from "bootstrap";
import DeleteModal from "@/components/modals/DeleteModal.vue";
// Props
// =============================================================================
......@@ -24,6 +25,7 @@ const props = defineProps<{
// =============================================================================
const parameterExtensionForm = ref<HTMLFormElement | null>(null);
let successToast: Toast | null = null;
let deleteToast: Toast | null = null;
// Repositories
// =============================================================================
......@@ -35,6 +37,7 @@ const resourceRepository = useResourceStore();
// =============================================================================
const parameterState = reactive<{
loading: boolean;
makingRequest: boolean;
extension: ParameterExtension_Input;
resourceParametersDefault: Set<string>;
resourceParametersMapping: Set<string>;
......@@ -42,6 +45,7 @@ const parameterState = reactive<{
formValidated: boolean;
}>({
loading: true,
makingRequest: false,
extension: {},
resourceParametersDefault: new Set(),
resourceParametersMapping: new Set(),
......@@ -102,7 +106,11 @@ function updateParameterPools(newVal?: object) {
if (newVal) {
const parameters = extractParameterList(newVal);
parameterPools.defaults = parameters.slice();
parameterPools.mapping = parameters.slice();
parameterPools.mapping = parameters.filter(
(param) =>
parameterSchema.value?.[param]?.["type"] !== "boolean" &&
!parameterSchema.value?.[param]?.["enum"],
);
}
if (
workflowRepository.ownVersionMapping[props.versionId]?.parameter_extension
......@@ -137,6 +145,7 @@ function submitForm() {
}
parameterState.formValidated = true;
if (parameterExtensionForm.value?.checkValidity()) {
parameterState.makingRequest = true;
workflowRepository
.updateWorkflowExtension(
props.workflowId,
......@@ -145,6 +154,9 @@ function submitForm() {
)
.then(() => {
successToast?.show();
})
.finally(() => {
parameterState.makingRequest = false;
});
}
}
......@@ -222,6 +234,17 @@ function makeResourceParameterDefault(param: string) {
};
}
function makeResourceParameterMapping(param: string, val: string) {
if (parameterState.extension.mapping?.[param]?.[val]) {
parameterState.formValidated = false;
parameterState.resourceParametersMapping.add(param);
parameterState.extension.mapping[param][val] = {
resource_id: "",
resource_version_id: "",
};
}
}
function deleteDefaultParameter(param: string) {
if (
!workflowRepository.documentationFiles[
......@@ -284,14 +307,47 @@ function deleteMappingParameter(param: string) {
}
}
function deleteParameterExtension() {
parameterState.makingRequest = true;
workflowRepository
.deleteWorkflowExtension(props.workflowId, props.versionId)
.then(() => {
parameterState.extension = {};
updateParameterPools(
workflowRepository.documentationFiles[props.versionId]
?.parameter_schema,
);
updateResourceParameters(
workflowRepository.documentationFiles[props.versionId]?.clowm_info,
);
deleteToast?.show();
})
.finally(() => {
parameterState.makingRequest = false;
});
}
// Lifecycle Events
// =============================================================================
onMounted(() => {
successToast = new Toast("#save-parameter-extension-success");
successToast = new Toast("#save-parameter-extension-success-toast");
deleteToast = new Toast("#delete-parameter-extension-success-toast");
workflowRepository.fetchWorkflow(props.workflowId, true, () => {
parameterState.extension =
workflowRepository.ownVersionMapping[props.versionId]
?.parameter_extension ?? {};
for (const param of Object.keys(parameterState.extension?.mapping ?? {})) {
for (const paramOption of Object.keys(
parameterState.extension?.mapping?.[param] ?? {},
)) {
if (
typeof parameterState.extension?.mapping?.[param]?.[paramOption] ===
"object"
) {
parameterState.resourceParametersMapping.add(param);
}
}
}
workflowRepository
.fetchWorkflowDocumentation(
props.workflowId,
......@@ -318,13 +374,13 @@ onMounted(() => {
);
});
});
resourceRepository.fetchPublicResources();
});
</script>
<template>
<bootstrap-toast
toast-id="save-parameter-extension-success"
toast-id="save-parameter-extension-success-toast"
color-class="success"
>
<template #default>Successfully saved Parameter Extension</template>
......@@ -351,13 +407,43 @@ onMounted(() => {
</div>
</template>
</bootstrap-toast>
<div class="row border-bottom mb-4">
<h2 class="mb-2">
<bootstrap-toast
toast-id="delete-parameter-extension-success-toast"
color-class="success"
>
Successfully deleted Parameter Extension
</bootstrap-toast>
<delete-modal
v-if="
workflowRepository.ownVersionMapping[props.versionId]?.parameter_extension
"
modal-id="delete-parameter-extension-modal"
:object-name-delete="`parameter extension of ${nameRepository.getName(props.workflowId)}@${nameRepository.getName(props.versionId)}`"
@confirm-delete="deleteParameterExtension"
/>
<div class="d-flex justify-content-between border-bottom mb-4 pb-2">
<h2 class="w-fit">
Add parameter metadata to
{{ nameRepository.getName(props.workflowId) }}@{{
nameRepository.getName(props.versionId)
}}
</h2>
<div
v-if="
workflowRepository.ownVersionMapping[props.versionId]
?.parameter_extension
"
>
<button
type="button"
class="btn btn-danger"
data-bs-toggle="modal"
data-bs-target="#delete-parameter-extension-modal"
:disabled="parameterState.loading || parameterState.makingRequest"
>
Delete
</button>
</div>
</div>
<div v-if="parameterState.loading" class="d-flex justify-content-center">
<div class="spinner-border" role="status">
......@@ -446,7 +532,7 @@ onMounted(() => {
class="d-flex flex-wrap overflow-y-auto p-1 border rounded-top border-dashed"
style="max-height: 30vh"
>
<b class="ms-1 w-100">Workflow parameters:</b>
<b class="ms-1 w-100">Eligible workflow parameters:</b>
<template v-if="parameterPools.mapping.length > 0">
<div
class="w-fit border px-2 rounded cursor-pointer m-1 parameter-container"
......@@ -479,25 +565,27 @@ onMounted(() => {
Remove
</button>
</div>
<div class="d-flex mb-4">
<button
type="button"
class="btn btn-primary me-2"
:disabled="
parameterState.mappingParameterValues[param]?.length === 0
"
@click="
addMappingParameterValue(
param,
parameterState.mappingParameterValues[param],
)
"
>
Add
</button>
<div class="d-flex mb-5">
<div class="me-2">
<button
type="button"
class="btn btn-primary"
:disabled="
parameterState.mappingParameterValues[param]?.length === 0
"
@click="
addMappingParameterValue(
param,
parameterState.mappingParameterValues[param],
)
"
>
Add Option
</button>
</div>
<input
type="text"
class="form-control"
class="form-control flex-fill w-fit"
v-model="parameterState.mappingParameterValues[param]"
/>
</div>
......@@ -505,15 +593,21 @@ onMounted(() => {
<div
v-for="key in Object.keys(parameterState.extension.mapping[param])"
:key="key"
class="mb-2"
class="mb-5 position-relative"
>
<div class="p-0 d-flex justify-content-between mb-n1">
<code
class="p-1 rounded-top border-bottom-0 border border-secondary-subtle"
>{{ key }}</code
<code
class="p-1 position-absolute top-0 start-0 pt-0 rounded-top border-bottom-0 border border-secondary-subtle bla"
>{{ key }}</code
>
<div class="position-absolute top-0 end-0 bla">
<span
v-if="!parameterState.resourceParametersMapping.has(param)"
class="p-1 me-2 rounded-top border-bottom-0 border pseudo-primary-btn border-primary-subtle cursor-pointer"
@click="makeResourceParameterMapping(param, key)"
>Resource</span
>
<span
class="px-1 pb-1 rounded-top border-bottom-0 border pseudo-danger-btn border-danger-subtle cursor-pointer"
class="p-1 rounded-top border-bottom-0 border pseudo-danger-btn border-danger-subtle cursor-pointer"
@click="deleteMappingParameterValue(param, key)"
>Remove</span
>
......@@ -527,6 +621,9 @@ onMounted(() => {
size-modifier="sm"
border="secondary-subtle"
v-model="parameterState.extension.mapping[param][key]"
:resource-parameter="
parameterState.resourceParametersMapping.has(param)
"
/>
</div>
</div>
......@@ -541,6 +638,7 @@ onMounted(() => {
form="parameter-extension-form"
:disabled="
parameterState.loading ||
parameterState.makingRequest ||
Object.keys(parameterState.extension).length === 0
"
>
......@@ -565,7 +663,18 @@ onMounted(() => {
border-color: var(--bs-danger);
}
.mb-n1 {
margin-bottom: -0.25rem !important;
.pseudo-primary-btn {
color: var(--bs-primary);
background-color: var(--bs-white);
}
.pseudo-primary-btn:hover {
color: var(--bs-white);
background-color: var(--bs-primary);
border-color: var(--bs-primary);
}
.bla {
transform: translateY(-90%) !important;
}
</style>
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