mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-20 03:12:15 +00:00
fix(editor): Don't render now when startedAt is null (#15283)
This commit is contained in:
@@ -157,4 +157,48 @@ describe('GlobalExecutionsListItem', () => {
|
||||
|
||||
expect(globalExecutionsListItemQueuedTooltipRenderSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vitest.useRealTimers();
|
||||
});
|
||||
|
||||
it('uses `createdAt` to calculate running time if `startedAt` is undefined', async () => {
|
||||
const createdAt = new Date('2024-09-27T12:00:00Z');
|
||||
const now = new Date('2024-09-27T12:30:00Z');
|
||||
vitest.useFakeTimers({ now });
|
||||
const { getByTestId } = renderComponent({
|
||||
props: {
|
||||
execution: { status: 'running', id: 123, workflowName: 'Test Workflow', createdAt },
|
||||
workflowPermissions: {},
|
||||
concurrencyCap: 5,
|
||||
},
|
||||
});
|
||||
|
||||
const executionTimeElement = getByTestId('execution-time');
|
||||
expect(executionTimeElement).toBeVisible();
|
||||
expect(executionTimeElement.textContent).toBe('-1727438401s');
|
||||
});
|
||||
|
||||
it('uses `createdAt` to calculate running time if `startedAt` is undefined and `stoppedAt` is defined', async () => {
|
||||
const createdAt = new Date('2024-09-27T12:00:00Z');
|
||||
const now = new Date('2024-09-27T12:30:00Z');
|
||||
vitest.useFakeTimers({ now });
|
||||
const { getByTestId } = renderComponent({
|
||||
props: {
|
||||
execution: {
|
||||
status: 'running',
|
||||
id: 123,
|
||||
workflowName: 'Test Workflow',
|
||||
createdAt,
|
||||
stoppedAt: now,
|
||||
},
|
||||
workflowPermissions: {},
|
||||
concurrencyCap: 5,
|
||||
},
|
||||
});
|
||||
|
||||
const executionTimeElement = getByTestId('execution-time');
|
||||
expect(executionTimeElement).toBeVisible();
|
||||
expect(executionTimeElement.textContent).toBe('30:00m');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -130,7 +130,7 @@ const formattedStoppedAtDate = computed(() => {
|
||||
return props.execution.stoppedAt
|
||||
? locale.displayTimer(
|
||||
new Date(props.execution.stoppedAt).getTime() -
|
||||
new Date(props.execution.startedAt).getTime(),
|
||||
new Date(props.execution.startedAt ?? props.execution.createdAt).getTime(),
|
||||
true,
|
||||
)
|
||||
: '';
|
||||
@@ -233,11 +233,11 @@ async function handleActionItemClick(commandData: Command) {
|
||||
<td>
|
||||
{{ formattedStartedAtDate }}
|
||||
</td>
|
||||
<td>
|
||||
<td data-test-id="execution-time">
|
||||
<template v-if="formattedStoppedAtDate">
|
||||
{{ formattedStoppedAtDate }}
|
||||
</template>
|
||||
<ExecutionsTime v-else :start-time="execution.startedAt" />
|
||||
<ExecutionsTime v-else :start-time="execution.startedAt ?? execution.createdAt" />
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="execution.id">{{ execution.id }}</span>
|
||||
|
||||
@@ -172,4 +172,29 @@ describe('WorkflowExecutionsCard', () => {
|
||||
expect(executionTimeElement).toBeVisible();
|
||||
expect(executionTimeElement.textContent).toBe('27 Sep - Starting soon');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vitest.useRealTimers();
|
||||
});
|
||||
|
||||
test('uses `createdAt` to calculate running time if `startedAt` is undefined', () => {
|
||||
const createdAt = new Date('2024-09-27T12:00:00Z');
|
||||
const now = new Date('2024-09-27T12:30:00Z');
|
||||
vitest.useFakeTimers({ now });
|
||||
const props = {
|
||||
execution: {
|
||||
id: '1',
|
||||
mode: 'webhook',
|
||||
status: 'running',
|
||||
createdAt: createdAt.toISOString(),
|
||||
},
|
||||
workflowPermissions: { execute: true },
|
||||
};
|
||||
|
||||
const { getByTestId } = renderComponent({ props });
|
||||
|
||||
const executionTimeElement = getByTestId('execution-time-in-status');
|
||||
expect(executionTimeElement).toBeVisible();
|
||||
expect(executionTimeElement.textContent).toBe('for -1727438401s');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -108,9 +108,11 @@ function onRetryMenuItemSelect(action: string): void {
|
||||
v-if="executionUIDetails.name === 'running'"
|
||||
:color="isActive ? 'text-dark' : 'text-base'"
|
||||
size="small"
|
||||
data-test-id="execution-time-in-status"
|
||||
>
|
||||
{{ locale.baseText('executionDetails.runningTimeRunning') }}
|
||||
<ExecutionsTime :start-time="execution.startedAt" />
|
||||
<!-- Just here to make typescript happy, since `startedAt` will always be defined for running executions -->
|
||||
<ExecutionsTime :start-time="execution.startedAt ?? execution.createdAt" />
|
||||
</N8nText>
|
||||
<N8nText
|
||||
v-if="executionUIDetails.name === 'new' && execution.createdAt"
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useExecutionHelpers } from '@/composables/useExecutionHelpers';
|
||||
import type { ExecutionSummary } from 'n8n-workflow';
|
||||
import { i18n } from '@/plugins/i18n';
|
||||
import { convertToDisplayDate } from '@/utils/formatters/dateFormatter';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
|
||||
const { resolve, track } = vi.hoisted(() => ({
|
||||
resolve: vi.fn(),
|
||||
@@ -48,6 +49,21 @@ describe('useExecutionHelpers()', () => {
|
||||
expect(uiDetails.runningTime).toEqual('0s');
|
||||
},
|
||||
);
|
||||
|
||||
it('use `createdAt` if `startedAt` is null', async () => {
|
||||
const date = new Date('2025-01-01T00:00:00.000Z');
|
||||
const execution = mock<ExecutionSummary>({
|
||||
id: '1',
|
||||
startedAt: null,
|
||||
createdAt: date,
|
||||
stoppedAt: date,
|
||||
status: 'error',
|
||||
});
|
||||
const { getUIDetails } = useExecutionHelpers();
|
||||
const uiDetails = getUIDetails(execution);
|
||||
|
||||
expect(uiDetails.startTime).toEqual('Jan 1, 00:00:00');
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatDate()', () => {
|
||||
|
||||
@@ -25,7 +25,7 @@ export function useExecutionHelpers() {
|
||||
const status = {
|
||||
name: 'unknown',
|
||||
createdAt: execution.createdAt?.toString() ?? '',
|
||||
startTime: formatDate(execution.startedAt),
|
||||
startTime: formatDate(execution.startedAt ?? execution.createdAt),
|
||||
label: 'Status unknown',
|
||||
runningTime: '',
|
||||
showTimestamp: true,
|
||||
|
||||
@@ -58,12 +58,13 @@ describe('executions.store', () => {
|
||||
});
|
||||
|
||||
it('should delete executions started before given date', async () => {
|
||||
const deleteBefore = mockExecutions[1].startedAt;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const deleteBefore = mockExecutions[1].startedAt!;
|
||||
await executionsStore.deleteExecutions({ deleteBefore });
|
||||
|
||||
expect(executionsStore.executions.length).toBe(2);
|
||||
executionsStore.executions.forEach(({ startedAt }) =>
|
||||
expect(startedAt.getTime()).toBeGreaterThanOrEqual(deleteBefore.getTime()),
|
||||
expect(startedAt?.getTime()).toBeGreaterThanOrEqual(deleteBefore.getTime()),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ export const useExecutionsStore = defineStore('executions', () => {
|
||||
|
||||
const currentExecutionsById = ref<Record<string, ExecutionSummaryWithScopes>>({});
|
||||
const startedAtSortFn = (a: ExecutionSummary, b: ExecutionSummary) =>
|
||||
new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime();
|
||||
new Date(b.startedAt ?? b.createdAt).getTime() - new Date(a.startedAt ?? a.createdAt).getTime();
|
||||
|
||||
/**
|
||||
* Prioritize `running` over `new` executions, then sort by start timestamp.
|
||||
@@ -268,7 +268,7 @@ export const useExecutionsStore = defineStore('executions', () => {
|
||||
if (sendData.deleteBefore) {
|
||||
const deleteBefore = new Date(sendData.deleteBefore);
|
||||
allExecutions.value.forEach((execution) => {
|
||||
if (new Date(execution.startedAt) < deleteBefore) {
|
||||
if (new Date(execution.startedAt ?? execution.createdAt) < deleteBefore) {
|
||||
removeExecution(execution.id);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user