>>(() => {
+ return props.actions.map((action) => ({
+ id: action.value,
+ label: action.label,
+ disabled: action.disabled,
+ }));
+});
+
const onAction = (value: string) => {
const action = value as WorkflowHistoryActionTypes[number];
emit('action', {
@@ -201,6 +223,9 @@ onMounted(() => {
{{ mainTooltipContent }}
+
+ {{ mainTooltipContent }}
+
{{ mainTooltipContent }}
@@ -232,6 +257,10 @@ onMounted(() => {
v-else-if="props.index === 0 && !props.isVersionActive"
:class="[$style.timelineDot, $style.timelineDotLatest]"
/>
+
@@ -285,14 +314,25 @@ onMounted(() => {
-
+ @select="onAction"
+ @update:model-value="onVisibleChange"
+ >
+
+
+
+
@@ -384,6 +424,12 @@ $hoverBackground: var(--color--background--light-1);
border-color: var(--color--mint-600);
}
+.timelineDotDraft {
+ background-color: var(--color--foreground--tint-1);
+ border: var(--border);
+ border-color: var(--color--foreground--tint-1);
+}
+
.timelineLine {
@include timeline-line-style;
position: absolute;
diff --git a/packages/frontend/editor-ui/src/features/workflows/workflowHistory/components/WorkflowVersionRenameModal.vue b/packages/frontend/editor-ui/src/features/workflows/workflowHistory/components/WorkflowVersionRenameModal.vue
new file mode 100644
index 00000000000..5fd27cdc646
--- /dev/null
+++ b/packages/frontend/editor-ui/src/features/workflows/workflowHistory/components/WorkflowVersionRenameModal.vue
@@ -0,0 +1,175 @@
+
+
+
+
+
+ {{ i18n.baseText('workflows.renameVersion.title') }}
+
+
+
+
+
+
+
+
diff --git a/packages/frontend/editor-ui/src/features/workflows/workflowHistory/views/WorkflowHistory.vue b/packages/frontend/editor-ui/src/features/workflows/workflowHistory/views/WorkflowHistory.vue
index 69a518c467d..e2ac85182b0 100644
--- a/packages/frontend/editor-ui/src/features/workflows/workflowHistory/views/WorkflowHistory.vue
+++ b/packages/frontend/editor-ui/src/features/workflows/workflowHistory/views/WorkflowHistory.vue
@@ -6,6 +6,8 @@ import {
VIEWS,
WORKFLOW_HISTORY_VERSION_UNPUBLISH,
WORKFLOW_HISTORY_PUBLISH_MODAL_KEY,
+ WORKFLOW_SAVE_DRAFT_MODAL_KEY,
+ WORKFLOW_VERSION_RENAME_MODAL_KEY,
} from '@/app/constants';
import { useI18n } from '@n8n/i18n';
import { useToast } from '@/app/composables/useToast';
@@ -32,12 +34,10 @@ import { N8nBadge, N8nButton, N8nHeading } from '@n8n/design-system';
import { createEventBus } from '@n8n/utils/event-bus';
import type { WorkflowHistoryVersionUnpublishModalEventBusEvents } from '../components/WorkflowHistoryVersionUnpublishModal.vue';
import type { WorkflowHistoryPublishModalEventBusEvents } from '../components/WorkflowHistoryPublishModal.vue';
+import type { WorkflowSaveDraftModalEventBusEvents } from '@/app/components/MainHeader/WorkflowSaveDraftModal.vue';
+import type { WorkflowVersionRenameModalEventBusEvents } from '../components/WorkflowVersionRenameModal.vue';
import type { WorkflowHistoryAction } from '@/features/workflows/workflowHistory/types';
-type WorkflowHistoryActionRecord = {
- [K in Uppercase]: Lowercase;
-};
-
const workflowHistoryActionTypes: WorkflowHistoryActionTypes = [
'restore',
'publish',
@@ -45,11 +45,9 @@ const workflowHistoryActionTypes: WorkflowHistoryActionTypes = [
'clone',
'open',
'download',
+ 'saveAsNamed',
+ 'rename',
];
-const WORKFLOW_HISTORY_ACTIONS = workflowHistoryActionTypes.reduce(
- (record, key) => ({ ...record, [key.toUpperCase()]: key }),
- {} as WorkflowHistoryActionRecord,
-);
const route = useRoute();
const router = useRouter();
@@ -92,12 +90,14 @@ const actions = computed>>(() =>
disabled:
(value === 'clone' && !workflowPermissions.value.create) ||
(value === 'restore' && !workflowPermissions.value.update) ||
- ((value === 'publish' || value === 'unpublish') && !workflowPermissions.value.update),
+ ((value === 'publish' || value === 'unpublish') && !workflowPermissions.value.update) ||
+ (value === 'rename' && !workflowPermissions.value.update),
value,
})),
);
const isFirstItemShown = computed(() => workflowHistory.value[0]?.versionId === versionId.value);
+const evaluatedPruneDays = computed(() => Math.floor(workflowHistoryStore.evaluatedPruneTime / 24));
const sendTelemetry = (event: string) => {
telemetry.track(event, {
@@ -289,31 +289,126 @@ const unpublishWorkflowVersion = (id: WorkflowVersionId, data: WorkflowHistoryAc
});
};
+const renameWorkflowVersion = (id: WorkflowVersionId, data: WorkflowHistoryAction['data']) => {
+ const renameEventBus = createEventBus();
+
+ renameEventBus.once('renamed', ({ version }) => {
+ // Update the history list with the new name and description
+ const historyItem = workflowHistory.value.find((item) => item.versionId === version.versionId);
+ if (historyItem) {
+ historyItem.name = version.name;
+ historyItem.description = version.description;
+ }
+
+ // Update the selected workflow version if it's the one that was renamed
+ if (selectedWorkflowVersion.value?.versionId === version.versionId) {
+ selectedWorkflowVersion.value = {
+ ...selectedWorkflowVersion.value,
+ name: version.name,
+ description: version.description,
+ };
+ }
+
+ toast.showMessage({
+ title: i18n.baseText('workflowHistory.action.rename.success.title'),
+ type: 'success',
+ });
+
+ sendTelemetry('User renamed version from history');
+ });
+
+ // Find the version in the history to get current name and description
+ const version = workflowHistory.value.find((item) => item.versionId === id);
+
+ uiStore.openModalWithData({
+ name: WORKFLOW_VERSION_RENAME_MODAL_KEY,
+ data: {
+ workflowId: workflowId.value,
+ versionId: id,
+ version: version || { versionId: id, name: data.versionName, description: data.description },
+ eventBus: renameEventBus,
+ },
+ });
+};
+
+const saveVersionAsNamed = async (
+ versionId: WorkflowVersionId,
+ _data: WorkflowHistoryAction['data'],
+) => {
+ const saveDraftEventBus = createEventBus();
+
+ saveDraftEventBus.once('saved', async (savedData) => {
+ // Update the history item with the new name and description
+ const historyItem = workflowHistory.value.find(
+ (item) => item.versionId === savedData.versionId,
+ );
+ if (historyItem) {
+ historyItem.name = savedData.name;
+ historyItem.description = savedData.description || null;
+ }
+
+ // Close the modal
+ uiStore.closeModal(WORKFLOW_SAVE_DRAFT_MODAL_KEY);
+
+ sendTelemetry('User converted autosaved version to named version');
+
+ // Reload workflow history to ensure we have the latest data
+ // Clear current history and reload
+ workflowHistory.value = [];
+ await loadMore({ take: requestNumberOfItems.value });
+
+ // Navigate to the newly created named version
+ await router.push({
+ name: VIEWS.WORKFLOW_HISTORY,
+ params: {
+ workflowId: workflowId.value,
+ versionId: savedData.versionId,
+ },
+ });
+ });
+
+ // Open the save draft modal with the version ID and event bus
+ uiStore.openModalWithData({
+ name: WORKFLOW_SAVE_DRAFT_MODAL_KEY,
+ data: {
+ workflowId: workflowId.value,
+ versionId,
+ eventBus: saveDraftEventBus,
+ },
+ });
+};
+
const onAction = async ({ action, id, data }: WorkflowHistoryAction) => {
try {
switch (action) {
- case WORKFLOW_HISTORY_ACTIONS.OPEN:
+ case 'open':
openInNewTab(id);
sendTelemetry('User opened version in new tab');
break;
- case WORKFLOW_HISTORY_ACTIONS.DOWNLOAD:
+ case 'download':
await workflowHistoryStore.downloadVersion(workflowId.value, id, data);
sendTelemetry('User downloaded version');
break;
- case WORKFLOW_HISTORY_ACTIONS.CLONE:
+ case 'clone':
await cloneWorkflowVersion(id, data);
sendTelemetry('User cloned version');
break;
- case WORKFLOW_HISTORY_ACTIONS.RESTORE:
+ case 'restore':
await restoreWorkflowVersion(id);
sendTelemetry('User restored version');
break;
- case WORKFLOW_HISTORY_ACTIONS.PUBLISH:
+ case 'publish':
publishWorkflowVersion(id, data);
break;
- case WORKFLOW_HISTORY_ACTIONS.UNPUBLISH:
+ case 'unpublish':
unpublishWorkflowVersion(id, data);
break;
+ case 'saveAsNamed':
+ await saveVersionAsNamed(id, data);
+ break;
+ case 'rename':
+ renameWorkflowVersion(id, data);
+ break;
}
} catch (error) {
toast.showError(
@@ -407,7 +502,7 @@ watchEffect(async () => {
:actions="actions"
:request-number-of-items="requestNumberOfItems"
:should-upgrade="workflowHistoryStore.shouldUpgrade"
- :evaluated-prune-time-in-hours="workflowHistoryStore.evaluatedPruneTime"
+ :evaluated-prune-days="evaluatedPruneDays"
:is-list-loading="isListLoading"
:active-version-id="workflowActiveVersionId"
@action="onAction"
diff --git a/packages/frontend/editor-ui/src/features/workflows/workflowHistory/workflowHistory.store.ts b/packages/frontend/editor-ui/src/features/workflows/workflowHistory/workflowHistory.store.ts
index 892a90927ea..ef9580516f5 100644
--- a/packages/frontend/editor-ui/src/features/workflows/workflowHistory/workflowHistory.store.ts
+++ b/packages/frontend/editor-ui/src/features/workflows/workflowHistory/workflowHistory.store.ts
@@ -103,12 +103,21 @@ export const useWorkflowHistoryStore = defineStore('workflowHistory', () => {
});
};
+ const renameVersion = async (
+ workflowId: string,
+ versionId: string,
+ data: { name?: string; description?: string },
+ ): Promise => {
+ return await whApi.updateWorkflowVersion(rootStore.restApiContext, workflowId, versionId, data);
+ };
+
return {
getWorkflowHistory,
getWorkflowVersion,
downloadVersion,
cloneIntoNewWorkflow,
restoreWorkflow,
+ renameVersion,
evaluatedPruneTime,
shouldUpgrade,
};