mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 10:02:05 +00:00
fix(editor): Fix broken types for globally defined components (no-changelog) (#16505)
Co-authored-by: Mutasem Aldmour <mutasem@n8n.io>
This commit is contained in:
@@ -76,7 +76,7 @@ const getExpirationTime = (apiKey: ApiKey): string => {
|
||||
|
||||
<template #append>
|
||||
<div ref="cardActions" :class="$style.cardActions">
|
||||
<n8n-action-toggle :actions="ACTION_LIST" theme="dark" @action="onAction" />
|
||||
<N8nActionToggle :actions="ACTION_LIST" theme="dark" @action="onAction" />
|
||||
</div>
|
||||
</template>
|
||||
</n8n-card>
|
||||
|
||||
@@ -6,7 +6,7 @@ import Modal from '@/components/Modal.vue';
|
||||
import { useUsersStore } from '@/stores/users.store';
|
||||
import { createFormEventBus } from '@n8n/design-system/utils';
|
||||
import { createEventBus } from '@n8n/utils/event-bus';
|
||||
import type { IFormInputs, IFormInput } from '@/Interface';
|
||||
import type { IFormInputs, IFormInput, FormFieldValueUpdate, FormValues } from '@/Interface';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
|
||||
const config = ref<IFormInputs | null>(null);
|
||||
@@ -33,17 +33,14 @@ const passwordsMatch = (value: string | number | boolean | null | undefined) =>
|
||||
return false;
|
||||
};
|
||||
|
||||
const onInput = (e: { name: string; value: string }) => {
|
||||
if (e.name === 'password') {
|
||||
const onInput = (e: FormFieldValueUpdate) => {
|
||||
if (e.name === 'password' && typeof e.value === 'string') {
|
||||
password.value = e.value;
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = async (values: {
|
||||
currentPassword: string;
|
||||
password: string;
|
||||
mfaCode?: string;
|
||||
}) => {
|
||||
const onSubmit = async (data: FormValues) => {
|
||||
const values = data as { currentPassword: string; password: string; mfaCode?: string };
|
||||
try {
|
||||
loading.value = true;
|
||||
await usersStore.updateCurrentUserPassword({
|
||||
@@ -143,6 +140,7 @@ onMounted(() => {
|
||||
>
|
||||
<template #content>
|
||||
<n8n-form-inputs
|
||||
v-if="config"
|
||||
:inputs="config"
|
||||
:event-bus="formBus"
|
||||
:column-view="true"
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import type { PublicInstalledPackage } from 'n8n-workflow';
|
||||
import type { IUser, PublicInstalledPackage } from 'n8n-workflow';
|
||||
import { NPM_PACKAGE_DOCS_BASE_URL, COMMUNITY_PACKAGE_MANAGE_ACTIONS } from '@/constants';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import type { UserAction } from '@n8n/design-system';
|
||||
|
||||
interface Props {
|
||||
communityPackage?: PublicInstalledPackage | null;
|
||||
@@ -22,7 +23,7 @@ const i18n = useI18n();
|
||||
const telemetry = useTelemetry();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const packageActions = [
|
||||
const packageActions: Array<UserAction<IUser>> = [
|
||||
{
|
||||
label: i18n.baseText('settings.communityNodes.viewDocsAction.label'),
|
||||
value: COMMUNITY_PACKAGE_MANAGE_ACTIONS.VIEW_DOCS,
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useProjectsStore } from '@/stores/projects.store';
|
||||
import ProjectCardBadge from '@/components/Projects/ProjectCardBadge.vue';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { ResourceType } from '@/utils/projects.utils';
|
||||
import type { CredentialsResource } from './layouts/ResourcesListLayout.vue';
|
||||
import type { CredentialsResource } from '@/Interface';
|
||||
|
||||
const CREDENTIAL_LIST_ITEM_ACTIONS = {
|
||||
OPEN: 'open',
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { BaseTextKey } from '@n8n/i18n';
|
||||
import type { TestTableColumn } from '@/components/Evaluations.ee/shared/TestTableBase.vue';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import type { BadgeTheme } from '@n8n/design-system';
|
||||
|
||||
defineProps<{
|
||||
column: TestTableColumn<T>;
|
||||
@@ -39,7 +40,7 @@ const errorTooltipMap: Record<string, BaseTextKey> = {
|
||||
};
|
||||
|
||||
// FIXME: move status logic to a parent component
|
||||
const statusThemeMap: Record<string, string> = {
|
||||
const statusThemeMap: Record<string, BadgeTheme> = {
|
||||
new: 'default',
|
||||
running: 'warning',
|
||||
evaluation_running: 'warning',
|
||||
|
||||
@@ -206,7 +206,7 @@ defineExpose({ focus, select });
|
||||
outline
|
||||
type="tertiary"
|
||||
icon="external-link-alt"
|
||||
size="xsmall"
|
||||
size="mini"
|
||||
:class="$style['expression-editor-modal-opener']"
|
||||
data-test-id="expander"
|
||||
@click="emit('modal-opener-click')"
|
||||
|
||||
@@ -9,6 +9,7 @@ import { mockedStore } from '@/__tests__/utils';
|
||||
import { useProjectsStore } from '@/stores/projects.store';
|
||||
import { ProjectTypes, type Project } from '@/types/projects.types';
|
||||
import { useFoldersStore } from '@/stores/folders.store';
|
||||
import type { IUser } from 'n8n-workflow';
|
||||
|
||||
vi.mock('vue-router', async (importOriginal) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||
@@ -41,7 +42,7 @@ const TEST_FOLDER_CHILD: FolderShortInfo = {
|
||||
parentFolder: TEST_FOLDER.id,
|
||||
};
|
||||
|
||||
const TEST_ACTIONS: UserAction[] = [
|
||||
const TEST_ACTIONS: Array<UserAction<IUser>> = [
|
||||
{ label: 'Action 1', value: 'action1', disabled: false },
|
||||
{ label: 'Action 2', value: 'action2', disabled: true },
|
||||
];
|
||||
|
||||
@@ -7,11 +7,12 @@ import { type PathItem } from '@n8n/design-system/components/N8nBreadcrumbs/Brea
|
||||
import { computed, onBeforeUnmount, ref, watch } from 'vue';
|
||||
import { useFoldersStore } from '@/stores/folders.store';
|
||||
import type { FolderPathItem, FolderShortInfo } from '@/Interface';
|
||||
import type { IUser } from 'n8n-workflow';
|
||||
|
||||
type Props = {
|
||||
// Current folder can be null when showing breadcrumbs for workflows in project root
|
||||
currentFolder?: FolderShortInfo | null;
|
||||
actions?: UserAction[];
|
||||
actions?: Array<UserAction<IUser>>;
|
||||
hiddenItemsTrigger?: 'hover' | 'click';
|
||||
currentFolderAsLink?: boolean;
|
||||
visibleLevels?: 1 | 2;
|
||||
|
||||
@@ -2,8 +2,8 @@ import { createComponentRenderer } from '@/__tests__/render';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import FolderCard from './FolderCard.vue';
|
||||
import { createPinia, setActivePinia } from 'pinia';
|
||||
import type { FolderResource } from '../layouts/ResourcesListLayout.vue';
|
||||
import type { FolderPathItem, UserAction } from '@/Interface';
|
||||
import type { FolderResource, FolderPathItem, UserAction } from '@/Interface';
|
||||
import type { IUser } from 'n8n-workflow';
|
||||
|
||||
vi.mock('vue-router', () => {
|
||||
const push = vi.fn();
|
||||
@@ -54,7 +54,7 @@ const renderComponent = createComponentRenderer(FolderCard, {
|
||||
actions: [
|
||||
{ label: 'Open', value: 'open', disabled: false },
|
||||
{ label: 'Delete', value: 'delete', disabled: false },
|
||||
] as const satisfies UserAction[],
|
||||
] as const satisfies Array<UserAction<IUser>>,
|
||||
breadcrumbs: DEFAULT_BREADCRUMBS,
|
||||
},
|
||||
global: {
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { FOLDER_LIST_ITEM_ACTIONS } from './constants';
|
||||
import type { FolderResource } from '../layouts/ResourcesListLayout.vue';
|
||||
import { ProjectTypes, type Project } from '@/types/projects.types';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { VIEWS } from '@/constants';
|
||||
import type { UserAction } from '@/Interface';
|
||||
import type { FolderResource, UserAction } from '@/Interface';
|
||||
import { ResourceType } from '@/utils/projects.utils';
|
||||
import type { PathItem } from '@n8n/design-system/components/N8nBreadcrumbs/Breadcrumbs.vue';
|
||||
import { useFoldersStore } from '@/stores/folders.store';
|
||||
import { type IUser } from 'n8n-workflow';
|
||||
|
||||
type Props = {
|
||||
data: FolderResource;
|
||||
personalProject: Project | null;
|
||||
actions: UserAction[];
|
||||
actions: Array<UserAction<IUser>>;
|
||||
readOnly?: boolean;
|
||||
showOwnershipBadge?: boolean;
|
||||
};
|
||||
@@ -36,6 +36,7 @@ const emit = defineEmits<{
|
||||
}>();
|
||||
|
||||
const hiddenBreadcrumbsItemsAsync = ref<Promise<PathItem[]>>(new Promise(() => {}));
|
||||
|
||||
const cachedHiddenBreadcrumbsItems = ref<PathItem[]>([]);
|
||||
|
||||
const resourceTypeLabel = computed(() => i18n.baseText('generic.folder').toLowerCase());
|
||||
|
||||
@@ -118,7 +118,7 @@ const onClaimCreditsClicked = async () => {
|
||||
})
|
||||
}}</n8n-text
|
||||
>
|
||||
<n8n-text size="small" bold="true">
|
||||
<n8n-text size="small" :bold="true">
|
||||
{{ i18n.baseText('freeAi.credits.callout.success.title.part2') }}</n8n-text
|
||||
>
|
||||
</n8n-callout>
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
NodeConnectionTypes,
|
||||
traverseNodeParameters,
|
||||
} from 'n8n-workflow';
|
||||
import type { IFormInput } from '@n8n/design-system';
|
||||
import type { FormFieldValueUpdate, IFormInput } from '@n8n/design-system';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
@@ -249,9 +249,11 @@ const onExecute = async () => {
|
||||
};
|
||||
|
||||
// Add handler for tool selection change
|
||||
const onUpdate = (change: { name: string; value: string }) => {
|
||||
const onUpdate = (change: FormFieldValueUpdate) => {
|
||||
if (change.name !== 'toolName') return;
|
||||
selectedTool.value = change.value;
|
||||
if (typeof change.value === 'string') {
|
||||
selectedTool.value = change.value;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
import { nextTick, ref } from 'vue';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { onClickOutside } from '@vueuse/core';
|
||||
import type { InputType } from '@n8n/design-system';
|
||||
|
||||
interface Props {
|
||||
modelValue: string;
|
||||
subtitle?: string;
|
||||
type: string;
|
||||
type: InputType;
|
||||
readonly?: boolean;
|
||||
placeholder?: string;
|
||||
maxlength?: number;
|
||||
required?: boolean;
|
||||
autosize?: boolean | { minRows: number; maxRows: number };
|
||||
inputType?: string;
|
||||
inputType?: InputType;
|
||||
maxHeight?: string;
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
|
||||
@@ -2,7 +2,13 @@
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import Modal from './Modal.vue';
|
||||
import type { IFormInputs, IInviteResponse, IUser, InvitableRoleName } from '@/Interface';
|
||||
import type {
|
||||
FormFieldValueUpdate,
|
||||
IFormInputs,
|
||||
IInviteResponse,
|
||||
IUser,
|
||||
InvitableRoleName,
|
||||
} from '@/Interface';
|
||||
import { EnterpriseEditionFeature, VALID_EMAIL_REGEX, INVITE_USER_MODAL_KEY } from '@/constants';
|
||||
import { ROLE } from '@n8n/api-types';
|
||||
import { useUsersStore } from '@/stores/users.store';
|
||||
@@ -127,11 +133,15 @@ const validateEmails = (value: string | number | boolean | null | undefined) =>
|
||||
return false;
|
||||
};
|
||||
|
||||
function onInput(e: { name: string; value: InvitableRoleName }) {
|
||||
if (e.name === 'emails') {
|
||||
function isInvitableRoleName(val: unknown): val is InvitableRoleName {
|
||||
return typeof val === 'string' && [ROLE.Member, ROLE.Admin].includes(val as InvitableRoleName);
|
||||
}
|
||||
|
||||
function onInput(e: FormFieldValueUpdate) {
|
||||
if (e.name === 'emails' && typeof e.value === 'string') {
|
||||
emails.value = e.value;
|
||||
}
|
||||
if (e.name === 'role') {
|
||||
if (e.name === 'role' && isInvitableRoleName(e.value)) {
|
||||
role.value = e.value;
|
||||
}
|
||||
}
|
||||
@@ -312,7 +322,7 @@ function getEmail(email: string): string {
|
||||
</n8n-users-list>
|
||||
</div>
|
||||
<n8n-form-inputs
|
||||
v-else
|
||||
v-else-if="config"
|
||||
:inputs="config"
|
||||
:event-bus="formBus"
|
||||
:column-view="true"
|
||||
|
||||
@@ -16,8 +16,8 @@ const emit = defineEmits<{
|
||||
'update:modelValue': [tab: MAIN_HEADER_TABS, event: MouseEvent];
|
||||
}>();
|
||||
|
||||
function onUpdateModelValue(tab: MAIN_HEADER_TABS, event: MouseEvent): void {
|
||||
emit('update:modelValue', tab, event);
|
||||
function onUpdateModelValue(tab: string, event: MouseEvent): void {
|
||||
emit('update:modelValue', tab as MAIN_HEADER_TABS, event);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -428,7 +428,8 @@ async function handleFileImport(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function onWorkflowMenuSelect(action: WORKFLOW_MENU_ACTIONS): Promise<void> {
|
||||
async function onWorkflowMenuSelect(value: string): Promise<void> {
|
||||
const action = value as WORKFLOW_MENU_ACTIONS;
|
||||
switch (action) {
|
||||
case WORKFLOW_MENU_ACTIONS.DUPLICATE: {
|
||||
uiStore.openModalWithData({
|
||||
|
||||
@@ -25,6 +25,7 @@ import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper
|
||||
|
||||
import { useGlobalEntityCreation } from '@/composables/useGlobalEntityCreation';
|
||||
import { N8nNavigationDropdown, N8nTooltip, N8nLink, N8nIconButton } from '@n8n/design-system';
|
||||
import type { IMenuItem } from '@n8n/design-system';
|
||||
import { onClickOutside, type VueInstance } from '@vueuse/core';
|
||||
import Logo from './Logo/Logo.vue';
|
||||
|
||||
@@ -67,7 +68,7 @@ const userMenuItems = ref([
|
||||
},
|
||||
]);
|
||||
|
||||
const mainMenuItems = computed(() => [
|
||||
const mainMenuItems = computed<IMenuItem[]>(() => [
|
||||
{
|
||||
id: 'cloud-admin',
|
||||
position: 'bottom',
|
||||
|
||||
@@ -8,7 +8,7 @@ import { LOCAL_STORAGE_MAIN_PANEL_RELATIVE_WIDTH, MAIN_NODE_PANEL_WIDTH } from '
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
import { ndvEventBus } from '@/event-bus';
|
||||
import NDVFloatingNodes from '@/components/NDVFloatingNodes.vue';
|
||||
import type { MainPanelType, XYPosition } from '@/Interface';
|
||||
import type { Direction, MainPanelType, XYPosition } from '@/Interface';
|
||||
import { ref, onMounted, onBeforeUnmount, computed, watch, nextTick } from 'vue';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { useThrottleFn } from '@vueuse/core';
|
||||
@@ -151,8 +151,8 @@ const outputPanelRelativeTranslate = computed((): number => {
|
||||
return currentRelativeLeftDelta > 0 ? currentRelativeLeftDelta : 0;
|
||||
});
|
||||
|
||||
const supportedResizeDirections = computed((): string[] => {
|
||||
const supportedDirections = ['right'];
|
||||
const supportedResizeDirections = computed((): Direction[] => {
|
||||
const supportedDirections = ['right' as Direction];
|
||||
|
||||
if (props.isDraggable) supportedDirections.push('left');
|
||||
return supportedDirections;
|
||||
|
||||
@@ -99,10 +99,11 @@ describe('NDVSubConnections', () => {
|
||||
<div class="connectionType"><span class="connectionLabel">Tools</span>
|
||||
<div>
|
||||
<div class="connectedNodesWrapper" style="--nodes-length: 0;">
|
||||
<div class="plusButton">
|
||||
<n8n-tooltip placement="top" teleported="true" offset="10" show-after="300" disabled="false">
|
||||
<n8n-icon-button size="medium" icon="plus" type="tertiary" data-test-id="add-subnode-ai_tool-0"></n8n-icon-button>
|
||||
</n8n-tooltip>
|
||||
<div class="plusButton"><button class="button button tertiary medium withIcon square el-tooltip__trigger el-tooltip__trigger" aria-live="polite" data-test-id="add-subnode-ai_tool-0"><span class="icon"><span class="n8n-text compact size-medium regular n8n-icon n8n-icon"><!----></span></span>
|
||||
<!--v-if-->
|
||||
</button>
|
||||
<!--teleport start-->
|
||||
<!--teleport end-->
|
||||
</div>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
|
||||
@@ -26,10 +26,11 @@ import { useRunWorkflow } from '@/composables/useRunWorkflow';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { type IUpdateInformation } from '@/Interface';
|
||||
import type { ButtonSize, IUpdateInformation } from '@/Interface';
|
||||
import { generateCodeForAiTransform } from '@/components/ButtonParameter/utils';
|
||||
import { needsAgentInput } from '@/utils/nodes/nodeTransforms';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import type { ButtonType } from '@n8n/design-system';
|
||||
|
||||
const NODE_TEST_STEP_POPUP_COUNT_KEY = 'N8N_NODE_TEST_STEP_POPUP_COUNT';
|
||||
const MAX_POPUP_COUNT = 10;
|
||||
@@ -41,8 +42,8 @@ const props = withDefaults(
|
||||
telemetrySource: string;
|
||||
disabled?: boolean;
|
||||
label?: string;
|
||||
type?: string;
|
||||
size?: string;
|
||||
type?: ButtonType;
|
||||
size?: ButtonSize;
|
||||
transparent?: boolean;
|
||||
hideIcon?: boolean;
|
||||
tooltip?: string;
|
||||
|
||||
@@ -119,7 +119,7 @@ const options = computed<ITab[]>(() => {
|
||||
return options;
|
||||
});
|
||||
|
||||
function onTabSelect(tab: string) {
|
||||
function onTabSelect(tab: string | number) {
|
||||
if (tab === 'docs' && props.nodeType) {
|
||||
void externalHooks.run('dataDisplay.onDocumentationUrlClick', {
|
||||
nodeType: props.nodeType,
|
||||
@@ -147,7 +147,7 @@ function onTabSelect(tab: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function onTooltipClick(tab: string, event: MouseEvent) {
|
||||
function onTooltipClick(tab: string | number, event: MouseEvent) {
|
||||
if (tab === 'communityNode' && (event.target as Element).localName === 'a') {
|
||||
telemetry.track('user clicked cnr docs link', { source: 'node details view' });
|
||||
}
|
||||
|
||||
@@ -26,9 +26,9 @@ const emit = defineEmits<{
|
||||
v-if="!isReadOnly"
|
||||
type="tertiary"
|
||||
:class="['n8n-input', $style.overrideCloseButton]"
|
||||
outline="false"
|
||||
:outline="false"
|
||||
icon="xmark"
|
||||
size="xsmall"
|
||||
size="mini"
|
||||
@click="emit('close')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -114,438 +114,441 @@ const isSaving = ref(false);
|
||||
const userPermissions = computed(() =>
|
||||
getResourcePermissions(usersStore.currentUser?.globalScopes),
|
||||
);
|
||||
const survey = computed<IFormInputs>(() => [
|
||||
{
|
||||
name: COMPANY_TYPE_KEY,
|
||||
properties: {
|
||||
label: i18n.baseText('personalizationModal.whatBestDescribesYourCompany'),
|
||||
type: 'select',
|
||||
placeholder: i18n.baseText('personalizationModal.select'),
|
||||
options: [
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.saas'),
|
||||
value: SAAS_COMPANY_TYPE,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.eCommerce'),
|
||||
value: ECOMMERCE_COMPANY_TYPE,
|
||||
},
|
||||
const survey = computed<IFormInputs>(
|
||||
() =>
|
||||
[
|
||||
{
|
||||
name: COMPANY_TYPE_KEY,
|
||||
properties: {
|
||||
label: i18n.baseText('personalizationModal.whatBestDescribesYourCompany'),
|
||||
type: 'select',
|
||||
placeholder: i18n.baseText('personalizationModal.select'),
|
||||
options: [
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.saas'),
|
||||
value: SAAS_COMPANY_TYPE,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.eCommerce'),
|
||||
value: ECOMMERCE_COMPANY_TYPE,
|
||||
},
|
||||
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.digitalAgencyOrConsultant'),
|
||||
value: DIGITAL_AGENCY_COMPANY_TYPE,
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.digitalAgencyOrConsultant'),
|
||||
value: DIGITAL_AGENCY_COMPANY_TYPE,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.systemsIntegrator'),
|
||||
value: SYSTEMS_INTEGRATOR_COMPANY_TYPE,
|
||||
},
|
||||
{
|
||||
value: EDUCATION_TYPE,
|
||||
label: i18n.baseText('personalizationModal.education'),
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.other'),
|
||||
value: OTHER_COMPANY_TYPE,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.imNotUsingN8nForWork'),
|
||||
value: PERSONAL_COMPANY_TYPE,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.systemsIntegrator'),
|
||||
value: SYSTEMS_INTEGRATOR_COMPANY_TYPE,
|
||||
},
|
||||
{
|
||||
name: COMPANY_INDUSTRY_EXTENDED_KEY,
|
||||
properties: {
|
||||
type: 'multi-select',
|
||||
label: i18n.baseText('personalizationModal.whichIndustriesIsYourCompanyIn'),
|
||||
placeholder: i18n.baseText('personalizationModal.select'),
|
||||
options: [
|
||||
{
|
||||
value: FINANCE_INSURANCE_INDUSTRY,
|
||||
label: i18n.baseText('personalizationModal.financeOrInsurance'),
|
||||
},
|
||||
{
|
||||
value: GOVERNMENT_INDUSTRY,
|
||||
label: i18n.baseText('personalizationModal.government'),
|
||||
},
|
||||
{
|
||||
value: HEALTHCARE_INDUSTRY,
|
||||
label: i18n.baseText('personalizationModal.healthcare'),
|
||||
},
|
||||
{
|
||||
value: IT_INDUSTRY,
|
||||
label: i18n.baseText('personalizationModal.it'),
|
||||
},
|
||||
{
|
||||
value: LEGAL_INDUSTRY,
|
||||
label: i18n.baseText('personalizationModal.legal'),
|
||||
},
|
||||
{
|
||||
value: MSP_INDUSTRY,
|
||||
label: i18n.baseText('personalizationModal.managedServiceProvider'),
|
||||
},
|
||||
{
|
||||
value: MARKETING_INDUSTRY,
|
||||
label: i18n.baseText('personalizationModal.marketing'),
|
||||
},
|
||||
{
|
||||
value: MEDIA_INDUSTRY,
|
||||
label: i18n.baseText('personalizationModal.media'),
|
||||
},
|
||||
{
|
||||
value: MANUFACTURING_INDUSTRY,
|
||||
label: i18n.baseText('personalizationModal.manufacturing'),
|
||||
},
|
||||
{
|
||||
value: PHYSICAL_RETAIL_OR_SERVICES,
|
||||
label: i18n.baseText('personalizationModal.physicalRetailOrServices'),
|
||||
},
|
||||
{
|
||||
value: REAL_ESTATE_OR_CONSTRUCTION,
|
||||
label: i18n.baseText('personalizationModal.realEstateOrConstruction'),
|
||||
},
|
||||
{
|
||||
value: SECURITY_INDUSTRY,
|
||||
label: i18n.baseText('personalizationModal.security'),
|
||||
},
|
||||
{
|
||||
value: TELECOMS_INDUSTRY,
|
||||
label: i18n.baseText('personalizationModal.telecoms'),
|
||||
},
|
||||
{
|
||||
value: OTHER_INDUSTRY_OPTION,
|
||||
label: i18n.baseText('personalizationModal.otherPleaseSpecify'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: EDUCATION_TYPE,
|
||||
label: i18n.baseText('personalizationModal.education'),
|
||||
shouldDisplay(values): boolean {
|
||||
const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
|
||||
return companyType === OTHER_COMPANY_TYPE;
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.other'),
|
||||
value: OTHER_COMPANY_TYPE,
|
||||
},
|
||||
{
|
||||
name: OTHER_COMPANY_INDUSTRY_EXTENDED_KEY,
|
||||
properties: {
|
||||
placeholder: i18n.baseText('personalizationModal.specifyYourCompanysIndustry'),
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.imNotUsingN8nForWork'),
|
||||
value: PERSONAL_COMPANY_TYPE,
|
||||
shouldDisplay(values): boolean {
|
||||
const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
|
||||
const companyIndustry = (values as IPersonalizationLatestVersion)[
|
||||
COMPANY_INDUSTRY_EXTENDED_KEY
|
||||
];
|
||||
return (
|
||||
companyType === OTHER_COMPANY_TYPE &&
|
||||
!!companyIndustry &&
|
||||
companyIndustry.includes(OTHER_INDUSTRY_OPTION)
|
||||
);
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: COMPANY_INDUSTRY_EXTENDED_KEY,
|
||||
properties: {
|
||||
type: 'multi-select',
|
||||
label: i18n.baseText('personalizationModal.whichIndustriesIsYourCompanyIn'),
|
||||
placeholder: i18n.baseText('personalizationModal.select'),
|
||||
options: [
|
||||
{
|
||||
value: FINANCE_INSURANCE_INDUSTRY,
|
||||
label: i18n.baseText('personalizationModal.financeOrInsurance'),
|
||||
},
|
||||
{
|
||||
name: ROLE_KEY,
|
||||
properties: {
|
||||
type: 'select',
|
||||
label: i18n.baseText('personalizationModal.whichRoleBestDescribesYou'),
|
||||
placeholder: i18n.baseText('personalizationModal.select'),
|
||||
options: [
|
||||
{
|
||||
value: ROLE_BUSINESS_OWNER,
|
||||
label: i18n.baseText('personalizationModal.businessOwner'),
|
||||
},
|
||||
{
|
||||
value: ROLE_CUSTOMER_SUPPORT,
|
||||
label: i18n.baseText('personalizationModal.customerSupport'),
|
||||
},
|
||||
{
|
||||
value: ROLE_DATA_SCIENCE,
|
||||
label: i18n.baseText('personalizationModal.dataScience'),
|
||||
},
|
||||
{
|
||||
value: ROLE_DEVOPS,
|
||||
label: i18n.baseText('personalizationModal.devops'),
|
||||
},
|
||||
{
|
||||
value: ROLE_IT,
|
||||
label: i18n.baseText('personalizationModal.it'),
|
||||
},
|
||||
{
|
||||
value: ROLE_ENGINEERING,
|
||||
label: i18n.baseText('personalizationModal.engineering'),
|
||||
},
|
||||
{
|
||||
value: ROLE_SALES_AND_MARKETING,
|
||||
label: i18n.baseText('personalizationModal.salesAndMarketing'),
|
||||
},
|
||||
{
|
||||
value: ROLE_SECURITY,
|
||||
label: i18n.baseText('personalizationModal.security'),
|
||||
},
|
||||
{
|
||||
value: ROLE_OTHER,
|
||||
label: i18n.baseText('personalizationModal.otherPleaseSpecify'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: GOVERNMENT_INDUSTRY,
|
||||
label: i18n.baseText('personalizationModal.government'),
|
||||
shouldDisplay(values): boolean {
|
||||
const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
|
||||
return companyType !== PERSONAL_COMPANY_TYPE;
|
||||
},
|
||||
{
|
||||
value: HEALTHCARE_INDUSTRY,
|
||||
label: i18n.baseText('personalizationModal.healthcare'),
|
||||
},
|
||||
{
|
||||
name: ROLE_OTHER_KEY,
|
||||
properties: {
|
||||
placeholder: i18n.baseText('personalizationModal.specifyYourRole'),
|
||||
},
|
||||
{
|
||||
value: IT_INDUSTRY,
|
||||
label: i18n.baseText('personalizationModal.it'),
|
||||
shouldDisplay(values): boolean {
|
||||
const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
|
||||
const role = (values as IPersonalizationLatestVersion)[ROLE_KEY];
|
||||
return companyType !== PERSONAL_COMPANY_TYPE && role === ROLE_OTHER;
|
||||
},
|
||||
{
|
||||
value: LEGAL_INDUSTRY,
|
||||
label: i18n.baseText('personalizationModal.legal'),
|
||||
},
|
||||
{
|
||||
name: DEVOPS_AUTOMATION_GOAL_KEY,
|
||||
properties: {
|
||||
type: 'multi-select',
|
||||
label: i18n.baseText('personalizationModal.whatAreYouLookingToAutomate'),
|
||||
placeholder: i18n.baseText('personalizationModal.select'),
|
||||
options: [
|
||||
{
|
||||
value: DEVOPS_AUTOMATION_CI_CD_GOAL,
|
||||
label: i18n.baseText('personalizationModal.cicd'),
|
||||
},
|
||||
{
|
||||
value: DEVOPS_AUTOMATION_CLOUD_INFRASTRUCTURE_ORCHESTRATION_GOAL,
|
||||
label: i18n.baseText('personalizationModal.cloudInfrastructureOrchestration'),
|
||||
},
|
||||
{
|
||||
value: DEVOPS_AUTOMATION_DATA_SYNCING_GOAL,
|
||||
label: i18n.baseText('personalizationModal.dataSynching'),
|
||||
},
|
||||
{
|
||||
value: DEVOPS_INCIDENT_RESPONSE_GOAL,
|
||||
label: i18n.baseText('personalizationModal.incidentResponse'),
|
||||
},
|
||||
{
|
||||
value: DEVOPS_MONITORING_AND_ALERTING_GOAL,
|
||||
label: i18n.baseText('personalizationModal.monitoringAndAlerting'),
|
||||
},
|
||||
{
|
||||
value: DEVOPS_REPORTING_GOAL,
|
||||
label: i18n.baseText('personalizationModal.reporting'),
|
||||
},
|
||||
{
|
||||
value: DEVOPS_TICKETING_SYSTEMS_INTEGRATIONS_GOAL,
|
||||
label: i18n.baseText('personalizationModal.ticketingSystemsIntegrations'),
|
||||
},
|
||||
{
|
||||
value: OTHER_AUTOMATION_GOAL,
|
||||
label: i18n.baseText('personalizationModal.other'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: MSP_INDUSTRY,
|
||||
label: i18n.baseText('personalizationModal.managedServiceProvider'),
|
||||
shouldDisplay(values): boolean {
|
||||
const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
|
||||
const role = (values as IPersonalizationLatestVersion)[ROLE_KEY] as string;
|
||||
return (
|
||||
companyType !== PERSONAL_COMPANY_TYPE &&
|
||||
[ROLE_DEVOPS, ROLE_ENGINEERING, ROLE_IT].includes(role)
|
||||
);
|
||||
},
|
||||
{
|
||||
value: MARKETING_INDUSTRY,
|
||||
label: i18n.baseText('personalizationModal.marketing'),
|
||||
},
|
||||
{
|
||||
name: DEVOPS_AUTOMATION_GOAL_OTHER_KEY,
|
||||
properties: {
|
||||
placeholder: i18n.baseText('personalizationModal.specifyYourAutomationGoal'),
|
||||
},
|
||||
{
|
||||
value: MEDIA_INDUSTRY,
|
||||
label: i18n.baseText('personalizationModal.media'),
|
||||
shouldDisplay(values): boolean {
|
||||
const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
|
||||
const goals = (values as IPersonalizationLatestVersion)[DEVOPS_AUTOMATION_GOAL_KEY];
|
||||
const role = (values as IPersonalizationLatestVersion)[ROLE_KEY] as string;
|
||||
return (
|
||||
companyType !== PERSONAL_COMPANY_TYPE &&
|
||||
[ROLE_DEVOPS, ROLE_ENGINEERING, ROLE_IT].includes(role) &&
|
||||
!!goals &&
|
||||
goals.includes(DEVOPS_AUTOMATION_OTHER)
|
||||
);
|
||||
},
|
||||
{
|
||||
value: MANUFACTURING_INDUSTRY,
|
||||
label: i18n.baseText('personalizationModal.manufacturing'),
|
||||
},
|
||||
{
|
||||
name: MARKETING_AUTOMATION_GOAL_KEY,
|
||||
properties: {
|
||||
type: 'multi-select',
|
||||
label: i18n.baseText('personalizationModal.specifySalesMarketingGoal'),
|
||||
placeholder: i18n.baseText('personalizationModal.select'),
|
||||
options: [
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.leadGeneration'),
|
||||
value: MARKETING_AUTOMATION_LEAD_GENERATION_GOAL,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.customerCommunication'),
|
||||
value: MARKETING_AUTOMATION_CUSTOMER_COMMUNICATION,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.customerActions'),
|
||||
value: MARKETING_AUTOMATION_ACTIONS,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.adCampaign'),
|
||||
value: MARKETING_AUTOMATION_AD_CAMPAIGN,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.reporting'),
|
||||
value: MARKETING_AUTOMATION_REPORTING,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.dataSynching'),
|
||||
value: MARKETING_AUTOMATION_DATA_SYNCHING,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.other'),
|
||||
value: MARKETING_AUTOMATION_OTHER,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: PHYSICAL_RETAIL_OR_SERVICES,
|
||||
label: i18n.baseText('personalizationModal.physicalRetailOrServices'),
|
||||
shouldDisplay(values): boolean {
|
||||
const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
|
||||
const role = (values as IPersonalizationLatestVersion)[ROLE_KEY];
|
||||
return companyType !== PERSONAL_COMPANY_TYPE && role === ROLE_SALES_AND_MARKETING;
|
||||
},
|
||||
{
|
||||
value: REAL_ESTATE_OR_CONSTRUCTION,
|
||||
label: i18n.baseText('personalizationModal.realEstateOrConstruction'),
|
||||
},
|
||||
{
|
||||
name: OTHER_MARKETING_AUTOMATION_GOAL_KEY,
|
||||
properties: {
|
||||
placeholder: i18n.baseText('personalizationModal.specifyOtherSalesAndMarketingGoal'),
|
||||
},
|
||||
{
|
||||
value: SECURITY_INDUSTRY,
|
||||
label: i18n.baseText('personalizationModal.security'),
|
||||
shouldDisplay(values): boolean {
|
||||
const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
|
||||
const goals = (values as IPersonalizationLatestVersion)[MARKETING_AUTOMATION_GOAL_KEY];
|
||||
const role = (values as IPersonalizationLatestVersion)[ROLE_KEY];
|
||||
return (
|
||||
companyType !== PERSONAL_COMPANY_TYPE &&
|
||||
role === ROLE_SALES_AND_MARKETING &&
|
||||
!!goals &&
|
||||
goals.includes(MARKETING_AUTOMATION_OTHER)
|
||||
);
|
||||
},
|
||||
{
|
||||
value: TELECOMS_INDUSTRY,
|
||||
label: i18n.baseText('personalizationModal.telecoms'),
|
||||
},
|
||||
{
|
||||
name: AUTOMATION_BENEFICIARY_KEY,
|
||||
properties: {
|
||||
type: 'select',
|
||||
label: i18n.baseText('personalizationModal.specifyAutomationBeneficiary'),
|
||||
placeholder: i18n.baseText('personalizationModal.select'),
|
||||
options: [
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.myself'),
|
||||
value: AUTOMATION_BENEFICIARY_SELF,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.myTeam'),
|
||||
value: AUTOMATION_BENEFICIARY_MY_TEAM,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.otherTeams'),
|
||||
value: AUTOMATION_BENEFICIARY_OTHER_TEAMS,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: OTHER_INDUSTRY_OPTION,
|
||||
label: i18n.baseText('personalizationModal.otherPleaseSpecify'),
|
||||
shouldDisplay(values): boolean {
|
||||
const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
|
||||
return companyType !== PERSONAL_COMPANY_TYPE;
|
||||
},
|
||||
],
|
||||
},
|
||||
shouldDisplay(values): boolean {
|
||||
const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
|
||||
return companyType === OTHER_COMPANY_TYPE;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: OTHER_COMPANY_INDUSTRY_EXTENDED_KEY,
|
||||
properties: {
|
||||
placeholder: i18n.baseText('personalizationModal.specifyYourCompanysIndustry'),
|
||||
},
|
||||
shouldDisplay(values): boolean {
|
||||
const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
|
||||
const companyIndustry = (values as IPersonalizationLatestVersion)[
|
||||
COMPANY_INDUSTRY_EXTENDED_KEY
|
||||
];
|
||||
return (
|
||||
companyType === OTHER_COMPANY_TYPE &&
|
||||
!!companyIndustry &&
|
||||
companyIndustry.includes(OTHER_INDUSTRY_OPTION)
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ROLE_KEY,
|
||||
properties: {
|
||||
type: 'select',
|
||||
label: i18n.baseText('personalizationModal.whichRoleBestDescribesYou'),
|
||||
placeholder: i18n.baseText('personalizationModal.select'),
|
||||
options: [
|
||||
{
|
||||
value: ROLE_BUSINESS_OWNER,
|
||||
label: i18n.baseText('personalizationModal.businessOwner'),
|
||||
},
|
||||
{
|
||||
name: COMPANY_SIZE_KEY,
|
||||
properties: {
|
||||
type: 'select',
|
||||
label: i18n.baseText('personalizationModal.howBigIsYourCompany'),
|
||||
placeholder: i18n.baseText('personalizationModal.select'),
|
||||
options: [
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.lessThan20People'),
|
||||
value: COMPANY_SIZE_20_OR_LESS,
|
||||
},
|
||||
{
|
||||
label: `20-99 ${i18n.baseText('personalizationModal.people')}`,
|
||||
value: COMPANY_SIZE_20_99,
|
||||
},
|
||||
{
|
||||
label: `100-499 ${i18n.baseText('personalizationModal.people')}`,
|
||||
value: COMPANY_SIZE_100_499,
|
||||
},
|
||||
{
|
||||
label: `500-999 ${i18n.baseText('personalizationModal.people')}`,
|
||||
value: COMPANY_SIZE_500_999,
|
||||
},
|
||||
{
|
||||
label: `1000+ ${i18n.baseText('personalizationModal.people')}`,
|
||||
value: COMPANY_SIZE_1000_OR_MORE,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.imNotUsingN8nForWork'),
|
||||
value: COMPANY_SIZE_PERSONAL_USE,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: ROLE_CUSTOMER_SUPPORT,
|
||||
label: i18n.baseText('personalizationModal.customerSupport'),
|
||||
shouldDisplay(values): boolean {
|
||||
const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
|
||||
return companyType !== PERSONAL_COMPANY_TYPE;
|
||||
},
|
||||
{
|
||||
value: ROLE_DATA_SCIENCE,
|
||||
label: i18n.baseText('personalizationModal.dataScience'),
|
||||
},
|
||||
{
|
||||
name: REPORTED_SOURCE_KEY,
|
||||
properties: {
|
||||
type: 'select',
|
||||
label: i18n.baseText('personalizationModal.howDidYouHearAboutN8n'),
|
||||
placeholder: i18n.baseText('personalizationModal.select'),
|
||||
options: [
|
||||
{
|
||||
label: 'Google',
|
||||
value: REPORTED_SOURCE_GOOGLE,
|
||||
},
|
||||
{
|
||||
label: 'Twitter',
|
||||
value: REPORTED_SOURCE_TWITTER,
|
||||
},
|
||||
{
|
||||
label: 'LinkedIn',
|
||||
value: REPORTED_SOURCE_LINKEDIN,
|
||||
},
|
||||
{
|
||||
label: 'YouTube',
|
||||
value: REPORTED_SOURCE_YOUTUBE,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.friendWordOfMouth'),
|
||||
value: REPORTED_SOURCE_FRIEND,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.podcast'),
|
||||
value: REPORTED_SOURCE_PODCAST,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.event'),
|
||||
value: REPORTED_SOURCE_EVENT,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.otherPleaseSpecify'),
|
||||
value: REPORTED_SOURCE_OTHER,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: ROLE_DEVOPS,
|
||||
label: i18n.baseText('personalizationModal.devops'),
|
||||
},
|
||||
{
|
||||
name: REPORTED_SOURCE_OTHER_KEY,
|
||||
properties: {
|
||||
placeholder: i18n.baseText('personalizationModal.specifyReportedSource'),
|
||||
},
|
||||
{
|
||||
value: ROLE_IT,
|
||||
label: i18n.baseText('personalizationModal.it'),
|
||||
shouldDisplay(values): boolean {
|
||||
const reportedSource = (values as IPersonalizationLatestVersion)[REPORTED_SOURCE_KEY];
|
||||
return reportedSource === REPORTED_SOURCE_OTHER;
|
||||
},
|
||||
{
|
||||
value: ROLE_ENGINEERING,
|
||||
label: i18n.baseText('personalizationModal.engineering'),
|
||||
},
|
||||
{
|
||||
value: ROLE_SALES_AND_MARKETING,
|
||||
label: i18n.baseText('personalizationModal.salesAndMarketing'),
|
||||
},
|
||||
{
|
||||
value: ROLE_SECURITY,
|
||||
label: i18n.baseText('personalizationModal.security'),
|
||||
},
|
||||
{
|
||||
value: ROLE_OTHER,
|
||||
label: i18n.baseText('personalizationModal.otherPleaseSpecify'),
|
||||
},
|
||||
],
|
||||
},
|
||||
shouldDisplay(values): boolean {
|
||||
const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
|
||||
return companyType !== PERSONAL_COMPANY_TYPE;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: ROLE_OTHER_KEY,
|
||||
properties: {
|
||||
placeholder: i18n.baseText('personalizationModal.specifyYourRole'),
|
||||
},
|
||||
shouldDisplay(values): boolean {
|
||||
const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
|
||||
const role = (values as IPersonalizationLatestVersion)[ROLE_KEY];
|
||||
return companyType !== PERSONAL_COMPANY_TYPE && role === ROLE_OTHER;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: DEVOPS_AUTOMATION_GOAL_KEY,
|
||||
properties: {
|
||||
type: 'multi-select',
|
||||
label: i18n.baseText('personalizationModal.whatAreYouLookingToAutomate'),
|
||||
placeholder: i18n.baseText('personalizationModal.select'),
|
||||
options: [
|
||||
{
|
||||
value: DEVOPS_AUTOMATION_CI_CD_GOAL,
|
||||
label: i18n.baseText('personalizationModal.cicd'),
|
||||
},
|
||||
{
|
||||
value: DEVOPS_AUTOMATION_CLOUD_INFRASTRUCTURE_ORCHESTRATION_GOAL,
|
||||
label: i18n.baseText('personalizationModal.cloudInfrastructureOrchestration'),
|
||||
},
|
||||
{
|
||||
value: DEVOPS_AUTOMATION_DATA_SYNCING_GOAL,
|
||||
label: i18n.baseText('personalizationModal.dataSynching'),
|
||||
},
|
||||
{
|
||||
value: DEVOPS_INCIDENT_RESPONSE_GOAL,
|
||||
label: i18n.baseText('personalizationModal.incidentResponse'),
|
||||
},
|
||||
{
|
||||
value: DEVOPS_MONITORING_AND_ALERTING_GOAL,
|
||||
label: i18n.baseText('personalizationModal.monitoringAndAlerting'),
|
||||
},
|
||||
{
|
||||
value: DEVOPS_REPORTING_GOAL,
|
||||
label: i18n.baseText('personalizationModal.reporting'),
|
||||
},
|
||||
{
|
||||
value: DEVOPS_TICKETING_SYSTEMS_INTEGRATIONS_GOAL,
|
||||
label: i18n.baseText('personalizationModal.ticketingSystemsIntegrations'),
|
||||
},
|
||||
{
|
||||
value: OTHER_AUTOMATION_GOAL,
|
||||
label: i18n.baseText('personalizationModal.other'),
|
||||
},
|
||||
],
|
||||
},
|
||||
shouldDisplay(values): boolean {
|
||||
const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
|
||||
const role = (values as IPersonalizationLatestVersion)[ROLE_KEY] as string;
|
||||
return (
|
||||
companyType !== PERSONAL_COMPANY_TYPE &&
|
||||
[ROLE_DEVOPS, ROLE_ENGINEERING, ROLE_IT].includes(role)
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: DEVOPS_AUTOMATION_GOAL_OTHER_KEY,
|
||||
properties: {
|
||||
placeholder: i18n.baseText('personalizationModal.specifyYourAutomationGoal'),
|
||||
},
|
||||
shouldDisplay(values): boolean {
|
||||
const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
|
||||
const goals = (values as IPersonalizationLatestVersion)[DEVOPS_AUTOMATION_GOAL_KEY];
|
||||
const role = (values as IPersonalizationLatestVersion)[ROLE_KEY] as string;
|
||||
return (
|
||||
companyType !== PERSONAL_COMPANY_TYPE &&
|
||||
[ROLE_DEVOPS, ROLE_ENGINEERING, ROLE_IT].includes(role) &&
|
||||
!!goals &&
|
||||
goals.includes(DEVOPS_AUTOMATION_OTHER)
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: MARKETING_AUTOMATION_GOAL_KEY,
|
||||
properties: {
|
||||
type: 'multi-select',
|
||||
label: i18n.baseText('personalizationModal.specifySalesMarketingGoal'),
|
||||
placeholder: i18n.baseText('personalizationModal.select'),
|
||||
options: [
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.leadGeneration'),
|
||||
value: MARKETING_AUTOMATION_LEAD_GENERATION_GOAL,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.customerCommunication'),
|
||||
value: MARKETING_AUTOMATION_CUSTOMER_COMMUNICATION,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.customerActions'),
|
||||
value: MARKETING_AUTOMATION_ACTIONS,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.adCampaign'),
|
||||
value: MARKETING_AUTOMATION_AD_CAMPAIGN,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.reporting'),
|
||||
value: MARKETING_AUTOMATION_REPORTING,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.dataSynching'),
|
||||
value: MARKETING_AUTOMATION_DATA_SYNCHING,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.other'),
|
||||
value: MARKETING_AUTOMATION_OTHER,
|
||||
},
|
||||
],
|
||||
},
|
||||
shouldDisplay(values): boolean {
|
||||
const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
|
||||
const role = (values as IPersonalizationLatestVersion)[ROLE_KEY];
|
||||
return companyType !== PERSONAL_COMPANY_TYPE && role === ROLE_SALES_AND_MARKETING;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: OTHER_MARKETING_AUTOMATION_GOAL_KEY,
|
||||
properties: {
|
||||
placeholder: i18n.baseText('personalizationModal.specifyOtherSalesAndMarketingGoal'),
|
||||
},
|
||||
shouldDisplay(values): boolean {
|
||||
const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
|
||||
const goals = (values as IPersonalizationLatestVersion)[MARKETING_AUTOMATION_GOAL_KEY];
|
||||
const role = (values as IPersonalizationLatestVersion)[ROLE_KEY];
|
||||
return (
|
||||
companyType !== PERSONAL_COMPANY_TYPE &&
|
||||
role === ROLE_SALES_AND_MARKETING &&
|
||||
!!goals &&
|
||||
goals.includes(MARKETING_AUTOMATION_OTHER)
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: AUTOMATION_BENEFICIARY_KEY,
|
||||
properties: {
|
||||
type: 'select',
|
||||
label: i18n.baseText('personalizationModal.specifyAutomationBeneficiary'),
|
||||
placeholder: i18n.baseText('personalizationModal.select'),
|
||||
options: [
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.myself'),
|
||||
value: AUTOMATION_BENEFICIARY_SELF,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.myTeam'),
|
||||
value: AUTOMATION_BENEFICIARY_MY_TEAM,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.otherTeams'),
|
||||
value: AUTOMATION_BENEFICIARY_OTHER_TEAMS,
|
||||
},
|
||||
],
|
||||
},
|
||||
shouldDisplay(values): boolean {
|
||||
const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
|
||||
return companyType !== PERSONAL_COMPANY_TYPE;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: COMPANY_SIZE_KEY,
|
||||
properties: {
|
||||
type: 'select',
|
||||
label: i18n.baseText('personalizationModal.howBigIsYourCompany'),
|
||||
placeholder: i18n.baseText('personalizationModal.select'),
|
||||
options: [
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.lessThan20People'),
|
||||
value: COMPANY_SIZE_20_OR_LESS,
|
||||
},
|
||||
{
|
||||
label: `20-99 ${i18n.baseText('personalizationModal.people')}`,
|
||||
value: COMPANY_SIZE_20_99,
|
||||
},
|
||||
{
|
||||
label: `100-499 ${i18n.baseText('personalizationModal.people')}`,
|
||||
value: COMPANY_SIZE_100_499,
|
||||
},
|
||||
{
|
||||
label: `500-999 ${i18n.baseText('personalizationModal.people')}`,
|
||||
value: COMPANY_SIZE_500_999,
|
||||
},
|
||||
{
|
||||
label: `1000+ ${i18n.baseText('personalizationModal.people')}`,
|
||||
value: COMPANY_SIZE_1000_OR_MORE,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.imNotUsingN8nForWork'),
|
||||
value: COMPANY_SIZE_PERSONAL_USE,
|
||||
},
|
||||
],
|
||||
},
|
||||
shouldDisplay(values): boolean {
|
||||
const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
|
||||
return companyType !== PERSONAL_COMPANY_TYPE;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: REPORTED_SOURCE_KEY,
|
||||
properties: {
|
||||
type: 'select',
|
||||
label: i18n.baseText('personalizationModal.howDidYouHearAboutN8n'),
|
||||
placeholder: i18n.baseText('personalizationModal.select'),
|
||||
options: [
|
||||
{
|
||||
label: 'Google',
|
||||
value: REPORTED_SOURCE_GOOGLE,
|
||||
},
|
||||
{
|
||||
label: 'Twitter',
|
||||
value: REPORTED_SOURCE_TWITTER,
|
||||
},
|
||||
{
|
||||
label: 'LinkedIn',
|
||||
value: REPORTED_SOURCE_LINKEDIN,
|
||||
},
|
||||
{
|
||||
label: 'YouTube',
|
||||
value: REPORTED_SOURCE_YOUTUBE,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.friendWordOfMouth'),
|
||||
value: REPORTED_SOURCE_FRIEND,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.podcast'),
|
||||
value: REPORTED_SOURCE_PODCAST,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.event'),
|
||||
value: REPORTED_SOURCE_EVENT,
|
||||
},
|
||||
{
|
||||
label: i18n.baseText('personalizationModal.otherPleaseSpecify'),
|
||||
value: REPORTED_SOURCE_OTHER,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: REPORTED_SOURCE_OTHER_KEY,
|
||||
properties: {
|
||||
placeholder: i18n.baseText('personalizationModal.specifyReportedSource'),
|
||||
},
|
||||
shouldDisplay(values): boolean {
|
||||
const reportedSource = (values as IPersonalizationLatestVersion)[REPORTED_SOURCE_KEY];
|
||||
return reportedSource === REPORTED_SOURCE_OTHER;
|
||||
},
|
||||
},
|
||||
]);
|
||||
},
|
||||
] as const,
|
||||
);
|
||||
|
||||
const onSave = () => {
|
||||
formBus.emit('submit');
|
||||
@@ -575,7 +578,7 @@ const closeDialog = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = async (values: IPersonalizationLatestVersion) => {
|
||||
const onSubmit = async (values: object) => {
|
||||
isSaving.value = true;
|
||||
|
||||
try {
|
||||
|
||||
@@ -4,11 +4,7 @@ import { useI18n } from '@n8n/i18n';
|
||||
import { ResourceType, splitName } from '@/utils/projects.utils';
|
||||
import type { Project, ProjectIcon as BadgeIcon } from '@/types/projects.types';
|
||||
import { ProjectTypes } from '@/types/projects.types';
|
||||
import type {
|
||||
CredentialsResource,
|
||||
FolderResource,
|
||||
WorkflowResource,
|
||||
} from '../layouts/ResourcesListLayout.vue';
|
||||
import type { CredentialsResource, FolderResource, WorkflowResource } from '@/Interface';
|
||||
import { VIEWS } from '@/constants';
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import type { ButtonType } from '@n8n/design-system';
|
||||
import type { ButtonType, UserAction } from '@n8n/design-system';
|
||||
import { N8nIconButton, N8nActionToggle } from '@n8n/design-system';
|
||||
import { ref } from 'vue';
|
||||
import type { IUser } from 'n8n-workflow';
|
||||
import { useTemplateRef } from 'vue';
|
||||
|
||||
type Action = {
|
||||
label: string;
|
||||
value: string;
|
||||
disabled: boolean;
|
||||
};
|
||||
defineProps<{
|
||||
actions: Action[];
|
||||
actions: Array<UserAction<IUser>>;
|
||||
disabled?: boolean;
|
||||
type?: ButtonType;
|
||||
}>();
|
||||
@@ -18,7 +14,7 @@ const emit = defineEmits<{
|
||||
action: [id: string];
|
||||
}>();
|
||||
|
||||
const actionToggleRef = ref<InstanceType<typeof N8nActionToggle> | null>(null);
|
||||
const actionToggleRef = useTemplateRef('actionToggleRef');
|
||||
|
||||
defineExpose({
|
||||
openActionToggle: (isOpen: boolean) => actionToggleRef.value?.openActionToggle(isOpen),
|
||||
|
||||
@@ -16,6 +16,7 @@ import ProjectCreateResource from '@/components/Projects/ProjectCreateResource.v
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { useProjectPages } from '@/composables/useProjectPages';
|
||||
import { truncateTextToFitWidth } from '@/utils/formatters/textFormatter';
|
||||
import type { IUser } from 'n8n-workflow';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
@@ -96,7 +97,7 @@ const createWorkflowButton = computed(() => ({
|
||||
}));
|
||||
|
||||
const menu = computed(() => {
|
||||
const items: UserAction[] = [
|
||||
const items: Array<UserAction<IUser>> = [
|
||||
{
|
||||
value: ACTION_TYPES.CREDENTIAL,
|
||||
label: i18n.baseText('projects.header.create.credential'),
|
||||
|
||||
@@ -240,7 +240,7 @@ onMounted(async () => {
|
||||
v-for="p in filteredProjects"
|
||||
:key="p.id"
|
||||
:value="p.id"
|
||||
:label="p.name"
|
||||
:label="p.name ?? ''"
|
||||
></N8nOption>
|
||||
</N8nSelect>
|
||||
<N8nText>
|
||||
|
||||
@@ -43,10 +43,10 @@ const shared = computed<IMenuItem>(() => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const getProjectMenuItem = (project: ProjectListItem) => ({
|
||||
const getProjectMenuItem = (project: ProjectListItem): IMenuItem => ({
|
||||
id: project.id,
|
||||
label: project.name,
|
||||
icon: project.icon,
|
||||
label: project.name ?? '',
|
||||
icon: project.icon as IMenuItem['icon'],
|
||||
route: {
|
||||
to: {
|
||||
name: VIEWS.PROJECTS_WORKFLOWS,
|
||||
@@ -70,6 +70,14 @@ const personalProject = computed<IMenuItem>(() => ({
|
||||
const showAddFirstProject = computed(
|
||||
() => projectsStore.isTeamProjectFeatureEnabled && !displayProjects.value.length,
|
||||
);
|
||||
|
||||
const activeTabId = computed(() => {
|
||||
return (
|
||||
(Array.isArray(projectsStore.projectNavActiveId)
|
||||
? projectsStore.projectNavActiveId[0]
|
||||
: projectsStore.projectNavActiveId) ?? undefined
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -78,7 +86,7 @@ const showAddFirstProject = computed(
|
||||
<N8nMenuItem
|
||||
:item="home"
|
||||
:compact="props.collapsed"
|
||||
:active-tab="projectsStore.projectNavActiveId"
|
||||
:active-tab="activeTabId"
|
||||
mode="tabs"
|
||||
data-test-id="project-home-menu-item"
|
||||
/>
|
||||
@@ -86,7 +94,7 @@ const showAddFirstProject = computed(
|
||||
v-if="projectsStore.isTeamProjectFeatureEnabled || isFoldersFeatureEnabled"
|
||||
:item="personalProject"
|
||||
:compact="props.collapsed"
|
||||
:active-tab="projectsStore.projectNavActiveId"
|
||||
:active-tab="activeTabId"
|
||||
mode="tabs"
|
||||
data-test-id="project-personal-menu-item"
|
||||
/>
|
||||
@@ -94,7 +102,7 @@ const showAddFirstProject = computed(
|
||||
v-if="projectsStore.isTeamProjectFeatureEnabled || isFoldersFeatureEnabled"
|
||||
:item="shared"
|
||||
:compact="props.collapsed"
|
||||
:active-tab="projectsStore.projectNavActiveId"
|
||||
:active-tab="activeTabId"
|
||||
mode="tabs"
|
||||
data-test-id="project-shared-menu-item"
|
||||
/>
|
||||
@@ -136,7 +144,7 @@ const showAddFirstProject = computed(
|
||||
}"
|
||||
:item="getProjectMenuItem(project)"
|
||||
:compact="props.collapsed"
|
||||
:active-tab="projectsStore.projectNavActiveId"
|
||||
:active-tab="activeTabId"
|
||||
mode="tabs"
|
||||
data-test-id="project-menu-item"
|
||||
/>
|
||||
|
||||
@@ -136,7 +136,7 @@ watch(
|
||||
v-for="project in filteredProjects"
|
||||
:key="project.id"
|
||||
:value="project.id"
|
||||
:label="project.name"
|
||||
:label="project.name ?? ''"
|
||||
>
|
||||
<ProjectSharingInfo :project="project" />
|
||||
</N8nOption>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useRoute } from 'vue-router';
|
||||
import { VIEWS } from '@/constants';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import type { BaseTextKey } from '@n8n/i18n';
|
||||
import type { TabOptions } from '@n8n/design-system';
|
||||
|
||||
type Props = {
|
||||
showSettings?: boolean;
|
||||
@@ -23,6 +24,8 @@ const route = useRoute();
|
||||
|
||||
const selectedTab = ref<RouteRecordName | null | undefined>('');
|
||||
|
||||
const selectedTabLabel = computed(() => (selectedTab.value ? String(selectedTab.value) : ''));
|
||||
|
||||
const projectId = computed(() => {
|
||||
return Array.isArray(route?.params?.projectId)
|
||||
? route.params.projectId[0]
|
||||
@@ -70,16 +73,16 @@ const createTab = (
|
||||
label: BaseTextKey,
|
||||
routeKey: string,
|
||||
routes: Record<string, { name: RouteRecordName; params?: Record<string, string | number> }>,
|
||||
) => {
|
||||
): TabOptions<string> => {
|
||||
return {
|
||||
label: locale.baseText(label),
|
||||
value: routes[routeKey].name,
|
||||
value: routes[routeKey].name as string,
|
||||
to: routes[routeKey],
|
||||
};
|
||||
};
|
||||
|
||||
// Generate the tabs configuration
|
||||
const options = computed(() => {
|
||||
const options = computed<Array<TabOptions<string>>>(() => {
|
||||
const routes = getRouteConfigs();
|
||||
const tabs = [
|
||||
createTab('mainSidebar.workflows', 'workflows', routes),
|
||||
@@ -93,7 +96,7 @@ const options = computed(() => {
|
||||
if (props.showSettings) {
|
||||
tabs.push({
|
||||
label: locale.baseText('projects.settings'),
|
||||
value: VIEWS.PROJECT_SETTINGS,
|
||||
value: VIEWS.PROJECT_SETTINGS as string,
|
||||
to: { name: VIEWS.PROJECT_SETTINGS, params: { projectId: projectId.value } },
|
||||
});
|
||||
}
|
||||
@@ -110,8 +113,17 @@ watch(
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
function onSelectTab(value: string | number) {
|
||||
selectedTab.value = value as RouteRecordName;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<N8nTabs v-model="selectedTab" :options="options" data-test-id="project-tabs" />
|
||||
<N8nTabs
|
||||
:model-value="selectedTabLabel"
|
||||
:options="options"
|
||||
data-test-id="project-tabs"
|
||||
@update:model-value="onSelectTab"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -4,7 +4,7 @@ import Modal from '../Modal.vue';
|
||||
import { PROMPT_MFA_CODE_MODAL_KEY } from '@/constants';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { promptMfaCodeBus } from '@/event-bus';
|
||||
import type { IFormInputs } from '@/Interface';
|
||||
import { type IFormInput } from '@/Interface';
|
||||
import { createFormEventBus } from '@n8n/design-system/utils';
|
||||
import { validate as validateUuid } from 'uuid';
|
||||
|
||||
@@ -13,7 +13,7 @@ const i18n = useI18n();
|
||||
const formBus = createFormEventBus();
|
||||
const readyToSubmit = ref(false);
|
||||
|
||||
const formFields: IFormInputs = [
|
||||
const formFields: IFormInput[] = [
|
||||
{
|
||||
name: 'mfaCodeOrMfaRecoveryCode',
|
||||
initialValue: '',
|
||||
@@ -25,9 +25,14 @@ const formFields: IFormInputs = [
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
] as const;
|
||||
|
||||
function onSubmit(values: { mfaCodeOrMfaRecoveryCode: string }) {
|
||||
function onSubmit(values: object) {
|
||||
if (
|
||||
!('mfaCodeOrMfaRecoveryCode' in values && typeof values.mfaCodeOrMfaRecoveryCode === 'string')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (validateUuid(values.mfaCodeOrMfaRecoveryCode)) {
|
||||
promptMfaCodeBus.emit('close', {
|
||||
mfaRecoveryCode: values.mfaCodeOrMfaRecoveryCode,
|
||||
|
||||
@@ -220,9 +220,9 @@ const hasMultipleModes = computed(() => {
|
||||
});
|
||||
|
||||
const hasOnlyListMode = computed(() => hasOnlyListModeUtil(props.parameter));
|
||||
const valueToDisplay = computed<NodeParameterValue>(() => {
|
||||
const valueToDisplay = computed<INodeParameterResourceLocator['value']>(() => {
|
||||
if (typeof props.modelValue !== 'object') {
|
||||
return props.modelValue;
|
||||
return `${props.modelValue}`;
|
||||
}
|
||||
|
||||
if (isListMode.value) {
|
||||
@@ -398,6 +398,9 @@ const handleAddResourceClick = async () => {
|
||||
const newResource = (await nodeTypesStore.getNodeParameterActionResult(
|
||||
requestParams,
|
||||
)) as NodeParameterValue;
|
||||
if (typeof newResource === 'boolean') {
|
||||
return;
|
||||
}
|
||||
|
||||
refreshList();
|
||||
await loadResources();
|
||||
@@ -563,7 +566,7 @@ function findModeByName(name: string): INodePropertyMode | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
function getModeLabel(mode: INodePropertyMode): string | null {
|
||||
function getModeLabel(mode: INodePropertyMode): string | undefined {
|
||||
if (mode.name === 'id' || mode.name === 'url' || mode.name === 'list') {
|
||||
return i18n.baseText(`resourceLocator.mode.${mode.name}`);
|
||||
}
|
||||
@@ -571,7 +574,7 @@ function getModeLabel(mode: INodePropertyMode): string | null {
|
||||
return mode.displayName;
|
||||
}
|
||||
|
||||
function onInputChange(value: NodeParameterValue): void {
|
||||
function onInputChange(value: INodeParameterResourceLocator['value']): void {
|
||||
const params: INodeParameterResourceLocator = { __rl: true, value, mode: selectedMode.value };
|
||||
if (isListMode.value) {
|
||||
const resource = currentQueryResults.value.find((result) => result.value === value);
|
||||
@@ -823,7 +826,7 @@ function showResourceDropdown() {
|
||||
resourceDropdownVisible.value = true;
|
||||
}
|
||||
|
||||
function onListItemSelected(value: NodeParameterValue) {
|
||||
function onListItemSelected(value: INodeParameterResourceLocator['value']) {
|
||||
onInputChange(value);
|
||||
hideResourceDropdown();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { IResourceLocatorResultExpanded } from '@/Interface';
|
||||
import { N8nLoading } from '@n8n/design-system';
|
||||
import type { EventBus } from '@n8n/utils/event-bus';
|
||||
import { createEventBus } from '@n8n/utils/event-bus';
|
||||
import type { NodeParameterValue } from 'n8n-workflow';
|
||||
import type { INodeParameterResourceLocator, NodeParameterValue } from 'n8n-workflow';
|
||||
import { computed, onBeforeUnmount, onMounted, ref, useCssModule, watch } from 'vue';
|
||||
|
||||
const SEARCH_BAR_HEIGHT_PX = 40;
|
||||
@@ -43,7 +43,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: NodeParameterValue];
|
||||
'update:modelValue': [value: INodeParameterResourceLocator['value']];
|
||||
loadMore: [];
|
||||
filter: [filter: string];
|
||||
addResourceClick: [];
|
||||
@@ -164,7 +164,7 @@ function onKeyDown(e: KeyboardEvent) {
|
||||
const selected = sortedResources.value[hoverIndex.value - 1]?.value;
|
||||
|
||||
// Selected resource can be empty when loading or empty results
|
||||
if (selected) {
|
||||
if (selected && typeof selected !== 'boolean') {
|
||||
emit('update:modelValue', selected);
|
||||
}
|
||||
}
|
||||
@@ -175,6 +175,10 @@ function onFilterInput(value: string) {
|
||||
}
|
||||
|
||||
function onItemClick(selected: string | number | boolean) {
|
||||
if (typeof selected === 'boolean') {
|
||||
return;
|
||||
}
|
||||
|
||||
emit('update:modelValue', selected);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ const onSSOLogin = async () => {
|
||||
<div :class="$style.divider">
|
||||
<span>{{ i18n.baseText('sso.login.divider') }}</span>
|
||||
</div>
|
||||
<n8n-button
|
||||
<N8nButton
|
||||
size="large"
|
||||
type="primary"
|
||||
outline
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
import KeyboardShortcutTooltip from '@/components/KeyboardShortcutTooltip.vue';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { computed } from 'vue';
|
||||
import type { ButtonType } from '@n8n/design-system';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
saved: boolean;
|
||||
isSaving?: boolean;
|
||||
disabled?: boolean;
|
||||
type?: string;
|
||||
type?: ButtonType;
|
||||
withShortcut?: boolean;
|
||||
shortcutTooltip?: string;
|
||||
savingLabel?: string;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts" setup>
|
||||
import type { WorkflowResource } from '@/components/layouts/ResourcesListLayout.vue';
|
||||
import ProjectCardBadge from '@/components/Projects/ProjectCardBadge.vue';
|
||||
import { useLoadingService } from '@/composables/useLoadingService';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
@@ -41,6 +40,7 @@ import { useRoute } from 'vue-router';
|
||||
import { DynamicScroller, DynamicScrollerItem } from 'vue-virtual-scroller';
|
||||
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';
|
||||
import Modal from './Modal.vue';
|
||||
import { type WorkflowResource } from '@/Interface';
|
||||
|
||||
const props = defineProps<{
|
||||
data: { eventBus: EventBus; status: SourceControlledFile[] };
|
||||
|
||||
@@ -9,13 +9,15 @@ import type {
|
||||
ITemplatesNode,
|
||||
ITemplatesWorkflow,
|
||||
} from '@n8n/rest-api-client/api/templates';
|
||||
import type { ITag } from '@n8n/rest-api-client/api/tags';
|
||||
import { useTemplatesStore } from '@/stores/templates.store';
|
||||
import TimeAgo from '@/components/TimeAgo.vue';
|
||||
import { isFullTemplatesCollection, isTemplatesWorkflow } from '@/utils/templates/typeGuards';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { computed } from 'vue';
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
template: ITemplatesWorkflow | ITemplatesCollection | ITemplatesCollectionFull | null;
|
||||
blockTitle: string;
|
||||
loading: boolean;
|
||||
@@ -26,6 +28,15 @@ const i18n = useI18n();
|
||||
|
||||
const templatesStore = useTemplatesStore();
|
||||
|
||||
const categoriesAsTags = computed<ITag[]>(() =>
|
||||
props.template && 'categories' in props.template
|
||||
? props.template.categories.map((category) => ({
|
||||
id: `${category.id}`,
|
||||
name: category.name,
|
||||
}))
|
||||
: [],
|
||||
);
|
||||
|
||||
const redirectToCategory = (id: string) => {
|
||||
templatesStore.resetSessionId();
|
||||
void router.push(`/templates?categories=${id}`);
|
||||
@@ -62,10 +73,10 @@ const redirectToSearchPage = (node: ITemplatesNode) => {
|
||||
</TemplateDetailsBlock>
|
||||
|
||||
<TemplateDetailsBlock
|
||||
v-if="!loading && isFullTemplatesCollection(template) && template.categories.length > 0"
|
||||
v-if="!loading && isFullTemplatesCollection(template) && categoriesAsTags.length > 0"
|
||||
:title="i18n.baseText('template.details.categories')"
|
||||
>
|
||||
<n8n-tags :tags="template.categories" @click:tag="redirectToCategory" />
|
||||
<n8n-tags :tags="categoriesAsTags" @click:tag="redirectToCategory" />
|
||||
</TemplateDetailsBlock>
|
||||
|
||||
<TemplateDetailsBlock
|
||||
|
||||
@@ -473,7 +473,7 @@ const onDragEnd = (el: HTMLElement) => {
|
||||
</VirtualSchemaItem>
|
||||
|
||||
<N8nTooltip v-else-if="item.type === 'icon'" :content="item.tooltip" placement="top">
|
||||
<N8nIcon :size="14" :icon="item.icon" class="icon" />
|
||||
<N8nIcon size="small" :icon="item.icon" class="icon" />
|
||||
</N8nTooltip>
|
||||
|
||||
<div
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import type { IconColor } from '@n8n/design-system';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
icon?: string;
|
||||
iconColor?: string;
|
||||
iconColor?: IconColor;
|
||||
initialExpanded?: boolean;
|
||||
}>(),
|
||||
{
|
||||
icon: 'tasks',
|
||||
iconColor: 'black',
|
||||
iconColor: 'text-dark',
|
||||
initialExpanded: true,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -95,7 +95,7 @@ orchestrationStore.$onAction(({ name, store }) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<WorkerAccordion icon="tasks" icon-color="black" :initial-expanded="false">
|
||||
<WorkerAccordion icon="tasks" icon-color="text-dark" :initial-expanded="false">
|
||||
<template #title>
|
||||
{{ i18n.baseText('workerList.item.chartsTitle') }}
|
||||
</template>
|
||||
|
||||
@@ -20,7 +20,7 @@ function runningSince(started: Date): string {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<WorkerAccordion icon="tasks" icon-color="black" :initial-expanded="true">
|
||||
<WorkerAccordion icon="tasks" icon-color="text-dark" :initial-expanded="true">
|
||||
<template #title>
|
||||
{{ i18n.baseText('workerList.item.jobListTitle') }} ({{ items.length }})
|
||||
</template>
|
||||
|
||||
@@ -25,7 +25,7 @@ function onCopyToClipboard(content: string) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<WorkerAccordion icon="tasks" icon-color="black" :initial-expanded="false">
|
||||
<WorkerAccordion icon="tasks" icon-color="text-dark" :initial-expanded="false">
|
||||
<template #title>
|
||||
{{ i18n.baseText('workerList.item.netListTitle') }} ({{ items.length }})
|
||||
</template>
|
||||
|
||||
@@ -51,7 +51,7 @@ const onClick = async () => {
|
||||
<div :class="$style.container">
|
||||
<div>
|
||||
<n8n-text color="text-base"> You can deactivate </n8n-text>
|
||||
<n8n-link :to="workflowUrl" underline="true"> '{{ data.workflowName }}' </n8n-link>
|
||||
<n8n-link :to="workflowUrl" :underline="true"> '{{ data.workflowName }}' </n8n-link>
|
||||
<n8n-text color="text-base">
|
||||
and activate this one, or adjust the following URL path in either workflow:
|
||||
</n8n-text>
|
||||
|
||||
@@ -24,7 +24,7 @@ import { useRoute, useRouter } from 'vue-router';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { ResourceType } from '@/utils/projects.utils';
|
||||
import type { EventBus } from '@n8n/utils/event-bus';
|
||||
import type { WorkflowResource } from './layouts/ResourcesListLayout.vue';
|
||||
import type { WorkflowResource } from '@/Interface';
|
||||
import type { IUser } from 'n8n-workflow';
|
||||
import { type ProjectSharingData, ProjectTypes } from '@/types/projects.types';
|
||||
import type { PathItem } from '@n8n/design-system/components/N8nBreadcrumbs/Breadcrumbs.vue';
|
||||
@@ -406,6 +406,11 @@ const onBreadcrumbItemClick = async (item: PathItem) => {
|
||||
await router.push(item.href);
|
||||
}
|
||||
};
|
||||
|
||||
const tags = computed(
|
||||
() =>
|
||||
props.data.tags?.map((tag) => (typeof tag === 'string' ? { id: tag, name: tag } : tag)) ?? [],
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -448,7 +453,7 @@ const onBreadcrumbItemClick = async (item: PathItem) => {
|
||||
:class="$style.cardTags"
|
||||
>
|
||||
<n8n-tags
|
||||
:tags="data.tags"
|
||||
:tags="tags"
|
||||
:truncate-at="3"
|
||||
truncate
|
||||
data-test-id="workflow-card-tags"
|
||||
|
||||
@@ -9,9 +9,10 @@ import WorkflowHistoryContent from '@/components/WorkflowHistory/WorkflowHistory
|
||||
import type { WorkflowHistoryActionTypes } from '@n8n/rest-api-client/api/workflowHistory';
|
||||
import { workflowVersionDataFactory } from '@/stores/__tests__/utils/workflowHistoryTestUtils';
|
||||
import type { IWorkflowDb } from '@/Interface';
|
||||
import type { IUser } from 'n8n-workflow';
|
||||
|
||||
const actionTypes: WorkflowHistoryActionTypes = ['restore', 'clone', 'open', 'download'];
|
||||
const actions: UserAction[] = actionTypes.map((value) => ({
|
||||
const actions: Array<UserAction<IUser>> = actionTypes.map((value) => ({
|
||||
label: value,
|
||||
disabled: false,
|
||||
value,
|
||||
|
||||
@@ -9,13 +9,14 @@ import type {
|
||||
import WorkflowPreview from '@/components/WorkflowPreview.vue';
|
||||
import WorkflowHistoryListItem from '@/components/WorkflowHistory/WorkflowHistoryListItem.vue';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import type { IUser } from 'n8n-workflow';
|
||||
|
||||
const i18n = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
workflow: IWorkflowDb | null;
|
||||
workflowVersion: WorkflowVersion | null;
|
||||
actions: UserAction[];
|
||||
actions: Array<UserAction<IUser>>;
|
||||
isListLoading?: boolean;
|
||||
isFirstItemShown?: boolean;
|
||||
}>();
|
||||
|
||||
@@ -7,6 +7,7 @@ import { createComponentRenderer } from '@/__tests__/render';
|
||||
import WorkflowHistoryList from '@/components/WorkflowHistory/WorkflowHistoryList.vue';
|
||||
import type { WorkflowHistoryActionTypes } from '@n8n/rest-api-client/api/workflowHistory';
|
||||
import { workflowHistoryDataFactory } from '@/stores/__tests__/utils/workflowHistoryTestUtils';
|
||||
import type { IUser } from 'n8n-workflow';
|
||||
|
||||
vi.stubGlobal(
|
||||
'IntersectionObserver',
|
||||
@@ -19,7 +20,7 @@ vi.stubGlobal(
|
||||
);
|
||||
|
||||
const actionTypes: WorkflowHistoryActionTypes = ['restore', 'clone', 'open', 'download'];
|
||||
const actions: UserAction[] = actionTypes.map((value) => ({
|
||||
const actions: Array<UserAction<IUser>> = actionTypes.map((value) => ({
|
||||
label: value,
|
||||
disabled: false,
|
||||
value,
|
||||
|
||||
@@ -9,11 +9,12 @@ import type {
|
||||
WorkflowHistoryRequestParams,
|
||||
} from '@n8n/rest-api-client/api/workflowHistory';
|
||||
import WorkflowHistoryListItem from '@/components/WorkflowHistory/WorkflowHistoryListItem.vue';
|
||||
import type { IUser } from 'n8n-workflow';
|
||||
|
||||
const props = defineProps<{
|
||||
items: WorkflowHistory[];
|
||||
activeItem: WorkflowHistory | null;
|
||||
actions: UserAction[];
|
||||
actions: Array<UserAction<IUser>>;
|
||||
requestNumberOfItems: number;
|
||||
lastReceivedItemsLength: number;
|
||||
evaluatedPruneTime: number;
|
||||
|
||||
@@ -5,9 +5,10 @@ import { createComponentRenderer } from '@/__tests__/render';
|
||||
import WorkflowHistoryListItem from '@/components/WorkflowHistory/WorkflowHistoryListItem.vue';
|
||||
import type { WorkflowHistoryActionTypes } from '@n8n/rest-api-client/api/workflowHistory';
|
||||
import { workflowHistoryDataFactory } from '@/stores/__tests__/utils/workflowHistoryTestUtils';
|
||||
import { type IUser } from 'n8n-workflow';
|
||||
|
||||
const actionTypes: WorkflowHistoryActionTypes = ['restore', 'clone', 'open', 'download'];
|
||||
const actions: UserAction[] = actionTypes.map((value) => ({
|
||||
const actions: Array<UserAction<IUser>> = actionTypes.map((value) => ({
|
||||
label: value,
|
||||
disabled: false,
|
||||
value,
|
||||
|
||||
@@ -8,11 +8,12 @@ import type {
|
||||
WorkflowHistoryActionTypes,
|
||||
} from '@n8n/rest-api-client/api/workflowHistory';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import type { IUser } from 'n8n-workflow';
|
||||
|
||||
const props = defineProps<{
|
||||
item: WorkflowHistory;
|
||||
index: number;
|
||||
actions: UserAction[];
|
||||
actions: Array<UserAction<IUser>>;
|
||||
isActive: boolean;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
@@ -62,7 +63,8 @@ const idLabel = computed<string>(() =>
|
||||
i18n.baseText('workflowHistory.item.id', { interpolate: { id: props.item.versionId } }),
|
||||
);
|
||||
|
||||
const onAction = (action: WorkflowHistoryActionTypes[number]) => {
|
||||
const onAction = (value: string) => {
|
||||
const action = value as WorkflowHistoryActionTypes[number];
|
||||
emit('action', {
|
||||
action,
|
||||
id: props.item.versionId,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import Modal from '@/components/Modal.vue';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import type { ButtonType } from '@n8n/design-system';
|
||||
|
||||
const props = defineProps<{
|
||||
modalName: string;
|
||||
@@ -11,7 +12,7 @@ const props = defineProps<{
|
||||
beforeClose: () => void;
|
||||
buttons: Array<{
|
||||
text: string;
|
||||
type: string;
|
||||
type: ButtonType;
|
||||
action: () => void;
|
||||
}>;
|
||||
};
|
||||
|
||||
@@ -122,9 +122,9 @@ const getCreateResourceLabel = computed(() => {
|
||||
});
|
||||
});
|
||||
|
||||
const valueToDisplay = computed<NodeParameterValue>(() => {
|
||||
const valueToDisplay = computed<INodeParameterResourceLocator['value']>(() => {
|
||||
if (typeof props.modelValue !== 'object') {
|
||||
return props.modelValue;
|
||||
return props.modelValue ?? '';
|
||||
}
|
||||
|
||||
if (isListMode.value) {
|
||||
@@ -208,9 +208,6 @@ async function refreshCachedWorkflow() {
|
||||
}
|
||||
|
||||
const workflowId = props.modelValue.value;
|
||||
if (workflowId === true) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await workflowsStore.fetchWorkflow(`${workflowId}`);
|
||||
onInputChange(workflowId);
|
||||
|
||||
@@ -49,7 +49,7 @@ export function useWorkflowResourceLocatorModes(
|
||||
};
|
||||
}
|
||||
|
||||
function getModeLabel(mode: INodePropertyMode): string | null {
|
||||
function getModeLabel(mode: INodePropertyMode): string | undefined {
|
||||
if (mode.name === 'id' || mode.name === 'list') {
|
||||
return i18n.baseText(`resourceLocator.mode.${mode.name}`);
|
||||
}
|
||||
|
||||
@@ -701,7 +701,7 @@ onMounted(async () => {
|
||||
>
|
||||
<N8nOption
|
||||
v-for="option of saveManualOptions"
|
||||
:key="option.key"
|
||||
:key="`${option.key}`"
|
||||
:label="option.value"
|
||||
:value="option.key"
|
||||
>
|
||||
@@ -730,7 +730,7 @@ onMounted(async () => {
|
||||
>
|
||||
<N8nOption
|
||||
v-for="option of saveExecutionProgressOptions"
|
||||
:key="option.key"
|
||||
:key="`${option.key}`"
|
||||
:label="option.value"
|
||||
:value="option.key"
|
||||
>
|
||||
|
||||
@@ -316,7 +316,7 @@ exports[`VirtualSchema.vue > renders preview schema when enabled and available 1
|
||||
|
||||
|
||||
<span
|
||||
class="n8n-text compact size-14 regular n8n-icon icon el-tooltip__trigger el-tooltip__trigger icon el-tooltip__trigger el-tooltip__trigger n8n-icon icon el-tooltip__trigger el-tooltip__trigger icon el-tooltip__trigger el-tooltip__trigger"
|
||||
class="n8n-text compact size-small regular n8n-icon icon el-tooltip__trigger el-tooltip__trigger icon el-tooltip__trigger el-tooltip__trigger n8n-icon icon el-tooltip__trigger el-tooltip__trigger icon el-tooltip__trigger el-tooltip__trigger"
|
||||
data-v-d00cba9a=""
|
||||
>
|
||||
|
||||
@@ -325,7 +325,7 @@ exports[`VirtualSchema.vue > renders preview schema when enabled and available 1
|
||||
beatfade="false"
|
||||
border="false"
|
||||
bounce="false"
|
||||
class="14"
|
||||
class="small"
|
||||
fade="false"
|
||||
fixedwidth="false"
|
||||
flash="false"
|
||||
|
||||
@@ -3,10 +3,11 @@ import { useUIStore } from '@/stores/ui.store';
|
||||
import { computed, useSlots } from 'vue';
|
||||
import type { BannerName } from '@n8n/api-types';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import type { CalloutTheme } from '@n8n/design-system';
|
||||
|
||||
interface Props {
|
||||
name: BannerName;
|
||||
theme?: string;
|
||||
theme?: CalloutTheme;
|
||||
customIcon?: string;
|
||||
dismissible?: boolean;
|
||||
}
|
||||
|
||||
@@ -157,6 +157,12 @@ const goToUpgrade = () => {
|
||||
void pageRedirectionHelper.goToUpgrade('custom-data-filter', 'upgrade-custom-data-filter');
|
||||
};
|
||||
|
||||
const onExactMatchChange = (e: string | number | boolean) => {
|
||||
if (typeof e === 'boolean') {
|
||||
onFilterMetaChange(0, 'exactMatch', e);
|
||||
}
|
||||
};
|
||||
|
||||
onBeforeMount(() => {
|
||||
isCustomDataFilterTracked.value = false;
|
||||
});
|
||||
@@ -348,7 +354,7 @@ onBeforeMount(() => {
|
||||
:model-value="filter.metadata[0]?.exactMatch"
|
||||
:disabled="!isAdvancedExecutionFilterEnabled"
|
||||
data-test-id="execution-filter-saved-data-exact-match-checkbox"
|
||||
@update:model-value="onFilterMetaChange(0, 'exactMatch', $event)"
|
||||
@update:model-value="onExactMatchChange"
|
||||
/>
|
||||
</n8n-tooltip>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,8 @@ import { deepCopy } from 'n8n-workflow';
|
||||
import { useNpsSurveyStore } from '@/stores/npsSurvey.store';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { useWorkflowSaving } from '@/composables/useWorkflowSaving';
|
||||
import type { IconColor } from '@n8n/design-system';
|
||||
import { type IAccordionItem } from '@n8n/design-system/components/N8nInfoAccordion/InfoAccordion.vue';
|
||||
|
||||
interface IWorkflowSaveSettings {
|
||||
saveFailedExecutions: boolean;
|
||||
@@ -47,7 +49,7 @@ const workflowSaveSettings = ref({
|
||||
saveTestExecutions: false,
|
||||
} as IWorkflowSaveSettings);
|
||||
|
||||
const accordionItems = computed(() => [
|
||||
const accordionItems = computed((): IAccordionItem[] => [
|
||||
{
|
||||
id: 'productionExecutions',
|
||||
label: locale.baseText('executionsLandingPage.emptyState.accordion.productionExecutions'),
|
||||
@@ -77,7 +79,7 @@ const shouldExpandAccordion = computed(() => {
|
||||
!workflowSaveSettings.value.saveTestExecutions
|
||||
);
|
||||
});
|
||||
const productionExecutionsIcon = computed(() => {
|
||||
const productionExecutionsIcon = computed((): { color: IconColor; icon: string } => {
|
||||
if (productionExecutionsStatus.value === 'saving') {
|
||||
return { icon: 'check', color: 'success' };
|
||||
} else if (productionExecutionsStatus.value === 'not-saving') {
|
||||
@@ -104,9 +106,9 @@ const accordionIcon = computed(() => {
|
||||
!workflowSaveSettings.value.saveTestExecutions ||
|
||||
productionExecutionsStatus.value !== 'saving'
|
||||
) {
|
||||
return { icon: 'exclamation-triangle', color: 'warning' };
|
||||
return { icon: 'exclamation-triangle', color: 'warning' as IconColor };
|
||||
}
|
||||
return null;
|
||||
return undefined;
|
||||
});
|
||||
const currentWorkflowId = computed(() => workflowsStore.workflowId);
|
||||
const isNewWorkflow = computed(() => {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { EnterpriseEditionFeature } from '@/constants';
|
||||
import { useProjectsStore } from '@/stores/projects.store';
|
||||
import type { ProjectSharingData } from '@/types/projects.types';
|
||||
import ProjectSharing from '@/components/Projects/ProjectSharing.vue';
|
||||
import type { BaseFilters } from '../layouts/ResourcesListLayout.vue';
|
||||
import type { BaseFilters } from '@/Interface';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
|
||||
type IResourceFiltersType = Record<string, boolean | string | string[]>;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { setActivePinia } from 'pinia';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import { createComponentRenderer } from '@/__tests__/render';
|
||||
import ResourcesListLayout, { type Resource } from '@/components/layouts/ResourcesListLayout.vue';
|
||||
import ResourcesListLayout from '@/components/layouts/ResourcesListLayout.vue';
|
||||
import type { Resource } from '@/Interface';
|
||||
import type router from 'vue-router';
|
||||
import type { ProjectSharingData } from 'n8n-workflow';
|
||||
import { waitAllPromises } from '@/__tests__/utils';
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
<script lang="ts" setup generic="ResourceType extends Resource = Resource">
|
||||
import { computed, nextTick, ref, onMounted, watch, onBeforeUnmount } from 'vue';
|
||||
|
||||
import { type ProjectSharingData } from '@/types/projects.types';
|
||||
import PageViewLayout from '@/components/layouts/PageViewLayout.vue';
|
||||
import PageViewLayoutList from '@/components/layouts/PageViewLayoutList.vue';
|
||||
import ResourceFiltersDropdown from '@/components/forms/ResourceFiltersDropdown.vue';
|
||||
@@ -13,64 +12,12 @@ import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import type { BaseTextKey } from '@n8n/i18n';
|
||||
import type { Scope } from '@n8n/permissions';
|
||||
import type { ITag } from '@n8n/rest-api-client/api/tags';
|
||||
import type { BaseFolderItem, BaseResource, ResourceParentFolder } from '@/Interface';
|
||||
import type { BaseFilters, Resource, SortingAndPaginationUpdates } from '@/Interface';
|
||||
import { isSharedResource, isResourceSortableByDate } from '@/utils/typeGuards';
|
||||
import { useN8nLocalStorage } from '@/composables/useN8nLocalStorage';
|
||||
|
||||
type ResourceKeyType = 'credentials' | 'workflows' | 'variables' | 'folders';
|
||||
|
||||
export type FolderResource = BaseFolderItem & {
|
||||
resourceType: 'folder';
|
||||
};
|
||||
|
||||
export type WorkflowResource = BaseResource & {
|
||||
resourceType: 'workflow';
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
active: boolean;
|
||||
isArchived: boolean;
|
||||
homeProject?: ProjectSharingData;
|
||||
scopes?: Scope[];
|
||||
tags?: ITag[] | string[];
|
||||
sharedWithProjects?: ProjectSharingData[];
|
||||
readOnly: boolean;
|
||||
parentFolder?: ResourceParentFolder;
|
||||
};
|
||||
|
||||
export type VariableResource = BaseResource & {
|
||||
resourceType: 'variable';
|
||||
key?: string;
|
||||
value?: string;
|
||||
};
|
||||
|
||||
export type CredentialsResource = BaseResource & {
|
||||
resourceType: 'credential';
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
type: string;
|
||||
homeProject?: ProjectSharingData;
|
||||
scopes?: Scope[];
|
||||
sharedWithProjects?: ProjectSharingData[];
|
||||
readOnly: boolean;
|
||||
needsSetup: boolean;
|
||||
};
|
||||
|
||||
export type Resource = WorkflowResource | FolderResource | CredentialsResource | VariableResource;
|
||||
|
||||
export type BaseFilters = {
|
||||
search: string;
|
||||
homeProject: string;
|
||||
[key: string]: boolean | string | string[];
|
||||
};
|
||||
|
||||
export type SortingAndPaginationUpdates = {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
sort?: string;
|
||||
};
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const i18n = useI18n();
|
||||
@@ -82,19 +29,19 @@ const n8nLocalStorage = useN8nLocalStorage();
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
resourceKey: ResourceKeyType;
|
||||
displayName?: (resource: Resource) => string;
|
||||
resources: Resource[];
|
||||
displayName?: (resource: ResourceType) => string;
|
||||
resources: ResourceType[];
|
||||
disabled: boolean;
|
||||
initialize?: () => Promise<void>;
|
||||
filters?: BaseFilters;
|
||||
additionalFiltersHandler?: (
|
||||
resource: Resource,
|
||||
resource: ResourceType,
|
||||
filters: BaseFilters,
|
||||
matches: boolean,
|
||||
) => boolean;
|
||||
shareable?: boolean;
|
||||
showFiltersDropdown?: boolean;
|
||||
sortFns?: Record<string, (a: Resource, b: Resource) => number>;
|
||||
sortFns?: Record<string, (a: ResourceType, b: ResourceType) => number>;
|
||||
sortOptions?: string[];
|
||||
type?: 'datatable' | 'list-full' | 'list-paginated';
|
||||
typeProps: { itemSize: number } | { columns: DatatableColumn[] };
|
||||
@@ -108,7 +55,7 @@ const props = withDefaults(
|
||||
hasEmptyState?: boolean;
|
||||
}>(),
|
||||
{
|
||||
displayName: (resource: Resource) => resource.name || '',
|
||||
displayName: (resource: ResourceType) => resource.name || '',
|
||||
initialize: async () => {},
|
||||
filters: () => ({ search: '', homeProject: '' }),
|
||||
sortFns: () => ({}),
|
||||
@@ -190,7 +137,7 @@ const filterKeys = computed(() => {
|
||||
return Object.keys(filtersModel.value);
|
||||
});
|
||||
|
||||
const filteredAndSortedResources = computed(() => {
|
||||
const filteredAndSortedResources = computed((): ResourceType[] => {
|
||||
if (props.dontPerformSortingAndFiltering) {
|
||||
return props.resources;
|
||||
}
|
||||
@@ -200,7 +147,11 @@ const filteredAndSortedResources = computed(() => {
|
||||
if (filtersModel.value.homeProject && isSharedResource(resource)) {
|
||||
matches =
|
||||
matches &&
|
||||
!!(resource.homeProject && resource.homeProject.id === filtersModel.value.homeProject);
|
||||
!!(
|
||||
'homeProject' in resource &&
|
||||
resource.homeProject &&
|
||||
resource.homeProject.id === filtersModel.value.homeProject
|
||||
);
|
||||
}
|
||||
|
||||
if (filtersModel.value.search) {
|
||||
@@ -222,16 +173,27 @@ const filteredAndSortedResources = computed(() => {
|
||||
if (!sortableByDate) {
|
||||
return 0;
|
||||
}
|
||||
return props.sortFns.lastUpdated
|
||||
? props.sortFns.lastUpdated(a, b)
|
||||
: new Date(b.updatedAt ?? '').valueOf() - new Date(a.updatedAt ?? '').valueOf();
|
||||
|
||||
if ('updatedAt' in a && 'updatedAt' in b) {
|
||||
return props.sortFns.lastUpdated
|
||||
? props.sortFns.lastUpdated(a, b)
|
||||
: new Date(b.updatedAt ?? '').valueOf() - new Date(a.updatedAt ?? '').valueOf();
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
case 'lastCreated':
|
||||
if (!sortableByDate) {
|
||||
return 0;
|
||||
}
|
||||
return props.sortFns.lastCreated
|
||||
? props.sortFns.lastCreated(a, b)
|
||||
: new Date(b.createdAt ?? '').valueOf() - new Date(a.createdAt ?? '').valueOf();
|
||||
|
||||
if ('createdAt' in a && 'createdAt' in b) {
|
||||
return props.sortFns.lastCreated
|
||||
? props.sortFns.lastCreated(a, b)
|
||||
: new Date(b.createdAt ?? '').valueOf() - new Date(a.createdAt ?? '').valueOf();
|
||||
}
|
||||
|
||||
return 0;
|
||||
case 'nameAsc':
|
||||
return props.sortFns.nameAsc
|
||||
? props.sortFns.nameAsc(a, b)
|
||||
@@ -718,7 +680,7 @@ defineExpose({
|
||||
data-test-id="resources-list"
|
||||
:items="filteredAndSortedResources"
|
||||
:item-size="itemSize()"
|
||||
item-key="id"
|
||||
:item-key="'id'"
|
||||
>
|
||||
<template #default="{ item, updateItemSize }">
|
||||
<slot :data="item" :update-item-size="updateItemSize" />
|
||||
|
||||
Reference in New Issue
Block a user