mirror of
https://github.com/n8n-io/n8n.git
synced 2026-09-24 23:22:38 +08:00
fix(editor): Fix displaying of workflow publish info (no-changelog) (#22605)
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
SharedWorkflowRepository,
|
||||
WorkflowRepository,
|
||||
WorkflowHistoryRepository,
|
||||
WorkflowPublishHistoryRepository,
|
||||
} from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import type { WorkflowSharingRole } from '@n8n/permissions';
|
||||
@@ -284,5 +285,15 @@ export async function createActiveWorkflow(
|
||||
await setActiveVersion(workflow.id, workflow.versionId);
|
||||
|
||||
workflow.activeVersionId = workflow.versionId;
|
||||
|
||||
if (userOrProject instanceof User) {
|
||||
await Container.get(WorkflowPublishHistoryRepository).save({
|
||||
workflowId: workflow.id,
|
||||
versionId: workflow.versionId,
|
||||
event: 'activated',
|
||||
userId: userOrProject.id,
|
||||
});
|
||||
}
|
||||
|
||||
return workflow;
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ export class SharedWorkflowRepository extends Repository<SharedWorkflow> {
|
||||
shared: { project: { projectRelations: { user: true } } },
|
||||
tags: includeTags,
|
||||
parentFolder: includeParentFolder,
|
||||
activeVersion: includeActiveVersion,
|
||||
activeVersion: includeActiveVersion ? { workflowPublishHistory: true } : false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -552,26 +552,26 @@ export class WorkflowService {
|
||||
updatedAt: workflow.updatedAt,
|
||||
});
|
||||
|
||||
const updatedWorkflow = await this.workflowRepository.findOne({
|
||||
const workflowForActivation = await this.workflowRepository.findOne({
|
||||
where: { id: workflowId },
|
||||
relations: ['activeVersion'],
|
||||
});
|
||||
|
||||
if (!updatedWorkflow) {
|
||||
if (!workflowForActivation) {
|
||||
throw new NotFoundError(`Workflow with ID "${workflowId}" could not be found.`);
|
||||
}
|
||||
|
||||
this.eventService.emit('workflow-activated', {
|
||||
user,
|
||||
workflowId,
|
||||
workflow: updatedWorkflow,
|
||||
workflow: workflowForActivation,
|
||||
publicApi,
|
||||
});
|
||||
|
||||
await this._addToActiveWorkflowManager(
|
||||
user,
|
||||
workflowId,
|
||||
updatedWorkflow,
|
||||
workflowForActivation,
|
||||
activationMode,
|
||||
publicApi,
|
||||
);
|
||||
@@ -583,6 +583,20 @@ export class WorkflowService {
|
||||
await this.workflowHistoryService.updateVersion(versionToActivate, workflowId, updateFields);
|
||||
}
|
||||
|
||||
// Fetch workflow again with workflowPublishHistory after activation to include the new entry
|
||||
const updatedWorkflow = await this.workflowRepository.findOne({
|
||||
where: { id: workflowId },
|
||||
relations: {
|
||||
activeVersion: {
|
||||
workflowPublishHistory: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!updatedWorkflow) {
|
||||
throw new NotFoundError(`Workflow with ID "${workflowId}" could not be found.`);
|
||||
}
|
||||
|
||||
return updatedWorkflow;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@ import { createWorkflowHistoryItem } from '../shared/db/workflow-history';
|
||||
|
||||
let globalConfig: GlobalConfig;
|
||||
let workflowService: WorkflowService;
|
||||
let workflowPublishHistoryRepository: WorkflowPublishHistoryRepository;
|
||||
const activeWorkflowManager = mockInstance(ActiveWorkflowManager);
|
||||
const workflowHistoryService = mockInstance(WorkflowHistoryService);
|
||||
const workflowPublishHistoryRepository = mockInstance(WorkflowPublishHistoryRepository);
|
||||
mockInstance(MessageEventBus);
|
||||
mockInstance(Telemetry);
|
||||
|
||||
@@ -32,6 +32,7 @@ beforeAll(async () => {
|
||||
await testDb.init();
|
||||
|
||||
globalConfig = Container.get(GlobalConfig);
|
||||
workflowPublishHistoryRepository = Container.get(WorkflowPublishHistoryRepository);
|
||||
workflowService = new WorkflowService(
|
||||
mock(),
|
||||
Container.get(SharedWorkflowRepository),
|
||||
@@ -56,7 +57,7 @@ beforeAll(async () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await testDb.truncate(['WorkflowEntity', 'WorkflowHistory']);
|
||||
await testDb.truncate(['WorkflowEntity', 'WorkflowHistory', 'WorkflowPublishHistory']);
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
@@ -129,6 +130,12 @@ describe('activateWorkflow()', () => {
|
||||
|
||||
expect(updatedWorkflow.active).toBe(true);
|
||||
expect(updatedWorkflow.activeVersionId).toBe(workflow.versionId);
|
||||
expect(updatedWorkflow.activeVersion).toBeDefined();
|
||||
expect(updatedWorkflow.activeVersion?.workflowPublishHistory).toHaveLength(1);
|
||||
expect(updatedWorkflow.activeVersion?.workflowPublishHistory[0]).toMatchObject({
|
||||
event: 'activated',
|
||||
versionId: workflow.versionId,
|
||||
});
|
||||
expect(addRecordSpy).toBeCalledWith({
|
||||
event: 'activated',
|
||||
workflowId: workflow.id,
|
||||
@@ -153,6 +160,11 @@ describe('activateWorkflow()', () => {
|
||||
expect(updatedWorkflow.active).toBe(true);
|
||||
expect(updatedWorkflow.activeVersionId).toBe(newVersionId);
|
||||
expect(updatedWorkflow.versionId).toBe(workflow.versionId);
|
||||
expect(updatedWorkflow.activeVersion?.workflowPublishHistory).toHaveLength(1);
|
||||
expect(updatedWorkflow.activeVersion?.workflowPublishHistory[0]).toMatchObject({
|
||||
event: 'activated',
|
||||
versionId: newVersionId,
|
||||
});
|
||||
|
||||
expect(addRecordSpy).toBeCalledWith({
|
||||
event: 'activated',
|
||||
|
||||
@@ -591,7 +591,7 @@ describe('GET /workflows/:workflowId', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('should return active version', async () => {
|
||||
test('should return active version with workflowPublishHistory', async () => {
|
||||
const workflow = await createActiveWorkflow({}, owner);
|
||||
|
||||
const response = await authOwnerAgent.get(`/workflows/${workflow.id}`).expect(200);
|
||||
@@ -603,6 +603,11 @@ describe('GET /workflows/:workflowId', () => {
|
||||
versionId: workflow.activeVersionId,
|
||||
workflowId: workflow.id,
|
||||
});
|
||||
expect(activeVersion.workflowPublishHistory).toHaveLength(1);
|
||||
expect(activeVersion.workflowPublishHistory[0]).toMatchObject({
|
||||
event: 'activated',
|
||||
versionId: workflow.activeVersionId,
|
||||
});
|
||||
});
|
||||
|
||||
test('should return parent folder', async () => {
|
||||
@@ -2755,6 +2760,11 @@ describe('POST /workflows/:workflowId/activate', () => {
|
||||
expect(data.id).toBe(workflow.id);
|
||||
expect(data.activeVersionId).toBe(newVersionId);
|
||||
expect(data.activeVersion.versionId).toBe(newVersionId);
|
||||
expect(data.activeVersion.workflowPublishHistory).toHaveLength(1);
|
||||
expect(data.activeVersion.workflowPublishHistory[0]).toMatchObject({
|
||||
event: 'activated',
|
||||
versionId: newVersionId,
|
||||
});
|
||||
|
||||
expect(addRecordSpy).toBeCalledWith({
|
||||
event: 'activated',
|
||||
|
||||
+52
@@ -134,6 +134,58 @@ describe('WorkflowHeaderDraftPublishActions', () => {
|
||||
|
||||
expect(getByTestId('workflow-active-version-indicator')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should use latest activation date from workflowPublishHistory when available', () => {
|
||||
const oldDate = '2024-01-01T00:00:00.000Z';
|
||||
const latestActivationDate = '2024-06-15T10:30:00.000Z';
|
||||
workflowsStore.workflow.activeVersion = {
|
||||
...createMockActiveVersion('active-version-1'),
|
||||
createdAt: oldDate,
|
||||
workflowPublishHistory: [
|
||||
{
|
||||
id: 1,
|
||||
createdAt: oldDate,
|
||||
event: 'activated' as const,
|
||||
userId: 'user-1',
|
||||
versionId: 'active-version-1',
|
||||
workflowId: '1',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
createdAt: '2024-03-01T00:00:00.000Z',
|
||||
event: 'deactivated' as const,
|
||||
userId: 'user-1',
|
||||
versionId: 'active-version-1',
|
||||
workflowId: '1',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
createdAt: latestActivationDate,
|
||||
event: 'activated' as const,
|
||||
userId: 'user-1',
|
||||
versionId: 'active-version-1',
|
||||
workflowId: '1',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const { getByTestId } = renderComponent({
|
||||
global: {
|
||||
stubs: {
|
||||
N8nTooltip: {
|
||||
template: '<div><slot name="content" /></div>',
|
||||
},
|
||||
TimeAgo: {
|
||||
props: ['date'],
|
||||
template: '<div data-test-id="time-ago-stub">{{ date }}</div>',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(getByTestId('workflow-active-version-indicator')).toBeInTheDocument();
|
||||
expect(getByTestId('time-ago-stub')).toHaveTextContent(latestActivationDate);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Publish button behavior', () => {
|
||||
|
||||
+7
-1
@@ -15,6 +15,7 @@ import TimeAgo from '@/app/components/TimeAgo.vue';
|
||||
import { getActivatableTriggerNodes } from '@/app/utils/nodeTypesUtils';
|
||||
import { useWorkflowSaving } from '@/app/composables/useWorkflowSaving';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { getLastPublishedByUser } from '@/features/workflows/workflowHistory/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
readOnly?: boolean;
|
||||
@@ -88,6 +89,11 @@ const showPublishIndicator = computed(() => {
|
||||
|
||||
const activeVersion = computed(() => workflowsStore.workflow.activeVersion);
|
||||
|
||||
const latestPublishDate = computed(() => {
|
||||
const latestPublish = getLastPublishedByUser(activeVersion.value?.workflowPublishHistory ?? []);
|
||||
return latestPublish?.createdAt;
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
importFileRef,
|
||||
});
|
||||
@@ -103,7 +109,7 @@ defineExpose({
|
||||
<N8nTooltip>
|
||||
<template #content>
|
||||
{{ activeVersion.name }}<br />{{ i18n.baseText('workflowHistory.item.active') }}
|
||||
<TimeAgo :date="activeVersion.createdAt" />
|
||||
<TimeAgo v-if="latestPublishDate" :date="latestPublishDate" />
|
||||
</template>
|
||||
<N8nIcon icon="circle-check" color="success" size="xlarge" :class="$style.icon" />
|
||||
</N8nTooltip>
|
||||
|
||||
+12
-4
@@ -279,13 +279,21 @@ const publishWorkflowVersion = (id: WorkflowVersionId, data: WorkflowHistoryActi
|
||||
const publishEventBus = createEventBus<WorkflowHistoryPublishModalEventBusEvents>();
|
||||
|
||||
publishEventBus.once('publish', (publishData) => {
|
||||
// Update the history list with the new name and description
|
||||
// Refresh the active workflow to get the updated activeVersion with workflowPublishHistory
|
||||
activeWorkflow.value = workflowsStore.getWorkflowById(workflowId.value);
|
||||
|
||||
// Update the history list with the new name, description, and workflowPublishHistory
|
||||
const historyItem = workflowHistory.value.find(
|
||||
(item) => item.versionId === publishData.versionId,
|
||||
);
|
||||
if (historyItem) {
|
||||
historyItem.name = publishData.name;
|
||||
historyItem.description = publishData.description;
|
||||
// Update workflowPublishHistory from the store's activeVersion
|
||||
if (activeWorkflow.value?.activeVersion?.workflowPublishHistory) {
|
||||
historyItem.workflowPublishHistory =
|
||||
activeWorkflow.value.activeVersion.workflowPublishHistory;
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh the selected workflow version if it's the one that was published
|
||||
@@ -294,12 +302,12 @@ const publishWorkflowVersion = (id: WorkflowVersionId, data: WorkflowHistoryActi
|
||||
...selectedWorkflowVersion.value,
|
||||
name: publishData.name,
|
||||
description: publishData.description,
|
||||
workflowPublishHistory:
|
||||
activeWorkflow.value?.activeVersion?.workflowPublishHistory ??
|
||||
selectedWorkflowVersion.value.workflowPublishHistory,
|
||||
};
|
||||
}
|
||||
|
||||
// Refresh the active workflow to get the updated activeVersion
|
||||
activeWorkflow.value = workflowsStore.getWorkflowById(workflowId.value);
|
||||
|
||||
sendTelemetry('User published version from history');
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user