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, default: NodeView,
}, },
meta: { meta: {
nodeView: true,
middleware: ['authenticated'], middleware: ['authenticated'],
middlewareOptions: { middlewareOptions: {
authenticated: { authenticated: {

View file

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

View file

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

View file

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