fix: avoid blocking creation of flow when edition is restricted to a namespace (#13694)

This commit is contained in:
Barthélémy Ledoux
2025-12-16 14:24:16 +01:00
committed by GitHub
parent 67ada7f61b
commit abcf76f7b4
7 changed files with 52 additions and 48 deletions

View File

@@ -20,6 +20,9 @@
import {useVueTour} from "../../composables/useVueTour";
import type {BlueprintType} from "../../stores/blueprints"
import {useAuthStore} from "../../override/stores/auth";
import permission from "../../models/permission";
import action from "../../models/action";
const route = useRoute();
const {t} = useI18n();
@@ -29,13 +32,21 @@
const blueprintsStore = useBlueprintsStore();
const coreStore = useCoreStore();
const flowStore = useFlowStore();
const authStore = useAuthStore();
const setupFlow = async () => {
const blueprintId = route.query.blueprintId as string;
const blueprintSource = route.query.blueprintSource as BlueprintType;
const implicitDefaultNamespace = authStore.user.getNamespacesForAction(
permission.FLOW,
action.CREATE,
)[0];
let flowYaml = "";
const id = getRandomID();
const selectedNamespace = (route.query.namespace as string) || defaultNamespace() || "company.team";
const selectedNamespace = (route.query.namespace as string)
?? defaultNamespace()
?? implicitDefaultNamespace
?? "company.team";
if (route.query.copy && flowStore.flow) {
flowYaml = flowStore.flow.source;

View File

@@ -1,7 +1,7 @@
<template>
<span ref="rootContainer">
<!-- Valid -->
<el-button v-if="!errors && !warnings &&!infos" v-bind="$attrs" :link="link" :size="size" type="default" class="success square" disabled>
<el-button v-if="!errors && !warnings && !infos" v-bind="$attrs" :link="link" :size="size" type="default" class="success square" disabled>
<CheckBoldIcon class="text-success" />
</el-button>
@@ -157,6 +157,7 @@
}
&.success {
cursor: default;
border-color: var(--ks-border-success);
}

View File

@@ -5,19 +5,19 @@
<ValidationError
class="validation"
tooltipPlacement="bottom-start"
:errors="flowErrors"
:errors="flowStore.flowErrors"
:warnings="flowWarnings"
:infos="flowInfos"
:infos="flowStore.flowInfos"
/>
<EditorButtons
:isCreating="flowStore.isCreating"
:isReadOnly="isReadOnly"
:isReadOnly="flowStore.isReadOnly"
:canDelete="true"
:isAllowedEdit="isAllowedEdit"
:isAllowedEdit="flowStore.isAllowedEdit"
:haveChange="haveChange"
:flowHaveTasks="Boolean(flowHaveTasks)"
:errors="flowErrors"
:flowHaveTasks="Boolean(flowStore.flowHaveTasks)"
:errors="flowStore.flowErrors"
:warnings="flowWarnings"
@save="save"
@copy="
@@ -49,7 +49,6 @@
import ValidationError from "../flows/ValidationError.vue";
import localUtils from "../../utils/utils";
import {useFlowOutdatedErrors} from "./flowOutdatedErrors";
import {useFlowStore} from "../../stores/flow";
import {useToast} from "../../utils/toast";
@@ -73,22 +72,14 @@
const route = useRoute()
const routeParams = computed(() => route.params)
const {translateError, translateErrorWithKey} = useFlowOutdatedErrors();
// If playground is not defined, enable it by default
const isSettingsPlaygroundEnabled = computed(() => localStorage.getItem("editorPlayground") === "false" ? false : true);
const isReadOnly = computed(() => flowStore.isReadOnly)
const isAllowedEdit = computed(() => flowStore.isAllowedEdit)
const flowHaveTasks = computed(() => flowStore.flowHaveTasks)
const flowErrors = computed(() => flowStore.flowErrors?.map(translateError));
const flowInfos = computed(() => flowStore.flowInfos)
const toast = useToast();
const flowWarnings = computed(() => {
const outdatedWarning =
flowStore.flowValidation?.outdated && !flowStore.isCreating
? [translateErrorWithKey(flowStore.flowValidation?.constraints ?? "")]
? flowStore.flowValidation?.constraints?.split(", ") ?? []
: [];
const deprecationWarnings =

View File

@@ -1,22 +0,0 @@
import {useI18n} from "vue-i18n";
export function useFlowOutdatedErrors(){
const {t} = useI18n();
function translateError(error: string): string {
if(error.startsWith(">>>>")){
const key = error.substring(4).trim();
return translateErrorWithKey(key);
} else {
return error;
}
}
function translateErrorWithKey(key: string): string {
return `${t(key + ".description")} ${t(key + ".details")}`
}
return {
translateError,
translateErrorWithKey
}
}

View File

@@ -28,6 +28,10 @@ export class Me {
hasAnyRole() {
return true;
}
getNamespacesForAction(_permission: any, _action: any): string[] {
return [];
}
}
export const useAuthStore = defineStore("auth", {

View File

@@ -195,7 +195,7 @@ export const useFlowStore = defineStore("flow", () => {
return validateFlow({
flow: (isCreating.value ? flowYaml.value : yamlWithNextRevision.value) ?? ""
})
.then((value: {constraints?: any}) => {
.then((value: {constraints?: string}) => {
if (
topologyVisible &&
flowHaveTasks.value &&
@@ -566,7 +566,7 @@ function deleteFlowAndDependencies() {
coreStore.message = {
title: "Couldn't expand subflow",
message: error.response.data.message,
variant: "danger"
variant: "error"
};
}
@@ -644,19 +644,37 @@ function deleteFlowAndDependencies() {
function enableFlowByQuery(options: { namespace: string, id: string }) {
return axios.post(`${apiUrl()}/flows/enable/by-query`, options, {params: options})
}
function deleteFlowByIds(options: { ids: {id: string, namespace: string}[] }) {
return axios.delete(`${apiUrl()}/flows/delete/by-ids`, {data: options.ids})
}
function deleteFlowByQuery(options: { namespace: string, id: string }) {
return axios.delete(`${apiUrl()}/flows/delete/by-query`, {params: options})
}
function validateFlow(options: { flow: string }) {
const flowValidationIssues: FlowValidations = {};
if(isCreating.value) {
const {namespace} = YAML_UTILS.getMetadata(options.flow);
if(authStore.user && !authStore.user.isAllowed(
permission.FLOW,
action.CREATE,
namespace,
)) {
flowValidationIssues.constraints = t("flow creation denied in namespace", {namespace});
}
}
return axios.post(`${apiUrl()}/flows/validate`, options.flow, {...textYamlHeader, withCredentials: true})
.then(response => {
flowValidation.value = response.data[0]
return response.data[0]
const constraintsArray = [response?.data[0]?.constraints, flowValidationIssues.constraints].filter(Boolean)
flowValidation.value = constraintsArray.length === 0 ? {} : {
constraints: constraintsArray.join(", ")
};
return flowValidation.value
})
}
function validateTask(options: { task: string, section: string }) {
return axios.post(`${apiUrl()}/flows/validate/task`, options.task, {...textYamlHeader, withCredentials: true, params: {section: options.section}})
.then(response => {
@@ -752,7 +770,8 @@ function deleteFlowAndDependencies() {
return false;
}
return authStore.user.isAllowed(
return (isCreating.value && authStore.user.hasAnyAction(permission.FLOW, action.UPDATE))
|| authStore.user.isAllowed(
permission.FLOW,
action.UPDATE,
flow.value?.namespace,
@@ -777,9 +796,10 @@ function deleteFlowAndDependencies() {
})
const flowErrors = computed((): string[] | undefined => {
const key = baseOutdatedTranslationKey.value;
const flowExistsError =
flowValidation.value?.outdated && isCreating.value
? [`>>>>${baseOutdatedTranslationKey.value}`] // because translating is impossible here
? [`${t(key + ".description")} ${t(key + ".details")}`]
: [];
const constraintsError =
@@ -794,8 +814,6 @@ function deleteFlowAndDependencies() {
const infos = flowValidation.value?.infos ?? [];
return infos.length === 0 ? undefined : infos;
return undefined;
})
const flowHaveTasks = computed((): boolean => {

View File

@@ -574,6 +574,7 @@
"can not save": "Can not save",
"flow must not be empty": "Flow must not be empty",
"flow must have id and namespace": "Flow must have an id and a namespace.",
"flow creation denied in namespace": "You don't have permission to create flows in the namespace `{namespace}`.",
"readonly property": "Read-only property",
"namespace and id readonly": "The properties `namespace` and `id` cannot be changed — they are now set to their initial values. If you want to rename a flow or change its namespace, you can create a new flow and remove the old one.",
"avg": "Average",