mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-22 04:21:32 +02:00
progress
This commit is contained in:
parent
317ba0d3fc
commit
841c3a68a2
|
|
@ -67,6 +67,7 @@ export { ImportWorkflowFromUrlDto } from './workflows/import-workflow-from-url.d
|
|||
export { TransferWorkflowBodyDto } from './workflows/transfer.dto';
|
||||
export { ActivateWorkflowDto } from './workflows/activate-workflow.dto';
|
||||
export { SaveWorkflowVersionDto } from './workflows/save-workflow-version.dto';
|
||||
export { UpdateWorkflowVersionDto } from './workflows/update-workflow-version.dto';
|
||||
|
||||
export { CreateOrUpdateTagRequestDto } from './tag/create-or-update-tag-request.dto';
|
||||
export { RetrieveTagQueryDto } from './tag/retrieve-tag-query.dto';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
import { Z } from 'zod-class';
|
||||
|
||||
import {
|
||||
workflowVersionNameSchema,
|
||||
workflowVersionDescriptionSchema,
|
||||
} from '../../schemas/workflow-version.schema';
|
||||
|
||||
export class UpdateWorkflowVersionDto extends Z.class({
|
||||
name: workflowVersionNameSchema.optional(),
|
||||
description: workflowVersionDescriptionSchema.optional(),
|
||||
}) {}
|
||||
|
|
@ -1,5 +1,9 @@
|
|||
import { PaginationDto, WorkflowHistoryVersionsByIdsDto } from '@n8n/api-types';
|
||||
import { RestController, Get, Post, Query, Body } from '@n8n/decorators';
|
||||
import {
|
||||
PaginationDto,
|
||||
WorkflowHistoryVersionsByIdsDto,
|
||||
UpdateWorkflowVersionDto,
|
||||
} from '@n8n/api-types';
|
||||
import { RestController, Get, Post, Patch, Query, Body } from '@n8n/decorators';
|
||||
|
||||
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
||||
import { SharedWorkflowNotFoundError } from '@/errors/shared-workflow-not-found.error';
|
||||
|
|
@ -69,4 +73,27 @@ export class WorkflowHistoryController {
|
|||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Patch('/workflow/:workflowId/version/:versionId')
|
||||
async updateVersion(
|
||||
req: WorkflowHistoryRequest.GetVersion,
|
||||
_res: Response,
|
||||
@Body body: UpdateWorkflowVersionDto,
|
||||
) {
|
||||
try {
|
||||
return await this.historyService.updateVersionWithUser(
|
||||
req.user,
|
||||
req.params.workflowId,
|
||||
req.params.versionId,
|
||||
body,
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof SharedWorkflowNotFoundError) {
|
||||
throw new NotFoundError('Could not find workflow');
|
||||
} else if (e instanceof WorkflowHistoryVersionNotFoundError) {
|
||||
throw new NotFoundError('Could not find version');
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,6 +138,41 @@ export class WorkflowHistoryService {
|
|||
await this.workflowHistoryRepository.update({ versionId, workflowId }, { ...updateData });
|
||||
}
|
||||
|
||||
async updateVersionWithUser(
|
||||
user: User,
|
||||
workflowId: string,
|
||||
versionId: string,
|
||||
updateData: Pick<WorkflowHistoryUpdate, 'name' | 'description'>,
|
||||
) {
|
||||
const workflow = await this.workflowFinderService.findWorkflowForUser(workflowId, user, [
|
||||
'workflow:update',
|
||||
]);
|
||||
|
||||
if (!workflow) {
|
||||
throw new SharedWorkflowNotFoundError('');
|
||||
}
|
||||
|
||||
const version = await this.workflowHistoryRepository.findOne({
|
||||
where: {
|
||||
workflowId: workflow.id,
|
||||
versionId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!version) {
|
||||
throw new WorkflowHistoryVersionNotFoundError('');
|
||||
}
|
||||
|
||||
await this.workflowHistoryRepository.update({ versionId, workflowId }, { ...updateData });
|
||||
|
||||
return await this.workflowHistoryRepository.findOne({
|
||||
where: {
|
||||
workflowId,
|
||||
versionId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get multiple versions by their IDs
|
||||
* Returns only versions that exist, skipping non-existent ones
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@
|
|||
"generic.docs": "Docs",
|
||||
"generic.documentation": "Documentation",
|
||||
"generic.back": "Back",
|
||||
|
||||
"generic.cancel": "Cancel",
|
||||
"generic.connect": "Connect",
|
||||
"generic.open": "Open",
|
||||
|
|
@ -222,7 +223,6 @@
|
|||
"auth.signup.setupYourAccountError": "Problem setting up your account",
|
||||
"auth.signup.tokenValidationError": "Issue validating invite token",
|
||||
"aiAssistant.name": "n8n AI",
|
||||
"aiAssistant.assistant": "Assistant",
|
||||
"aiAssistant.tabs.ask": "Ask",
|
||||
"aiAssistant.tabs.build": "Build",
|
||||
"aiAssistant.builder.mode": "AI Builder",
|
||||
|
|
@ -235,7 +235,6 @@
|
|||
"aiAssistant.builder.invalidPrompt": "Prompt validation failed. Please try again with a clearer description of your workflow requirements and supported integrations.",
|
||||
"aiAssistant.builder.workflowParsingError.title": "Unable to insert workflow",
|
||||
"aiAssistant.builder.workflowParsingError.content": "The workflow returned by AI could not be parsed. Please try again.",
|
||||
"aiAssistant.builder.error.title": "Workflow update failed",
|
||||
"aiAssistant.builder.canvasPrompt.title": "What would you like to automate?",
|
||||
"aiAssistant.builder.canvasPrompt.confirmTitle": "Replace current prompt?",
|
||||
"aiAssistant.builder.canvasPrompt.confirmMessage": "This will replace your current prompt. Are you sure?",
|
||||
|
|
@ -261,12 +260,7 @@
|
|||
"aiAssistant.builder.restoreError.title": "Failed to restore version",
|
||||
"aiAssistant.builder.disabledTooltip.autosaving": "Saving workflow...",
|
||||
"aiAssistant.builder.disabledTooltip.readOnly": "Another user is currently editing this workflow",
|
||||
"aiAssistant.builder.notification.title": "Workflow ready - n8n",
|
||||
"aiAssistant.builder.notification.body": "Done building {workflowName}",
|
||||
"aiAssistant.builder.notification.inputNeeded.title": "Input needed - n8n",
|
||||
"aiAssistant.builder.notification.inputNeeded.body": "Waiting for your response on {workflowName}",
|
||||
"aiAssistant.builder.notificationBanner.text": "Want to be notified when the agent finishes building?",
|
||||
"aiAssistant.builder.notificationBanner.notify": "Notify",
|
||||
"aiAssistant.assistant": "n8n AI",
|
||||
"aiAssistant.newSessionModal.title.part1": "Start new",
|
||||
"aiAssistant.newSessionModal.title.part2": "session",
|
||||
"aiAssistant.newSessionModal.message": "You already have an active n8n AI session. Starting a new session will clear your current conversation history.",
|
||||
|
|
@ -278,7 +272,7 @@
|
|||
"aiAssistant.codeUpdated.message.body1": "Open the",
|
||||
"aiAssistant.codeUpdated.message.body2": "node to see the changes",
|
||||
"aiAssistant.thinkingSteps.analyzingError": "Analyzing the error...",
|
||||
"aiAssistant.thinkingSteps.thinking": "Thinking",
|
||||
"aiAssistant.thinkingSteps.thinking": "Thinking...",
|
||||
"aiAssistant.prompts.currentView.workflowList": "The user is currently looking at the list of workflows.",
|
||||
"aiAssistant.prompts.currentView.credentialsList": "The user is currently looking at the list of credentials.",
|
||||
"aiAssistant.prompts.currentView.executionsView": "The user is currently looking at the list of executions for the currently open workflow.",
|
||||
|
|
@ -448,9 +442,6 @@
|
|||
"chatHub.error.updateModelFailed": "Failed to update model",
|
||||
"chatHub.error.updateToolsFailed": "Failed to update tools",
|
||||
"chatHub.models.selector.defaultLabel": "Select model",
|
||||
"chatHub.models.selector.personalProject": "Personal",
|
||||
"chatHub.models.selector.noMatch": "No agents match your search criteria.",
|
||||
"chatHub.models.selector.moreModels": "More {providerName} models...",
|
||||
"chatHub.models.byIdSelector.title": "Choose {provider} model by ID",
|
||||
"chatHub.models.byIdSelector.choose": "Enter model identifier (e.g. \"gpt-4\")",
|
||||
"chatHub.models.byIdSelector.confirm": "Select",
|
||||
|
|
@ -786,11 +777,7 @@
|
|||
"codeNodeEditor.optional": "optional",
|
||||
"codeNodeEditor.defaultsTo": "Defaults to {default}.",
|
||||
"collectionParameter.choose": "Choose...",
|
||||
"collectionParameter.addItem": "Add item",
|
||||
"collectionParameter.deleteItem": "Delete",
|
||||
"collectionParameter.dragItem": "Drag to reorder",
|
||||
"collectionParameter.noProperties": "No properties",
|
||||
"collectionParameter.allOptionsAdded": "All options have been added",
|
||||
"communityNodeFooter.legacy": "Legacy",
|
||||
"communityNodeFooter.manage": "Manage",
|
||||
"communityNodeFooter.reportIssue": "Report issue",
|
||||
|
|
@ -1198,14 +1185,10 @@
|
|||
"expressionModalInput.pairedItemInvalidPinnedError": "Unpin node ‘{node}’ and execute",
|
||||
"expressionModalInput.pairedItemError": "Can’t determine which item to use",
|
||||
"expressionModalInput.pairedItemError.noRunData": "Can't determine which item to use - execute node for more info",
|
||||
"fixedCollectionParameter.addItem": "Add item",
|
||||
"fixedCollectionParameter.addParameter": "Add {parameter}",
|
||||
"fixedCollectionParameter.allOptionsAdded": "All options have been added",
|
||||
"fixedCollectionParameter.addField": "Add Field",
|
||||
"fixedCollectionParameter.choose": "Choose...",
|
||||
"fixedCollectionParameter.currentlyNoItemsExist": "Currently no items exist",
|
||||
"fixedCollectionParameter.deleteItem": "Delete item",
|
||||
"fixedCollectionParameter.deleteParameter": "Delete {parameter}",
|
||||
"fixedCollectionParameter.dragItem": "Drag item",
|
||||
"fixedCollectionParameter.moveDown": "Move down",
|
||||
"fixedCollectionParameter.moveUp": "Move up",
|
||||
|
|
@ -1231,6 +1214,8 @@
|
|||
"folders.add": "Add folder",
|
||||
"folders.add.here.message": "Create a new folder here",
|
||||
"folders.add.to.parent.message": "Create folder in '{parent}'",
|
||||
"folders.add.overview.community.message": "Folders available in your personal space",
|
||||
"folders.add.overview.withProjects.message": "Folders available in projects or your personal space",
|
||||
"folders.add.success.title": "Folder created",
|
||||
"folders.add.success.message": "Created new folder '{folderName}'<br><a href=\"{link}\">Open folder</a>",
|
||||
"folders.invalidName.empty.message": "Folder name cannot be empty",
|
||||
|
|
@ -1515,8 +1500,6 @@
|
|||
"ndv.tooMuchData.showDataAnyway": "Show data",
|
||||
"ndv.tooMuchData.title": "Show {size} MB of data?",
|
||||
"ndv.httpRequest.credentialOnly.docsNotice": "Use the <a target=\"_blank\" href=\"{docsUrl}\">{nodeName} docs</a> to construct your request. We'll take care of the authentication part if you add a {nodeName} credential below.",
|
||||
"ndv.binaryData.combinedTooltip": "Specify the path to the binary data entry in the input item or use an expression to access the binary data in previous nodes, e.g. {example}",
|
||||
"ndv.binaryData.separateTooltip": "Specify the property name of the binary data in the input item or use an expression to access the binary data in previous nodes, e.g. {example}",
|
||||
"noTagsView.readyToOrganizeYourWorkflows": "Ready to organize your workflows?",
|
||||
"noTagsView.withWorkflowTagsYouReFree": "With workflow tags, you're free to create the perfect tagging system for your flows",
|
||||
"noAnnotationTagsView.title": "Organize your executions",
|
||||
|
|
@ -1678,6 +1661,8 @@
|
|||
"nodeCreator.nodeItem.aiIconTitle": "LangChain AI Node",
|
||||
"nodeCreator.nodeItem.deprecated": "Deprecated",
|
||||
"nodeCreator.nodeItem.beta": "Beta",
|
||||
"nodeCreator.preBuiltAgents.title": "Pre-built agents",
|
||||
"nodeCreator.preBuiltAgents.description": "Get started faster with ready to go agents",
|
||||
"nodeCredentials.createNew": "Create new credential",
|
||||
"nodeCredentials.createNew.permissionDenied": "Your current role does not allow you to create credentials",
|
||||
"nodeCredentials.credentialFor": "Credential for {credentialType}",
|
||||
|
|
@ -2325,8 +2310,6 @@
|
|||
"runData.executionStatus.failed": "Execution failed",
|
||||
"runData.executionStatus.canceled": "Execution canceled",
|
||||
"runData.downloadBinaryData": "Download",
|
||||
"runData.downloadBinaryData.error.title": "Error downloading file",
|
||||
"runData.downloadBinaryData.error.message": "File could not be downloaded",
|
||||
"runData.executeNode": "Test Node",
|
||||
"runData.executionTime": "Execution Time",
|
||||
"runData.fileExtension": "File Extension",
|
||||
|
|
@ -2347,7 +2330,6 @@
|
|||
"runData.nodeReturnedALargeAmountOfData": "Node returned a large amount of data",
|
||||
"runData.output": "Output",
|
||||
"runData.showBinaryData": "View",
|
||||
"runData.legacyBinaryMode": "Try the new \"v2\" Execution Logic mode by selecting the corresponding option in the workflow",
|
||||
"runData.startTime": "Start Time",
|
||||
"runData.table": "Table",
|
||||
"runData.table.viewSubExecution": "View sub-execution {id}",
|
||||
|
|
@ -2523,7 +2505,6 @@
|
|||
"settings.users.actions.delete": "Delete User",
|
||||
"settings.users.actions.reinvite": "Resend Invite",
|
||||
"settings.users.actions.copyInviteLink": "Copy Invite Link",
|
||||
"settings.users.actions.generateInviteLink": "Generate Invite Link",
|
||||
"settings.users.actions.copyPasswordResetLink": "Copy Password Reset Link",
|
||||
"settings.users.actions.allowSSOManualLogin": "Allow Manual Login",
|
||||
"settings.users.actions.disallowSSOManualLogin": "Disallow Manual Login",
|
||||
|
|
@ -2545,7 +2526,6 @@
|
|||
"settings.users.inviteXUser.inviteUrl": "Create {count} invite links",
|
||||
"settings.users.inviteUrlCreated": "Invite link copied to clipboard",
|
||||
"settings.users.inviteUrlCreated.message": "Send the invite link to your invitee for activation",
|
||||
"settings.users.inviteLinkError": "Could not generate invite link",
|
||||
"settings.users.passwordResetUrlCreated": "Password reset link copied to clipboard",
|
||||
"settings.users.passwordResetUrlCreated.message": "Send the reset link to your user for them to reset their password",
|
||||
"settings.users.passwordResetLinkError": "Could not retrieve password reset link",
|
||||
|
|
@ -2849,7 +2829,7 @@
|
|||
"settings.sourceControl.modals.push.buttons.save": "Commit and push",
|
||||
"settings.sourceControl.modals.push.success.title": "Pushed successfully",
|
||||
"settings.sourceControl.modals.push.success.description": "were committed and pushed to your remote repository",
|
||||
"settings.sourceControl.modals.push.projectAdmin.callout": "If you want to push workflows from your personal space, move them to a project first.",
|
||||
"settings.sourceControl.modals.push.projectAdmin.callout": "If you want to push workflows from your personal space, move then to a project first.",
|
||||
"settings.sourceControl.status.modified": "Modified",
|
||||
"settings.sourceControl.status.deleted": "Deleted",
|
||||
"settings.sourceControl.status.created": "New",
|
||||
|
|
@ -3174,18 +3154,8 @@
|
|||
"workflowRun.showError.title": "Problem running workflow",
|
||||
"workflowRun.showError.payloadTooLarge": "Please execute the whole workflow, rather than just the node. (Existing execution data is too large.)",
|
||||
"workflowRun.showError.resolveOutstandingIssues": "Please resolve outstanding issues before you activate it",
|
||||
"workflowRun.showError.unsupportedExecutionLogic.title": "Unsupported Execution Logic",
|
||||
"workflowRun.showError.unsupportedExecutionLogic.description": "Execution Logic \"v2\" is not supported when filesystem mode is \"default\". Please change the Execution Logic to \"v1\" in workflow settings and update expressions accordingly.",
|
||||
"workflowRun.showMessage.message": "Please fix them before executing",
|
||||
"workflowRun.showMessage.title": "Workflow has issues",
|
||||
"workflowSettings.executionLogic.v0.title": "v0 (legacy)",
|
||||
"workflowSettings.executionLogic.v1.title": "v1 (recommended)",
|
||||
"workflowSettings.executionLogic.v2.title": "v2 (experimental)",
|
||||
"workflowSettings.executionLogic.v0.description": "Executes the first node of each branch, then the second node of each branch, and so on.",
|
||||
"workflowSettings.executionLogic.v1.description": "Executes each branch in turn, from topmost to bottommost, completing one branch before starting another.",
|
||||
"workflowSettings.executionLogic.v2.description": "Executes each branch in turn, with binaries included directly in the item structure to simplify expressions.",
|
||||
"workflowSettings.executionLogic.changed.title": "Execution Logic changed",
|
||||
"workflowSettings.executionLogic.changed.description": "Please update expressions that reference binary data to match the new execution logic mode. ",
|
||||
"workflowSettings.callerIds": "IDs of workflows that can call this one",
|
||||
"workflowSettings.callerIds.placeholder": "e.g. 14, 18",
|
||||
"workflowSettings.callerPolicy": "This workflow can be called by",
|
||||
|
|
@ -3198,8 +3168,7 @@
|
|||
"workflowSettings.defaultTimezone": "Default - {defaultTimezoneValue}",
|
||||
"workflowSettings.defaultTimezoneNotValid": "Default Timezone not valid",
|
||||
"workflowSettings.errorWorkflow": "Error Workflow (to notify when this one errors)",
|
||||
"workflowSettings.executionLogic": "Execution Logic",
|
||||
"workflowSettings.binaryMode": "Binary Mode",
|
||||
"workflowSettings.executionOrder": "Execution Order",
|
||||
"workflowSettings.helpTexts.errorWorkflow": "A second workflow to run if the current one fails.<br />The second workflow should have an 'Error Trigger' node.",
|
||||
"workflowSettings.helpTexts.executionTimeout": "How long the workflow should wait before timing out",
|
||||
"workflowSettings.helpTexts.executionTimeoutToggle": "Whether to cancel workflow execution after a defined time",
|
||||
|
|
@ -3282,12 +3251,6 @@
|
|||
"credentialResolverEdit.sidebar.details": "Details",
|
||||
"credentialResolverEdit.details.id": "ID",
|
||||
"credentialResolverEdit.details.notSaved": "Not saved yet",
|
||||
"credentialResolverEdit.clearCredentials.label": "Clear credentials",
|
||||
"credentialResolverEdit.clearCredentials.placeholder": "Select an option",
|
||||
"credentialResolverEdit.clearCredentials.yes": "Yes",
|
||||
"credentialResolverEdit.clearCredentials.no": "No",
|
||||
"credentialResolverEdit.clearCredentials.error.required": "Clear credentials selection is required",
|
||||
"credentialResolverEdit.clearCredentials.warning": "When making changes to a resolver, it is advised to clear the existing credentials for that resolver. Please choose whether to take that action.",
|
||||
"workflowSettings.timeSavedPerExecution": "Estimated time saved",
|
||||
"workflowSettings.timeSavedPerExecution.hint": "Minutes per production execution",
|
||||
"workflowSettings.timeSavedPerExecution.tooltip": "Total time savings are summarised in the Overview page.",
|
||||
|
|
@ -3317,15 +3280,16 @@
|
|||
"workflowHistory.item.actions.clone": "Clone to new workflow",
|
||||
"workflowHistory.item.actions.open": "Open version in new tab",
|
||||
"workflowHistory.item.actions.download": "Download",
|
||||
"workflowHistory.item.actions.saveAsNamed": "Create draft",
|
||||
"workflowHistory.item.actions.rename": "Rename version",
|
||||
"workflowHistory.item.unsaved.title": "Unsaved version",
|
||||
"workflowHistory.item.latest": "Latest saved",
|
||||
"workflowHistory.item.active": "Published",
|
||||
"workflowHistory.item.publishedBy": "Published by",
|
||||
"workflowHistory.item.currentChanges": "Current changes",
|
||||
"workflowHistory.limitDay": "Version history is limited to 1 day",
|
||||
"workflowHistory.limitDays": "Version history is limited to {days} days",
|
||||
"workflowHistory.limitHour": "Version history is limited to 1 hour",
|
||||
"workflowHistory.limitHours": "Version history is limited to {hours} hours",
|
||||
"workflowHistory.item.createdBy": "Created by",
|
||||
"workflowHistory.item.neverPublished": "Never published",
|
||||
"workflowHistory.limit": "Version history is limited to {days} days",
|
||||
"workflowHistory.upgrade": "{link} to activate full history",
|
||||
"workflowHistory.upgrade.link": "Upgrade plan",
|
||||
"workflowHistory.action.error.title": "Failed to {action}",
|
||||
|
|
@ -3335,7 +3299,9 @@
|
|||
"workflowHistory.action.restore.modal.button.restore": "Restore",
|
||||
"workflowHistory.action.restore.modal.button.cancel": "Cancel",
|
||||
"workflowHistory.action.restore.success.title": "Successfully restored workflow version",
|
||||
"workflowHistory.action.saveAsNamed.success.title": "Draft created successfully",
|
||||
"workflowHistory.action.restore.alreadyRestored": "Workflow version is already restored",
|
||||
"workflowHistory.action.rename.success.title": "Version renamed successfully",
|
||||
"workflowHistory.action.clone.success.title": "Successfully cloned workflow version",
|
||||
"workflowHistory.action.clone.success.message": "Open cloned workflow in a new tab",
|
||||
"workflowHistory.action.unpublish.success.title": "Workflow unpublished successfully",
|
||||
|
|
@ -3412,6 +3378,7 @@
|
|||
"workflows.empty.learnN8n": "Learn n8n",
|
||||
"workflows.empty.button.disabled.tooltip": "Your current role in the project does not allow you to create workflows",
|
||||
"workflows.empty.easyAI": "Test a simple AI Agent example",
|
||||
"workflows.empty.preBuiltAgents": "Try a pre-built agent",
|
||||
"workflows.empty.shared-with-me": "No {resource} has been shared with you",
|
||||
"workflows.empty.shared-with-me.link": "<a href=\"#\">Back to Personal</a>",
|
||||
"workflows.empty.readyToRun": "Try an AI workflow",
|
||||
|
|
@ -3465,6 +3432,8 @@
|
|||
"workflows.readyToRunWorkflows.error": "Error loading n8n collection. Please try again later.",
|
||||
"workflows.archivedOnly.hint": "Archived workflows are hidden in this view. {link}",
|
||||
"workflows.archivedOnly.hint.link": "Update filters",
|
||||
"workflows.preBuiltAgents.callout": "Get started faster with our",
|
||||
"workflows.preBuiltAgents.linkText": "pre-built agents",
|
||||
"workflowSelectorParameterInput.createNewSubworkflow.name": "My Sub-Workflow",
|
||||
"workflows.publishModal.title": "Publish workflow",
|
||||
"workflows.publishModal.noTriggerMessage": "This workflow has no trigger nodes that require publishing",
|
||||
|
|
@ -3473,6 +3442,17 @@
|
|||
"workflows.publishModal.noChanges": "No changes to publish",
|
||||
"workflows.publishModal.changes": "New changes to publish",
|
||||
"workflows.publishModal.lastPublished": "Last published by {author}, {date} at {time}",
|
||||
"workflows.saveDraft": "Create draft",
|
||||
"workflows.saveDraft.title": "Create draft",
|
||||
"workflows.saveDraft.success.title": "Draft created successfully",
|
||||
"workflows.saveDraft.success.message": "Your draft version has been created",
|
||||
"workflows.saveDraft.error.title": "Failed to create draft",
|
||||
"workflows.saveDraft.error.message": "There was an error creating your draft version",
|
||||
"workflows.renameVersion.title": "Rename version",
|
||||
"workflows.renameVersion.success.title": "Version renamed successfully",
|
||||
"workflows.renameVersion.success.message": "Your version has been renamed",
|
||||
"workflows.renameVersion.error.title": "Failed to rename version",
|
||||
"workflows.renameVersion.error.message": "There was an error renaming your version",
|
||||
"importCurlModal.title": "Import cURL command",
|
||||
"importCurlModal.input.label": "cURL Command",
|
||||
"importCurlModal.input.placeholder": "Paste the cURL command here",
|
||||
|
|
@ -4015,9 +3995,6 @@
|
|||
"assignment.addAll": "Add All",
|
||||
"assignment.clearAll": "Clear All",
|
||||
"assignment.binaryData.placeholder": "Name of binary input (or expression referencing it)",
|
||||
"assignment.field": "Field {number}",
|
||||
"assignment.remove": "Remove field",
|
||||
"assignment.dragToReorder": "Drag to reorder",
|
||||
"templateSetup.title": "Set up '{name}' template",
|
||||
"templateSetup.instructions": "You need {0} account to setup this template",
|
||||
"templateSetup.skip": "Skip",
|
||||
|
|
@ -4165,7 +4142,7 @@
|
|||
"freeAi.credits.callout.claim.title": "Get {credits} free OpenAI API credits",
|
||||
"freeAi.credits.callout.claim.button.label": "Claim credits",
|
||||
"freeAi.credits.callout.success.title.part1": "Claimed {credits} free OpenAI API credits! Please note these free credits are only for the following models:",
|
||||
"freeAi.credits.callout.success.title.part2": "gpt-5-mini, gpt-4.1-mini, gpt-4.1-nano, gpt-4o-mini, text-embedding-3-small, dall-e-3, tts-1, whisper-1, and text-moderation-latest",
|
||||
"freeAi.credits.callout.success.title.part2": "gpt-4o-mini, text-embedding-3-small, dall-e-3, tts-1, whisper-1, and text-moderation-latest",
|
||||
"freeAi.credits.credentials.edit": "This is a managed credential and cannot be edited.",
|
||||
"freeAi.credits.showError.claim.title": "Free AI credits",
|
||||
"freeAi.credits.showError.claim.message": "Enable to claim credits",
|
||||
|
|
@ -4243,6 +4220,9 @@
|
|||
"workflowDiff.newWorkflow": "New workflow",
|
||||
"workflowDiff.newWorkflow.database": "The workflow will be created in the database",
|
||||
"workflowDiff.newWorkflow.remote": "The workflow will be created on remote",
|
||||
"preBuiltAgentTemplates.title": "Pre-built agents",
|
||||
"preBuiltAgentTemplates.tutorials": "Tutorial templates",
|
||||
"preBuiltAgentTemplates.viewAllLink": "View all templates",
|
||||
"commandBar.placeholder": "Type a command or search...",
|
||||
"commandBar.noResults": "No results found",
|
||||
"commandBar.sections.recent": "Recent",
|
||||
|
|
@ -4321,13 +4301,5 @@
|
|||
"commandBar.chat.deleteCurrent": "Delete current chat",
|
||||
"commandBar.chat.deleteSession": "Delete \"{title}\"",
|
||||
"commandBar.chat.open.searchPlaceholder": "Search by f title...",
|
||||
"commandBar.chat.stop": "Stop message generation",
|
||||
"browserNotifications.permission.request.title": "Enable desktop notifications",
|
||||
"browserNotifications.permission.request.message": "Get notified when your workflows complete or need attention.",
|
||||
"browserNotifications.permission.request.allow": "Allow notifications",
|
||||
"browserNotifications.permission.request.dismiss": "Not now",
|
||||
"browserNotifications.permission.denied.title": "Notifications blocked",
|
||||
"browserNotifications.permission.denied.message": "Desktop notifications are blocked. To enable them, click the lock icon in your browser's address bar and allow notifications.",
|
||||
"browserNotifications.permission.denied.checkAgain": "Check again",
|
||||
"browserNotifications.permission.granted.message": "Desktop notifications are enabled"
|
||||
"commandBar.chat.stop": "Stop message generation"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { IConnections, INode } from 'n8n-workflow';
|
||||
|
||||
import type { IRestApiContext } from '../types';
|
||||
import { get, post } from '../utils';
|
||||
import { get, post, patch } from '../utils';
|
||||
|
||||
export type WorkflowHistory = {
|
||||
versionId: string;
|
||||
|
|
@ -31,7 +31,7 @@ export type WorkflowVersion = WorkflowHistory & {
|
|||
};
|
||||
|
||||
export type WorkflowHistoryActionTypes = Array<
|
||||
'restore' | 'publish' | 'unpublish' | 'clone' | 'open' | 'download'
|
||||
'restore' | 'publish' | 'unpublish' | 'clone' | 'open' | 'download' | 'saveAsNamed' | 'rename'
|
||||
>;
|
||||
|
||||
export type WorkflowHistoryRequestParams = { take: number; skip?: number };
|
||||
|
|
@ -73,3 +73,17 @@ export const getWorkflowVersionsByIds = async (
|
|||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const updateWorkflowVersion = async (
|
||||
context: IRestApiContext,
|
||||
workflowId: string,
|
||||
versionId: string,
|
||||
body: { name?: string; description?: string },
|
||||
): Promise<WorkflowHistory> => {
|
||||
const { data } = await patch(
|
||||
context.baseUrl,
|
||||
`/workflow-history/workflow/${workflowId}/version/${versionId}`,
|
||||
body,
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import {
|
|||
WORKFLOW_DESCRIPTION_MODAL_KEY,
|
||||
WORKFLOW_PUBLISH_MODAL_KEY,
|
||||
WORKFLOW_HISTORY_PUBLISH_MODAL_KEY,
|
||||
WORKFLOW_VERSION_RENAME_MODAL_KEY,
|
||||
CREDENTIAL_RESOLVER_EDIT_MODAL_KEY,
|
||||
} from '@/app/constants';
|
||||
import {
|
||||
|
|
@ -120,6 +121,7 @@ import StopManyExecutionsModal from './StopManyExecutionsModal.vue';
|
|||
import WorkflowDescriptionModal from '@/app/components/WorkflowDescriptionModal.vue';
|
||||
import WorkflowPublishModal from '@/app/components/MainHeader/WorkflowPublishModal.vue';
|
||||
import WorkflowHistoryPublishModal from '@/features/workflows/workflowHistory/components/WorkflowHistoryPublishModal.vue';
|
||||
import WorkflowVersionRenameModal from '@/features/workflows/workflowHistory/components/WorkflowVersionRenameModal.vue';
|
||||
import UpdatesPanel from './UpdatesPanel.vue';
|
||||
import CredentialResolverEditModal from '@/app/components/CredentialResolverEditModal.vue';
|
||||
</script>
|
||||
|
|
@ -449,6 +451,12 @@ import CredentialResolverEditModal from '@/app/components/CredentialResolverEdit
|
|||
</template>
|
||||
</ModalRoot>
|
||||
|
||||
<ModalRoot :name="WORKFLOW_VERSION_RENAME_MODAL_KEY">
|
||||
<template #default="{ modalName, data }">
|
||||
<WorkflowVersionRenameModal :modal-name="modalName" :data="data" />
|
||||
</template>
|
||||
</ModalRoot>
|
||||
|
||||
<ModalRoot :name="CREDENTIAL_RESOLVER_EDIT_MODAL_KEY">
|
||||
<template #default="{ modalName, data }">
|
||||
<CredentialResolverEditModal :modal-name="modalName" :data="data" />
|
||||
|
|
|
|||
|
|
@ -38,4 +38,6 @@ export const BINARY_DATA_VIEW_MODAL_KEY = 'binaryDataView';
|
|||
export const WORKFLOW_DESCRIPTION_MODAL_KEY = 'workflowDescription';
|
||||
export const WORKFLOW_PUBLISH_MODAL_KEY = 'workflowPublish';
|
||||
export const WORKFLOW_HISTORY_PUBLISH_MODAL_KEY = 'workflowHistoryPublish';
|
||||
export const WORKFLOW_SAVE_DRAFT_MODAL_KEY = 'workflowSaveDraft';
|
||||
export const WORKFLOW_VERSION_RENAME_MODAL_KEY = 'workflowVersionRename';
|
||||
export const CREDENTIAL_RESOLVER_EDIT_MODAL_KEY = 'credentialResolverEdit';
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ import type {
|
|||
import { useI18n } from '@n8n/i18n';
|
||||
import type { IUser } from 'n8n-workflow';
|
||||
|
||||
import { N8nActionToggle, N8nTooltip, N8nText } from '@n8n/design-system';
|
||||
import { N8nTooltip, N8nText, N8nIconButton } from '@n8n/design-system';
|
||||
import N8nDropdownMenu from '@n8n/design-system/v2/components/DropdownMenu/DropdownMenu.vue';
|
||||
import type { DropdownMenuItemProps } from '@n8n/design-system/v2/components/DropdownMenu/DropdownMenu.types';
|
||||
import {
|
||||
getLastPublishedVersion,
|
||||
formatTimestamp,
|
||||
|
|
@ -97,6 +99,14 @@ const versionPublishInfo = computed(() => {
|
|||
return publishInfo;
|
||||
});
|
||||
|
||||
const isNeverActivated = computed(() => {
|
||||
// A version is never activated if it has a name but no publish history
|
||||
return (
|
||||
props.item.name &&
|
||||
(!props.item.workflowPublishHistory || props.item.workflowPublishHistory.length === 0)
|
||||
);
|
||||
});
|
||||
|
||||
const getPublishedUserName = (userId: string | undefined | null) => {
|
||||
if (!userId) {
|
||||
return null;
|
||||
|
|
@ -118,6 +128,10 @@ const mainTooltipContent = computed(() => {
|
|||
return i18n.baseText('workflowHistory.item.currentChanges');
|
||||
}
|
||||
|
||||
if (isNeverActivated.value) {
|
||||
return i18n.baseText('workflowHistory.item.neverPublished');
|
||||
}
|
||||
|
||||
if (versionPublishInfo.value) {
|
||||
return `${i18n.baseText('workflowHistory.item.publishedBy')}`;
|
||||
}
|
||||
|
|
@ -165,6 +179,14 @@ const mainTooltipFormattedDate = computed(() => {
|
|||
return i18n.baseText('workflowHistory.item.createdAt', { interpolate: { date, time } });
|
||||
});
|
||||
|
||||
const dropdownMenuItems = computed<Array<DropdownMenuItemProps<string>>>(() => {
|
||||
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(() => {
|
|||
<div v-if="props.index === 0 && !props.isVersionActive">
|
||||
{{ mainTooltipContent }}
|
||||
</div>
|
||||
<div v-else-if="isNeverActivated">
|
||||
{{ mainTooltipContent }}
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ mainTooltipContent }}
|
||||
<template v-if="mainTooltipUser">
|
||||
|
|
@ -232,6 +257,10 @@ onMounted(() => {
|
|||
v-else-if="props.index === 0 && !props.isVersionActive"
|
||||
:class="[$style.timelineDot, $style.timelineDotLatest]"
|
||||
/>
|
||||
<span
|
||||
v-else-if="isNeverActivated"
|
||||
:class="[$style.timelineDot, $style.timelineDotDraft]"
|
||||
/>
|
||||
<span v-else :class="[$style.timelineDot, $style.timelineDotDefault]" />
|
||||
</template>
|
||||
<span v-else :class="$style.timelineLine" />
|
||||
|
|
@ -285,14 +314,25 @@ onMounted(() => {
|
|||
</N8nText>
|
||||
</div>
|
||||
</div>
|
||||
<N8nActionToggle
|
||||
:class="$style.actions"
|
||||
:actions="props.actions"
|
||||
<N8nDropdownMenu
|
||||
:items="dropdownMenuItems"
|
||||
:teleported="true"
|
||||
placement="bottom-end"
|
||||
@action="onAction"
|
||||
@click.stop
|
||||
@visible-change="onVisibleChange"
|
||||
/>
|
||||
@select="onAction"
|
||||
@update:model-value="onVisibleChange"
|
||||
>
|
||||
<template #trigger>
|
||||
<N8nIconButton
|
||||
:class="$style.actions"
|
||||
icon="ellipsis"
|
||||
type="highlight"
|
||||
size="small"
|
||||
aria-label="More actions"
|
||||
data-test-id="workflow-history-item-actions"
|
||||
@click.stop
|
||||
/>
|
||||
</template>
|
||||
</N8nDropdownMenu>
|
||||
</div>
|
||||
</li>
|
||||
</N8nTooltip>
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,175 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, onMounted, onBeforeUnmount, useTemplateRef } from 'vue';
|
||||
import Modal from '@/app/components/Modal.vue';
|
||||
import { WORKFLOW_VERSION_RENAME_MODAL_KEY } from '@/app/constants';
|
||||
import { useWorkflowHistoryStore } from '@/features/workflows/workflowHistory/workflowHistory.store';
|
||||
import { createEventBus, type EventBus } from '@n8n/utils/event-bus';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { N8nHeading, N8nButton } from '@n8n/design-system';
|
||||
import WorkflowPublishForm from '@/app/components/WorkflowPublishForm.vue';
|
||||
import { useToast } from '@/app/composables/useToast';
|
||||
import type { WorkflowHistory } from '@n8n/rest-api-client/api/workflowHistory';
|
||||
|
||||
const modalBus = createEventBus();
|
||||
const i18n = useI18n();
|
||||
|
||||
const workflowHistoryStore = useWorkflowHistoryStore();
|
||||
const { showMessage } = useToast();
|
||||
const saving = ref(false);
|
||||
|
||||
const publishForm = useTemplateRef<InstanceType<typeof WorkflowPublishForm>>('publishForm');
|
||||
|
||||
const description = ref('');
|
||||
const versionName = ref('');
|
||||
|
||||
export type WorkflowVersionRenameModalEventBusEvents = {
|
||||
renamed: { version: WorkflowHistory };
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
modalName: string;
|
||||
data: {
|
||||
workflowId: string;
|
||||
versionId: string;
|
||||
version: WorkflowHistory;
|
||||
eventBus?: EventBus<WorkflowVersionRenameModalEventBusEvents>;
|
||||
};
|
||||
}>();
|
||||
|
||||
const data = ref<{
|
||||
workflowId: string;
|
||||
versionId: string;
|
||||
version: WorkflowHistory;
|
||||
eventBus?: EventBus<WorkflowVersionRenameModalEventBusEvents>;
|
||||
}>({
|
||||
workflowId: '',
|
||||
versionId: '',
|
||||
version: {} as WorkflowHistory,
|
||||
});
|
||||
|
||||
const isSaveDisabled = computed(() => {
|
||||
return saving.value || versionName.value.trim().length === 0;
|
||||
});
|
||||
|
||||
function onModalOpened() {
|
||||
if (publishForm.value) {
|
||||
publishForm.value.focusInput();
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.data) {
|
||||
data.value = props.data;
|
||||
versionName.value = props.data.version.name || '';
|
||||
description.value = props.data.version.description || '';
|
||||
}
|
||||
modalBus.on('opened', onModalOpened);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
modalBus.off('opened', onModalOpened);
|
||||
});
|
||||
|
||||
async function handleRename() {
|
||||
if (isSaveDisabled.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
saving.value = true;
|
||||
|
||||
try {
|
||||
const updatedVersion = await workflowHistoryStore.renameVersion(
|
||||
data.value.workflowId,
|
||||
data.value.versionId,
|
||||
{
|
||||
name: versionName.value,
|
||||
description: description.value,
|
||||
},
|
||||
);
|
||||
|
||||
showMessage({
|
||||
title: i18n.baseText('workflows.renameVersion.success.title'),
|
||||
message: i18n.baseText('workflows.renameVersion.success.message'),
|
||||
type: 'success',
|
||||
});
|
||||
|
||||
if (data.value.eventBus) {
|
||||
data.value.eventBus.emit('renamed', {
|
||||
version: updatedVersion,
|
||||
});
|
||||
}
|
||||
|
||||
modalBus.emit('close');
|
||||
} catch (error) {
|
||||
showMessage({
|
||||
title: i18n.baseText('workflows.renameVersion.error.title'),
|
||||
message:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: i18n.baseText('workflows.renameVersion.error.message'),
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
|
||||
saving.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
max-width="500px"
|
||||
max-height="85vh"
|
||||
:name="WORKFLOW_VERSION_RENAME_MODAL_KEY"
|
||||
:center="true"
|
||||
:show-close="true"
|
||||
:close-on-click-modal="false"
|
||||
:event-bus="modalBus"
|
||||
>
|
||||
<template #header>
|
||||
<N8nHeading size="xlarge">{{ i18n.baseText('workflows.renameVersion.title') }}</N8nHeading>
|
||||
</template>
|
||||
<template #content>
|
||||
<div :class="$style.content">
|
||||
<WorkflowPublishForm
|
||||
ref="publishForm"
|
||||
v-model:version-name="versionName"
|
||||
v-model:description="description"
|
||||
:disabled="saving"
|
||||
version-name-test-id="workflow-rename-version-name-input"
|
||||
description-test-id="workflow-rename-version-description-input"
|
||||
@submit="handleRename"
|
||||
/>
|
||||
<div :class="$style.actions">
|
||||
<N8nButton
|
||||
:disabled="saving"
|
||||
type="secondary"
|
||||
label="Cancel"
|
||||
data-test-id="workflow-rename-version-cancel-button"
|
||||
@click="modalBus.emit('close')"
|
||||
/>
|
||||
<N8nButton
|
||||
:disabled="isSaveDisabled"
|
||||
:loading="saving"
|
||||
label="Save"
|
||||
data-test-id="workflow-rename-version-save-button"
|
||||
@click="handleRename"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing--lg);
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: var(--spacing--xs);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -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<WorkflowHistoryActionTypes[number]>]: Lowercase<K>;
|
||||
};
|
||||
|
||||
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<Array<UserAction<IUser>>>(() =>
|
|||
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<WorkflowVersionRenameModalEventBusEvents>();
|
||||
|
||||
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<WorkflowSaveDraftModalEventBusEvents>();
|
||||
|
||||
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"
|
||||
|
|
|
|||
|
|
@ -103,12 +103,21 @@ export const useWorkflowHistoryStore = defineStore('workflowHistory', () => {
|
|||
});
|
||||
};
|
||||
|
||||
const renameVersion = async (
|
||||
workflowId: string,
|
||||
versionId: string,
|
||||
data: { name?: string; description?: string },
|
||||
): Promise<WorkflowHistory> => {
|
||||
return await whApi.updateWorkflowVersion(rootStore.restApiContext, workflowId, versionId, data);
|
||||
};
|
||||
|
||||
return {
|
||||
getWorkflowHistory,
|
||||
getWorkflowVersion,
|
||||
downloadVersion,
|
||||
cloneIntoNewWorkflow,
|
||||
restoreWorkflow,
|
||||
renameVersion,
|
||||
evaluatedPruneTime,
|
||||
shouldUpgrade,
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user