mirror of
https://github.com/n8n-io/n8n.git
synced 2025-02-02 07:01:30 -08:00
Enhance workflow evaluation UI with localization and styling improvements
• Add localization for UI text • Improve styling and layout consistency • Refactor component props and structure • Update button labels and placeholders
This commit is contained in:
parent
eda0f65041
commit
e6e20315d7
|
@ -4,7 +4,7 @@ interface Props {
|
|||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
modelValue: 'Change me Description Default',
|
||||
modelValue: '',
|
||||
});
|
||||
|
||||
defineEmits<{ 'update:modelValue': [value: string] }>();
|
||||
|
@ -16,7 +16,7 @@ defineEmits<{ 'update:modelValue': [value: string] }>();
|
|||
<N8nInput
|
||||
:model-value="modelValue"
|
||||
type="textarea"
|
||||
placeholder="Enter evaluation description"
|
||||
:placeholder="$locale.baseText('workflowEvaluation.edit.descriptionPlaceholder')"
|
||||
@update:model-value="$emit('update:modelValue', $event)"
|
||||
/>
|
||||
</n8n-input-label>
|
||||
|
|
|
@ -20,12 +20,14 @@ defineProps<EvaluationHeaderProps>();
|
|||
icon="arrow-left"
|
||||
:class="$style.backButton"
|
||||
type="tertiary"
|
||||
:title="$locale.baseText('common.back')"
|
||||
:title="$locale.baseText('workflowEvaluation.edit.backButtonTitle')"
|
||||
@click="$router.back()"
|
||||
/>
|
||||
<h2 :class="$style.title">
|
||||
<template v-if="!modelValue.isEditing">
|
||||
{{ modelValue.value }}
|
||||
<span :class="$style.titleText">
|
||||
{{ modelValue.value }}
|
||||
</span>
|
||||
<n8n-icon-button
|
||||
:class="$style.editInputButton"
|
||||
icon="pen"
|
||||
|
@ -38,7 +40,7 @@ defineProps<EvaluationHeaderProps>();
|
|||
ref="nameInput"
|
||||
:model-value="modelValue.tempValue"
|
||||
type="text"
|
||||
:placeholder="$locale.baseText('common.name')"
|
||||
:placeholder="$locale.baseText('workflowEvaluation.edit.namePlaceholder')"
|
||||
@update:model-value="$emit('update:modelValue', { ...modelValue, tempValue: $event })"
|
||||
@blur="() => saveChanges('name')"
|
||||
@keydown="(e: KeyboardEvent) => handleKeydown(e, 'name')"
|
||||
|
@ -64,19 +66,30 @@ defineProps<EvaluationHeaderProps>();
|
|||
.title {
|
||||
margin: 0;
|
||||
flex-grow: 1;
|
||||
font-size: var(--font-size-l);
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-dark);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
.titleText {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.editInputButton {
|
||||
--button-font-color: var(--prim-gray-490);
|
||||
opacity: 0;
|
||||
border: none;
|
||||
--button-font-color: var(--prim-gray-490);
|
||||
}
|
||||
|
||||
.backButton {
|
||||
border: none;
|
||||
--button-font-color: var(--color-text-light);
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
export interface MetricsInputProps {
|
||||
modelValue: string[];
|
||||
helpText: string;
|
||||
}
|
||||
const props = defineProps<MetricsInputProps>();
|
||||
const emit = defineEmits<{ 'update:modelValue': [value: MetricsInputProps['modelValue']] }>();
|
||||
|
@ -19,24 +18,31 @@ function updateMetric(index: number, value: string) {
|
|||
|
||||
<template>
|
||||
<div :class="[$style.formGroup, $style.metrics]">
|
||||
<n8n-text color="text-dark">Metrics</n8n-text>
|
||||
<n8n-text color="text-dark">{{
|
||||
$locale.baseText('workflowEvaluation.edit.metricsTitle')
|
||||
}}</n8n-text>
|
||||
<hr :class="$style.metricsDivider" />
|
||||
<n8n-text size="small" color="text-light">
|
||||
{{ helpText }}
|
||||
{{ $locale.baseText('workflowEvaluation.edit.metricsHelpText') }}
|
||||
</n8n-text>
|
||||
<n8n-input-label label="Output field(s)" :bold="false" size="small" :class="$style.metricField">
|
||||
<n8n-input-label
|
||||
:label="$locale.baseText('workflowEvaluation.edit.metricsFields')"
|
||||
:bold="false"
|
||||
size="small"
|
||||
:class="$style.metricField"
|
||||
>
|
||||
<div :class="$style.metricsContainer">
|
||||
<div v-for="(metric, index) in modelValue" :key="index">
|
||||
<N8nInput
|
||||
:ref="`metric_${index}`"
|
||||
:model-value="metric"
|
||||
:placeholder="'Enter metric name'"
|
||||
:placeholder="$locale.baseText('workflowEvaluation.edit.metricsPlaceholder')"
|
||||
@update:model-value="(value: string) => updateMetric(index, value)"
|
||||
/>
|
||||
</div>
|
||||
<n8n-button
|
||||
type="tertiary"
|
||||
:label="'New metric'"
|
||||
:label="$locale.baseText('workflowEvaluation.edit.metricsNew')"
|
||||
:class="$style.newMetricButton"
|
||||
@click="addNewMetric"
|
||||
/>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import type { ITag } from '@/Interface';
|
||||
import { createEventBus } from 'n8n-design-system';
|
||||
import { computed } from 'vue';
|
||||
|
@ -14,7 +15,6 @@ export interface TagsInputProps {
|
|||
startEditing: (field: string) => void;
|
||||
saveChanges: (field: string) => void;
|
||||
cancelEditing: (field: string) => void;
|
||||
helpText: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<TagsInputProps>(), {
|
||||
|
@ -44,7 +44,9 @@ function updateTags(tags: string[]) {
|
|||
<div :class="$style.formGroup">
|
||||
<n8n-input-label label="Tag name" :bold="false" size="small">
|
||||
<div v-if="!modelValue.isEditing" :class="$style.tagsRead" @click="startEditing('tags')">
|
||||
<n8n-text v-if="modelValue.appliedTagIds.length === 0" size="small">Select tag...</n8n-text>
|
||||
<n8n-text v-if="modelValue.appliedTagIds.length === 0" size="small">
|
||||
{{ $locale.baseText('workflowEvaluation.edit.selectTag') }}
|
||||
</n8n-text>
|
||||
<n8n-tag v-for="tagId in modelValue.appliedTagIds" :key="tagId" :text="getTagName(tagId)" />
|
||||
<n8n-icon-button
|
||||
:class="$style.editInputButton"
|
||||
|
@ -69,7 +71,9 @@ function updateTags(tags: string[]) {
|
|||
@blur="saveChanges('tags')"
|
||||
/>
|
||||
</n8n-input-label>
|
||||
<n8n-text size="small" color="text-light">{{ helpText }}</n8n-text>
|
||||
<n8n-text size="small" color="text-light">{{
|
||||
$locale.baseText('workflowEvaluation.edit.tagsHelpText')
|
||||
}}</n8n-text>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -2,13 +2,12 @@
|
|||
import type { INodeParameterResourceLocator } from 'n8n-workflow';
|
||||
interface WorkflowSelectorProps {
|
||||
modelValue: INodeParameterResourceLocator;
|
||||
helpText: string;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<WorkflowSelectorProps>(), {
|
||||
modelValue: () => ({
|
||||
mode: 'id',
|
||||
value: 'Test Workflow?',
|
||||
value: '',
|
||||
__rl: true,
|
||||
}),
|
||||
});
|
||||
|
@ -17,17 +16,21 @@ defineEmits<{ 'update:modelValue': [value: WorkflowSelectorProps['modelValue']]
|
|||
</script>
|
||||
<template>
|
||||
<div :class="$style.formGroup">
|
||||
<n8n-input-label label="Evaluation workflow" :bold="false" size="small">
|
||||
<n8n-input-label
|
||||
:label="$locale.baseText('workflowEvaluation.edit.workflowSelectorLabel')"
|
||||
:bold="false"
|
||||
size="small"
|
||||
>
|
||||
<WorkflowSelectorParameterInput
|
||||
ref="workflowInput"
|
||||
:parameter="{
|
||||
displayName: 'Workflow',
|
||||
displayName: $locale.baseText('workflowEvaluation.edit.workflowSelectorDisplayName'),
|
||||
name: 'workflowId',
|
||||
type: 'workflowSelector',
|
||||
default: '',
|
||||
}"
|
||||
:model-value="modelValue"
|
||||
:display-title="'Evaluation Workflow'"
|
||||
:display-title="$locale.baseText('workflowEvaluation.edit.workflowSelectorTitle')"
|
||||
:is-value-expression="false"
|
||||
:expression-edit-dialog-visible="false"
|
||||
:path="'workflows'"
|
||||
|
@ -36,7 +39,7 @@ defineEmits<{ 'update:modelValue': [value: WorkflowSelectorProps['modelValue']]
|
|||
/>
|
||||
</n8n-input-label>
|
||||
<n8n-text size="small" color="text-light">
|
||||
{{ helpText }}
|
||||
{{ $locale.baseText('workflowEvaluation.edit.workflowSelectorHelpText') }}
|
||||
</n8n-text>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -37,7 +37,7 @@ export function useEvaluationForm(testId?: number) {
|
|||
const state = ref<IEvaluationFormState>({
|
||||
description: '',
|
||||
name: {
|
||||
value: 'My Test',
|
||||
value: `My Test [${new Date().toLocaleString(undefined, { month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric' })}]`,
|
||||
isEditing: false,
|
||||
tempValue: '',
|
||||
},
|
||||
|
|
|
@ -2695,5 +2695,29 @@
|
|||
"communityPlusModal.features.third.description": "Search and organize past workflow executions for easier review",
|
||||
"communityPlusModal.input.email.label": "Enter email to receive your license key",
|
||||
"communityPlusModal.button.skip": "Skip",
|
||||
"communityPlusModal.button.confirm": "Send me a free license key"
|
||||
"communityPlusModal.button.confirm": "Send me a free license key",
|
||||
"workflowEvaluation.edit.descriptionPlaceholder": "",
|
||||
"workflowEvaluation.edit.backButtonTitle": "Back to Workflow Evaluation",
|
||||
"workflowEvaluation.edit.namePlaceholder": "Enter test name",
|
||||
"workflowEvaluation.edit.metricsTitle": "Metrics",
|
||||
"workflowEvaluation.edit.metricsHelpText": "The output field of the last node in the evaluation workflow. Metrics will be averaged across all test cases.",
|
||||
"workflowEvaluation.edit.metricsFields": "Output field(s)",
|
||||
"workflowEvaluation.edit.metricsPlaceholder": "Enter metric name",
|
||||
"workflowEvaluation.edit.metricsNew": "New metric",
|
||||
"workflowEvaluation.edit.selectTag": "Select tag...",
|
||||
"workflowEvaluation.edit.tagsHelpText": "Executions with this tag will be added as test cases to this test.",
|
||||
"workflowEvaluation.edit.workflowSelectorLabel": "Evaluation workflow",
|
||||
"workflowEvaluation.edit.workflowSelectorDisplayName": "Workflow",
|
||||
"workflowEvaluation.edit.workflowSelectorTitle": "Evaluation Workflow",
|
||||
"workflowEvaluation.edit.workflowSelectorHelpText": "This workflow will be called once for each test case.",
|
||||
"workflowEvaluation.edit.updateTest": "Update test",
|
||||
"workflowEvaluation.edit.saveTest": "Run test",
|
||||
"workflowEvaluation.edit.testSaved": "Test saved",
|
||||
"workflowEvaluation.edit.testSaveFailed": "Failed to save test",
|
||||
"workflowEvaluation.list.testDeleted": "Test deleted",
|
||||
"workflowEvaluation.runTest": "Run Test",
|
||||
"workflowEvaluation.notImplemented": "This feature is not implemented yet!",
|
||||
"workflowEvaluation.viewDetails": "View Details",
|
||||
"workflowEvaluation.editTest": "Edit Test",
|
||||
"workflowEvaluation.deleteTest": "Delete Test"
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import TagsInput from '@/components/WorkflowEvaluation/EditEvaluation/TagsInput.
|
|||
import WorkflowSelector from '@/components/WorkflowEvaluation/EditEvaluation/WorkflowSelector.vue';
|
||||
import MetricsInput from '@/components/WorkflowEvaluation/EditEvaluation/MetricsInput.vue';
|
||||
import { useEvaluationForm } from '@/components/WorkflowEvaluation/composables/useEvaluationForm';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
|
||||
const props = defineProps<{
|
||||
testId?: number;
|
||||
|
@ -16,8 +17,13 @@ const props = defineProps<{
|
|||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const testId = props.testId ?? (route.params.testId as unknown as number);
|
||||
|
||||
const locale = useI18n();
|
||||
const testId = computed(() => props.testId ?? (route.params.testId as unknown as number));
|
||||
const buttonLabel = computed(() =>
|
||||
isEditing.value
|
||||
? locale.baseText('workflowEvaluation.edit.updateTest')
|
||||
: locale.baseText('workflowEvaluation.edit.saveTest'),
|
||||
);
|
||||
const toast = useToast();
|
||||
const {
|
||||
state,
|
||||
|
@ -32,17 +38,7 @@ const {
|
|||
saveChanges,
|
||||
cancelEditing,
|
||||
handleKeydown,
|
||||
} = useEvaluationForm(testId);
|
||||
|
||||
// Help texts
|
||||
const helpText = computed(
|
||||
() => 'Executions with this tag will be added as test cases to this test.',
|
||||
);
|
||||
const workflowHelpText = computed(() => 'This workflow will be called once for each test case.');
|
||||
const metricsHelpText = computed(
|
||||
() =>
|
||||
'The output field of the last node in the evaluation workflow. Metrics will be averaged across all test cases.',
|
||||
);
|
||||
} = useEvaluationForm(testId.value);
|
||||
|
||||
onMounted(() => {
|
||||
void init();
|
||||
|
@ -51,10 +47,13 @@ onMounted(() => {
|
|||
async function onSaveTest() {
|
||||
try {
|
||||
await saveTest();
|
||||
toast.showMessage({ title: 'Test saved', type: 'success' });
|
||||
toast.showMessage({
|
||||
title: locale.baseText('workflowEvaluation.edit.testSaved'),
|
||||
type: 'success',
|
||||
});
|
||||
void router.push({ name: VIEWS.WORKFLOW_EVALUATION });
|
||||
} catch (e: unknown) {
|
||||
toast.showError(e, 'Failed to save test');
|
||||
toast.showError(e, locale.baseText('workflowEvaluation.edit.testSaveFailed'));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -78,27 +77,21 @@ async function onSaveTest() {
|
|||
:start-editing="startEditing"
|
||||
:save-changes="saveChanges"
|
||||
:cancel-editing="cancelEditing"
|
||||
:help-text="helpText"
|
||||
/>
|
||||
|
||||
<WorkflowSelector v-model="state.evaluationWorkflow" :help-text="workflowHelpText" />
|
||||
<WorkflowSelector v-model="state.evaluationWorkflow" />
|
||||
|
||||
<MetricsInput v-model="state.metrics" :help-text="metricsHelpText" />
|
||||
<MetricsInput v-model="state.metrics" />
|
||||
|
||||
<div :class="$style.footer">
|
||||
<n8n-button
|
||||
type="primary"
|
||||
:label="isEditing ? 'Update Test' : 'Save Test'"
|
||||
:loading="isSaving"
|
||||
@click="onSaveTest"
|
||||
/>
|
||||
<n8n-button type="primary" :label="buttonLabel" :loading="isSaving" @click="onSaveTest" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
.container {
|
||||
width: 383px;
|
||||
width: var(--evaluation-edit-panel-width, 24rem);
|
||||
height: 100%;
|
||||
padding: var(--spacing-s);
|
||||
border-right: 1px solid var(--color-foreground-base);
|
||||
|
|
|
@ -38,15 +38,13 @@ function getTagName(tagId: string) {
|
|||
// TODO: Replace with actual API call once implemented
|
||||
function getTestExecution(_testId: number): TestExecution {
|
||||
const mockExecutions = {
|
||||
12: {
|
||||
lastRun: 'an hour ago',
|
||||
errorRate: 0,
|
||||
metrics: { metric1: 0.12, metric2: 0.99, metric3: 0.87 },
|
||||
},
|
||||
lastRun: 'an hour ago',
|
||||
errorRate: 0,
|
||||
metrics: { metric1: 0.12, metric2: 0.99, metric3: 0.87 },
|
||||
};
|
||||
|
||||
return (
|
||||
mockExecutions[12] || {
|
||||
mockExecutions || {
|
||||
lastRun: null,
|
||||
errorRate: null,
|
||||
metrics: { metric1: null, metric2: null, metric3: null },
|
||||
|
@ -59,13 +57,20 @@ function onCreateTest() {
|
|||
void router.push({ name: VIEWS.NEW_WORKFLOW_EVALUATION });
|
||||
}
|
||||
|
||||
function onRunTest(testId: number) {
|
||||
console.log('Running test:', testId);
|
||||
function onRunTest(_testId: number) {
|
||||
// TODO: Implement test run logic
|
||||
toast.showMessage({
|
||||
title: locale.baseText('workflowEvaluation.notImplemented'),
|
||||
type: 'warning',
|
||||
});
|
||||
}
|
||||
|
||||
function onViewDetails(testId: number) {
|
||||
void router.push({ name: VIEWS.WORKFLOW_EVALUATION_EDIT, params: { testId } });
|
||||
function onViewDetails(_testId: number) {
|
||||
// TODO: Implement test details view
|
||||
toast.showMessage({
|
||||
title: locale.baseText('workflowEvaluation.notImplemented'),
|
||||
type: 'warning',
|
||||
});
|
||||
}
|
||||
|
||||
function onEditTest(testId: number) {
|
||||
|
@ -76,7 +81,7 @@ async function onDeleteTest(testId: number) {
|
|||
await evaluationsStore.deleteById(testId);
|
||||
|
||||
toast.showMessage({
|
||||
title: locale.baseText('generic.deleted'),
|
||||
title: locale.baseText('workflowEvaluation.list.testDeleted'),
|
||||
type: 'success',
|
||||
});
|
||||
}
|
||||
|
@ -95,10 +100,10 @@ async function loadInitialData() {
|
|||
onMounted(() => {
|
||||
if (!evaluationsStore.isFeatureEnabled) {
|
||||
toast.showMessage({
|
||||
// message: "Feature not enabled",
|
||||
title: 'Feature not enabled',
|
||||
type: 'error',
|
||||
title: locale.baseText('workflowEvaluation.notImplemented'),
|
||||
type: 'warning',
|
||||
});
|
||||
|
||||
void router.push({
|
||||
name: VIEWS.WORKFLOW,
|
||||
params: { name: router.currentRoute.value.params.name },
|
||||
|
|
Loading…
Reference in a new issue