feat(core): Extend user list to allow expanding the user list to projects (#16314)

Co-authored-by: Guillaume Jacquart <jacquart.guillaume@gmail.com>
Co-authored-by: Csaba Tuncsik <csaba@n8n.io>
Co-authored-by: Csaba Tuncsik <csaba.tuncsik@gmail.com>
This commit is contained in:
Andreas Fitzek
2025-06-18 22:26:50 +02:00
committed by GitHub
parent 701c31cfbc
commit c0d1ff6e4c
20 changed files with 780 additions and 180 deletions

View File

@@ -8,6 +8,7 @@ import type {
IVersionNotificationSettings,
ROLE,
Role,
User,
} from '@n8n/api-types';
import type { Scope } from '@n8n/permissions';
import type { NodeCreatorTag } from '@n8n/design-system';
@@ -53,7 +54,6 @@ import type { Cloud, InstanceUsage } from '@n8n/rest-api-client/api/cloudPlans';
import type {
AI_NODE_CREATOR_VIEW,
CREDENTIAL_EDIT_MODAL_KEY,
SignInType,
TRIGGER_NODE_CREATOR_VIEW,
REGULAR_NODE_CREATOR_VIEW,
AI_OTHERS_NODE_CREATOR_VIEW,
@@ -571,18 +571,10 @@ export type IPersonalizationSurveyVersions =
export type InvitableRoleName = (typeof ROLE)['Member' | 'Admin'];
export interface IUserResponse {
id: string;
firstName?: string;
lastName?: string;
email?: string;
createdAt?: string;
role?: Role;
export interface IUserResponse extends User {
globalScopes?: Scope[];
personalizationAnswers?: IPersonalizationSurveyVersions | null;
isPending: boolean;
signInType?: SignInType;
settings?: IUserSettings;
settings?: IUserSettings | null;
}
export interface CurrentUserResponse extends IUserResponse {

View File

@@ -2,6 +2,8 @@ import type {
LoginRequestDto,
PasswordUpdateRequestDto,
SettingsUpdateRequestDto,
UsersList,
UsersListFilterDto,
UserUpdateRequestDto,
} from '@n8n/api-types';
import type {
@@ -126,8 +128,11 @@ export async function deleteUser(
await makeRestApiRequest(context, 'DELETE', `/users/${id}`, transferId ? { transferId } : {});
}
export async function getUsers(context: IRestApiContext): Promise<IUserResponse[]> {
return await makeRestApiRequest(context, 'GET', '/users');
export async function getUsers(
context: IRestApiContext,
filter?: UsersListFilterDto,
): Promise<UsersList> {
return await makeRestApiRequest(context, 'GET', '/users', filter);
}
export async function getInviteLink(

View File

@@ -28,7 +28,7 @@ export const useNpsSurveyStore = defineStore('npsSurvey', () => {
const currentUserId = ref<string | undefined>();
const promptsData = ref<N8nPrompts | undefined>();
function setupNpsSurveyOnLogin(userId: string, settings?: IUserSettings): void {
function setupNpsSurveyOnLogin(userId: string, settings?: IUserSettings | null): void {
currentUserId.value = userId;
if (settings) {

View File

@@ -36,7 +36,6 @@ function setCurrentUser() {
{
id: CURRENT_USER_ID,
isPending: false,
createdAt: '2023-03-17T14:01:36.432Z',
},
]);
@@ -115,7 +114,6 @@ describe('Posthog store', () => {
const userId = `${CURRENT_INSTANCE_ID}#${CURRENT_USER_ID}`;
expect(window.posthog?.identify).toHaveBeenCalledWith(userId, {
created_at_timestamp: 1679061696432,
instance_id: CURRENT_INSTANCE_ID,
});
});

View File

@@ -3,6 +3,7 @@ import {
type PasswordUpdateRequestDto,
type SettingsUpdateRequestDto,
type UserUpdateRequestDto,
type User,
ROLE,
} from '@n8n/api-types';
import type { UpdateGlobalRolePayload } from '@/api/users';
@@ -119,8 +120,8 @@ export const useUsersStore = defineStore(STORES.USERS, () => {
// Methods
const addUsers = (newUsers: IUserResponse[]) => {
newUsers.forEach((userResponse: IUserResponse) => {
const addUsers = (newUsers: User[]) => {
newUsers.forEach((userResponse) => {
const prevUser = usersById.value[userResponse.id] || {};
const updatedUser = {
...prevUser,
@@ -309,8 +310,8 @@ export const useUsersStore = defineStore(STORES.USERS, () => {
};
const fetchUsers = async () => {
const users = await usersApi.getUsers(rootStore.restApiContext);
addUsers(users);
const { items } = await usersApi.getUsers(rootStore.restApiContext, { take: -1, skip: 0 });
addUsers(items);
};
const inviteUsers = async (params: Array<{ email: string; role: InvitableRoleName }>) => {

View File

@@ -360,7 +360,10 @@ describe('WorkflowsView', () => {
workflowsStore.fetchActiveWorkflows.mockResolvedValue([]);
});
it('should reinitialize on source control pullWorkfolder', async () => {
vi.spyOn(usersApi, 'getUsers').mockResolvedValue([]);
vi.spyOn(usersApi, 'getUsers').mockResolvedValue({
count: 0,
items: [],
});
const userStore = mockedStore(useUsersStore);
const sourceControl = useSourceControlStore();