mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
chore: Clean up welcome sticky feature (no-changelog) (#11987)
This commit is contained in:
@@ -6,10 +6,6 @@ export class WorkflowsConfig {
|
||||
@Env('WORKFLOWS_DEFAULT_NAME')
|
||||
defaultName: string = 'My workflow';
|
||||
|
||||
/** Show onboarding flow in new workflow */
|
||||
@Env('N8N_ONBOARDING_FLOW_DISABLED')
|
||||
onboardingFlowDisabled: boolean = false;
|
||||
|
||||
/** Default option for which workflows may call the current workflow */
|
||||
@Env('N8N_WORKFLOW_CALLER_POLICY_DEFAULT_OPTION')
|
||||
callerPolicyDefaultOption: 'any' | 'none' | 'workflowsFromAList' | 'workflowsFromSameOwner' =
|
||||
|
||||
@@ -150,7 +150,6 @@ describe('GlobalConfig', () => {
|
||||
},
|
||||
workflows: {
|
||||
defaultName: 'My workflow',
|
||||
onboardingFlowDisabled: false,
|
||||
callerPolicyDefaultOption: 'workflowsFromSameOwner',
|
||||
},
|
||||
endpoints: {
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
|
||||
import { In } from '@n8n/typeorm';
|
||||
import { Service } from 'typedi';
|
||||
|
||||
import type { User } from '@/databases/entities/user';
|
||||
import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository';
|
||||
import { WorkflowRepository } from '@/databases/repositories/workflow.repository';
|
||||
import { UserService } from '@/services/user.service';
|
||||
|
||||
@Service()
|
||||
export class UserOnboardingService {
|
||||
constructor(
|
||||
private readonly sharedWorkflowRepository: SharedWorkflowRepository,
|
||||
private readonly workflowRepository: WorkflowRepository,
|
||||
private readonly userService: UserService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Check if user owns more than 15 workflows or more than 2 workflows with at least 2 nodes.
|
||||
* If user does, set flag in its settings.
|
||||
*/
|
||||
async isBelowThreshold(user: User): Promise<boolean> {
|
||||
let belowThreshold = true;
|
||||
const skippedTypes = ['n8n-nodes-base.start', 'n8n-nodes-base.stickyNote'];
|
||||
|
||||
const ownedWorkflowsIds = await this.sharedWorkflowRepository
|
||||
.find({
|
||||
where: {
|
||||
project: {
|
||||
projectRelations: {
|
||||
role: 'project:personalOwner',
|
||||
userId: user.id,
|
||||
},
|
||||
},
|
||||
role: 'workflow:owner',
|
||||
},
|
||||
select: ['workflowId'],
|
||||
})
|
||||
.then((ownedWorkflows) => ownedWorkflows.map(({ workflowId }) => workflowId));
|
||||
|
||||
if (ownedWorkflowsIds.length > 15) {
|
||||
belowThreshold = false;
|
||||
} else {
|
||||
// just fetch workflows' nodes to keep memory footprint low
|
||||
const workflows = await this.workflowRepository.find({
|
||||
where: { id: In(ownedWorkflowsIds) },
|
||||
select: ['nodes'],
|
||||
});
|
||||
|
||||
// valid workflow: 2+ nodes without start node
|
||||
const validWorkflowCount = workflows.reduce((counter, workflow) => {
|
||||
if (counter <= 2 && workflow.nodes.length > 2) {
|
||||
const nodes = workflow.nodes.filter((node) => !skippedTypes.includes(node.type));
|
||||
if (nodes.length >= 2) {
|
||||
return counter + 1;
|
||||
}
|
||||
}
|
||||
return counter;
|
||||
}, 0);
|
||||
|
||||
// more than 2 valid workflows required
|
||||
belowThreshold = validWorkflowCount <= 2;
|
||||
}
|
||||
|
||||
// user is above threshold --> set flag in settings
|
||||
if (!belowThreshold) {
|
||||
void this.userService.updateSettings(user.id, { isOnboarded: true });
|
||||
}
|
||||
|
||||
return belowThreshold;
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,6 @@ import * as ResponseHelper from '@/response-helper';
|
||||
import { NamingService } from '@/services/naming.service';
|
||||
import { ProjectService } from '@/services/project.service';
|
||||
import { TagService } from '@/services/tag.service';
|
||||
import { UserOnboardingService } from '@/services/user-onboarding.service';
|
||||
import { UserManagementMailer } from '@/user-management/email';
|
||||
import * as utils from '@/utils';
|
||||
import * as WorkflowHelpers from '@/workflow-helpers';
|
||||
@@ -55,7 +54,6 @@ export class WorkflowsController {
|
||||
private readonly workflowHistoryService: WorkflowHistoryService,
|
||||
private readonly tagService: TagService,
|
||||
private readonly namingService: NamingService,
|
||||
private readonly userOnboardingService: UserOnboardingService,
|
||||
private readonly workflowRepository: WorkflowRepository,
|
||||
private readonly workflowService: WorkflowService,
|
||||
private readonly workflowExecutionService: WorkflowExecutionService,
|
||||
@@ -213,13 +211,7 @@ export class WorkflowsController {
|
||||
const requestedName = req.query.name ?? this.globalConfig.workflows.defaultName;
|
||||
|
||||
const name = await this.namingService.getUniqueWorkflowName(requestedName);
|
||||
|
||||
const onboardingFlowEnabled =
|
||||
!this.globalConfig.workflows.onboardingFlowDisabled &&
|
||||
!req.user.settings?.isOnboarded &&
|
||||
(await this.userOnboardingService.isBelowThreshold(req.user));
|
||||
|
||||
return { name, onboardingFlowEnabled };
|
||||
return { name };
|
||||
}
|
||||
|
||||
@Get('/from-url')
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 137 KiB |
@@ -250,7 +250,6 @@ export interface IWorkflowToShare extends IWorkflowDataUpdate {
|
||||
|
||||
export interface NewWorkflowResponse {
|
||||
name: string;
|
||||
onboardingFlowEnabled?: boolean;
|
||||
defaultSettings: IWorkflowSettings;
|
||||
}
|
||||
|
||||
@@ -277,7 +276,6 @@ export interface IWorkflowTemplate {
|
||||
|
||||
export interface INewWorkflowData {
|
||||
name: string;
|
||||
onboardingFlowEnabled: boolean;
|
||||
}
|
||||
|
||||
export interface WorkflowMetadata {
|
||||
|
||||
@@ -22,7 +22,6 @@ export async function getNewWorkflow(context: IRestApiContext, data?: IDataObjec
|
||||
);
|
||||
return {
|
||||
name: response.name,
|
||||
onboardingFlowEnabled: response.onboardingFlowEnabled === true,
|
||||
settings: response.defaultSettings,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import type { Workflow } from 'n8n-workflow';
|
||||
import { isNumber, isString } from '@/utils/typeGuards';
|
||||
import type { INodeUi, XYPosition } from '@/Interface';
|
||||
|
||||
import { QUICKSTART_NOTE_NAME } from '@/constants';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
@@ -205,16 +204,7 @@ const onEdit = (edit: boolean) => {
|
||||
|
||||
const onMarkdownClick = (link: HTMLAnchorElement) => {
|
||||
if (link) {
|
||||
const isOnboardingNote = props.name === QUICKSTART_NOTE_NAME;
|
||||
const isWelcomeVideo = link.querySelector('img[alt="n8n quickstart video"]');
|
||||
const type =
|
||||
isOnboardingNote && isWelcomeVideo
|
||||
? 'welcome_video'
|
||||
: isOnboardingNote && link.getAttribute('href') === '/templates'
|
||||
? 'templates'
|
||||
: 'other';
|
||||
|
||||
telemetry.track('User clicked note link', { type });
|
||||
telemetry.track('User clicked note link', { type: 'other' });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
|
||||
import {
|
||||
EnterpriseEditionFeature,
|
||||
FORM_TRIGGER_NODE_TYPE,
|
||||
QUICKSTART_NOTE_NAME,
|
||||
STICKY_NODE_TYPE,
|
||||
UPDATE_WEBHOOK_ID_NODE_TYPES,
|
||||
WEBHOOK_NODE_TYPE,
|
||||
@@ -365,7 +364,6 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||
if (node.type === STICKY_NODE_TYPE) {
|
||||
telemetry.track('User deleted workflow note', {
|
||||
workflow_id: workflowsStore.workflowId,
|
||||
is_welcome_note: node.name === QUICKSTART_NOTE_NAME,
|
||||
});
|
||||
} else {
|
||||
void externalHooks.run('node.deleteNode', { node });
|
||||
|
||||
@@ -35,7 +35,6 @@ export const MIN_WORKFLOW_NAME_LENGTH = 1;
|
||||
export const MAX_WORKFLOW_NAME_LENGTH = 128;
|
||||
export const DUPLICATE_POSTFFIX = ' copy';
|
||||
export const NODE_OUTPUT_DEFAULT_KEY = '_NODE_OUTPUT_DEFAULT_KEY_';
|
||||
export const QUICKSTART_NOTE_NAME = '_QUICKSTART_NOTE_';
|
||||
|
||||
// tags
|
||||
export const MAX_TAG_NAME_LENGTH = 24;
|
||||
|
||||
@@ -1381,7 +1381,6 @@
|
||||
"nodeWebhooks.webhookUrls": "Webhook URLs",
|
||||
"nodeWebhooks.webhookUrls.formTrigger": "Form URLs",
|
||||
"nodeWebhooks.webhookUrls.chatTrigger": "Chat URL",
|
||||
"onboardingWorkflow.stickyContent": "## 👇 Get started faster \nLightning tour of the key concepts [4 min] \n\n[](https://www.youtube.com/watch?v=1MwSoB0gnM4)",
|
||||
"openWorkflow.workflowImportError": "Could not import workflow",
|
||||
"openWorkflow.workflowNotFoundError": "Could not find workflow",
|
||||
"parameterInput.expressionResult": "e.g. {result}",
|
||||
|
||||
@@ -468,7 +468,6 @@ describe('useWorkflowsStore', () => {
|
||||
const expectedName = `${name}${DUPLICATE_POSTFFIX}`;
|
||||
vi.mocked(workflowsApi).getNewWorkflow.mockResolvedValue({
|
||||
name: expectedName,
|
||||
onboardingFlowEnabled: false,
|
||||
settings: {} as IWorkflowSettings,
|
||||
});
|
||||
const newName = await workflowsStore.getDuplicateCurrentWorkflowName(name);
|
||||
|
||||
@@ -494,7 +494,6 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
||||
async function getNewWorkflowData(name?: string, projectId?: string): Promise<INewWorkflowData> {
|
||||
let workflowData = {
|
||||
name: '',
|
||||
onboardingFlowEnabled: false,
|
||||
settings: { ...defaults.settings },
|
||||
};
|
||||
try {
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
MODAL_CANCEL,
|
||||
MODAL_CONFIRM,
|
||||
PLACEHOLDER_EMPTY_WORKFLOW_ID,
|
||||
QUICKSTART_NOTE_NAME,
|
||||
START_NODE_TYPE,
|
||||
STICKY_NODE_TYPE,
|
||||
VIEWS,
|
||||
@@ -3581,7 +3580,6 @@ export default defineComponent({
|
||||
if (node.type === STICKY_NODE_TYPE) {
|
||||
this.$telemetry.track('User deleted workflow note', {
|
||||
workflow_id: this.workflowsStore.workflowId,
|
||||
is_welcome_note: node.name === QUICKSTART_NOTE_NAME,
|
||||
});
|
||||
} else {
|
||||
void this.externalHooks.run('node.deleteNode', { node });
|
||||
|
||||
Reference in New Issue
Block a user