1
1
mirror of https://github.com/n8n-io/n8n.git synced 2024-10-06 09:37:36 +03:00

fix(editor): Fix workflow loading after switching to executions view in new canvas (no-changelog) (#10655)

This commit is contained in:
Alex Grozav 2024-09-04 13:18:20 +03:00 committed by GitHub
parent 8750b287f5
commit 0f91fd2b2e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 107 additions and 74 deletions

View File

@ -328,6 +328,7 @@ export const routes: RouteRecordRaw[] = [
default: NodeView,
},
meta: {
nodeView: true,
middleware: ['authenticated'],
middlewareOptions: {
authenticated: {

View File

@ -428,9 +428,9 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
async function fetchWorkflow(id: string): Promise<IWorkflowDb> {
const rootStore = useRootStore();
const workflow = await workflowsApi.getWorkflow(rootStore.restApiContext, id);
addWorkflow(workflow);
return workflow;
const workflowData = await workflowsApi.getWorkflow(rootStore.restApiContext, id);
addWorkflow(workflowData);
return workflowData;
}
async function getNewWorkflowData(name?: string, projectId?: string): Promise<INewWorkflowData> {

View File

@ -5,13 +5,13 @@ import {
nextTick,
onActivated,
onBeforeMount,
onBeforeUnmount,
onDeactivated,
onMounted,
ref,
useCssModule,
watch,
h,
onBeforeUnmount,
} from 'vue';
import { useRoute, useRouter } from 'vue-router';
import WorkflowCanvas from '@/components/canvas/WorkflowCanvas.vue';
@ -53,7 +53,9 @@ import {
MAIN_HEADER_TABS,
MANUAL_CHAT_TRIGGER_NODE_TYPE,
MODAL_CONFIRM,
NEW_WORKFLOW_ID,
NODE_CREATOR_OPEN_SOURCES,
PLACEHOLDER_EMPTY_WORKFLOW_ID,
START_NODE_TYPE,
STICKY_NODE_TYPE,
VALID_WORKFLOW_IMPORT_URL_REGEX,
@ -152,7 +154,7 @@ const canvasEventBus = createEventBus<CanvasEventBusEvents>();
const { addBeforeUnloadEventBindings, removeBeforeUnloadEventBindings } = useBeforeUnload({
route,
});
const { registerCustomAction } = useGlobalLinkActions();
const { registerCustomAction, unregisterCustomAction } = useGlobalLinkActions();
const { runWorkflow, stopCurrentExecution, stopWaitingForWebhook } = useRunWorkflow({ router });
const {
updateNodePosition,
@ -201,12 +203,18 @@ const isExecutionPreview = ref(false);
const canOpenNDV = ref(true);
const hideNodeIssues = ref(false);
const workflowId = computed<string>(() => route.params.name as string);
const workflow = computed(() => workflowsStore.workflowsById[workflowId.value]);
const initializedWorkflowId = ref<string | undefined>();
const workflowId = computed(() => {
const workflowIdParam = route.params.name as string;
return [PLACEHOLDER_EMPTY_WORKFLOW_ID, NEW_WORKFLOW_ID].includes(workflowIdParam)
? undefined
: workflowIdParam;
});
const isNewWorkflowRoute = computed(() => route.name === VIEWS.NEW_WORKFLOW);
const isNewWorkflowRoute = computed(() => route.name === VIEWS.NEW_WORKFLOW || !workflowId.value);
const isWorkflowRoute = computed(() => !!route?.meta?.nodeView);
const isDemoRoute = computed(() => route.name === VIEWS.DEMO);
const isReadOnlyRoute = computed(() => route?.meta?.readOnlyCanvas === true);
const isReadOnlyRoute = computed(() => !!route?.meta?.readOnlyCanvas);
const isReadOnlyEnvironment = computed(() => {
return sourceControlStore.preferences.branchReadOnly;
});
@ -287,6 +295,10 @@ async function initializeRoute() {
return;
}
const isAlreadyInitialized =
initializedWorkflowId.value &&
[NEW_WORKFLOW_ID, workflowId.value].includes(initializedWorkflowId.value);
// This function is called on route change as well, so we need to do the following:
// - if the redirect is blank, then do nothing
// - if the route is the template import view, then open the template
@ -296,11 +308,11 @@ async function initializeRoute() {
} else if (route.name === VIEWS.TEMPLATE_IMPORT) {
const templateId = route.params.id;
await openWorkflowTemplate(templateId.toString());
} else {
} else if (isWorkflowRoute.value && !isAlreadyInitialized) {
historyStore.reset();
// If there is no workflow id, treat it as a new workflow
if (!workflowId.value || isNewWorkflowRoute.value) {
if (isNewWorkflowRoute.value || !workflowId.value) {
if (route.meta?.nodeView === true) {
await initializeWorkspaceForNewWorkflow();
}
@ -308,14 +320,14 @@ async function initializeRoute() {
}
await initializeWorkspaceForExistingWorkflow(workflowId.value);
nodeHelpers.updateNodesInputIssues();
nodeHelpers.updateNodesCredentialsIssues();
nodeHelpers.updateNodesParameterIssues();
await loadCredentials();
await initializeDebugMode();
}
nodeHelpers.updateNodesInputIssues();
nodeHelpers.updateNodesCredentialsIssues();
nodeHelpers.updateNodesParameterIssues();
await loadCredentials();
await initializeDebugMode();
}
async function initializeWorkspaceForNewWorkflow() {
@ -325,11 +337,10 @@ async function initializeWorkspaceForNewWorkflow() {
workflowsStore.makeNewWorkflowShareable();
uiStore.nodeViewInitialized = true;
initializedWorkflowId.value = NEW_WORKFLOW_ID;
}
async function initializeWorkspaceForExistingWorkflow(id: string) {
resetWorkspace();
try {
const workflowData = await workflowsStore.fetchWorkflow(id);
@ -339,7 +350,9 @@ async function initializeWorkspaceForExistingWorkflow(id: string) {
trackOpenWorkflowFromOnboardingTemplate();
}
await projectsStore.setProjectNavActiveIdByWorkflowHomeProject(workflow.value.homeProject);
await projectsStore.setProjectNavActiveIdByWorkflowHomeProject(
editableWorkflow.value.homeProject,
);
collaborationStore.notifyWorkflowOpened(id);
} catch (error) {
@ -350,6 +363,7 @@ async function initializeWorkspaceForExistingWorkflow(id: string) {
});
} finally {
uiStore.nodeViewInitialized = true;
initializedWorkflowId.value = workflowId.value;
}
}
@ -359,7 +373,7 @@ async function initializeWorkspaceForExistingWorkflow(id: string) {
async function openWorkflow(data: IWorkflowDb) {
resetWorkspace();
titleSet(workflow.value.name, 'IDLE');
titleSet(editableWorkflow.value.name, 'IDLE');
await initializeWorkspace(data);
@ -382,7 +396,7 @@ async function openWorkflow(data: IWorkflowDb) {
function trackOpenWorkflowFromOnboardingTemplate() {
telemetry.track(
`User opened workflow from onboarding template with ID ${workflow.value.meta?.onboardingId}`,
`User opened workflow from onboarding template with ID ${editableWorkflow.value.meta?.onboardingId}`,
{
workflow_id: workflowId.value,
},
@ -716,8 +730,8 @@ function onClickNodeAdd(source: string, sourceHandle: string) {
async function loadCredentials() {
let options: { workflowId: string } | { projectId: string };
if (workflow.value) {
options = { workflowId: workflow.value.id };
if (editableWorkflow.value) {
options = { workflowId: editableWorkflow.value.id };
} else {
const queryParam =
typeof route.query?.projectId === 'string' ? route.query?.projectId : undefined;
@ -917,7 +931,9 @@ function onClickConnectionAdd(connection: Connection) {
*/
const workflowPermissions = computed(() => {
return getResourcePermissions(workflowsStore.getWorkflowById(workflowId.value)?.scopes).workflow;
return workflowId.value
? getResourcePermissions(workflowsStore.getWorkflowById(workflowId.value)?.scopes).workflow
: {};
});
const projectPermissions = computed(() => {
@ -1200,7 +1216,7 @@ async function onSourceControlPull() {
loadCredentials(),
]);
if (workflowId.value !== null && !uiStore.stateIsDirty) {
if (workflowId.value && !uiStore.stateIsDirty) {
const workflowData = await workflowsStore.fetchWorkflow(workflowId.value);
if (workflowData) {
titleSet(workflowData.name, 'IDLE');
@ -1306,6 +1322,10 @@ async function onPostMessageReceived(messageEvent: MessageEvent) {
*/
function checkIfEditingIsAllowed(): boolean {
if (!initializedWorkflowId.value) {
return true;
}
if (readOnlyNotification.value?.visible) {
return false;
}
@ -1438,6 +1458,12 @@ function registerCustomActions() {
});
}
function unregisterCustomActions() {
unregisterCustomAction('openNodeDetail');
unregisterCustomAction('openSelectiveNodeCreator');
unregisterCustomAction('showNodeCreator');
}
/**
* Routing
*/
@ -1445,10 +1471,6 @@ function registerCustomActions() {
watch(
() => route.name,
async () => {
if (!checkIfEditingIsAllowed()) {
return;
}
await initializeRoute();
},
);
@ -1464,8 +1486,9 @@ onBeforeMount(() => {
}
});
onMounted(async () => {
onMounted(() => {
canvasStore.startLoading();
titleReset();
resetWorkspace();
@ -1479,6 +1502,8 @@ onMounted(async () => {
.finally(() => {
isLoading.value = false;
canvasStore.stopLoading();
void externalHooks.run('nodeView.mount').catch(() => {});
});
void usersStore.showPersonalizationSurvey();
@ -1486,34 +1511,31 @@ onMounted(async () => {
checkIfRouteIsAllowed();
});
addUndoRedoEventBindings();
addPostMessageEventBindings();
addSourceControlEventBindings();
addPostMessageEventBindings();
addWorkflowSavedEventBindings();
addBeforeUnloadEventBindings();
addImportEventBindings();
addExecutionOpenedEventBindings();
addWorkflowSavedEventBindings();
registerCustomActions();
// @TODO: This currently breaks since front-end hooks are still not updated to work with pinia store
void externalHooks.run('nodeView.mount').catch(() => {});
});
onActivated(() => {
addBeforeUnloadEventBindings();
});
onBeforeUnmount(() => {
removeUndoRedoEventBindings();
removePostMessageEventBindings();
removeSourceControlEventBindings();
removeImportEventBindings();
removeExecutionOpenedEventBindings();
removeWorkflowSavedEventBindings();
onActivated(async () => {
addUndoRedoEventBindings();
});
onDeactivated(() => {
removeUndoRedoEventBindings();
});
onBeforeUnmount(() => {
removeSourceControlEventBindings();
removePostMessageEventBindings();
removeWorkflowSavedEventBindings();
removeBeforeUnloadEventBindings();
removeImportEventBindings();
removeExecutionOpenedEventBindings();
unregisterCustomActions();
collaborationStore.terminate();
});
</script>

View File

@ -8,7 +8,7 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { NO_NETWORK_ERROR_CODE } from '@/utils/apiUtils';
import { useToast } from '@/composables/useToast';
import { PLACEHOLDER_EMPTY_WORKFLOW_ID, VIEWS } from '@/constants';
import { NEW_WORKFLOW_ID, PLACEHOLDER_EMPTY_WORKFLOW_ID, VIEWS } from '@/constants';
import { useRoute, useRouter } from 'vue-router';
import type { ExecutionSummary } from 'n8n-workflow';
import { useDebounce } from '@/composables/useDebounce';
@ -34,15 +34,22 @@ const loadingMore = ref(false);
const workflow = ref<IWorkflowDb | undefined>();
const workflowId = computed(() => {
return (route.params.name as string) || workflowsStore.workflowId;
const workflowIdParam = route.params.name as string;
return [PLACEHOLDER_EMPTY_WORKFLOW_ID, NEW_WORKFLOW_ID].includes(workflowIdParam)
? undefined
: workflowIdParam;
});
const executionId = computed(() => route.params.executionId as string);
const executions = computed(() => [
...(executionsStore.currentExecutionsByWorkflowId[workflowId.value] ?? []),
...(executionsStore.executionsByWorkflowId[workflowId.value] ?? []),
]);
const executions = computed(() =>
workflowId.value
? [
...(executionsStore.currentExecutionsByWorkflowId[workflowId.value] ?? []),
...(executionsStore.executionsByWorkflowId[workflowId.value] ?? []),
]
: [],
);
const execution = computed(() => {
return executions.value.find((e) => e.id === executionId.value) ?? currentExecution.value;
@ -65,13 +72,12 @@ watch(
);
onMounted(async () => {
await nodeTypesStore.loadNodeTypesIfNotLoaded();
await Promise.all([
nodeTypesStore.loadNodeTypesIfNotLoaded(),
fetchWorkflow(),
executionsStore.initialize(workflowId.value),
]);
await fetchExecution();
await Promise.all([nodeTypesStore.loadNodeTypesIfNotLoaded(), fetchWorkflow()]);
if (workflowId.value) {
await Promise.all([executionsStore.initialize(workflowId.value), fetchExecution()]);
}
await initializeRoute();
document.addEventListener('visibilitychange', onDocumentVisibilityChange);
});
@ -116,19 +122,23 @@ async function initializeRoute() {
}
async function fetchWorkflow() {
// Check if the workflow already has an ID
// In other words: are we coming from the Editor tab or browser loaded the Executions tab directly
if (workflowsStore.workflow.id === PLACEHOLDER_EMPTY_WORKFLOW_ID) {
try {
await workflowsStore.fetchActiveWorkflows();
const data = await workflowsStore.fetchWorkflow(workflowId.value);
workflowHelpers.initState(data);
await nodeHelpers.addNodes(data.nodes, data.connections);
} catch (error) {
toast.showError(error, i18n.baseText('nodeView.showError.openWorkflow.title'));
if (workflowId.value) {
// Check if we are loading the Executions tab directly, without having loaded the workflow
if (workflowsStore.workflow.id === PLACEHOLDER_EMPTY_WORKFLOW_ID) {
try {
await workflowsStore.fetchActiveWorkflows();
const data = await workflowsStore.fetchWorkflow(workflowId.value);
workflowHelpers.initState(data);
await nodeHelpers.addNodes(data.nodes, data.connections);
} catch (error) {
toast.showError(error, i18n.baseText('nodeView.showError.openWorkflow.title'));
}
}
workflow.value = workflowsStore.getWorkflowById(workflowId.value);
} else {
workflow.value = workflowsStore.workflow;
}
workflow.value = workflowsStore.getWorkflowById(workflowId.value);
}
async function onAutoRefreshToggle(value: boolean) {