refactor: Clean up hooks and fake doors (no-changelog) (#11498)

This commit is contained in:
Mutasem Aldmour 2024-11-01 15:23:41 +01:00 committed by GitHub
parent c5191e697a
commit 02b77367bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 2 additions and 362 deletions

View file

@ -52,7 +52,6 @@ import type {
AI_NODE_CREATOR_VIEW,
CREDENTIAL_EDIT_MODAL_KEY,
SignInType,
FAKE_DOOR_FEATURES,
TRIGGER_NODE_CREATOR_VIEW,
REGULAR_NODE_CREATOR_VIEW,
AI_OTHERS_NODE_CREATOR_VIEW,
@ -62,7 +61,6 @@ import type { BulkCommand, Undoable } from '@/models/history';
import type { PartialBy, TupleToUnion } from '@/utils/typeHelpers';
import type { ProjectSharingData } from '@/types/projects.types';
import type { BaseTextKey } from './plugins/i18n';
export * from 'n8n-design-system/types';
@ -1036,24 +1034,6 @@ export interface NotificationOptions extends Partial<ElementNotificationOptions>
message: string | ElementNotificationOptions['message'];
}
export type IFakeDoor = {
id: FAKE_DOOR_FEATURES;
featureName: BaseTextKey;
icon?: string;
infoText?: BaseTextKey;
actionBoxTitle: BaseTextKey;
actionBoxDescription: BaseTextKey;
actionBoxButtonLabel?: BaseTextKey;
linkURL: string;
uiLocations: IFakeDoorLocation[];
};
export type IFakeDoorLocation =
| 'settings'
| 'settings/users'
| 'credentialsModal'
| 'workflowShareModal';
export type NodeFilterType =
| typeof REGULAR_NODE_CREATOR_VIEW
| typeof TRIGGER_NODE_CREATOR_VIEW

View file

@ -66,9 +66,6 @@ describe('Init', () => {
vi.mocked(useRootStore).mockReturnValue({ defaultLocale: 'es' } as ReturnType<
typeof useRootStore
>);
vi.mock('@/hooks/register', () => ({
initializeCloudHooks: vi.fn(),
}));
});
afterEach(() => {

View file

@ -22,7 +22,6 @@ import { NodeHelpers } from 'n8n-workflow';
import CredentialConfig from '@/components/CredentialEdit/CredentialConfig.vue';
import CredentialInfo from '@/components/CredentialEdit/CredentialInfo.vue';
import CredentialSharing from '@/components/CredentialEdit/CredentialSharing.ee.vue';
import FeatureComingSoon from '@/components/FeatureComingSoon.vue';
import InlineNameEdit from '@/components/InlineNameEdit.vue';
import Modal from '@/components/Modal.vue';
import SaveButton from '@/components/SaveButton.vue';
@ -518,14 +517,13 @@ async function loadCurrentCredential() {
function onTabSelect(tab: string) {
activeTab.value = tab;
const tabName: string = tab.replaceAll('coming-soon/', '');
const credType: string = credentialType.value ? credentialType.value.name : '';
const activeNode: INode | null = ndvStore.activeNode;
telemetry.track('User viewed credential tab', {
credential_type: credType,
node_type: activeNode ? activeNode.type : null,
tab: tabName,
tab,
workflow_id: workflowsStore.workflowId,
credential_id: credentialId.value,
sharing_enabled: EnterpriseEditionFeature.Sharing,
@ -1130,9 +1128,6 @@ function resetCredentialData(): void {
<div v-else-if="activeTab === 'details' && credentialType" :class="$style.mainContent">
<CredentialInfo :current-credential="currentCredential" />
</div>
<div v-else-if="activeTab.startsWith('coming-soon')" :class="$style.mainContent">
<FeatureComingSoon :feature-id="activeTab.split('/')[1]"></FeatureComingSoon>
</div>
</div>
</template>
</Modal>

View file

@ -1,82 +0,0 @@
<script lang="ts">
import { defineComponent } from 'vue';
import { mapStores } from 'pinia';
import type { IFakeDoor } from '@/Interface';
import { useRootStore } from '@/stores/root.store';
import { useSettingsStore } from '@/stores/settings.store';
import { useUIStore } from '@/stores/ui.store';
import { useUsersStore } from '@/stores/users.store';
export default defineComponent({
name: 'FeatureComingSoon',
props: {
featureId: {
type: String,
required: true,
},
showTitle: {
type: Boolean,
default: false,
},
},
computed: {
...mapStores(useRootStore, useSettingsStore, useUIStore, useUsersStore),
userId(): string {
return this.usersStore.currentUserId || '';
},
instanceId(): string {
return this.rootStore.instanceId;
},
featureInfo(): IFakeDoor | undefined {
return this.uiStore.fakeDoorsById[this.featureId];
},
},
methods: {
openLinkPage() {
if (this.featureInfo) {
window.open(
`${this.featureInfo.linkURL}&u=${this.instanceId}#${this.userId}&v=${this.rootStore.versionCli}`,
'_blank',
);
this.$telemetry.track('user clicked feature waiting list button', {
feature: this.featureId,
});
}
},
},
});
</script>
<template>
<div v-if="featureInfo" :class="[$style.container]">
<div v-if="showTitle" class="mb-2xl">
<n8n-heading size="2xlarge">
{{ $locale.baseText(featureInfo.featureName) }}
</n8n-heading>
</div>
<div v-if="featureInfo.infoText" class="mb-l">
<n8n-info-tip theme="info" type="note">
<span v-n8n-html="$locale.baseText(featureInfo.infoText)"></span>
</n8n-info-tip>
</div>
<div :class="$style.actionBoxContainer">
<n8n-action-box
:description="$locale.baseText(featureInfo.actionBoxDescription)"
:button-text="
$locale.baseText(featureInfo.actionBoxButtonLabel || 'fakeDoor.actionBox.button.label')
"
@click:button="openLinkPage"
>
<template #heading>
<span v-n8n-html="$locale.baseText(featureInfo.actionBoxTitle)" />
</template>
</n8n-action-box>
</div>
</div>
</template>
<style lang="scss" module>
.actionBoxContainer {
text-align: center;
}
</style>

View file

@ -2,9 +2,7 @@
import { computed } from 'vue';
import { ABOUT_MODAL_KEY, VIEWS } from '@/constants';
import { useUserHelpers } from '@/composables/useUserHelpers';
import type { IFakeDoor } from '@/Interface';
import type { IMenuItem } from 'n8n-design-system';
import type { BaseTextKey } from '@/plugins/i18n';
import { useUIStore } from '@/stores/ui.store';
import { useSettingsStore } from '@/stores/settings.store';
import { useRootStore } from '@/stores/root.store';
@ -26,23 +24,6 @@ const rootStore = useRootStore();
const settingsStore = useSettingsStore();
const uiStore = useUIStore();
const settingsFakeDoorFeatures = computed<IFakeDoor[]>(() =>
Object.keys(uiStore.fakeDoorsByLocation)
.filter((location: string) => location.includes('settings'))
.map((location) => uiStore.fakeDoorsByLocation[location]),
);
const handleSelect = (key: string) => {
switch (key) {
case 'users': // Fakedoor feature added via hooks when user management is disabled on cloud
case 'logging':
router.push({ name: VIEWS.FAKE_DOOR, params: { featureId: key } }).catch(() => {});
break;
default:
break;
}
};
const sidebarMenuItems = computed<IMenuItem[]>(() => {
const menuItems: IMenuItem[] = [
{
@ -122,19 +103,6 @@ const sidebarMenuItems = computed<IMenuItem[]>(() => {
},
];
for (const item of settingsFakeDoorFeatures.value) {
if (item.uiLocations.includes('settings')) {
menuItems.push({
id: item.id,
icon: item.icon ?? 'question',
label: i18n.baseText(item.featureName as BaseTextKey),
position: 'top',
available: true,
activateOnRoutePaths: [`/settings/coming-soon/${item.id}`],
});
}
}
menuItems.push({
id: 'settings-log-streaming',
icon: 'sign-in-alt',
@ -159,7 +127,7 @@ const sidebarMenuItems = computed<IMenuItem[]>(() => {
<template>
<div :class="$style.container">
<n8n-menu :items="sidebarMenuItems" @select="handleSelect">
<n8n-menu :items="sidebarMenuItems">
<template #header>
<div :class="$style.returnButton" data-test-id="settings-back" @click="emit('return')">
<i class="mr-xs">

View file

@ -479,7 +479,6 @@ export const enum VIEWS {
PERSONAL_SETTINGS = 'PersonalSettings',
API_SETTINGS = 'APISettings',
NOT_FOUND = 'NotFoundView',
FAKE_DOOR = 'ComingSoon',
COMMUNITY_NODES = 'CommunityNodes',
WORKFLOWS = 'WorkflowsView',
WORKFLOW_EXECUTIONS = 'WorkflowExecutions',
@ -500,12 +499,6 @@ export const enum VIEWS {
export const EDITABLE_CANVAS_VIEWS = [VIEWS.WORKFLOW, VIEWS.NEW_WORKFLOW, VIEWS.EXECUTION_DEBUG];
export const enum FAKE_DOOR_FEATURES {
ENVIRONMENTS = 'environments',
LOGGING = 'logging',
SSO = 'sso',
}
export const TEST_PIN_DATA = [
{
name: 'First item',

View file

@ -1,13 +0,0 @@
import { hooksAddFakeDoorFeatures } from '@/hooks/utils';
import type { PartialDeep } from 'type-fest';
import type { ExternalHooks } from '@/types';
export const n8nCloudHooks: PartialDeep<ExternalHooks> = {
app: {
mount: [
() => {
hooksAddFakeDoorFeatures();
},
],
},
};

View file

@ -1,40 +0,0 @@
import type { PartialDeep } from 'type-fest';
import type { ExternalHooks, ExternalHooksGenericContext } from '@/types';
export function extendExternalHooks(hooksExtension: PartialDeep<ExternalHooks>) {
if (typeof window.n8nExternalHooks === 'undefined') {
window.n8nExternalHooks = {};
}
for (const resource of Object.keys(hooksExtension) as Array<keyof ExternalHooks>) {
if (typeof window.n8nExternalHooks[resource] === 'undefined') {
window.n8nExternalHooks[resource] = {};
}
const extensionContext = hooksExtension[resource] as ExternalHooksGenericContext;
const context = window.n8nExternalHooks[resource] as ExternalHooksGenericContext;
for (const operator of Object.keys(extensionContext)) {
if (typeof context[operator] === 'undefined') {
context[operator] = [];
}
context[operator].push(...extensionContext[operator]);
}
}
}
let cloudHooksInitialized = false;
export async function initializeCloudHooks() {
if (cloudHooksInitialized) {
return;
}
try {
const { n8nCloudHooks } = await import('@/hooks/cloud');
extendExternalHooks(n8nCloudHooks);
} catch (error) {
throw new Error(`Failed to extend external hooks: ${error.message}`);
} finally {
cloudHooksInitialized = true;
}
}

View file

@ -1,6 +0,0 @@
import type { ITelemetryTrackProperties } from 'n8n-workflow';
export interface TelemetryEventData {
eventName: string;
properties?: ITelemetryTrackProperties;
}

View file

@ -1,34 +0,0 @@
import { useUIStore } from '@/stores/ui.store';
import type { IFakeDoor } from '@/Interface';
import { FAKE_DOOR_FEATURES } from '@/constants';
import type { BaseTextKey } from '@/plugins/i18n';
export function compileFakeDoorFeatures(): IFakeDoor[] {
const uiStore = useUIStore();
const fakeDoorFeatures: IFakeDoor[] = uiStore.fakeDoorFeatures.map((feature) => ({ ...feature }));
const environmentsFeature = fakeDoorFeatures.find(
(feature) => feature.id === FAKE_DOOR_FEATURES.ENVIRONMENTS,
);
if (environmentsFeature) {
environmentsFeature.actionBoxTitle += '.cloud';
environmentsFeature.linkURL += '&edition=cloud';
}
const loggingFeature = fakeDoorFeatures.find(
(feature) => feature.id === FAKE_DOOR_FEATURES.LOGGING,
);
if (loggingFeature) {
loggingFeature.actionBoxTitle += '.cloud';
loggingFeature.linkURL += '&edition=cloud';
loggingFeature.infoText = '' as BaseTextKey;
}
return fakeDoorFeatures;
}
export const hooksAddFakeDoorFeatures = () => {
const uiStore = useUIStore();
uiStore.fakeDoorFeatures = compileFakeDoorFeatures();
};

View file

@ -1 +0,0 @@
export * from './hooksAddFakeDoorFeatures';

View file

@ -5,7 +5,6 @@ import { useSettingsStore } from '@/stores/settings.store';
import { useSourceControlStore } from '@/stores/sourceControl.store';
import { useUsersStore } from '@/stores/users.store';
import { useExternalHooks } from '@/composables/useExternalHooks';
import { initializeCloudHooks } from '@/hooks/register';
import { useVersionsStore } from '@/stores/versions.store';
import { useProjectsStore } from '@/stores/projects.store';
import { useRolesStore } from './stores/roles.store';
@ -34,14 +33,6 @@ export async function initializeCore() {
await usersStore.initialize();
void versionsStore.checkForNewVersions();
if (settingsStore.isCloudDeployment) {
try {
await initializeCloudHooks();
} catch (e) {
console.error('Failed to initialize cloud hooks:', e);
}
}
}
coreInitialized = true;

View file

@ -825,12 +825,6 @@
"expressionModalInput.pairedItemConnectionError": "No path back to node",
"expressionModalInput.pairedItemInvalidPinnedError": "Unpin node {node} and execute",
"expressionModalInput.pairedItemError": "Cant determine which item to use",
"fakeDoor.settings.environments.name": "Environments",
"fakeDoor.settings.sso.name": "Single Sign-On",
"fakeDoor.settings.sso.actionBox.title": "Were working on this (as a paid feature)",
"fakeDoor.settings.sso.actionBox.title.cloud": "Were working on this",
"fakeDoor.settings.sso.actionBox.description": "SSO will offer a secured and convenient way to access n8n using your existing credentials (Google, Github, Keycloak…)",
"fakeDoor.actionBox.button.label": "Join the list",
"fixedCollectionParameter.choose": "Choose...",
"fixedCollectionParameter.currentlyNoItemsExist": "Currently no items exist",
"fixedCollectionParameter.deleteItem": "Delete item",

View file

@ -38,7 +38,6 @@ const SettingsCommunityNodesView = async () =>
await import('./views/SettingsCommunityNodesView.vue');
const SettingsApiView = async () => await import('./views/SettingsApiView.vue');
const SettingsLogStreamingView = async () => await import('./views/SettingsLogStreamingView.vue');
const SettingsFakeDoorView = async () => await import('./views/SettingsFakeDoorView.vue');
const SetupView = async () => await import('./views/SetupView.vue');
const SigninView = async () => await import('./views/SigninView.vue');
const SignupView = async () => await import('./views/SignupView.vue');
@ -642,24 +641,6 @@ export const routes: RouteRecordRaw[] = [
},
},
},
{
path: 'coming-soon/:featureId',
name: VIEWS.FAKE_DOOR,
components: {
settingsView: SettingsFakeDoorView,
},
meta: {
middleware: ['authenticated'],
telemetry: {
pageCategory: 'settings',
getProperties(route: RouteLocation) {
return {
feature: route.params.featureId,
};
},
},
},
},
{
path: 'ldap',
name: VIEWS.LDAP_SETTINGS,

View file

@ -11,7 +11,6 @@ import {
CREDENTIAL_SELECT_MODAL_KEY,
DELETE_USER_MODAL_KEY,
DUPLICATE_MODAL_KEY,
FAKE_DOOR_FEATURES,
IMPORT_CURL_MODAL_KEY,
INVITE_USER_MODAL_KEY,
LOG_STREAM_MODAL_KEY,
@ -41,7 +40,6 @@ import {
} from '@/constants';
import type {
CloudUpdateLinkSourceType,
IFakeDoorLocation,
INodeUi,
UTMCampaign,
XYPosition,
@ -51,7 +49,6 @@ import type {
NotificationOptions,
ModalState,
ModalKey,
IFakeDoor,
} from '@/Interface';
import { defineStore } from 'pinia';
import { useRootStore } from '@/stores/root.store';
@ -160,18 +157,6 @@ export const useUIStore = defineStore(STORES.UI, () => {
const modalStack = ref<string[]>([]);
const sidebarMenuCollapsed = ref<boolean>(true);
const currentView = ref<string>('');
const fakeDoorFeatures = ref<IFakeDoor[]>([
{
id: FAKE_DOOR_FEATURES.SSO,
featureName: 'fakeDoor.settings.sso.name',
icon: 'key',
actionBoxTitle: 'fakeDoor.settings.sso.actionBox.title',
actionBoxDescription: 'fakeDoor.settings.sso.actionBox.description',
linkURL: 'https://n8n-community.typeform.com/to/l7QOrERN#f=sso',
uiLocations: ['settings/users'],
},
]);
const draggable = ref<Draggable>({
isDragging: false,
type: '',
@ -306,22 +291,6 @@ export const useUIStore = defineStore(STORES.UI, () => {
const activeModals = computed(() => modalStack.value.map((modalName) => modalName));
const fakeDoorsByLocation = computed(() =>
fakeDoorFeatures.value.reduce((acc: { [uiLocation: string]: IFakeDoor }, fakeDoor) => {
fakeDoor.uiLocations.forEach((uiLocation: IFakeDoorLocation) => {
acc[uiLocation] = fakeDoor;
});
return acc;
}, {}),
);
const fakeDoorsById = computed(() =>
fakeDoorFeatures.value.reduce((acc: { [id: string]: IFakeDoor }, fakeDoor) => {
acc[fakeDoor.id.toString()] = fakeDoor;
return acc;
}, {}),
);
const isReadOnlyView = computed(() => {
return ![
VIEWS.WORKFLOW.toString(),
@ -635,7 +604,6 @@ export const useUIStore = defineStore(STORES.UI, () => {
getLastSelectedNode,
isVersionsOpen,
isModalActiveById,
fakeDoorsByLocation,
isReadOnlyView,
isActionActive,
activeActions,
@ -659,13 +627,11 @@ export const useUIStore = defineStore(STORES.UI, () => {
nodeViewInitialized,
addFirstStepOnLoad,
sidebarMenuCollapsed,
fakeDoorFeatures,
bannerStack,
theme,
modalsById,
currentView,
isAnyModalOpen,
fakeDoorsById,
pendingNotificationsForViews,
setTheme,
setMode,

View file

@ -1,49 +0,0 @@
<script lang="ts">
import type { IFakeDoor } from '@/Interface';
import { defineComponent } from 'vue';
import FeatureComingSoon from '@/components/FeatureComingSoon.vue';
import { mapStores } from 'pinia';
import { useUIStore } from '@/stores/ui.store';
export default defineComponent({
name: 'SettingsFakeDoorView',
components: {
FeatureComingSoon,
},
props: {
featureId: {
type: String,
required: true,
},
},
computed: {
...mapStores(useUIStore),
featureInfo(): IFakeDoor | undefined {
return this.uiStore.fakeDoorsById[this.featureId];
},
},
methods: {
openLinkPage() {
if (this.featureInfo) {
window.open(this.featureInfo.linkURL, '_blank');
}
},
},
});
</script>
<template>
<FeatureComingSoon :feature-id="featureId" show-title />
</template>
<style lang="scss" module>
.header {
display: flex;
align-items: center;
white-space: nowrap;
*:first-child {
flex-grow: 1;
}
}
</style>