Skip to content

Commit

Permalink
feat(editor): Overhaul document title management (#10999)
Browse files Browse the repository at this point in the history
  • Loading branch information
netroy authored Sep 30, 2024
1 parent 63e6f1f commit bb28956
Show file tree
Hide file tree
Showing 31 changed files with 155 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
import { useProjectsStore } from '@/stores/projects.store';
import { saveAs } from 'file-saver';
import { useTitleChange } from '@/composables/useTitleChange';
import { useDocumentTitle } from '@/composables/useDocumentTitle';
import { useMessage } from '@/composables/useMessage';
import { useToast } from '@/composables/useToast';
import { getResourcePermissions } from '@/permissions';
Expand Down Expand Up @@ -87,7 +87,7 @@ const locale = useI18n();
const telemetry = useTelemetry();
const message = useMessage();
const toast = useToast();
const titleChange = useTitleChange();
const documentTitle = useDocumentTitle();
const workflowHelpers = useWorkflowHelpers({ router });
const isTagsEditEnabled = ref(false);
Expand Down Expand Up @@ -558,7 +558,7 @@ async function onWorkflowMenuSelect(action: WORKFLOW_MENU_ACTIONS): Promise<void
}
uiStore.stateIsDirty = false;
// Reset tab title since workflow is deleted.
titleChange.titleReset();
documentTitle.reset();
toast.showMessage({
title: locale.baseText('mainSidebar.showMessage.handleSelect1.title'),
type: 'success',
Expand Down
5 changes: 3 additions & 2 deletions packages/editor-ui/src/components/WorkerList.ee.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { useI18n } from '@/composables/useI18n';
import { useToast } from '@/composables/useToast';
import { useUIStore } from '@/stores/ui.store';
import { useOrchestrationStore } from '@/stores/orchestration.store';
import { setPageTitle } from '@/utils/htmlUtils';
import { useDocumentTitle } from '@/composables/useDocumentTitle';
import WorkerCard from './Workers/WorkerCard.ee.vue';
import { usePushConnection } from '@/composables/usePushConnection';
import { usePushConnectionStore } from '@/stores/pushConnection.store';
Expand All @@ -36,6 +36,7 @@ export default defineComponent({
i18n,
pushConnection,
...useToast(),
documentTitle: useDocumentTitle(),
};
},
computed: {
Expand All @@ -58,7 +59,7 @@ export default defineComponent({
},
},
mounted() {
setPageTitle(`n8n - ${this.pageTitle}`);
this.documentTitle.set(this.pageTitle);
this.$telemetry.track('User viewed worker view', {
instance_id: this.rootStore.instanceId,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { mock } from 'vitest-mock-extended';
import { describe, it, expect } from 'vitest';
import type { FrontendSettings } from '@n8n/api-types';
import { useDocumentTitle } from '../useDocumentTitle';

const settings = mock<FrontendSettings>({ releaseChannel: 'stable' });
vi.mock('@/stores/settings.store', () => ({
useSettingsStore: vi.fn(() => ({ settings })),
}));

describe('useDocumentTitle', () => {
it('should set the document title', () => {
const { set } = useDocumentTitle();
set('Test Title');
expect(document.title).toBe('Test Title - n8n');
});

it('should reset the document title', () => {
const { set, reset } = useDocumentTitle();
set('Test Title');
reset();
expect(document.title).toBe('Workflow Automation - n8n');
});

it('should use the correct prefix for the release channel', () => {
settings.releaseChannel = 'beta';
const { set } = useDocumentTitle();
set('Test Title');
expect(document.title).toBe('Test Title - n8n[BETA]');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ vi.mock('@/composables/useWorkflowHelpers', () => ({
getCurrentWorkflow: vi.fn(),
saveCurrentWorkflow: vi.fn(),
getWorkflowDataToSave: vi.fn(),
setDocumentTitle: vi.fn(),
}),
}));

Expand All @@ -61,10 +62,6 @@ vi.mock('@/composables/useNodeHelpers', () => ({
}),
}));

vi.mock('@/composables/useTitleChange', () => ({
useTitleChange: vi.fn().mockReturnValue({ titleSet: vi.fn() }),
}));

vi.mock('vue-router', async (importOriginal) => {
const { RouterLink } = await importOriginal<typeof router>();
return {
Expand Down
21 changes: 21 additions & 0 deletions packages/editor-ui/src/composables/useDocumentTitle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useSettingsStore } from '@/stores/settings.store';

const DEFAULT_TITLE = 'Workflow Automation';

export function useDocumentTitle() {
const settingsStore = useSettingsStore();
const { releaseChannel } = settingsStore.settings;
const suffix =
!releaseChannel || releaseChannel === 'stable' ? 'n8n' : `n8n[${releaseChannel.toUpperCase()}]`;

const set = (title: string) => {
const sections = [title || DEFAULT_TITLE, suffix];
document.title = sections.join(' - ');
};

const reset = () => {
set('');
};

return { set, reset };
}
8 changes: 3 additions & 5 deletions packages/editor-ui/src/composables/usePushConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import type { PushMessage, PushPayload } from '@n8n/api-types';

import type { IExecutionResponse, IExecutionsCurrentSummaryExtended } from '@/Interface';
import { useNodeHelpers } from '@/composables/useNodeHelpers';
import { useTitleChange } from '@/composables/useTitleChange';
import { useToast } from '@/composables/useToast';
import { WORKFLOW_SETTINGS_MODAL_KEY } from '@/constants';
import { getTriggerNodeServiceName } from '@/utils/nodeTypesUtils';
Expand All @@ -43,7 +42,6 @@ type IPushDataExecutionFinishedPayload = PushPayload<'executionFinished'>;
export function usePushConnection({ router }: { router: ReturnType<typeof useRouter> }) {
const workflowHelpers = useWorkflowHelpers({ router });
const nodeHelpers = useNodeHelpers();
const titleChange = useTitleChange();
const toast = useToast();
const i18n = useI18n();
const telemetry = useTelemetry();
Expand Down Expand Up @@ -324,7 +322,7 @@ export function usePushConnection({ router }: { router: ReturnType<typeof useRou
}

// Workflow did start but had been put to wait
titleChange.titleSet(workflow.name as string, 'IDLE');
workflowHelpers.setDocumentTitle(workflow.name as string, 'IDLE');
toast.showToast({
title: 'Workflow started waiting',
message: `${action} <a href="https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.wait/" target="_blank">More info</a>`,
Expand All @@ -333,7 +331,7 @@ export function usePushConnection({ router }: { router: ReturnType<typeof useRou
dangerouslyUseHTMLString: true,
});
} else if (runDataExecuted.finished !== true) {
titleChange.titleSet(workflow.name as string, 'ERROR');
workflowHelpers.setDocumentTitle(workflow.name as string, 'ERROR');

if (
runDataExecuted.data.resultData.error?.name === 'ExpressionError' &&
Expand Down Expand Up @@ -442,7 +440,7 @@ export function usePushConnection({ router }: { router: ReturnType<typeof useRou
}
} else {
// Workflow did execute without a problem
titleChange.titleSet(workflow.name as string, 'IDLE');
workflowHelpers.setDocumentTitle(workflow.name as string, 'IDLE');

const execution = workflowsStore.getWorkflowExecution;
if (execution?.executedNode) {
Expand Down
10 changes: 4 additions & 6 deletions packages/editor-ui/src/composables/useRunWorkflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import { useToast } from '@/composables/useToast';
import { useNodeHelpers } from '@/composables/useNodeHelpers';

import { CHAT_TRIGGER_NODE_TYPE, WORKFLOW_LM_CHAT_MODAL_KEY } from '@/constants';
import { useTitleChange } from '@/composables/useTitleChange';
import { useRootStore } from '@/stores/root.store';
import { useUIStore } from '@/stores/ui.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
Expand All @@ -41,7 +40,6 @@ export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType<typeof u
const workflowHelpers = useWorkflowHelpers({ router: useRunWorkflowOpts.router });
const i18n = useI18n();
const toast = useToast();
const { titleSet } = useTitleChange();

const rootStore = useRootStore();
const uiStore = useUIStore();
Expand Down Expand Up @@ -93,7 +91,7 @@ export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType<typeof u
return;
}

titleSet(workflow.name as string, 'EXECUTING');
workflowHelpers.setDocumentTitle(workflow.name as string, 'EXECUTING');

toast.clearAllStickyNotifications();

Expand Down Expand Up @@ -309,7 +307,7 @@ export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType<typeof u

return runWorkflowApiResponse;
} catch (error) {
titleSet(workflow.name as string, 'ERROR');
workflowHelpers.setDocumentTitle(workflow.name as string, 'ERROR');
toast.showError(error, i18n.baseText('workflowRun.showError.title'));
return undefined;
}
Expand Down Expand Up @@ -387,7 +385,7 @@ export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType<typeof u
workflowsStore.executingNode.length = 0;
uiStore.removeActiveAction('workflowRunning');

titleSet(workflowsStore.workflowName, 'IDLE');
workflowHelpers.setDocumentTitle(workflowsStore.workflowName, 'IDLE');
toast.showMessage({
title: i18n.baseText('nodeView.showMessage.stopExecutionCatch.unsaved.title'),
message: i18n.baseText('nodeView.showMessage.stopExecutionCatch.unsaved.message'),
Expand All @@ -408,7 +406,7 @@ export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType<typeof u
retryOf: execution.retryOf,
};
workflowsStore.finishActiveExecution(pushData);
titleSet(execution.workflowData.name, 'IDLE');
workflowHelpers.setDocumentTitle(execution.workflowData.name, 'IDLE');
workflowsStore.executingNode.length = 0;
workflowsStore.setWorkflowExecutionData(executedData as IExecutionResponse);
uiStore.removeActiveAction('workflowRunning');
Expand Down
30 changes: 0 additions & 30 deletions packages/editor-ui/src/composables/useTitleChange.ts

This file was deleted.

14 changes: 14 additions & 0 deletions packages/editor-ui/src/composables/useWorkflowHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import type {
IWorkflowDataUpdate,
IWorkflowDb,
TargetItem,
WorkflowTitleStatus,
XYPosition,
} from '@/Interface';

Expand All @@ -57,6 +58,7 @@ import { getSourceItems } from '@/utils/pairedItemUtils';
import { v4 as uuid } from 'uuid';
import { useSettingsStore } from '@/stores/settings.store';
import { getCredentialTypeName, isCredentialOnlyNodeType } from '@/utils/credentialOnlyNodes';
import { useDocumentTitle } from '@/composables/useDocumentTitle';
import { useExternalHooks } from '@/composables/useExternalHooks';
import { useCanvasStore } from '@/stores/canvas.store';
import { useSourceControlStore } from '@/stores/sourceControl.store';
Expand Down Expand Up @@ -458,6 +460,17 @@ export function useWorkflowHelpers(options: { router: ReturnType<typeof useRoute
const message = useMessage();
const i18n = useI18n();
const telemetry = useTelemetry();
const documentTitle = useDocumentTitle();

const setDocumentTitle = (workflowName: string, status: WorkflowTitleStatus) => {
let icon = '⚠️';
if (status === 'EXECUTING') {
icon = '🔄';
} else if (status === 'IDLE') {
icon = '▶️';
}
documentTitle.set(`${icon} ${workflowName}`);
};

function getNodeTypesMaxCount() {
const nodes = workflowsStore.allNodes;
Expand Down Expand Up @@ -1172,6 +1185,7 @@ export function useWorkflowHelpers(options: { router: ReturnType<typeof useRoute
}

return {
setDocumentTitle,
resolveParameter,
resolveRequiredParameters,
getCurrentWorkflow,
Expand Down
4 changes: 0 additions & 4 deletions packages/editor-ui/src/stores/settings.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { useUIStore } from './ui.store';
import { useUsersStore } from './users.store';
import { useVersionsStore } from './versions.store';
import { makeRestApiRequest } from '@/utils/apiUtils';
import { useTitleChange } from '@/composables/useTitleChange';
import { useToast } from '@/composables/useToast';
import { i18n } from '@/plugins/i18n';

Expand Down Expand Up @@ -258,9 +257,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {

ExpressionEvaluatorProxy.setEvaluator(settings.value.expressions.evaluator);

// Re-compute title since settings are now available
useTitleChange().titleReset();

initialized.value = true;
} catch (e) {
showToast({
Expand Down
4 changes: 0 additions & 4 deletions packages/editor-ui/src/utils/htmlUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,6 @@ export const sanitizeIfString = <T>(message: T): string | T => {
return message;
};

export function setPageTitle(title: string) {
window.document.title = title;
}

export function convertRemToPixels(rem: string) {
return parseInt(rem, 10) * parseFloat(getComputedStyle(document.documentElement).fontSize);
}
Expand Down
3 changes: 3 additions & 0 deletions packages/editor-ui/src/views/CredentialsView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import ProjectTabs from '@/components/Projects/ProjectTabs.vue';
import useEnvironmentsStore from '@/stores/environments.ee.store';
import { useSettingsStore } from '@/stores/settings.store';
import { getResourcePermissions } from '@/permissions';
import { useDocumentTitle } from '@/composables/useDocumentTitle';
export default defineComponent({
name: 'CredentialsView',
Expand All @@ -35,6 +36,7 @@ export default defineComponent({
},
sourceControlStoreUnsubscribe: () => {},
loading: false,
documentTitle: useDocumentTitle(),
};
},
computed: {
Expand Down Expand Up @@ -86,6 +88,7 @@ export default defineComponent({
},
},
mounted() {
this.documentTitle.set(this.$locale.baseText('credentials.heading'));
this.sourceControlStoreUnsubscribe = this.sourceControlStore.$onAction(({ name, after }) => {
if (name === 'pullWorkfolder' && after) {
after(() => {
Expand Down
5 changes: 3 additions & 2 deletions packages/editor-ui/src/views/ExecutionsView.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<script lang="ts" setup>
import { onBeforeMount, onBeforeUnmount, onMounted } from 'vue';
import GlobalExecutionsList from '@/components/executions/global/GlobalExecutionsList.vue';
import { setPageTitle } from '@/utils/htmlUtils';
import { useI18n } from '@/composables/useI18n';
import { useTelemetry } from '@/composables/useTelemetry';
import { useExternalHooks } from '@/composables/useExternalHooks';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useExecutionsStore } from '@/stores/executions.store';
import { useToast } from '@/composables/useToast';
import { useDocumentTitle } from '@/composables/useDocumentTitle';
import { storeToRefs } from 'pinia';
import type { ExecutionFilterType } from '@/Interface';
Expand All @@ -16,6 +16,7 @@ const telemetry = useTelemetry();
const externalHooks = useExternalHooks();
const workflowsStore = useWorkflowsStore();
const executionsStore = useExecutionsStore();
const documentTitle = useDocumentTitle();
const toast = useToast();
Expand All @@ -32,7 +33,7 @@ onBeforeMount(async () => {
});
onMounted(async () => {
setPageTitle(`n8n - ${i18n.baseText('executionsList.workflowExecutions')}`);
documentTitle.set(i18n.baseText('executionsList.workflowExecutions'));
document.addEventListener('visibilitychange', onDocumentVisibilityChange);
await executionsStore.initialize();
Expand Down
Loading

0 comments on commit bb28956

Please sign in to comment.