refactor(editor): Fix type errors (no-changelog) (#9584)

This commit is contained in:
Csaba Tuncsik
2024-06-03 14:24:26 +02:00
committed by GitHub
parent c1b1ee57b1
commit 4eb6edc73a
11 changed files with 83 additions and 62 deletions

View File

@@ -81,13 +81,10 @@ export default defineComponent({
return this.workflowsStore.workflow; return this.workflowsStore.workflow;
}, },
currentWorkflow(): string { currentWorkflow(): string {
return this.$route.params.name || this.workflowsStore.workflowId; return String(this.$route.params.name || this.workflowsStore.workflowId);
}, },
onWorkflowPage(): boolean { onWorkflowPage(): boolean {
return ( return !!(this.$route.meta.nodeView || this.$route.meta.keepWorkflowAlive);
this.$route.meta &&
(this.$route.meta.nodeView || this.$route.meta.keepWorkflowAlive === true)
);
}, },
readOnly(): boolean { readOnly(): boolean {
return this.sourceControlStore.preferences.branchReadOnly; return this.sourceControlStore.preferences.branchReadOnly;
@@ -124,11 +121,15 @@ export default defineComponent({
this.activeHeaderTab = MAIN_HEADER_TABS.WORKFLOW; this.activeHeaderTab = MAIN_HEADER_TABS.WORKFLOW;
} }
if (to.params.name !== 'new') { if (to.params.name !== 'new' && typeof to.params.name === 'string') {
this.workflowToReturnTo = to.params.name; this.workflowToReturnTo = to.params.name;
} }
if (from?.name === VIEWS.EXECUTION_PREVIEW && to.params.name === from.params.name) { if (
from?.name === VIEWS.EXECUTION_PREVIEW &&
to.params.name === from.params.name &&
typeof from.params.executionId === 'string'
) {
this.executionToReturnTo = from.params.executionId; this.executionToReturnTo = from.params.executionId;
} }
}, },

View File

@@ -3,7 +3,6 @@ import { createComponentRenderer } from '@/__tests__/render';
import { STORES, WORKFLOW_SHARE_MODAL_KEY } from '@/constants'; import { STORES, WORKFLOW_SHARE_MODAL_KEY } from '@/constants';
import { createTestingPinia } from '@pinia/testing'; import { createTestingPinia } from '@pinia/testing';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useUIStore } from '@/stores/ui.store'; import { useUIStore } from '@/stores/ui.store';
vi.mock('vue-router', async () => { vi.mock('vue-router', async () => {
@@ -48,12 +47,10 @@ const renderComponent = createComponentRenderer(WorkflowDetails, {
pinia: createTestingPinia({ initialState }), pinia: createTestingPinia({ initialState }),
}); });
let workflowsStore: ReturnType<typeof useWorkflowsStore>;
let uiStore: ReturnType<typeof useUIStore>; let uiStore: ReturnType<typeof useUIStore>;
describe('WorkflowDetails', () => { describe('WorkflowDetails', () => {
beforeEach(() => { beforeEach(() => {
workflowsStore = useWorkflowsStore();
uiStore = useUIStore(); uiStore = useUIStore();
}); });
it('renders workflow name and tags', async () => { it('renders workflow name and tags', async () => {
@@ -101,8 +98,6 @@ describe('WorkflowDetails', () => {
}); });
it('opens share modal on share button click', async () => { it('opens share modal on share button click', async () => {
vi.spyOn(workflowsStore, 'getWorkflowById', 'get').mockReturnValue(() => ({}));
const openModalSpy = vi.spyOn(uiStore, 'openModalWithData'); const openModalSpy = vi.spyOn(uiStore, 'openModalWithData');
const { getByTestId } = renderComponent({ const { getByTestId } = renderComponent({

View File

@@ -67,8 +67,8 @@
> >
<div :class="{ [$style.avatar]: true, ['clickable']: isCollapsed }"> <div :class="{ [$style.avatar]: true, ['clickable']: isCollapsed }">
<n8n-avatar <n8n-avatar
:first-name="usersStore.currentUser.firstName" :first-name="usersStore.currentUser?.firstName"
:last-name="usersStore.currentUser.lastName" :last-name="usersStore.currentUser?.lastName"
size="small" size="small"
/> />
</div> </div>
@@ -88,7 +88,7 @@
:class="{ ['ml-2xs']: true, [$style.userName]: true, [$style.expanded]: fullyExpanded }" :class="{ ['ml-2xs']: true, [$style.userName]: true, [$style.expanded]: fullyExpanded }"
> >
<n8n-text size="small" :bold="true" color="text-dark">{{ <n8n-text size="small" :bold="true" color="text-dark">{{
usersStore.currentUser.fullName usersStore.currentUser?.fullName
}}</n8n-text> }}</n8n-text>
</div> </div>
<div :class="{ [$style.userActions]: true, [$style.expanded]: fullyExpanded }"> <div :class="{ [$style.userActions]: true, [$style.expanded]: fullyExpanded }">
@@ -142,7 +142,7 @@ export default defineComponent({
ProjectNavigation, ProjectNavigation,
}, },
mixins: [userHelpers], mixins: [userHelpers],
setup(props, ctx) { setup() {
const externalHooks = useExternalHooks(); const externalHooks = useExternalHooks();
const { callDebounced } = useDebounce(); const { callDebounced } = useDebounce();
@@ -435,11 +435,11 @@ export default defineComponent({
findFirstAccessibleSettingsRoute() { findFirstAccessibleSettingsRoute() {
const settingsRoutes = this.$router const settingsRoutes = this.$router
.getRoutes() .getRoutes()
.find((route) => route.path === '/settings')! .find((route) => route.path === '/settings')
.children.map((route) => route.name ?? ''); ?.children.map((route) => route.name ?? '');
let defaultSettingsRoute = { name: VIEWS.USERS_SETTINGS }; let defaultSettingsRoute = { name: VIEWS.USERS_SETTINGS };
for (const route of settingsRoutes) { for (const route of settingsRoutes ?? []) {
if (this.canUserAccessRouteByName(route.toString())) { if (this.canUserAccessRouteByName(route.toString())) {
defaultSettingsRoute = { defaultSettingsRoute = {
name: route.toString() as VIEWS, name: route.toString() as VIEWS,

View File

@@ -193,11 +193,6 @@ export default defineComponent({
openUpdatesPanel() { openUpdatesPanel() {
this.uiStore.openModal(VERSIONS_MODAL_KEY); this.uiStore.openModal(VERSIONS_MODAL_KEY);
}, },
async navigateTo(routeName: (typeof VIEWS)[keyof typeof VIEWS]) {
if (this.$router.currentRoute.name !== routeName) {
await this.$router.push({ name: routeName });
}
},
async handleSelect(key: string) { async handleSelect(key: string) {
switch (key) { switch (key) {
case 'users': // Fakedoor feature added via hooks when user management is disabled on cloud case 'users': // Fakedoor feature added via hooks when user management is disabled on cloud

View File

@@ -42,9 +42,9 @@ const files = ref<SourceControlAggregatedFile[]>(
const commitMessage = ref(''); const commitMessage = ref('');
const loading = ref(true); const loading = ref(true);
const context = ref<'workflow' | 'workflows' | 'credentials' | string>(''); const context = ref<'workflow' | 'workflows' | 'credentials' | ''>('');
const statusToBadgeThemeMap = { const statusToBadgeThemeMap: Record<string, string> = {
created: 'success', created: 'success',
deleted: 'danger', deleted: 'danger',
modified: 'warning', modified: 'warning',
@@ -64,7 +64,7 @@ const workflowId = computed(() => {
}); });
const sortedFiles = computed(() => { const sortedFiles = computed(() => {
const statusPriority = { const statusPriority: Record<string, number> = {
modified: 1, modified: 1,
renamed: 2, renamed: 2,
created: 3, created: 3,
@@ -86,7 +86,11 @@ const sortedFiles = computed(() => {
return 1; return 1;
} }
return a.updatedAt < b.updatedAt ? 1 : a.updatedAt > b.updatedAt ? -1 : 0; return (a.updatedAt ?? 0) < (b.updatedAt ?? 0)
? 1
: (a.updatedAt ?? 0) > (b.updatedAt ?? 0)
? -1
: 0;
}); });
}); });
@@ -151,13 +155,18 @@ function getContext() {
return ''; return '';
} }
function getStagedFilesByContext(files: SourceControlAggregatedFile[]): Record<string, boolean> { function getStagedFilesByContext(
const stagedFiles = files.reduce((acc, file) => { filesByContext: SourceControlAggregatedFile[],
acc[file.file] = false; ): Record<string, boolean> {
return acc; const stagedFiles = filesByContext.reduce(
}, {}); (acc, file) => {
acc[file.file] = false;
return acc;
},
{} as Record<string, boolean>,
);
files.forEach((file) => { filesByContext.forEach((file) => {
if (defaultStagedFileTypes.includes(file.type)) { if (defaultStagedFileTypes.includes(file.type)) {
stagedFiles[file.file] = true; stagedFiles[file.file] = true;
} }
@@ -184,13 +193,13 @@ function close() {
} }
function renderUpdatedAt(file: SourceControlAggregatedFile) { function renderUpdatedAt(file: SourceControlAggregatedFile) {
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear().toString();
return i18n.baseText('settings.sourceControl.lastUpdated', { return i18n.baseText('settings.sourceControl.lastUpdated', {
interpolate: { interpolate: {
date: dateformat( date: dateformat(
file.updatedAt, file.updatedAt,
`d mmm${file.updatedAt.startsWith(currentYear) ? '' : ', yyyy'}`, `d mmm${file.updatedAt?.startsWith(currentYear) ? '' : ', yyyy'}`,
), ),
time: dateformat(file.updatedAt, 'HH:MM'), time: dateformat(file.updatedAt, 'HH:MM'),
}, },
@@ -227,6 +236,22 @@ async function commitAndPush() {
loadingService.stopLoading(); loadingService.stopLoading();
} }
} }
function getStatusText(file: SourceControlAggregatedFile): string {
if (file.status === 'deleted') {
return i18n.baseText('settings.sourceControl.status.deleted');
}
if (file.status === 'created') {
return i18n.baseText('settings.sourceControl.status.created');
}
if (file.status === 'modified') {
return i18n.baseText('settings.sourceControl.status.modified');
}
return i18n.baseText('settings.sourceControl.status.renamed');
}
</script> </script>
<template> <template>
@@ -296,7 +321,7 @@ async function commitAndPush() {
Current workflow Current workflow
</n8n-badge> </n8n-badge>
<n8n-badge :theme="statusToBadgeThemeMap[file.status] || 'default'"> <n8n-badge :theme="statusToBadgeThemeMap[file.status] || 'default'">
{{ i18n.baseText(`settings.sourceControl.status.${file.status}`) }} {{ getStatusText(file) }}
</n8n-badge> </n8n-badge>
</div> </div>
</div> </div>

View File

@@ -8,7 +8,8 @@
<div :class="$style.cardDescription"> <div :class="$style.cardDescription">
<n8n-text color="text-light" size="small"> <n8n-text color="text-light" size="small">
<span v-show="data" <span v-show="data"
>{{ $locale.baseText('workflows.item.updated') }} <TimeAgo :date="data.updatedAt" /> | >{{ $locale.baseText('workflows.item.updated') }}
<TimeAgo :date="String(data.updatedAt)" /> |
</span> </span>
<span v-show="data" class="mr-2xs" <span v-show="data" class="mr-2xs"
>{{ $locale.baseText('workflows.item.created') }} {{ formattedCreatedAtDate }} >{{ $locale.baseText('workflows.item.created') }} {{ formattedCreatedAtDate }}
@@ -52,7 +53,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import type { IWorkflowDb, IUser, ITag } from '@/Interface'; import type { IWorkflowDb, IUser } from '@/Interface';
import { DUPLICATE_MODAL_KEY, MODAL_CONFIRM, VIEWS, WORKFLOW_SHARE_MODAL_KEY } from '@/constants'; import { DUPLICATE_MODAL_KEY, MODAL_CONFIRM, VIEWS, WORKFLOW_SHARE_MODAL_KEY } from '@/constants';
import { useMessage } from '@/composables/useMessage'; import { useMessage } from '@/composables/useMessage';
import { useToast } from '@/composables/useToast'; import { useToast } from '@/composables/useToast';
@@ -149,11 +150,11 @@ export default defineComponent({
return actions; return actions;
}, },
formattedCreatedAtDate(): string { formattedCreatedAtDate(): string {
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear().toString();
return dateformat( return dateformat(
this.data.createdAt, this.data.createdAt,
`d mmmm${this.data.createdAt.startsWith(currentYear) ? '' : ', yyyy'}`, `d mmmm${String(this.data.createdAt).startsWith(currentYear) ? '' : ', yyyy'}`,
); );
}, },
}, },
@@ -191,7 +192,9 @@ export default defineComponent({
data: { data: {
id: this.data.id, id: this.data.id,
name: this.data.name, name: this.data.name,
tags: (this.data.tags || []).map((tag: ITag) => tag.id), tags: (this.data.tags ?? []).map((tag) =>
typeof tag !== 'string' && 'id' in tag ? tag.id : tag,
),
}, },
}); });
} else if (action === WORKFLOW_LIST_ITEM_ACTIONS.SHARE) { } else if (action === WORKFLOW_LIST_ITEM_ACTIONS.SHARE) {

View File

@@ -279,7 +279,7 @@
<el-switch <el-switch
ref="inputField" ref="inputField"
:disabled="readOnlyEnv" :disabled="readOnlyEnv"
:model-value="workflowSettings.executionTimeout > -1" :model-value="(workflowSettings.executionTimeout ?? -1) > -1"
active-color="#13ce66" active-color="#13ce66"
data-test-id="workflow-settings-timeout-workflow" data-test-id="workflow-settings-timeout-workflow"
@update:model-value="toggleTimeout" @update:model-value="toggleTimeout"
@@ -288,7 +288,7 @@
</el-col> </el-col>
</el-row> </el-row>
<div <div
v-if="workflowSettings.executionTimeout > -1" v-if="(workflowSettings.executionTimeout ?? -1) > -1"
data-test-id="workflow-settings-timeout-form" data-test-id="workflow-settings-timeout-form"
> >
<el-row> <el-row>
@@ -306,7 +306,7 @@
:disabled="readOnlyEnv" :disabled="readOnlyEnv"
:model-value="timeoutHMS.hours" :model-value="timeoutHMS.hours"
:min="0" :min="0"
@update:model-value="(value) => setTimeout('hours', value)" @update:model-value="(value: string) => setTimeout('hours', value)"
> >
<template #append>{{ $locale.baseText('workflowSettings.hours') }}</template> <template #append>{{ $locale.baseText('workflowSettings.hours') }}</template>
</n8n-input> </n8n-input>
@@ -317,7 +317,7 @@
:model-value="timeoutHMS.minutes" :model-value="timeoutHMS.minutes"
:min="0" :min="0"
:max="60" :max="60"
@update:model-value="(value) => setTimeout('minutes', value)" @update:model-value="(value: string) => setTimeout('minutes', value)"
> >
<template #append>{{ $locale.baseText('workflowSettings.minutes') }}</template> <template #append>{{ $locale.baseText('workflowSettings.minutes') }}</template>
</n8n-input> </n8n-input>
@@ -328,7 +328,7 @@
:model-value="timeoutHMS.seconds" :model-value="timeoutHMS.seconds"
:min="0" :min="0"
:max="60" :max="60"
@update:model-value="(value) => setTimeout('seconds', value)" @update:model-value="(value: string) => setTimeout('seconds', value)"
> >
<template #append>{{ $locale.baseText('workflowSettings.seconds') }}</template> <template #append>{{ $locale.baseText('workflowSettings.seconds') }}</template>
</n8n-input> </n8n-input>
@@ -828,7 +828,10 @@ export default defineComponent({
data.versionId = this.workflowsStore.workflowVersionId; data.versionId = this.workflowsStore.workflowVersionId;
try { try {
const workflow = await this.workflowsStore.updateWorkflow(this.$route.params.name, data); const workflow = await this.workflowsStore.updateWorkflow(
String(this.$route.params.name),
data,
);
this.workflowsStore.setWorkflowVersionId(workflow.versionId); this.workflowsStore.setWorkflowVersionId(workflow.versionId);
} catch (error) { } catch (error) {
this.showError( this.showError(
@@ -840,12 +843,9 @@ export default defineComponent({
} }
// Get the settings without the defaults set for local workflow settings // Get the settings without the defaults set for local workflow settings
const localWorkflowSettings: IWorkflowSettings = {}; const localWorkflowSettings = Object.fromEntries(
for (const key of Object.keys(this.workflowSettings)) { Object.entries(this.workflowSettings).filter(([, value]) => value !== 'DEFAULT'),
if (this.workflowSettings[key] !== 'DEFAULT') { );
localWorkflowSettings[key] = this.workflowSettings[key];
}
}
const oldSettings = deepCopy(this.workflowsStore.workflowSettings); const oldSettings = deepCopy(this.workflowsStore.workflowSettings);

View File

@@ -31,7 +31,7 @@ const { debounce } = useDebounce();
const telemetry = useTelemetry(); const telemetry = useTelemetry();
const props = withDefaults(defineProps<ExecutionFilterProps>(), { const props = withDefaults(defineProps<ExecutionFilterProps>(), {
workflows: [] as Array<IWorkflowDb | IWorkflowShortResponse>, workflows: () => [] as Array<IWorkflowDb | IWorkflowShortResponse>,
popoverPlacement: 'bottom' as Placement, popoverPlacement: 'bottom' as Placement,
teleported: true, teleported: true,
}); });

View File

@@ -1,4 +1,4 @@
import { vi, describe, expect } from 'vitest'; import { describe, expect } from 'vitest';
import { render } from '@testing-library/vue'; import { render } from '@testing-library/vue';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { faker } from '@faker-js/faker'; import { faker } from '@faker-js/faker';
@@ -7,7 +7,7 @@ import { createPinia, PiniaVuePlugin, setActivePinia } from 'pinia';
import type { ExecutionSummary } from 'n8n-workflow'; import type { ExecutionSummary } from 'n8n-workflow';
import { useSettingsStore } from '@/stores/settings.store'; import { useSettingsStore } from '@/stores/settings.store';
import WorkflowExecutionsPreview from '@/components/executions/workflow/WorkflowExecutionsPreview.vue'; import WorkflowExecutionsPreview from '@/components/executions/workflow/WorkflowExecutionsPreview.vue';
import { VIEWS } from '@/constants'; import { EnterpriseEditionFeature, VIEWS } from '@/constants';
import { i18nInstance, I18nPlugin } from '@/plugins/i18n'; import { i18nInstance, I18nPlugin } from '@/plugins/i18n';
import { FontAwesomePlugin } from '@/plugins/icons'; import { FontAwesomePlugin } from '@/plugins/icons';
import { GlobalComponentsPlugin } from '@/plugins/components'; import { GlobalComponentsPlugin } from '@/plugins/components';
@@ -78,9 +78,10 @@ describe('WorkflowExecutionsPreview.vue', () => {
])( ])(
'when debug enterprise feature is %s it should handle debug link click accordingly', 'when debug enterprise feature is %s it should handle debug link click accordingly',
async (availability, path) => { async (availability, path) => {
vi.spyOn(settingsStore, 'isEnterpriseFeatureEnabled', 'get').mockReturnValue( settingsStore.settings.enterprise = {
() => availability, ...(settingsStore.settings.enterprise ?? {}),
); [EnterpriseEditionFeature.DebugInEditor]: availability,
};
// Not using createComponentRenderer helper here because this component should not stub `router-link` // Not using createComponentRenderer helper here because this component should not stub `router-link`
const { getByTestId } = render(WorkflowExecutionsPreview, { const { getByTestId } = render(WorkflowExecutionsPreview, {

View File

@@ -72,7 +72,7 @@ import WorkflowExecutionsInfoAccordion from '@/components/executions/workflow/Wo
import ExecutionsFilter from '@/components/executions/ExecutionsFilter.vue'; import ExecutionsFilter from '@/components/executions/ExecutionsFilter.vue';
import { VIEWS } from '@/constants'; import { VIEWS } from '@/constants';
import type { ExecutionSummary } from 'n8n-workflow'; import type { ExecutionSummary } from 'n8n-workflow';
import type { Route } from 'vue-router'; import type { RouteRecord } from 'vue-router';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
@@ -131,7 +131,7 @@ export default defineComponent({
...mapStores(useExecutionsStore, useWorkflowsStore), ...mapStores(useExecutionsStore, useWorkflowsStore),
}, },
watch: { watch: {
$route(to: Route, from: Route) { $route(to: RouteRecord, from: RouteRecord) {
if (from.name === VIEWS.EXECUTION_PREVIEW && to.name === VIEWS.EXECUTION_HOME) { if (from.name === VIEWS.EXECUTION_PREVIEW && to.name === VIEWS.EXECUTION_HOME) {
// Skip parent route when navigating through executions with back button // Skip parent route when navigating through executions with back button
this.$router.go(-1); this.$router.go(-1);

View File

@@ -1836,6 +1836,7 @@
"settings.sourceControl.status.modified": "Modified", "settings.sourceControl.status.modified": "Modified",
"settings.sourceControl.status.deleted": "Deleted", "settings.sourceControl.status.deleted": "Deleted",
"settings.sourceControl.status.created": "New", "settings.sourceControl.status.created": "New",
"settings.sourceControl.status.renamed": "Renamed",
"settings.sourceControl.pull.oneLastStep.title": "One last step", "settings.sourceControl.pull.oneLastStep.title": "One last step",
"settings.sourceControl.pull.oneLastStep.description": "You have new creds/vars. Fill them out to make sure your workflows function properly", "settings.sourceControl.pull.oneLastStep.description": "You have new creds/vars. Fill them out to make sure your workflows function properly",
"settings.sourceControl.pull.success.title": "Pulled successfully", "settings.sourceControl.pull.success.title": "Pulled successfully",