Refactor Variable Management and Enhance UI in Process Builder
- Updated BusinessRuleNodeConfiguration.vue to utilize props for available variables, improving data handling and consistency. - Enhanced VariableManager.vue to group variables by type, providing a clearer organization and improved user experience. - Introduced dynamic UI elements for variable usage indication and current value previews, enhancing interactivity. - Implemented functionality to check variable usage across nodes, ensuring better management and awareness of variable dependencies. - Added computed properties to force reactivity on variable updates, ensuring configuration modals reflect the latest data. - Improved overall code clarity and maintainability through refactoring and enhanced comments.
This commit is contained in:
parent
5501c00c7c
commit
d03eda54c8
@ -176,26 +176,26 @@
|
||||
>
|
||||
<option value="" disabled>Select variable</option>
|
||||
<option
|
||||
v-for="variable in availableVariables"
|
||||
:key="variable.name"
|
||||
v-for="(variable, index) in props.availableVariables"
|
||||
:key="variable.name || index"
|
||||
:value="variable.name"
|
||||
>
|
||||
{{ variable.label }}
|
||||
{{ variable.label || variable.name || `Variable ${index}` }}
|
||||
</option>
|
||||
</select>
|
||||
<div v-if="condition.variable" class="mt-1">
|
||||
<span
|
||||
class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium"
|
||||
:class="{
|
||||
'bg-purple-100 text-purple-800': ['int', 'decimal'].includes(availableVariables.find(v => v.name === condition.variable)?.type),
|
||||
'bg-blue-100 text-blue-800': availableVariables.find(v => v.name === condition.variable)?.type === 'string',
|
||||
'bg-indigo-100 text-indigo-800': availableVariables.find(v => v.name === condition.variable)?.type === 'boolean',
|
||||
'bg-amber-100 text-amber-800': ['date', 'datetime'].includes(availableVariables.find(v => v.name === condition.variable)?.type),
|
||||
'bg-emerald-100 text-emerald-800': availableVariables.find(v => v.name === condition.variable)?.type === 'object',
|
||||
'bg-gray-100 text-gray-800': !['int', 'decimal', 'string', 'boolean', 'date', 'datetime', 'object'].includes(availableVariables.find(v => v.name === condition.variable)?.type)
|
||||
'bg-purple-100 text-purple-800': ['int', 'decimal'].includes(props.availableVariables.find(v => v.name === condition.variable)?.type),
|
||||
'bg-blue-100 text-blue-800': props.availableVariables.find(v => v.name === condition.variable)?.type === 'string',
|
||||
'bg-indigo-100 text-indigo-800': props.availableVariables.find(v => v.name === condition.variable)?.type === 'boolean',
|
||||
'bg-amber-100 text-amber-800': ['date', 'datetime'].includes(props.availableVariables.find(v => v.name === condition.variable)?.type),
|
||||
'bg-emerald-100 text-emerald-800': props.availableVariables.find(v => v.name === condition.variable)?.type === 'object',
|
||||
'bg-gray-100 text-gray-800': !['int', 'decimal', 'string', 'boolean', 'date', 'datetime', 'object'].includes(props.availableVariables.find(v => v.name === condition.variable)?.type)
|
||||
}"
|
||||
>
|
||||
{{ availableVariables.find(v => v.name === condition.variable)?.type }} type
|
||||
{{ props.availableVariables.find(v => v.name === condition.variable)?.type }} type
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
@ -208,7 +208,7 @@
|
||||
<option value="" disabled>Select operator</option>
|
||||
<option
|
||||
v-for="op in getOperatorsForType(
|
||||
availableVariables.find(v => v.name === condition.variable)?.type
|
||||
props.availableVariables.find(v => v.name === condition.variable)?.type
|
||||
)"
|
||||
:key="op.value"
|
||||
:value="op.value"
|
||||
@ -219,7 +219,7 @@
|
||||
</td>
|
||||
<td class="px-3 py-2">
|
||||
<template v-if="getInputType(
|
||||
availableVariables.find(v => v.name === condition.variable)?.type,
|
||||
props.availableVariables.find(v => v.name === condition.variable)?.type,
|
||||
condition.operator
|
||||
) === 'none'">
|
||||
<div class="flex items-center text-gray-400 text-sm italic">
|
||||
@ -230,23 +230,23 @@
|
||||
|
||||
<!-- Range inputs for between operators -->
|
||||
<template v-else-if="getInputType(
|
||||
availableVariables.find(v => v.name === condition.variable)?.type,
|
||||
props.availableVariables.find(v => v.name === condition.variable)?.type,
|
||||
condition.operator
|
||||
) === 'range'">
|
||||
<div class="space-y-1">
|
||||
<div class="grid grid-cols-2 gap-1">
|
||||
<input
|
||||
v-model="condition.minValue"
|
||||
:type="availableVariables.find(v => v.name === condition.variable)?.type === 'date' ? 'date' :
|
||||
availableVariables.find(v => v.name === condition.variable)?.type === 'datetime' ? 'datetime-local' : 'number'"
|
||||
:type="props.availableVariables.find(v => v.name === condition.variable)?.type === 'date' ? 'date' :
|
||||
props.availableVariables.find(v => v.name === condition.variable)?.type === 'datetime' ? 'datetime-local' : 'number'"
|
||||
class="form-control text-xs"
|
||||
placeholder="Min"
|
||||
@blur="saveChanges"
|
||||
/>
|
||||
<input
|
||||
v-model="condition.maxValue"
|
||||
:type="availableVariables.find(v => v.name === condition.variable)?.type === 'date' ? 'date' :
|
||||
availableVariables.find(v => v.name === condition.variable)?.type === 'datetime' ? 'datetime-local' : 'number'"
|
||||
:type="props.availableVariables.find(v => v.name === condition.variable)?.type === 'date' ? 'date' :
|
||||
props.availableVariables.find(v => v.name === condition.variable)?.type === 'datetime' ? 'datetime-local' : 'number'"
|
||||
class="form-control text-xs"
|
||||
placeholder="Max"
|
||||
@blur="saveChanges"
|
||||
@ -258,7 +258,7 @@
|
||||
|
||||
<!-- Weekday selector -->
|
||||
<template v-else-if="getInputType(
|
||||
availableVariables.find(v => v.name === condition.variable)?.type,
|
||||
props.availableVariables.find(v => v.name === condition.variable)?.type,
|
||||
condition.operator
|
||||
) === 'weekday'">
|
||||
<select v-model="condition.value" class="form-select" @change="saveChanges">
|
||||
@ -274,7 +274,7 @@
|
||||
|
||||
<!-- Month selector -->
|
||||
<template v-else-if="getInputType(
|
||||
availableVariables.find(v => v.name === condition.variable)?.type,
|
||||
props.availableVariables.find(v => v.name === condition.variable)?.type,
|
||||
condition.operator
|
||||
) === 'month'">
|
||||
<select v-model="condition.value" class="form-select" @change="saveChanges">
|
||||
@ -298,12 +298,12 @@
|
||||
<input
|
||||
v-model="condition.value"
|
||||
:type="getInputType(
|
||||
availableVariables.find(v => v.name === condition.variable)?.type,
|
||||
props.availableVariables.find(v => v.name === condition.variable)?.type,
|
||||
condition.operator
|
||||
)"
|
||||
class="form-control"
|
||||
:placeholder="getValuePlaceholder(condition)"
|
||||
:step="availableVariables.find(v => v.name === condition.variable)?.type === 'decimal' ? '0.01' : undefined"
|
||||
:step="props.availableVariables.find(v => v.name === condition.variable)?.type === 'decimal' ? '0.01' : undefined"
|
||||
@blur="saveChanges"
|
||||
/>
|
||||
</template>
|
||||
@ -420,7 +420,7 @@
|
||||
>
|
||||
<option value="" disabled>Target variable</option>
|
||||
<option
|
||||
v-for="variable in availableVariables"
|
||||
v-for="variable in props.availableVariables"
|
||||
:key="variable.name"
|
||||
:value="variable.name"
|
||||
>
|
||||
@ -525,20 +525,8 @@ const localNodeData = ref({
|
||||
...props.nodeData
|
||||
});
|
||||
|
||||
// Get available variables for conditions and actions
|
||||
const availableVariables = computed(() => {
|
||||
const processVariables = processStore.getProcessVariables();
|
||||
if (!processVariables || typeof processVariables !== 'object') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Object.entries(processVariables).map(([name, variable]) => ({
|
||||
name: name,
|
||||
label: `${name} (${variable.type || 'string'})`,
|
||||
type: variable.type || 'string',
|
||||
scope: variable.scope || 'process'
|
||||
}));
|
||||
});
|
||||
// Use the availableVariables prop instead of creating a computed property
|
||||
// The prop is already properly formatted from the parent component
|
||||
|
||||
// Initialize with default values if needed
|
||||
onMounted(() => {
|
||||
@ -863,7 +851,7 @@ const parseValue = (value, type) => {
|
||||
|
||||
// Get placeholder text based on variable type and operator
|
||||
const getValuePlaceholder = (condition) => {
|
||||
const varType = availableVariables.value.find(v => v.name === condition.variable)?.type?.toLowerCase();
|
||||
const varType = props.availableVariables.find(v => v.name === condition.variable)?.type?.toLowerCase();
|
||||
const operator = condition.operator;
|
||||
|
||||
// Handle operators that don't need values
|
||||
@ -928,7 +916,7 @@ const getValuePlaceholder = (condition) => {
|
||||
// Add new methods for handling condition updates
|
||||
const updateConditionOperator = (groupIndex, condIndex) => {
|
||||
const condition = localNodeData.value.ruleGroups[groupIndex].conditions[condIndex];
|
||||
const varType = availableVariables.value.find(v => v.name === condition.variable)?.type;
|
||||
const varType = props.availableVariables.find(v => v.name === condition.variable)?.type;
|
||||
|
||||
// Reset values when operator changes
|
||||
if (getInputType(varType, condition.operator) === 'none') {
|
||||
@ -951,7 +939,7 @@ const updateConditionOperator = (groupIndex, condIndex) => {
|
||||
// Update condition variable
|
||||
const updateConditionVariable = (groupIndex, condIndex) => {
|
||||
const condition = localNodeData.value.ruleGroups[groupIndex].conditions[condIndex];
|
||||
const selectedVar = availableVariables.value.find(v => v.name === condition.variable);
|
||||
const selectedVar = props.availableVariables.find(v => v.name === condition.variable);
|
||||
|
||||
if (selectedVar) {
|
||||
// Reset operator to a valid one for this type
|
||||
|
@ -74,82 +74,145 @@
|
||||
</RsButton>
|
||||
</div>
|
||||
|
||||
<!-- Variable List -->
|
||||
<div v-else-if="filteredVariables.length" class="space-y-2">
|
||||
<!-- Variables by Type -->
|
||||
<div v-else-if="Object.keys(variablesByType).length" class="space-y-3">
|
||||
<div
|
||||
v-for="variable in filteredVariables"
|
||||
:key="variable.name"
|
||||
class="variable-item bg-white border border-gray-200 rounded-lg hover:border-blue-300 hover:shadow-sm transition-all duration-150 group"
|
||||
v-for="(typeData, type) in variablesByType"
|
||||
:key="type"
|
||||
class="variable-type-section"
|
||||
>
|
||||
<!-- Variable Header -->
|
||||
<div class="px-4 py-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3 min-w-0 flex-1">
|
||||
<!-- Variable Icon -->
|
||||
<div class="flex-shrink-0">
|
||||
<div
|
||||
class="w-8 h-8 rounded-lg flex items-center justify-center text-sm font-semibold"
|
||||
:class="{
|
||||
'bg-blue-100 text-blue-700': variable.type === 'string',
|
||||
'bg-purple-100 text-purple-700': ['int', 'decimal'].includes(variable.type),
|
||||
'bg-indigo-100 text-indigo-700': variable.type === 'boolean',
|
||||
'bg-amber-100 text-amber-700': ['date', 'datetime'].includes(variable.type),
|
||||
'bg-emerald-100 text-emerald-700': variable.type === 'object',
|
||||
'bg-gray-100 text-gray-700': !['string', 'int', 'decimal', 'boolean', 'date', 'datetime', 'object'].includes(variable.type)
|
||||
}"
|
||||
>
|
||||
<Icon :name="getVariableIcon(variable.type)" class="w-4 h-4" />
|
||||
<!-- Type Header -->
|
||||
<div
|
||||
class="flex items-center justify-between p-3 bg-gray-50 border border-gray-200 rounded-lg cursor-pointer hover:bg-gray-100 transition-colors"
|
||||
@click="toggleTypeSection(type)"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="w-8 h-8 rounded-lg flex items-center justify-center text-sm font-semibold"
|
||||
:class="typeData.colorClass"
|
||||
>
|
||||
<Icon :name="typeData.icon" class="w-4 h-4" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-gray-900">
|
||||
{{ typeData.label }}
|
||||
</h3>
|
||||
<p class="text-xs text-gray-500">
|
||||
{{ typeData.variables.length }} variable{{ typeData.variables.length !== 1 ? 's' : '' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<RsBadge
|
||||
:variant="getTypeColor(type)"
|
||||
size="sm"
|
||||
>
|
||||
{{ typeData.variables.length }}
|
||||
</RsBadge>
|
||||
<Icon
|
||||
:name="collapsedSections[type] ? 'material-symbols:keyboard-arrow-right' : 'material-symbols:keyboard-arrow-down'"
|
||||
class="w-5 h-5 text-gray-400 transition-transform duration-200"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Variables in this type -->
|
||||
<div
|
||||
v-if="!collapsedSections[type]"
|
||||
class="ml-4 space-y-1 border-l-2 border-gray-100 pl-4"
|
||||
>
|
||||
<div
|
||||
v-for="variable in typeData.variables"
|
||||
:key="variable.name"
|
||||
class="variable-item bg-white border border-gray-200 rounded-lg hover:border-blue-300 hover:shadow-sm transition-all duration-150 group"
|
||||
>
|
||||
<div class="px-3 py-2.5">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2 min-w-0 flex-1">
|
||||
<!-- Variable Name -->
|
||||
<h4 class="text-sm font-medium text-gray-900 truncate">{{ variable.name }}</h4>
|
||||
|
||||
<!-- Show usage indicator -->
|
||||
<div v-if="checkVariableUsage(variable.name).isUsed" class="mt-1">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800">
|
||||
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
Used in process
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Current Value Preview -->
|
||||
<div v-if="variable.value !== undefined && variable.value !== ''" class="flex-shrink-0">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-amber-100 text-amber-800">
|
||||
{{ getValuePreview(variable.value, variable.type) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<!-- View Usage Button (only show if variable is used) -->
|
||||
<button
|
||||
v-if="checkVariableUsage(variable.name).isUsed"
|
||||
@click="showVariableUsage(variable)"
|
||||
class="p-1 text-gray-400 hover:text-green-600 hover:bg-green-50 rounded transition-colors"
|
||||
title="View where this variable is used"
|
||||
>
|
||||
<Icon name="material-symbols:link" class="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button
|
||||
@click="editVariable(variable)"
|
||||
class="p-1 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded transition-colors"
|
||||
title="Edit variable"
|
||||
>
|
||||
<Icon name="material-symbols:edit" class="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button
|
||||
@click="deleteVariable(variable)"
|
||||
class="p-1 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded transition-colors"
|
||||
title="Delete variable"
|
||||
>
|
||||
<Icon name="material-symbols:delete" class="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Variable Info -->
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<h4 class="text-sm font-medium text-gray-900 truncate">{{ variable.name }}</h4>
|
||||
<RsBadge
|
||||
:variant="getTypeColor(variable.type)"
|
||||
size="sm"
|
||||
class="flex-shrink-0"
|
||||
>
|
||||
{{ variable.type }}
|
||||
</RsBadge>
|
||||
<!-- Description -->
|
||||
<p v-if="variable.description" class="text-xs text-gray-500 mt-1 line-clamp-1">
|
||||
{{ variable.description }}
|
||||
</p>
|
||||
|
||||
<!-- Full Current Value Display (expandable) -->
|
||||
<div v-if="variable.value !== undefined && variable.value !== '' && expandedValues[variable.name]" class="mt-2">
|
||||
<div class="bg-amber-50 rounded-md p-2 border border-amber-100">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<Icon name="material-symbols:settings" class="w-3 h-3 text-amber-600" />
|
||||
<span class="text-xs font-medium text-amber-700 uppercase tracking-wide">Current Value</span>
|
||||
</div>
|
||||
<button
|
||||
@click="expandedValues[variable.name] = false"
|
||||
class="text-amber-600 hover:text-amber-800 text-xs"
|
||||
>
|
||||
<Icon name="material-symbols:close" class="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="font-mono text-xs text-amber-800 break-all">
|
||||
{{ formatValue(variable.value, variable.type) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<p v-if="variable.description" class="text-xs text-gray-500 line-clamp-1">
|
||||
{{ variable.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button
|
||||
@click="editVariable(variable)"
|
||||
class="p-1.5 text-gray-400 hover:text-blue-600 hover:bg-blue-50 rounded transition-colors"
|
||||
title="Edit variable"
|
||||
>
|
||||
<Icon name="material-symbols:edit" class="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
@click="deleteVariable(variable)"
|
||||
class="p-1.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded transition-colors"
|
||||
title="Delete variable"
|
||||
>
|
||||
<Icon name="material-symbols:delete" class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Current Value Display -->
|
||||
<div v-if="variable.value !== undefined && variable.value !== ''" class="mt-3">
|
||||
<div class="bg-amber-50 rounded-md p-2 border border-amber-100">
|
||||
<div class="flex items-center gap-1.5 mb-1">
|
||||
<Icon name="material-symbols:settings" class="w-3.5 h-3.5 text-amber-600" />
|
||||
<span class="text-xs font-medium text-amber-700 uppercase tracking-wide">Current Value</span>
|
||||
</div>
|
||||
<div class="font-mono text-xs text-amber-800 break-all">
|
||||
{{ formatValue(variable.value, variable.type) }}
|
||||
|
||||
<!-- Show full value button -->
|
||||
<div v-else-if="variable.value !== undefined && variable.value !== '' && needsExpansion(variable.value, variable.type)" class="mt-1">
|
||||
<button
|
||||
@click="expandedValues[variable.name] = true"
|
||||
class="text-xs text-blue-600 hover:text-blue-800 flex items-center gap-1"
|
||||
>
|
||||
<Icon name="material-symbols:visibility" class="w-3 h-3" />
|
||||
Show full value
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -271,6 +334,8 @@ const processStore = useProcessBuilderStore();
|
||||
const showAddVariable = ref(false);
|
||||
const editingVariable = ref(null);
|
||||
const searchQuery = ref("");
|
||||
const collapsedSections = ref({});
|
||||
const expandedValues = ref({});
|
||||
const variableForm = ref({
|
||||
name: "",
|
||||
type: "string",
|
||||
@ -369,7 +434,97 @@ const filteredVariables = computed(() => {
|
||||
);
|
||||
});
|
||||
|
||||
// Group variables by type
|
||||
const variablesByType = computed(() => {
|
||||
const varsToUse = searchQuery.value ? filteredVariables.value : variables.value;
|
||||
const grouped = {};
|
||||
|
||||
// Define type metadata
|
||||
const typeMetadata = {
|
||||
string: {
|
||||
label: 'Text Variables',
|
||||
icon: 'material-symbols:text-fields',
|
||||
colorClass: 'bg-blue-100 text-blue-700'
|
||||
},
|
||||
int: {
|
||||
label: 'Integer Numbers',
|
||||
icon: 'material-symbols:pin',
|
||||
colorClass: 'bg-purple-100 text-purple-700'
|
||||
},
|
||||
decimal: {
|
||||
label: 'Decimal Numbers',
|
||||
icon: 'material-symbols:pin',
|
||||
colorClass: 'bg-purple-100 text-purple-700'
|
||||
},
|
||||
boolean: {
|
||||
label: 'Boolean Values',
|
||||
icon: 'material-symbols:toggle-on',
|
||||
colorClass: 'bg-indigo-100 text-indigo-700'
|
||||
},
|
||||
date: {
|
||||
label: 'Dates',
|
||||
icon: 'material-symbols:calendar-today',
|
||||
colorClass: 'bg-amber-100 text-amber-700'
|
||||
},
|
||||
datetime: {
|
||||
label: 'Date & Time',
|
||||
icon: 'material-symbols:schedule',
|
||||
colorClass: 'bg-amber-100 text-amber-700'
|
||||
},
|
||||
object: {
|
||||
label: 'Objects & Arrays',
|
||||
icon: 'material-symbols:data-object',
|
||||
colorClass: 'bg-emerald-100 text-emerald-700'
|
||||
}
|
||||
};
|
||||
|
||||
// Group variables by type
|
||||
varsToUse.forEach(variable => {
|
||||
const type = variable.type || 'string';
|
||||
if (!grouped[type]) {
|
||||
grouped[type] = {
|
||||
variables: [],
|
||||
...typeMetadata[type] || {
|
||||
label: type.charAt(0).toUpperCase() + type.slice(1),
|
||||
icon: 'material-symbols:data-object',
|
||||
colorClass: 'bg-gray-100 text-gray-700'
|
||||
}
|
||||
};
|
||||
|
||||
// Set all sections to collapsed by default
|
||||
if (collapsedSections.value[type] === undefined) {
|
||||
collapsedSections.value[type] = true;
|
||||
}
|
||||
}
|
||||
grouped[type].variables.push(variable);
|
||||
});
|
||||
|
||||
// Sort variables within each type
|
||||
Object.values(grouped).forEach(typeData => {
|
||||
typeData.variables.sort((a, b) => a.name.localeCompare(b.name));
|
||||
});
|
||||
|
||||
return grouped;
|
||||
});
|
||||
|
||||
// Methods
|
||||
const toggleTypeSection = (type) => {
|
||||
collapsedSections.value[type] = !collapsedSections.value[type];
|
||||
};
|
||||
|
||||
const getValuePreview = (value, type) => {
|
||||
const formatted = formatValue(value, type);
|
||||
if (formatted.length > 15) {
|
||||
return formatted.substring(0, 15) + '...';
|
||||
}
|
||||
return formatted;
|
||||
};
|
||||
|
||||
const needsExpansion = (value, type) => {
|
||||
const formatted = formatValue(value, type);
|
||||
return formatted.length > 15;
|
||||
};
|
||||
|
||||
const editVariable = (variable) => {
|
||||
editingVariable.value = variable;
|
||||
variableForm.value = {
|
||||
@ -380,8 +535,133 @@ const editVariable = (variable) => {
|
||||
};
|
||||
|
||||
const deleteVariable = (variable) => {
|
||||
if (confirm(`Are you sure you want to delete the variable "${variable.name}"? This might affect parts of your process that use this variable.`)) {
|
||||
// Check if the variable is being used in any nodes
|
||||
const usageInfo = checkVariableUsage(variable.name);
|
||||
|
||||
let confirmMessage = `Are you sure you want to delete the variable "${variable.name}"?`;
|
||||
|
||||
if (usageInfo.isUsed) {
|
||||
confirmMessage += `\n\nWARNING: This variable is currently being used in:\n${usageInfo.usageDetails.join('\n')}`;
|
||||
confirmMessage += '\n\nDeleting this variable may cause issues in your process. Continue?';
|
||||
}
|
||||
|
||||
if (confirm(confirmMessage)) {
|
||||
processStore.deleteProcessVariable(variable.name);
|
||||
|
||||
if (usageInfo.isUsed) {
|
||||
console.warn(`Deleted variable "${variable.name}" that was being used in:`, usageInfo.usageDetails);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to check where a variable is being used
|
||||
const checkVariableUsage = (variableName) => {
|
||||
const usageDetails = [];
|
||||
let isUsed = false;
|
||||
|
||||
if (!processStore.currentProcess?.nodes) {
|
||||
return { isUsed: false, usageDetails: [] };
|
||||
}
|
||||
|
||||
processStore.currentProcess.nodes.forEach(node => {
|
||||
if (!node.data) return;
|
||||
|
||||
const nodeLabel = node.data.label || `${node.type} node`;
|
||||
|
||||
switch (node.type) {
|
||||
case 'api':
|
||||
if (node.data.outputVariable === variableName) {
|
||||
usageDetails.push(`- ${nodeLabel}: API Output Variable`);
|
||||
isUsed = true;
|
||||
}
|
||||
if (node.data.errorVariable === variableName) {
|
||||
usageDetails.push(`- ${nodeLabel}: API Error Variable`);
|
||||
isUsed = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'script':
|
||||
if (node.data.errorVariable === variableName) {
|
||||
usageDetails.push(`- ${nodeLabel}: Script Error Variable`);
|
||||
isUsed = true;
|
||||
}
|
||||
if (node.data.outputVariables && Array.isArray(node.data.outputVariables)) {
|
||||
node.data.outputVariables.forEach(output => {
|
||||
if (output.name === variableName) {
|
||||
usageDetails.push(`- ${nodeLabel}: Script Output Variable`);
|
||||
isUsed = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'form':
|
||||
if (node.data.fieldMappings && Array.isArray(node.data.fieldMappings)) {
|
||||
node.data.fieldMappings.forEach(mapping => {
|
||||
if (mapping.processVariable === variableName) {
|
||||
usageDetails.push(`- ${nodeLabel}: Form Field Mapping`);
|
||||
isUsed = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'businessRule':
|
||||
if (node.data.conditions && Array.isArray(node.data.conditions)) {
|
||||
node.data.conditions.forEach(conditionGroup => {
|
||||
if (conditionGroup.conditions && Array.isArray(conditionGroup.conditions)) {
|
||||
conditionGroup.conditions.forEach(condition => {
|
||||
if (condition.variable === variableName) {
|
||||
usageDetails.push(`- ${nodeLabel}: Business Rule Condition`);
|
||||
isUsed = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'notification':
|
||||
const placeholderPattern = `{${variableName}}`;
|
||||
if (node.data.subject && node.data.subject.includes(placeholderPattern)) {
|
||||
usageDetails.push(`- ${nodeLabel}: Notification Subject`);
|
||||
isUsed = true;
|
||||
}
|
||||
if (node.data.content && node.data.content.includes(placeholderPattern)) {
|
||||
usageDetails.push(`- ${nodeLabel}: Notification Content`);
|
||||
isUsed = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'gateway':
|
||||
if (node.data.conditions && Array.isArray(node.data.conditions)) {
|
||||
node.data.conditions.forEach(conditionGroup => {
|
||||
if (conditionGroup.conditions && Array.isArray(conditionGroup.conditions)) {
|
||||
conditionGroup.conditions.forEach(condition => {
|
||||
if (condition.variable === variableName) {
|
||||
usageDetails.push(`- ${nodeLabel}: Gateway Condition`);
|
||||
isUsed = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return { isUsed, usageDetails };
|
||||
};
|
||||
|
||||
// Show variable usage details in an alert
|
||||
const showVariableUsage = (variable) => {
|
||||
const usageInfo = checkVariableUsage(variable.name);
|
||||
|
||||
if (usageInfo.isUsed) {
|
||||
const message = `Variable "${variable.name}" is currently being used in:\n\n${usageInfo.usageDetails.join('\n')}`;
|
||||
alert(message);
|
||||
} else {
|
||||
alert(`Variable "${variable.name}" is not currently being used in any nodes.`);
|
||||
}
|
||||
};
|
||||
|
||||
@ -429,8 +709,32 @@ const saveVariable = async (formData) => {
|
||||
};
|
||||
|
||||
if (editingVariable.value) {
|
||||
// Update existing variable
|
||||
processStore.updateProcessVariable(editingVariable.value.name, newVariable);
|
||||
// Check if the variable name has changed
|
||||
const oldName = editingVariable.value.name;
|
||||
const newName = formData.name;
|
||||
|
||||
if (oldName !== newName) {
|
||||
// Variable name has changed - use rename functionality to update all references
|
||||
const success = processStore.renameProcessVariable(oldName, newName);
|
||||
|
||||
if (!success) {
|
||||
alert(`Failed to rename variable. A variable named "${newName}" already exists or there was an error.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Additional update for other properties (type, description, value)
|
||||
// Since rename only handles the name change and basic data copy
|
||||
processStore.updateProcessVariable(newName, {
|
||||
type: formData.type,
|
||||
description: formData.description,
|
||||
value: processedDefaultValue
|
||||
});
|
||||
|
||||
console.log(`Variable "${oldName}" renamed to "${newName}" and all references updated`);
|
||||
} else {
|
||||
// Only properties changed, not the name
|
||||
processStore.updateProcessVariable(editingVariable.value.name, newVariable);
|
||||
}
|
||||
} else {
|
||||
// Add new variable
|
||||
processStore.addProcessVariable(newVariable);
|
||||
|
@ -258,6 +258,20 @@ const gatewayAvailableVariables = computed(() => {
|
||||
return allVars;
|
||||
});
|
||||
|
||||
// Computed key that changes when variables are updated to force re-render of configuration modals
|
||||
const variablesUpdateKey = computed(() => {
|
||||
if (!processStore.currentProcess || !processStore.currentProcess.variables) {
|
||||
return 'no-variables';
|
||||
}
|
||||
|
||||
// Create a hash of variable names to detect changes
|
||||
const variableNames = Object.keys(processStore.currentProcess.variables).sort().join(',');
|
||||
return `vars-${variableNames.length}-${variableNames.split('').reduce((a, b) => {
|
||||
a = ((a << 5) - a) + b.charCodeAt(0);
|
||||
return a & a;
|
||||
}, 0)}`;
|
||||
});
|
||||
|
||||
// Handle node selection
|
||||
const onNodeSelected = (node) => {
|
||||
// console.log('Node selected:', node);
|
||||
@ -1208,7 +1222,7 @@ watch(() => processStore.hasUnsavedChanges, (hasChanges) => {
|
||||
</div>
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<!-- Show variable manager when no node is selected -->
|
||||
<VariableManager v-if="!selectedNodeData" />
|
||||
<VariableManager v-if="!selectedNodeData" :key="`variables-${variablesUpdateKey}`" />
|
||||
|
||||
<!-- Show node properties when a node is selected -->
|
||||
<div v-else class="p-4 space-y-4">
|
||||
@ -1347,6 +1361,7 @@ watch(() => processStore.hasUnsavedChanges, (hasChanges) => {
|
||||
<FormNodeConfigurationModal
|
||||
v-if="selectedNodeData && selectedNodeData.type === 'form'"
|
||||
v-model="showFormConfigModal"
|
||||
:key="`form-${selectedNodeData.id}-${variablesUpdateKey}`"
|
||||
:nodeData="selectedNodeData.data"
|
||||
:availableVariables="gatewayAvailableVariables"
|
||||
@update="handleFormNodeUpdate"
|
||||
@ -1356,6 +1371,7 @@ watch(() => processStore.hasUnsavedChanges, (hasChanges) => {
|
||||
<ApiNodeConfigurationModal
|
||||
v-if="selectedNodeData && selectedNodeData.type === 'api'"
|
||||
v-model="showApiConfigModal"
|
||||
:key="`api-${selectedNodeData.id}-${variablesUpdateKey}`"
|
||||
:nodeData="selectedNodeData.data"
|
||||
:availableVariables="gatewayAvailableVariables"
|
||||
@update="handleApiNodeUpdate"
|
||||
@ -1365,6 +1381,7 @@ watch(() => processStore.hasUnsavedChanges, (hasChanges) => {
|
||||
<GatewayConditionManagerModal
|
||||
v-if="selectedNodeData && selectedNodeData.type === 'gateway'"
|
||||
v-model="showGatewayConfigModal"
|
||||
:key="`gateway-${selectedNodeData.id}-${variablesUpdateKey}`"
|
||||
:conditions="selectedNodeData.data.conditions || []"
|
||||
:availableVariables="gatewayAvailableVariables"
|
||||
:defaultPath="selectedNodeData.data.defaultPath || 'Default'"
|
||||
@ -1376,6 +1393,7 @@ watch(() => processStore.hasUnsavedChanges, (hasChanges) => {
|
||||
<BusinessRuleNodeConfigurationModal
|
||||
v-if="selectedNodeData && selectedNodeData.type === 'business-rule'"
|
||||
v-model="showBusinessRuleConfigModal"
|
||||
:key="`business-rule-${selectedNodeData.id}-${variablesUpdateKey}`"
|
||||
:nodeId="selectedNodeData.id"
|
||||
:nodeData="selectedNodeData.data"
|
||||
:availableVariables="gatewayAvailableVariables"
|
||||
@ -1386,6 +1404,7 @@ watch(() => processStore.hasUnsavedChanges, (hasChanges) => {
|
||||
<NotificationNodeConfigurationModal
|
||||
v-if="selectedNodeData && selectedNodeData.type === 'notification'"
|
||||
v-model="showNotificationConfigModal"
|
||||
:key="`notification-${selectedNodeData.id}-${variablesUpdateKey}`"
|
||||
:nodeData="selectedNodeData.data"
|
||||
:availableVariables="gatewayAvailableVariables"
|
||||
@update="handleNotificationNodeUpdate"
|
||||
@ -1395,6 +1414,7 @@ watch(() => processStore.hasUnsavedChanges, (hasChanges) => {
|
||||
<ScriptNodeConfigurationModal
|
||||
v-if="selectedNodeData && selectedNodeData.type === 'script'"
|
||||
v-model="showScriptConfigModal"
|
||||
:key="`script-${selectedNodeData.id}-${variablesUpdateKey}`"
|
||||
:nodeData="selectedNodeData.data"
|
||||
:availableVariables="gatewayAvailableVariables"
|
||||
@update="handleScriptNodeUpdate"
|
||||
|
@ -1023,6 +1023,196 @@ export const useProcessBuilderStore = defineStore('processBuilder', {
|
||||
}
|
||||
|
||||
return this.currentProcess.variables[name] || null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update all references to a variable when its name changes
|
||||
* This ensures data integrity across all nodes that reference the variable
|
||||
*/
|
||||
updateVariableReferences(oldVariableName, newVariableName) {
|
||||
if (!this.currentProcess || !this.currentProcess.nodes || oldVariableName === newVariableName) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Updating variable references from "${oldVariableName}" to "${newVariableName}"`);
|
||||
|
||||
this.currentProcess.nodes.forEach(node => {
|
||||
if (!node.data) return;
|
||||
|
||||
switch (node.type) {
|
||||
case 'api':
|
||||
// Update API node output and error variables
|
||||
if (node.data.outputVariable === oldVariableName) {
|
||||
node.data.outputVariable = newVariableName;
|
||||
console.log(`Updated API node ${node.id} outputVariable`);
|
||||
}
|
||||
if (node.data.errorVariable === oldVariableName) {
|
||||
node.data.errorVariable = newVariableName;
|
||||
console.log(`Updated API node ${node.id} errorVariable`);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'script':
|
||||
// Update script node output variables and error variable
|
||||
if (node.data.errorVariable === oldVariableName) {
|
||||
node.data.errorVariable = newVariableName;
|
||||
console.log(`Updated Script node ${node.id} errorVariable`);
|
||||
}
|
||||
if (node.data.outputVariables && Array.isArray(node.data.outputVariables)) {
|
||||
node.data.outputVariables.forEach(output => {
|
||||
if (output.name === oldVariableName) {
|
||||
output.name = newVariableName;
|
||||
console.log(`Updated Script node ${node.id} outputVariable`);
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'form':
|
||||
// Update form node field mappings (both input and output mappings)
|
||||
if (node.data.inputMappings && Array.isArray(node.data.inputMappings)) {
|
||||
node.data.inputMappings.forEach(mapping => {
|
||||
if (mapping.processVariable === oldVariableName) {
|
||||
mapping.processVariable = newVariableName;
|
||||
console.log(`Updated Form node ${node.id} input mapping`);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (node.data.outputMappings && Array.isArray(node.data.outputMappings)) {
|
||||
node.data.outputMappings.forEach(mapping => {
|
||||
if (mapping.processVariable === oldVariableName) {
|
||||
mapping.processVariable = newVariableName;
|
||||
console.log(`Updated Form node ${node.id} output mapping`);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (node.data.fieldMappings && Array.isArray(node.data.fieldMappings)) {
|
||||
node.data.fieldMappings.forEach(mapping => {
|
||||
if (mapping.processVariable === oldVariableName) {
|
||||
mapping.processVariable = newVariableName;
|
||||
console.log(`Updated Form node ${node.id} field mapping`);
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'businessRule':
|
||||
// Update business rule conditions (handle both ruleGroups and conditions structures)
|
||||
if (node.data.ruleGroups && Array.isArray(node.data.ruleGroups)) {
|
||||
node.data.ruleGroups.forEach(ruleGroup => {
|
||||
if (ruleGroup.conditions && Array.isArray(ruleGroup.conditions)) {
|
||||
ruleGroup.conditions.forEach(condition => {
|
||||
if (condition.variable === oldVariableName) {
|
||||
condition.variable = newVariableName;
|
||||
console.log(`Updated Business Rule node ${node.id} ruleGroup condition variable`);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if (node.data.conditions && Array.isArray(node.data.conditions)) {
|
||||
node.data.conditions.forEach(conditionGroup => {
|
||||
if (conditionGroup.conditions && Array.isArray(conditionGroup.conditions)) {
|
||||
conditionGroup.conditions.forEach(condition => {
|
||||
if (condition.variable === oldVariableName) {
|
||||
condition.variable = newVariableName;
|
||||
console.log(`Updated Business Rule node ${node.id} condition variable`);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'notification':
|
||||
// Update notification content that may contain variable placeholders
|
||||
if (node.data.subject) {
|
||||
const oldPlaceholder = `{${oldVariableName}}`;
|
||||
const newPlaceholder = `{${newVariableName}}`;
|
||||
if (node.data.subject.includes(oldPlaceholder)) {
|
||||
node.data.subject = node.data.subject.replace(new RegExp(`\\{${oldVariableName}\\}`, 'g'), newPlaceholder);
|
||||
console.log(`Updated Notification node ${node.id} subject`);
|
||||
}
|
||||
}
|
||||
if (node.data.content) {
|
||||
const oldPlaceholder = `{${oldVariableName}}`;
|
||||
const newPlaceholder = `{${newVariableName}}`;
|
||||
if (node.data.content.includes(oldPlaceholder)) {
|
||||
node.data.content = node.data.content.replace(new RegExp(`\\{${oldVariableName}\\}`, 'g'), newPlaceholder);
|
||||
console.log(`Updated Notification node ${node.id} content`);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'gateway':
|
||||
// Update gateway conditions
|
||||
if (node.data.conditions && Array.isArray(node.data.conditions)) {
|
||||
node.data.conditions.forEach(conditionGroup => {
|
||||
if (conditionGroup.conditions && Array.isArray(conditionGroup.conditions)) {
|
||||
conditionGroup.conditions.forEach(condition => {
|
||||
if (condition.variable === oldVariableName) {
|
||||
condition.variable = newVariableName;
|
||||
console.log(`Updated Gateway node ${node.id} condition variable`);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// For any other node types, check if there are variable references in the data
|
||||
const nodeDataStr = JSON.stringify(node.data);
|
||||
if (nodeDataStr.includes(oldVariableName)) {
|
||||
console.log(`Warning: Node ${node.id} (type: ${node.type}) may contain references to variable "${oldVariableName}" that need manual review`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
this.unsavedChanges = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Rename a variable and update all its references
|
||||
*/
|
||||
renameProcessVariable(oldName, newName) {
|
||||
if (!this.currentProcess || !this.currentProcess.variables || oldName === newName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if new name already exists
|
||||
if (this.currentProcess.variables[newName]) {
|
||||
console.error(`Variable "${newName}" already exists`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the variable data
|
||||
const variableData = this.currentProcess.variables[oldName];
|
||||
if (!variableData) {
|
||||
console.error(`Variable "${oldName}" not found`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update all references in nodes
|
||||
this.updateVariableReferences(oldName, newName);
|
||||
|
||||
// Update the variable in the variables object
|
||||
delete this.currentProcess.variables[oldName];
|
||||
this.currentProcess.variables[newName] = {
|
||||
...variableData,
|
||||
name: newName
|
||||
};
|
||||
|
||||
// Force reactivity update by creating a new object reference
|
||||
this.currentProcess.variables = { ...this.currentProcess.variables };
|
||||
|
||||
// Force update of the entire process to trigger reactivity in all components
|
||||
this.currentProcess = { ...this.currentProcess };
|
||||
|
||||
console.log(`Successfully renamed variable from "${oldName}" to "${newName}"`);
|
||||
this.unsavedChanges = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user