refactor: Migrate genericHelpers mixin to composable (#8220)
## Summary - Moved out canvas loading handling to canvas store - Tag editable routes via meta to remove router dependency from generic helpers - Replace all occurrences of `genericHelpers` mixin with composable and audit usage - Moved out `isRedirectSafe` and `getRedirectQueryParameter` out of genericHelpers to remove dependency on router Removing the router dependency is important, because `useRouter` and `useRoute` compostables are only available if called from component instance. So if composable is nested within another composable, we wouldn't be able to use these. In this case we'd always need to inject the router and pass it through several composables. That's why I moved the `readonly` logic to router meta and `isRedirectSafe` and `getRedirectQueryParameter` out as they were only used in a single component. --------- Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>
This commit is contained in:
@@ -2,9 +2,8 @@ import { defineComponent } from 'vue';
|
||||
import { mapStores } from 'pinia';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { i18n as locale } from '@/plugins/i18n';
|
||||
import { genericHelpers } from './genericHelpers';
|
||||
import type { IExecutionsSummary } from 'n8n-workflow';
|
||||
import { convertToDisplayDateComponents } from '@/utils/formatters/dateFormatter';
|
||||
import { convertToDisplayDate } from '@/utils/formatters/dateFormatter';
|
||||
|
||||
export interface IExecutionUIData {
|
||||
name: string;
|
||||
@@ -14,7 +13,6 @@ export interface IExecutionUIData {
|
||||
}
|
||||
|
||||
export const executionHelpers = defineComponent({
|
||||
mixins: [genericHelpers],
|
||||
computed: {
|
||||
...mapStores(useWorkflowsStore),
|
||||
executionId(): string {
|
||||
@@ -64,7 +62,7 @@ export const executionHelpers = defineComponent({
|
||||
const stoppedAt = execution.stoppedAt
|
||||
? new Date(execution.stoppedAt).getTime()
|
||||
: Date.now();
|
||||
status.runningTime = this.displayTimer(
|
||||
status.runningTime = this.$locale.displayTimer(
|
||||
stoppedAt - new Date(execution.startedAt).getTime(),
|
||||
true,
|
||||
);
|
||||
@@ -73,7 +71,7 @@ export const executionHelpers = defineComponent({
|
||||
return status;
|
||||
},
|
||||
formatDate(fullDate: Date | string | number) {
|
||||
const { date, time } = convertToDisplayDateComponents(fullDate);
|
||||
const { date, time } = convertToDisplayDate(fullDate);
|
||||
return locale.baseText('executionsList.started', { interpolate: { time, date } });
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
import { defineComponent } from 'vue';
|
||||
import { mapStores } from 'pinia';
|
||||
import { VIEWS } from '@/constants';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
||||
|
||||
export const genericHelpers = defineComponent({
|
||||
setup() {
|
||||
return {
|
||||
...useToast(),
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loadingService: null as null | { close: () => void; text: string },
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(useSourceControlStore),
|
||||
isReadOnlyRoute(): boolean {
|
||||
return ![
|
||||
VIEWS.WORKFLOW,
|
||||
VIEWS.NEW_WORKFLOW,
|
||||
VIEWS.LOG_STREAMING_SETTINGS,
|
||||
VIEWS.EXECUTION_DEBUG,
|
||||
].includes(this.$route.name as VIEWS);
|
||||
},
|
||||
readOnlyEnv(): boolean {
|
||||
return this.sourceControlStore.preferences.branchReadOnly;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
displayTimer(msPassed: number, showMs = false): string {
|
||||
if (msPassed < 60000) {
|
||||
if (!showMs) {
|
||||
return `${Math.floor(msPassed / 1000)}${this.$locale.baseText(
|
||||
'genericHelpers.secShort',
|
||||
)}`;
|
||||
}
|
||||
|
||||
return `${msPassed / 1000}${this.$locale.baseText('genericHelpers.secShort')}`;
|
||||
}
|
||||
|
||||
const secondsPassed = Math.floor(msPassed / 1000);
|
||||
const minutesPassed = Math.floor(secondsPassed / 60);
|
||||
const secondsLeft = (secondsPassed - minutesPassed * 60).toString().padStart(2, '0');
|
||||
|
||||
return `${minutesPassed}:${secondsLeft}${this.$locale.baseText('genericHelpers.minShort')}`;
|
||||
},
|
||||
|
||||
/**
|
||||
* @note Loading helpers extracted as composable in useLoadingService
|
||||
*/
|
||||
|
||||
startLoading(text?: string) {
|
||||
if (this.loadingService !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadingService = this.$loading({
|
||||
lock: true,
|
||||
text: text || this.$locale.baseText('genericHelpers.loading'),
|
||||
background: 'var(--color-dialog-overlay-background)',
|
||||
});
|
||||
},
|
||||
setLoadingText(text: string) {
|
||||
if (this.loadingService !== null) {
|
||||
this.loadingService.text = text;
|
||||
}
|
||||
},
|
||||
stopLoading() {
|
||||
if (this.loadingService !== null) {
|
||||
this.loadingService.close();
|
||||
this.loadingService = null;
|
||||
}
|
||||
},
|
||||
isRedirectSafe() {
|
||||
const redirect = this.getRedirectQueryParameter();
|
||||
return redirect.startsWith('/');
|
||||
},
|
||||
getRedirectQueryParameter() {
|
||||
let redirect = '';
|
||||
if (typeof this.$route.query.redirect === 'string') {
|
||||
redirect = decodeURIComponent(this.$route.query.redirect);
|
||||
}
|
||||
return redirect;
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -48,7 +48,6 @@ import type {
|
||||
import { useMessage } from '@/composables/useMessage';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||
import { genericHelpers } from '@/mixins/genericHelpers';
|
||||
|
||||
import { get, isEqual } from 'lodash-es';
|
||||
|
||||
@@ -68,6 +67,8 @@ import { v4 as uuid } from 'uuid';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { getCredentialTypeName, isCredentialOnlyNodeType } from '@/utils/credentialOnlyNodes';
|
||||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||
import { useCanvasStore } from '@/stores/canvas.store';
|
||||
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
||||
|
||||
export function getParentMainInputNode(workflow: Workflow, node: INode): INode {
|
||||
const nodeType = useNodeTypesStore().getNodeType(node.type);
|
||||
@@ -475,9 +476,9 @@ export function executeData(
|
||||
}
|
||||
|
||||
export const workflowHelpers = defineComponent({
|
||||
mixins: [genericHelpers],
|
||||
setup() {
|
||||
const nodeHelpers = useNodeHelpers();
|
||||
|
||||
return {
|
||||
...useToast(),
|
||||
...useMessage(),
|
||||
@@ -887,12 +888,13 @@ export const workflowHelpers = defineComponent({
|
||||
redirect = true,
|
||||
forceSave = false,
|
||||
): Promise<boolean> {
|
||||
if (this.readOnlyEnv) {
|
||||
return;
|
||||
const readOnlyEnv = useSourceControlStore().preferences.branchReadOnly;
|
||||
if (readOnlyEnv) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isLoading = useCanvasStore().isLoading;
|
||||
const currentWorkflow = id || this.$route.params.name;
|
||||
const isLoading = this.loadingService !== null;
|
||||
|
||||
if (!currentWorkflow || ['new', PLACEHOLDER_EMPTY_WORKFLOW_ID].includes(currentWorkflow)) {
|
||||
return this.saveAsNewWorkflow({ name, tags }, redirect);
|
||||
|
||||
Reference in New Issue
Block a user