mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 01:56:46 +00:00
fix(editor): Ai 672 minor UI fixes on evaluation creation (#13461)
This commit is contained in:
committed by
GitHub
parent
5ad950f603
commit
b791677ffa
@@ -25,6 +25,7 @@ interface InfoTipProps {
|
||||
type?: (typeof TYPE)[number];
|
||||
bold?: boolean;
|
||||
tooltipPlacement?: Placement;
|
||||
enterable?: boolean;
|
||||
}
|
||||
|
||||
defineOptions({ name: 'N8nInfoTip' });
|
||||
@@ -33,6 +34,7 @@ const props = withDefaults(defineProps<InfoTipProps>(), {
|
||||
type: 'note',
|
||||
bold: true,
|
||||
tooltipPlacement: 'top',
|
||||
enterable: true,
|
||||
});
|
||||
|
||||
const iconData = computed<{ icon: IconMap[keyof IconMap]; color: IconColor }>(() => {
|
||||
@@ -60,6 +62,7 @@ const iconData = computed<{ icon: IconMap[keyof IconMap]; color: IconColor }>(()
|
||||
:placement="tooltipPlacement"
|
||||
:popper-class="$style.tooltipPopper"
|
||||
:disabled="type !== 'tooltip'"
|
||||
:enterable
|
||||
>
|
||||
<span :class="$style.iconText">
|
||||
<N8nIcon :icon="iconData.icon" :color="iconData.color" />
|
||||
|
||||
@@ -1,125 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, useCssModule } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
state?: 'default' | 'error' | 'success';
|
||||
hoverable?: boolean;
|
||||
}>();
|
||||
|
||||
const css = useCssModule();
|
||||
|
||||
const classes = computed(() => ({
|
||||
[css.arrowConnector]: true,
|
||||
[css.hoverable]: props.hoverable,
|
||||
[css.error]: props.state === 'error',
|
||||
[css.success]: props.state === 'success',
|
||||
}));
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div :class="classes">
|
||||
<div :class="$style.blockArrow">
|
||||
<div :class="$style.stalk"></div>
|
||||
<div :class="$style.arrowHead"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
.arrowConnector {
|
||||
position: relative;
|
||||
height: var(--arrow-height, 3rem);
|
||||
margin: 0.1rem 0;
|
||||
.blockArrow {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stalk {
|
||||
position: relative;
|
||||
width: var(--stalk-width, 0.125rem);
|
||||
height: calc(100% - var(--arrow-tip-height, 0.5rem));
|
||||
background-color: var(--arrow-color, var(--color-text-dark));
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 1rem;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
min-height: 14px;
|
||||
width: 2px;
|
||||
background-color: var(--color-foreground-xdark);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.arrowHead {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: calc(var(--arrow-tip-width, 0.75rem) / 2) solid transparent;
|
||||
border-right: calc(var(--arrow-tip-width, 0.75rem) / 2) solid transparent;
|
||||
border-top: var(--arrow-tip-height, 0.5rem) solid var(--arrow-color, var(--color-text-dark));
|
||||
transition: all 0.2s ease;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.hoverable {
|
||||
--hover-scale: var(--arrow-hover-scale, 1.8);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
.stalk {
|
||||
width: calc(var(--stalk-width, 0.125rem) * var(--hover-scale));
|
||||
background-color: var(--arrow-hover-color, var(--arrow-color, var(--color-text-dark)));
|
||||
}
|
||||
|
||||
.arrowHead {
|
||||
border-left-width: calc(var(--arrow-tip-width, 0.75rem) / 2 * var(--hover-scale));
|
||||
border-right-width: calc(var(--arrow-tip-width, 0.75rem) / 2 * var(--hover-scale));
|
||||
border-top-width: calc(var(--arrow-tip-height, 0.5rem) * var(--hover-scale));
|
||||
border-top-color: var(--arrow-hover-color, var(--arrow-color, var(--color-text-dark)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
--stalk-width: 0.1875rem;
|
||||
--arrow-color: var(--color-danger);
|
||||
--arrow-tip-width: 1rem;
|
||||
--arrow-tip-height: 0.625rem;
|
||||
}
|
||||
|
||||
.success {
|
||||
--stalk-width: 0.1875rem;
|
||||
--arrow-color: var(--color-success);
|
||||
--arrow-tip-width: 1rem;
|
||||
--arrow-tip-height: 0.625rem;
|
||||
|
||||
.stalk {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '✓';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 1rem;
|
||||
color: var(--arrow-color, var(--color-success));
|
||||
}
|
||||
}
|
||||
border-top: 10px solid var(--color-foreground-xdark);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { ElCollapseTransition } from 'element-plus';
|
||||
import { computed, nextTick, ref, useCssModule } from 'vue';
|
||||
import { type Modifier, detectOverflow } from '@popperjs/core';
|
||||
import { N8nInfoTip, N8nText, N8nTooltip } from 'n8n-design-system';
|
||||
import { computed, ref, useCssModule } from 'vue';
|
||||
|
||||
interface EvaluationStep {
|
||||
title?: string;
|
||||
warning?: boolean;
|
||||
small?: boolean;
|
||||
expanded?: boolean;
|
||||
description?: string;
|
||||
issues?: Array<{ field: string; message: string }>;
|
||||
showIssues?: boolean;
|
||||
tooltip?: string;
|
||||
tooltip: string;
|
||||
externalTooltip?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<EvaluationStep>(), {
|
||||
description: '',
|
||||
warning: false,
|
||||
small: false,
|
||||
expanded: false,
|
||||
issues: () => [],
|
||||
showIssues: true,
|
||||
@@ -26,110 +26,115 @@ const props = withDefaults(defineProps<EvaluationStep>(), {
|
||||
|
||||
const locale = useI18n();
|
||||
const isExpanded = ref(props.expanded);
|
||||
const contentRef = ref<HTMLElement | null>(null);
|
||||
const containerRef = ref<HTMLElement | null>(null);
|
||||
const showTooltip = ref(false);
|
||||
const $style = useCssModule();
|
||||
|
||||
const hasIssues = computed(() => props.issues.length > 0);
|
||||
|
||||
const containerClass = computed(() => {
|
||||
return {
|
||||
[$style.wrap]: true,
|
||||
[$style.expanded]: isExpanded.value,
|
||||
[$style.hasIssues]: props.issues.length > 0,
|
||||
[$style.evaluationStep]: true,
|
||||
[$style['has-issues']]: true,
|
||||
};
|
||||
});
|
||||
|
||||
const toggleExpand = async () => {
|
||||
isExpanded.value = !isExpanded.value;
|
||||
if (isExpanded.value) {
|
||||
await nextTick();
|
||||
if (containerRef.value) {
|
||||
containerRef.value.style.height = 'auto';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
if (!props.tooltip) return;
|
||||
showTooltip.value = true;
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
showTooltip.value = false;
|
||||
};
|
||||
const toggleExpand = () => (isExpanded.value = !isExpanded.value);
|
||||
|
||||
const renderIssues = computed(() => props.showIssues && props.issues.length);
|
||||
const issuesList = computed(() => props.issues.map((issue) => issue.message).join(', '));
|
||||
|
||||
/**
|
||||
* @see https://popper.js.org/docs/v2/modifiers/#custom-modifiers
|
||||
*/
|
||||
const resizeModifier: Modifier<'resize', {}> = {
|
||||
name: 'resize',
|
||||
enabled: true,
|
||||
phase: 'beforeWrite',
|
||||
requires: ['preventOverflow'],
|
||||
fn({ state }) {
|
||||
const overflow = detectOverflow(state);
|
||||
const MARGIN_RIGHT = 15;
|
||||
|
||||
const maxWidth = state.rects.popper.width - overflow.right - MARGIN_RIGHT;
|
||||
|
||||
state.styles.popper.width = `${maxWidth}px`;
|
||||
},
|
||||
};
|
||||
|
||||
const popperModifiers = [
|
||||
resizeModifier,
|
||||
{ name: 'preventOverflow', options: { boundary: 'document' } },
|
||||
{ name: 'flip', enabled: false }, // prevent the tooltip from flipping
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="containerClass">
|
||||
<slot name="containerPrefix" />
|
||||
<div
|
||||
ref="containerRef"
|
||||
:class="[$style.evaluationStep, small && $style.small]"
|
||||
data-test-id="evaluation-step"
|
||||
@mouseenter="handleMouseEnter"
|
||||
@mouseleave="handleMouseLeave"
|
||||
>
|
||||
<div :class="$style.content">
|
||||
<div :class="containerClass" data-test-id="evaluation-step">
|
||||
<div :class="$style.content">
|
||||
<N8nTooltip
|
||||
placement="right"
|
||||
:disabled="!externalTooltip"
|
||||
:show-arrow="false"
|
||||
:popper-class="$style.evaluationTooltip"
|
||||
:popper-options="{ modifiers: popperModifiers }"
|
||||
:content="tooltip"
|
||||
>
|
||||
<div :class="$style.header" @click="toggleExpand">
|
||||
<h3 :class="$style.title">
|
||||
<span :class="$style.label">
|
||||
<div :class="$style.label">
|
||||
<N8nText bold>
|
||||
<slot v-if="$slots.title" name="title" />
|
||||
<span v-else>{{ title }}</span>
|
||||
<N8nInfoTip
|
||||
v-if="tooltip"
|
||||
:class="$style.infoTip"
|
||||
:bold="true"
|
||||
type="tooltip"
|
||||
theme="info"
|
||||
tooltip-placement="top"
|
||||
>
|
||||
{{ tooltip }}
|
||||
</N8nInfoTip>
|
||||
</span>
|
||||
</h3>
|
||||
<span v-if="renderIssues" :class="$style.warningIcon">
|
||||
<N8nInfoTip :bold="true" type="tooltip" theme="warning" tooltip-placement="right">
|
||||
<template v-else>{{ title }}</template>
|
||||
</N8nText>
|
||||
<N8nInfoTip
|
||||
v-if="!externalTooltip"
|
||||
:class="$style.infoTip"
|
||||
:bold="true"
|
||||
type="tooltip"
|
||||
theme="info"
|
||||
tooltip-placement="top"
|
||||
:enterable="false"
|
||||
>
|
||||
{{ tooltip }}
|
||||
</N8nInfoTip>
|
||||
</div>
|
||||
<div :class="$style.actions">
|
||||
<N8nInfoTip
|
||||
v-if="renderIssues"
|
||||
:bold="true"
|
||||
type="tooltip"
|
||||
theme="warning"
|
||||
tooltip-placement="top"
|
||||
:enterable="false"
|
||||
>
|
||||
{{ issuesList }}
|
||||
</N8nInfoTip>
|
||||
</span>
|
||||
<button
|
||||
v-if="$slots.cardContent"
|
||||
:class="$style.collapseButton"
|
||||
:aria-expanded="isExpanded"
|
||||
data-test-id="evaluation-step-collapse-button"
|
||||
>
|
||||
{{
|
||||
isExpanded
|
||||
? locale.baseText('testDefinition.edit.step.collapse')
|
||||
: locale.baseText('testDefinition.edit.step.configure')
|
||||
}}
|
||||
<font-awesome-icon :icon="isExpanded ? 'angle-down' : 'angle-right'" size="lg" />
|
||||
</button>
|
||||
</div>
|
||||
<ElCollapseTransition v-if="$slots.cardContent">
|
||||
<div v-show="isExpanded" :class="$style.cardContentWrapper">
|
||||
<div
|
||||
ref="contentRef"
|
||||
:class="$style.cardContent"
|
||||
data-test-id="evaluation-step-content"
|
||||
<N8nText
|
||||
v-if="$slots.cardContent"
|
||||
data-test-id="evaluation-step-collapse-button"
|
||||
size="xsmall"
|
||||
:color="hasIssues ? 'primary' : 'text-base'"
|
||||
bold
|
||||
>
|
||||
<div v-if="description" :class="$style.description">{{ description }}</div>
|
||||
<slot name="cardContent" />
|
||||
</div>
|
||||
{{
|
||||
isExpanded
|
||||
? locale.baseText('testDefinition.edit.step.collapse')
|
||||
: locale.baseText('testDefinition.edit.step.configure')
|
||||
}}
|
||||
<font-awesome-icon :icon="isExpanded ? 'angle-up' : 'angle-down'" size="lg" />
|
||||
</N8nText>
|
||||
</div>
|
||||
</ElCollapseTransition>
|
||||
</div>
|
||||
</N8nTooltip>
|
||||
<div v-if="$slots.cardContent && isExpanded" :class="$style.cardContentWrapper">
|
||||
<div :class="$style.cardContent" data-test-id="evaluation-step-content">
|
||||
<N8nText v-if="description" size="small" color="text-light">{{ description }}</N8nText>
|
||||
<slot name="cardContent" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
.wrap {
|
||||
position: relative;
|
||||
}
|
||||
.evaluationStep {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
@@ -140,11 +145,18 @@ const issuesList = computed(() => props.issues.map((issue) => issue.message).joi
|
||||
color: var(--color-text-dark);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
&.small {
|
||||
width: 80%;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.evaluationTooltip {
|
||||
&:global(.el-popper) {
|
||||
background-color: transparent;
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-light);
|
||||
line-height: 1rem;
|
||||
max-width: 25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -171,24 +183,23 @@ const issuesList = computed(() => props.issues.map((issue) => issue.message).joi
|
||||
padding: var(--spacing-s);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: var(--font-weight-bold);
|
||||
font-size: var(--font-size-s);
|
||||
line-height: 1.125rem;
|
||||
}
|
||||
.label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-4xs);
|
||||
}
|
||||
|
||||
.infoTip {
|
||||
opacity: 0;
|
||||
}
|
||||
.evaluationStep:hover .infoTip {
|
||||
opacity: 1;
|
||||
}
|
||||
.warningIcon {
|
||||
color: var(--color-warning);
|
||||
|
||||
.actions {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
gap: var(--spacing-2xs);
|
||||
}
|
||||
|
||||
.cardContent {
|
||||
@@ -196,43 +207,15 @@ const issuesList = computed(() => props.issues.map((issue) => issue.message).joi
|
||||
padding: 0 var(--spacing-s);
|
||||
margin: var(--spacing-s) 0;
|
||||
}
|
||||
.collapseButton {
|
||||
pointer-events: none;
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
font-size: var(--font-size-3xs);
|
||||
color: var(--color-text-base);
|
||||
margin-left: auto;
|
||||
text-wrap: none;
|
||||
overflow: hidden;
|
||||
min-width: fit-content;
|
||||
|
||||
.hasIssues & {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
}
|
||||
.cardContentWrapper {
|
||||
height: max-content;
|
||||
.expanded & {
|
||||
border-top: var(--border-base);
|
||||
}
|
||||
border-top: var(--border-base);
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: var(--font-size-2xs);
|
||||
color: var(--color-text-light);
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
.customTooltip {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
background: var(--color-background-dark);
|
||||
color: var(--color-text-light);
|
||||
padding: var(--spacing-3xs) var(--spacing-2xs);
|
||||
border-radius: var(--border-radius-base);
|
||||
font-size: var(--font-size-2xs);
|
||||
pointer-events: none;
|
||||
.has-issues {
|
||||
/**
|
||||
* This comment is needed or the css module
|
||||
* will interpret as undefined
|
||||
*/
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { useTemplateRef, nextTick } from 'vue';
|
||||
import type { TestMetricRecord } from '@/api/testDefinition.ee';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { N8nInput } from 'n8n-design-system';
|
||||
import { N8nInput, N8nButton, N8nIconButton } from 'n8n-design-system';
|
||||
|
||||
export interface MetricsInputProps {
|
||||
modelValue: Array<Partial<TestMetricRecord>>;
|
||||
@@ -38,62 +38,33 @@ function onDeleteMetric(metric: Partial<TestMetricRecord>, index: number) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="[$style.metrics]">
|
||||
<n8n-input-label
|
||||
:label="locale.baseText('testDefinition.edit.metricsFields')"
|
||||
:bold="false"
|
||||
:class="$style.metricField"
|
||||
<div>
|
||||
<div
|
||||
v-for="(metric, index) in modelValue"
|
||||
:key="index"
|
||||
:class="$style.metricItem"
|
||||
class="mb-xs"
|
||||
>
|
||||
<div :class="$style.metricsContainer">
|
||||
<div v-for="(metric, index) in modelValue" :key="index" :class="$style.metricItem">
|
||||
<N8nInput
|
||||
ref="metric"
|
||||
data-test-id="evaluation-metric-item"
|
||||
:model-value="metric.name"
|
||||
:placeholder="locale.baseText('testDefinition.edit.metricsPlaceholder')"
|
||||
@update:model-value="(value: string) => updateMetric(index, value)"
|
||||
/>
|
||||
<n8n-icon-button icon="trash" type="text" @click="onDeleteMetric(metric, index)" />
|
||||
</div>
|
||||
<n8n-button
|
||||
type="tertiary"
|
||||
:label="locale.baseText('testDefinition.edit.metricsNew')"
|
||||
:class="$style.newMetricButton"
|
||||
@click="addNewMetric"
|
||||
/>
|
||||
</div>
|
||||
</n8n-input-label>
|
||||
<N8nInput
|
||||
ref="metric"
|
||||
data-test-id="evaluation-metric-item"
|
||||
:model-value="metric.name"
|
||||
:placeholder="locale.baseText('testDefinition.edit.metricsPlaceholder')"
|
||||
@update:model-value="(value: string) => updateMetric(index, value)"
|
||||
/>
|
||||
<N8nIconButton icon="trash" type="secondary" text @click="onDeleteMetric(metric, index)" />
|
||||
</div>
|
||||
<N8nButton
|
||||
type="secondary"
|
||||
:label="locale.baseText('testDefinition.edit.metricsNew')"
|
||||
@click="addNewMetric"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
.metricsContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.metricItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.metricField {
|
||||
width: 100%;
|
||||
margin-top: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.metricsDivider {
|
||||
margin-top: var(--spacing-4xs);
|
||||
margin-bottom: var(--spacing-3xs);
|
||||
}
|
||||
|
||||
.newMetricButton {
|
||||
align-self: flex-start;
|
||||
margin-top: var(--spacing-2xs);
|
||||
width: 100%;
|
||||
background-color: var(--color-sticky-code-background);
|
||||
border-color: var(--color-button-secondary-focus-outline);
|
||||
color: var(--color-button-secondary-font);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -9,11 +9,11 @@ import { useI18n } from '@/composables/useI18n';
|
||||
import { useMessage } from '@/composables/useMessage';
|
||||
import { NODE_PINNING_MODAL_KEY } from '@/constants';
|
||||
import type { ITag, ModalState } from '@/Interface';
|
||||
import { N8nButton, N8nTag, N8nText } from 'n8n-design-system';
|
||||
import type { IPinData } from 'n8n-workflow';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
showConfig: boolean;
|
||||
tagsById: Record<string, ITag>;
|
||||
isLoading: boolean;
|
||||
examplePinnedData?: IPinData;
|
||||
@@ -33,14 +33,6 @@ const emit = defineEmits<{
|
||||
}>();
|
||||
|
||||
const locale = useI18n();
|
||||
const activeTooltip = ref<string | null>(null);
|
||||
const tooltipPosition = ref<{
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
right: number;
|
||||
} | null>(null);
|
||||
const tags = defineModel<EvaluationFormState['tags']>('tags', { required: true });
|
||||
|
||||
const renameTag = async () => {
|
||||
@@ -78,55 +70,29 @@ const mockedNodes = defineModel<EvaluationFormState['mockedNodes']>('mockedNodes
|
||||
|
||||
const nodePinningModal = ref<ModalState | null>(null);
|
||||
|
||||
const selectedTag = computed(() => {
|
||||
return props.tagsById[tags.value.value[0]] ?? {};
|
||||
});
|
||||
const selectedTag = computed(() => props.tagsById[tags.value.value[0]] ?? {});
|
||||
|
||||
function openExecutionsView() {
|
||||
emit('openExecutionsViewForTag');
|
||||
}
|
||||
|
||||
function showTooltip(event: MouseEvent, tooltip: string) {
|
||||
const container = event.target as HTMLElement;
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
|
||||
activeTooltip.value = tooltip;
|
||||
tooltipPosition.value = {
|
||||
x: containerRect.right,
|
||||
y: containerRect.top,
|
||||
width: containerRect.width,
|
||||
height: containerRect.height,
|
||||
right: window.innerWidth,
|
||||
};
|
||||
}
|
||||
|
||||
function hideTooltip() {
|
||||
activeTooltip.value = null;
|
||||
tooltipPosition.value = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="[$style.container, { [$style.hidden]: !showConfig }]">
|
||||
<div>
|
||||
<div :class="$style.editForm">
|
||||
<div :class="$style.panelIntro">
|
||||
{{ locale.baseText('testDefinition.edit.step.intro') }}
|
||||
</div>
|
||||
<template v-if="!hasRuns">
|
||||
<N8nText tag="div" color="text-dark" size="large" class="text-center">
|
||||
{{ locale.baseText('testDefinition.edit.step.intro') }}
|
||||
</N8nText>
|
||||
<BlockArrow class="mt-5xs mb-5xs" />
|
||||
</template>
|
||||
|
||||
<!-- Select Executions -->
|
||||
<EvaluationStep
|
||||
:class="[$style.step, $style.reducedSpacing]"
|
||||
:issues="getFieldIssues('tags')"
|
||||
:tooltip="
|
||||
hasRuns ? locale.baseText('testDefinition.edit.step.executions.tooltip') : undefined
|
||||
"
|
||||
@mouseenter="
|
||||
showTooltip($event, locale.baseText('testDefinition.edit.step.executions.tooltip'))
|
||||
"
|
||||
@mouseleave="hideTooltip"
|
||||
:tooltip="locale.baseText('testDefinition.edit.step.executions.tooltip')"
|
||||
:external-tooltip="!hasRuns"
|
||||
>
|
||||
<template #containerPrefix>
|
||||
<BlockArrow :class="[$style.middle, $style.diagramArrow, $style.sm]" />
|
||||
</template>
|
||||
<template #title>
|
||||
{{
|
||||
locale.baseText('testDefinition.edit.step.executions', {
|
||||
@@ -135,95 +101,73 @@ function hideTooltip() {
|
||||
}}
|
||||
</template>
|
||||
<template #cardContent>
|
||||
<div :class="$style.tagInputContainer">
|
||||
<div :class="$style.tagInputTag">
|
||||
<i18n-t keypath="testDefinition.edit.step.tag">
|
||||
<template #tag>
|
||||
<N8nTag :text="selectedTag.name" :clickable="true" @click="renameTag">
|
||||
<template #tag>
|
||||
{{ selectedTag.name }} <font-awesome-icon icon="pen" size="sm" />
|
||||
</template>
|
||||
</N8nTag>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
<div :class="$style.tagInputControls">
|
||||
<n8n-button
|
||||
label="Select executions"
|
||||
type="tertiary"
|
||||
size="small"
|
||||
@click="openExecutionsView"
|
||||
/>
|
||||
</div>
|
||||
<div :class="$style.tagInputTag">
|
||||
<i18n-t keypath="testDefinition.edit.step.tag">
|
||||
<template #tag>
|
||||
<N8nTag :text="selectedTag.name" :clickable="true" @click="renameTag">
|
||||
<template #tag>
|
||||
{{ selectedTag.name }} <font-awesome-icon icon="pen" size="sm" />
|
||||
</template>
|
||||
</N8nTag>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
</template>
|
||||
</EvaluationStep>
|
||||
<!-- Mocked Nodes -->
|
||||
<EvaluationStep
|
||||
:class="$style.step"
|
||||
:title="
|
||||
locale.baseText('testDefinition.edit.step.mockedNodes', {
|
||||
adjustToNumber: mockedNodes?.length ?? 0,
|
||||
})
|
||||
"
|
||||
:small="true"
|
||||
:issues="getFieldIssues('mockedNodes')"
|
||||
:tooltip="hasRuns ? locale.baseText('testDefinition.edit.step.nodes.tooltip') : undefined"
|
||||
@mouseenter="showTooltip($event, locale.baseText('testDefinition.edit.step.nodes.tooltip'))"
|
||||
@mouseleave="hideTooltip"
|
||||
>
|
||||
<template #containerPrefix>
|
||||
<BlockArrow :class="[$style.diagramArrow, $style.right]" />
|
||||
</template>
|
||||
<template #cardContent>
|
||||
<n8n-button
|
||||
size="small"
|
||||
data-test-id="select-nodes-button"
|
||||
:label="locale.baseText('testDefinition.edit.selectNodes')"
|
||||
<N8nButton
|
||||
label="Select executions"
|
||||
type="tertiary"
|
||||
@click="$emit('openPinningModal')"
|
||||
size="small"
|
||||
@click="openExecutionsView"
|
||||
/>
|
||||
</template>
|
||||
</EvaluationStep>
|
||||
<div :class="$style.nestedSteps">
|
||||
<BlockArrow class="mt-5xs mb-5xs" />
|
||||
<div style="display: flex; flex-direction: column">
|
||||
<BlockArrow class="mt-5xs mb-5xs ml-auto mr-2xl" />
|
||||
<!-- Mocked Nodes -->
|
||||
<EvaluationStep
|
||||
:issues="getFieldIssues('mockedNodes')"
|
||||
:tooltip="locale.baseText('testDefinition.edit.step.nodes.tooltip')"
|
||||
:external-tooltip="!hasRuns"
|
||||
>
|
||||
<template #title>
|
||||
{{
|
||||
locale.baseText('testDefinition.edit.step.mockedNodes', {
|
||||
adjustToNumber: mockedNodes?.length ?? 0,
|
||||
})
|
||||
}}
|
||||
<N8nText>({{ locale.baseText('generic.optional') }})</N8nText>
|
||||
</template>
|
||||
<template #cardContent>
|
||||
<N8nButton
|
||||
size="small"
|
||||
data-test-id="select-nodes-button"
|
||||
:label="locale.baseText('testDefinition.edit.selectNodes')"
|
||||
type="tertiary"
|
||||
@click="$emit('openPinningModal')"
|
||||
/>
|
||||
</template>
|
||||
</EvaluationStep>
|
||||
|
||||
<!-- Re-run Executions -->
|
||||
<EvaluationStep
|
||||
:class="$style.step"
|
||||
:title="locale.baseText('testDefinition.edit.step.reRunExecutions')"
|
||||
:small="true"
|
||||
:tooltip="
|
||||
hasRuns ? locale.baseText('testDefinition.edit.step.reRunExecutions.tooltip') : undefined
|
||||
"
|
||||
@mouseenter="
|
||||
showTooltip($event, locale.baseText('testDefinition.edit.step.reRunExecutions.tooltip'))
|
||||
"
|
||||
@mouseleave="hideTooltip"
|
||||
>
|
||||
<template #containerPrefix>
|
||||
<BlockArrow :class="[$style.right, $style.diagramArrow]" />
|
||||
</template>
|
||||
</EvaluationStep>
|
||||
<BlockArrow class="mt-5xs mb-5xs ml-auto mr-2xl" />
|
||||
<!-- Re-run Executions -->
|
||||
<EvaluationStep
|
||||
:title="locale.baseText('testDefinition.edit.step.reRunExecutions')"
|
||||
:tooltip="locale.baseText('testDefinition.edit.step.reRunExecutions.tooltip')"
|
||||
:external-tooltip="!hasRuns"
|
||||
/>
|
||||
<BlockArrow class="mt-5xs mb-5xs ml-auto mr-2xl" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Compare Executions -->
|
||||
<EvaluationStep
|
||||
:class="$style.step"
|
||||
:title="locale.baseText('testDefinition.edit.step.compareExecutions')"
|
||||
:description="locale.baseText('testDefinition.edit.workflowSelectorLabel')"
|
||||
:issues="getFieldIssues('evaluationWorkflow')"
|
||||
:tooltip="
|
||||
hasRuns
|
||||
? locale.baseText('testDefinition.edit.step.compareExecutions.tooltip')
|
||||
: undefined
|
||||
"
|
||||
@mouseenter="
|
||||
showTooltip($event, locale.baseText('testDefinition.edit.step.compareExecutions.tooltip'))
|
||||
"
|
||||
@mouseleave="hideTooltip"
|
||||
:tooltip="locale.baseText('testDefinition.edit.step.compareExecutions.tooltip')"
|
||||
:external-tooltip="!hasRuns"
|
||||
>
|
||||
<template #containerPrefix>
|
||||
<BlockArrow :class="[$style.right, $style.diagramArrow]" />
|
||||
<BlockArrow :class="[$style.left, $style.diagramArrow, $style.lg]" />
|
||||
</template>
|
||||
<template #cardContent>
|
||||
<WorkflowSelector
|
||||
v-model="evaluationWorkflow"
|
||||
@@ -235,25 +179,20 @@ function hideTooltip() {
|
||||
</template>
|
||||
</EvaluationStep>
|
||||
|
||||
<BlockArrow class="mt-5xs mb-5xs" />
|
||||
<!-- Metrics -->
|
||||
<EvaluationStep
|
||||
:class="$style.step"
|
||||
:title="locale.baseText('testDefinition.edit.step.metrics')"
|
||||
:issues="getFieldIssues('metrics')"
|
||||
:description="locale.baseText('testDefinition.edit.step.metrics.description')"
|
||||
:tooltip="hasRuns ? locale.baseText('testDefinition.edit.step.metrics.tooltip') : undefined"
|
||||
@mouseenter="
|
||||
showTooltip($event, locale.baseText('testDefinition.edit.step.metrics.tooltip'))
|
||||
"
|
||||
@mouseleave="hideTooltip"
|
||||
:tooltip="locale.baseText('testDefinition.edit.step.metrics.tooltip')"
|
||||
:external-tooltip="!hasRuns"
|
||||
>
|
||||
<template #containerPrefix>
|
||||
<BlockArrow :class="[$style.middle, $style.diagramArrow]" />
|
||||
</template>
|
||||
<template #cardContent>
|
||||
<MetricsInput
|
||||
v-model="metrics"
|
||||
:class="{ 'has-issues': getFieldIssues('metrics').length > 0 }"
|
||||
class="mt-xs"
|
||||
@delete-metric="(metric) => emit('deleteMetric', metric)"
|
||||
/>
|
||||
</template>
|
||||
@@ -261,115 +200,25 @@ function hideTooltip() {
|
||||
</div>
|
||||
<Modal ref="nodePinningModal" width="80vw" height="85vh" :name="NODE_PINNING_MODAL_KEY">
|
||||
<template #header>
|
||||
<N8nHeading size="large" :bold="true">{{
|
||||
locale.baseText('testDefinition.edit.selectNodes')
|
||||
}}</N8nHeading>
|
||||
<N8nHeading size="large" :bold="true">
|
||||
{{ locale.baseText('testDefinition.edit.selectNodes') }}
|
||||
</N8nHeading>
|
||||
<br />
|
||||
<N8nText :class="$style.modalDescription">{{
|
||||
locale.baseText('testDefinition.edit.modal.description')
|
||||
}}</N8nText>
|
||||
<N8nText>
|
||||
{{ locale.baseText('testDefinition.edit.modal.description') }}
|
||||
</N8nText>
|
||||
</template>
|
||||
<template #content>
|
||||
<NodesPinning v-model="mockedNodes" data-test-id="nodes-pinning-modal" />
|
||||
</template>
|
||||
</Modal>
|
||||
|
||||
<div
|
||||
v-if="tooltipPosition && !hasRuns"
|
||||
:class="$style.customTooltip"
|
||||
:style="{
|
||||
left: `${tooltipPosition.x}px`,
|
||||
top: `${tooltipPosition.y}px`,
|
||||
width: `${tooltipPosition.right - tooltipPosition.x}px`,
|
||||
height: `${tooltipPosition.height}px`,
|
||||
}"
|
||||
>
|
||||
{{ activeTooltip }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
.container {
|
||||
overflow-y: auto;
|
||||
overflow-x: visible;
|
||||
width: auto;
|
||||
margin-left: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.editForm {
|
||||
width: var(--evaluation-edit-panel-width);
|
||||
.nestedSteps {
|
||||
display: grid;
|
||||
height: fit-content;
|
||||
flex-shrink: 0;
|
||||
padding-bottom: var(--spacing-l);
|
||||
transition: width 0.2s ease;
|
||||
position: relative;
|
||||
gap: var(--spacing-l);
|
||||
margin: 0 auto;
|
||||
|
||||
&.hidden {
|
||||
margin-left: 0;
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
.noRuns & {
|
||||
overflow-y: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.customTooltip {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
padding: var(--spacing-xs);
|
||||
max-width: 25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-light);
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
.panelIntro {
|
||||
font-size: var(--font-size-m);
|
||||
color: var(--color-text-dark);
|
||||
|
||||
justify-self: center;
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.diagramArrow {
|
||||
--arrow-height: 4rem;
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: var(--spacing-2xl);
|
||||
z-index: 0;
|
||||
// increase hover radius of the arrow
|
||||
&.right {
|
||||
left: unset;
|
||||
right: var(--spacing-2xl);
|
||||
}
|
||||
&.middle {
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
&.sm {
|
||||
--arrow-height: 1.5rem;
|
||||
}
|
||||
|
||||
&.lg {
|
||||
--arrow-height: 14rem;
|
||||
}
|
||||
}
|
||||
|
||||
.tagInputContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-xs);
|
||||
grid-template-columns: 20% 1fr;
|
||||
}
|
||||
|
||||
.tagInputTag {
|
||||
@@ -377,9 +226,6 @@ function hideTooltip() {
|
||||
gap: var(--spacing-3xs);
|
||||
font-size: var(--font-size-2xs);
|
||||
color: var(--color-text-base);
|
||||
}
|
||||
.tagInputControls {
|
||||
display: flex;
|
||||
gap: var(--spacing-2xs);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -117,7 +117,7 @@ const statusRender = computed<IconDefinition & { label: string }>(() => {
|
||||
{{ key }}
|
||||
</N8nText>
|
||||
<N8nText color="text-base" size="small" bold>
|
||||
{{ value }}
|
||||
{{ Math.round((value + Number.EPSILON) * 100) / 100 }}
|
||||
</N8nText>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -19,7 +19,7 @@ describe('MetricsInput', () => {
|
||||
|
||||
it('should render correctly with initial metrics', () => {
|
||||
const { getAllByPlaceholderText } = renderComponent({ props });
|
||||
const inputs = getAllByPlaceholderText('Enter metric name');
|
||||
const inputs = getAllByPlaceholderText('e.g. latency');
|
||||
expect(inputs).toHaveLength(2);
|
||||
expect(inputs[0]).toHaveValue('Metric 1');
|
||||
expect(inputs[1]).toHaveValue('Metric 2');
|
||||
@@ -31,7 +31,7 @@ describe('MetricsInput', () => {
|
||||
modelValue: [{ name: '' }],
|
||||
},
|
||||
});
|
||||
const inputs = getAllByPlaceholderText('Enter metric name');
|
||||
const inputs = getAllByPlaceholderText('e.g. latency');
|
||||
await userEvent.type(inputs[0], 'Updated Metric 1');
|
||||
|
||||
// Every character typed triggers an update event. Let's check the last emission.
|
||||
@@ -88,7 +88,7 @@ describe('MetricsInput', () => {
|
||||
modelValue: [{ name: '' }],
|
||||
},
|
||||
});
|
||||
const inputs = getAllByPlaceholderText('Enter metric name');
|
||||
const inputs = getAllByPlaceholderText('e.g. latency');
|
||||
await userEvent.type(inputs[0], 'ABC');
|
||||
|
||||
const allEmits = emitted('update:modelValue');
|
||||
@@ -117,7 +117,7 @@ describe('MetricsInput', () => {
|
||||
const { getAllByPlaceholderText } = renderComponent({
|
||||
props: { modelValue: [{ name: '' }] },
|
||||
});
|
||||
const updatedInputs = getAllByPlaceholderText('Enter metric name');
|
||||
const updatedInputs = getAllByPlaceholderText('e.g. latency');
|
||||
expect(updatedInputs).toHaveLength(1);
|
||||
});
|
||||
|
||||
@@ -125,7 +125,7 @@ describe('MetricsInput', () => {
|
||||
const { getAllByPlaceholderText, getAllByRole, rerender, emitted } = renderComponent({
|
||||
props,
|
||||
});
|
||||
const inputs = getAllByPlaceholderText('Enter metric name');
|
||||
const inputs = getAllByPlaceholderText('e.g. latency');
|
||||
expect(inputs).toHaveLength(2);
|
||||
|
||||
const deleteButtons = getAllByRole('button', { name: '' });
|
||||
@@ -135,7 +135,7 @@ describe('MetricsInput', () => {
|
||||
expect(emitted('deleteMetric')[0]).toEqual([props.modelValue[0]]);
|
||||
|
||||
await rerender({ modelValue: [{ name: 'Metric 2' }] });
|
||||
const updatedInputs = getAllByPlaceholderText('Enter metric name');
|
||||
const updatedInputs = getAllByPlaceholderText('e.g. latency');
|
||||
expect(updatedInputs).toHaveLength(1);
|
||||
expect(updatedInputs[0]).toHaveValue('Metric 2');
|
||||
});
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
"generic.tag_plural": "Tags",
|
||||
"generic.tag": "Tag | {count} Tags",
|
||||
"generic.tests": "Tests",
|
||||
"generic.optional": "optional",
|
||||
"generic.or": "or",
|
||||
"generic.clickToCopy": "Click to copy",
|
||||
"generic.copiedToClipboard": "Copied to clipboard",
|
||||
@@ -2819,7 +2820,7 @@
|
||||
"testDefinition.edit.metricsTitle": "Metrics",
|
||||
"testDefinition.edit.metricsHelpText": "The output field of the last node in the evaluation workflow. Metrics will be averaged across all test cases.",
|
||||
"testDefinition.edit.metricsFields": "Output fields to use as metrics",
|
||||
"testDefinition.edit.metricsPlaceholder": "Enter metric name",
|
||||
"testDefinition.edit.metricsPlaceholder": "e.g. latency",
|
||||
"testDefinition.edit.metricsNew": "New metric",
|
||||
"testDefinition.edit.selectTag": "Select tag...",
|
||||
"testDefinition.edit.tagsHelpText": "Executions with this tag will be added as test cases to this test.",
|
||||
@@ -2862,7 +2863,7 @@
|
||||
"testDefinition.edit.pastRuns.total": "No runs | Past run ({count}) | Past runs ({count})",
|
||||
"testDefinition.edit.nodesPinning.pinButtonTooltip": "Use benchmark data for this node during evaluation execution",
|
||||
"testDefinition.edit.saving": "Saving...",
|
||||
"testDefinition.edit.saved": "Changes saved",
|
||||
"testDefinition.edit.saved": "Test saved",
|
||||
"testDefinition.list.testDeleted": "Test deleted",
|
||||
"testDefinition.list.tests": "Tests",
|
||||
"testDefinition.list.evaluations": "Evaluation",
|
||||
|
||||
@@ -173,13 +173,13 @@ function onEvaluationWorkflowCreated(workflowId: string) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="!isLoading" :class="[$style.container, { [$style.noRuns]: !hasRuns }]">
|
||||
<div v-if="!isLoading" :class="[$style.container]">
|
||||
<div :class="$style.header">
|
||||
<div style="display: flex; align-items: center">
|
||||
<N8nIconButton
|
||||
icon="arrow-left"
|
||||
type="tertiary"
|
||||
:class="$style.arrowBack"
|
||||
text
|
||||
@click="router.push({ name: VIEWS.TEST_DEFINITION, params: { testId } })"
|
||||
></N8nIconButton>
|
||||
<InlineNameEdit
|
||||
@@ -232,17 +232,17 @@ function onEvaluationWorkflowCreated(workflowId: string) {
|
||||
:maxlength="260"
|
||||
max-height="none"
|
||||
type="Test description"
|
||||
:class="$style.editDescription"
|
||||
@update:model-value="updateDescription"
|
||||
>
|
||||
<N8nText size="small" color="text-base">{{ state.description.value }}</N8nText>
|
||||
<N8nText size="medium" color="text-base">{{ state.description.value }}</N8nText>
|
||||
</InlineNameEdit>
|
||||
</div>
|
||||
|
||||
<div :class="$style.content">
|
||||
<div :class="{ [$style.content]: true, [$style.contentWithRuns]: hasRuns }">
|
||||
<RunsSection
|
||||
v-if="runs.length > 0"
|
||||
v-if="hasRuns"
|
||||
v-model:selectedMetric="selectedMetric"
|
||||
:class="$style.runs"
|
||||
:runs="runs"
|
||||
:test-id="testId"
|
||||
:applied-theme="appliedTheme"
|
||||
@@ -250,12 +250,13 @@ function onEvaluationWorkflowCreated(workflowId: string) {
|
||||
/>
|
||||
|
||||
<ConfigSection
|
||||
v-if="showConfig"
|
||||
v-model:tags="state.tags"
|
||||
v-model:evaluationWorkflow="state.evaluationWorkflow"
|
||||
v-model:metrics="state.metrics"
|
||||
v-model:mockedNodes="state.mockedNodes"
|
||||
:class="$style.config"
|
||||
:cancel-editing="cancelEditing"
|
||||
:show-config="showConfig"
|
||||
:tags-by-id="tagsById"
|
||||
:is-loading="isLoading"
|
||||
:get-field-issues="getFieldIssues"
|
||||
@@ -282,8 +283,16 @@ function onEvaluationWorkflowCreated(workflowId: string) {
|
||||
.content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
gap: var(--spacing-m);
|
||||
padding-bottom: var(--spacing-m);
|
||||
}
|
||||
|
||||
.config {
|
||||
width: 480px;
|
||||
|
||||
.contentWithRuns & {
|
||||
width: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
|
||||
@@ -29,19 +29,24 @@ const toast = useToast();
|
||||
const locale = useI18n();
|
||||
const { confirm } = useMessage();
|
||||
|
||||
const { state: tests, isLoading } = useAsyncState(
|
||||
const { isLoading } = useAsyncState(
|
||||
async () => {
|
||||
await testDefinitionStore.fetchAll({ workflowId: props.name });
|
||||
|
||||
const response = testDefinitionStore.allTestDefinitionsByWorkflowId[props.name] ?? [];
|
||||
response.forEach((test) => testDefinitionStore.updateRunFieldIssues(test.id));
|
||||
|
||||
return response;
|
||||
return [];
|
||||
},
|
||||
[],
|
||||
{ onError: (error) => toast.showError(error, locale.baseText('testDefinition.list.loadError')) },
|
||||
{
|
||||
onError: (error) => toast.showError(error, locale.baseText('testDefinition.list.loadError')),
|
||||
shallow: false,
|
||||
},
|
||||
);
|
||||
|
||||
const tests = computed(() => testDefinitionStore.allTestDefinitionsByWorkflowId[props.name]);
|
||||
|
||||
const listItems = computed(() =>
|
||||
orderBy(tests.value, [(test) => new Date(test.updatedAt ?? test.createdAt)], ['desc']).map(
|
||||
(test) => ({
|
||||
|
||||
@@ -134,11 +134,10 @@ describe('TestDefinitionListView', () => {
|
||||
it('should delete test and show success message', async () => {
|
||||
const testDefinitionStore = mockedStore(useTestDefinitionStore);
|
||||
testDefinitionStore.allTestDefinitionsByWorkflowId[workflowId] = mockTestDefinitions;
|
||||
testDefinitionStore.startTestRun.mockRejectedValueOnce(new Error('Run failed'));
|
||||
|
||||
message.confirm.mockResolvedValueOnce(MODAL_CONFIRM);
|
||||
|
||||
const { getByTestId } = renderComponent({
|
||||
const { getByTestId, queryByTestId } = renderComponent({
|
||||
props: { name: workflowId },
|
||||
});
|
||||
|
||||
@@ -157,6 +156,17 @@ describe('TestDefinitionListView', () => {
|
||||
|
||||
expect(testDefinitionStore.deleteById).toHaveBeenCalledWith(testToDelete);
|
||||
expect(toast.showMessage).toHaveBeenCalledWith(expect.objectContaining({ type: 'success' }));
|
||||
|
||||
/**
|
||||
* since the actions are mocked by default,
|
||||
* double check the UI updates correctly
|
||||
*/
|
||||
testDefinitionStore.allTestDefinitionsByWorkflowId = {
|
||||
[workflowId]: [mockTestDefinitions[1], mockTestDefinitions[2]],
|
||||
};
|
||||
await waitFor(() =>
|
||||
expect(queryByTestId(`test-actions-${testToDelete}`)).not.toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
|
||||
it('should sort tests by updated date in descending order', async () => {
|
||||
|
||||
Reference in New Issue
Block a user