Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(editor): Remove isOwner from IUser interface #8888

Merged
merged 4 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions packages/editor-ui/src/Interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import type {
REGULAR_NODE_CREATOR_VIEW,
AI_OTHERS_NODE_CREATOR_VIEW,
VIEWS,
} from './constants';
ROLE,
} from '@/constants';
import type { IMenuItem } from 'n8n-design-system';
import {
type GenericValue,
Expand Down Expand Up @@ -688,9 +689,9 @@ export type IPersonalizationSurveyVersions =
| IPersonalizationSurveyAnswersV2
| IPersonalizationSurveyAnswersV3;

export type IRole = 'default' | 'global:owner' | 'global:member' | 'global:admin';

export type InvitableRoleName = 'global:member' | 'global:admin';
export type Roles = typeof ROLE;
export type IRole = Roles[keyof Roles];
export type InvitableRoleName = Roles['Member' | 'Admin'];

export interface IUserResponse {
id: string;
Expand All @@ -714,7 +715,6 @@ export interface IUser extends IUserResponse {
isDefaultUser: boolean;
isPendingUser: boolean;
hasRecoveryCodesLeft: boolean;
isOwner: boolean;
inviteAcceptUrl?: string;
fullName?: string;
createdAt?: string;
Expand Down
5 changes: 4 additions & 1 deletion packages/editor-ui/src/__tests__/permissions.spec.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { parsePermissionsTable } from '@/permissions';
import type { IUser } from '@/Interface';
import { ROLE } from '@/constants';

describe('parsePermissionsTable()', () => {
const user: IUser = {
id: '1',
firstName: 'John',
lastName: 'Doe',
isDefaultUser: false,
isOwner: true,
isPending: false,
isPendingUser: false,
mfaEnabled: false,
hasRecoveryCodesLeft: false,
role: ROLE.Owner,
};

it('should return permissions object using generic permissions table', () => {
Expand Down
9 changes: 6 additions & 3 deletions packages/editor-ui/src/__tests__/server/factories/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ export const userFactory = Factory.extend<IUser>({
isDefaultUser() {
return false;
},
isOwner() {
return false;
},
isPending() {
return false;
},
Expand All @@ -28,4 +25,10 @@ export const userFactory = Factory.extend<IUser>({
signInType(): SignInType {
return SignInType.EMAIL;
},
mfaEnabled() {
return false;
},
hasRecoveryCodesLeft() {
return false;
},
});
4 changes: 2 additions & 2 deletions packages/editor-ui/src/api/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import type {
CurrentUserResponse,
IPersonalizationLatestVersion,
IRestApiContext,
IRole,
IUserResponse,
InvitableRoleName,
} from '@/Interface';
import type { IDataObject } from 'n8n-workflow';
import { makeRestApiRequest } from '@/utils/apiUtils';
Expand Down Expand Up @@ -157,7 +157,7 @@ export async function submitPersonalizationSurvey(

export interface UpdateGlobalRolePayload {
id: string;
newRoleName: Exclude<IRole, 'default' | 'global:owner'>;
newRoleName: InvitableRoleName;
}

export async function updateGlobalRole(
Expand Down
3 changes: 2 additions & 1 deletion packages/editor-ui/src/api/workflow-webhooks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { IOnboardingCallPrompt, IUser } from '@/Interface';
import { get, post } from '@/utils/apiUtils';
import { isUserGlobalOwner } from '@/utils/userUtils';

const N8N_API_BASE_URL = 'https://api.n8n.io/api';
const ONBOARDING_PROMPTS_ENDPOINT = '/prompts/onboarding';
Expand All @@ -12,7 +13,7 @@ export async function fetchNextOnboardingPrompt(
return await get(N8N_API_BASE_URL, ONBOARDING_PROMPTS_ENDPOINT, {
instance_id: instanceId,
user_id: `${instanceId}#${currentUser.id}`,
is_owner: currentUser.isOwner ?? false,
is_owner: isUserGlobalOwner(currentUser),
survey_results: currentUser.personalizationAnswers,
});
}
Expand Down
8 changes: 6 additions & 2 deletions packages/editor-ui/src/components/InviteUsersModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,12 @@ import { mapStores } from 'pinia';
import { useToast } from '@/composables/useToast';
import Modal from './Modal.vue';
import type { IFormInputs, IInviteResponse, IUser } from '@/Interface';
import { ROLE } from '@/utils/userUtils';
import { EnterpriseEditionFeature, VALID_EMAIL_REGEX, INVITE_USER_MODAL_KEY } from '@/constants';
import {
EnterpriseEditionFeature,
VALID_EMAIL_REGEX,
INVITE_USER_MODAL_KEY,
ROLE,
} from '@/constants';
import { useUsersStore } from '@/stores/users.store';
import { useSettingsStore } from '@/stores/settings.store';
import { useUIStore } from '@/stores/ui.store';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
import { useCollaborationStore } from '@/stores/collaboration.store';
import { onBeforeUnmount, onMounted, computed, ref } from 'vue';
import { TIME } from '@/constants';
import { isUserGlobalOwner } from '@/utils/userUtils';

const collaborationStore = useCollaborationStore();
const usersStore = useUsersStore();
Expand All @@ -16,7 +17,7 @@ const activeUsersSorted = computed(() => {
const currentWorkflowUsers = (collaborationStore.getUsersForCurrentWorkflow ?? []).map(
(userInfo) => userInfo.user,
);
const owner = currentWorkflowUsers.find((user) => user.role === 'global:owner');
const owner = currentWorkflowUsers.find(isUserGlobalOwner);
return {
defaultGroup: owner
? [owner, ...currentWorkflowUsers.filter((user) => user.id !== owner.id)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { merge } from 'lodash-es';
import userEvent from '@testing-library/user-event';

import { SETTINGS_STORE_DEFAULT_STATE } from '@/__tests__/utils';
import { STORES } from '@/constants';
import { ROLE, STORES } from '@/constants';

import { createTestingPinia } from '@pinia/testing';
import BannerStack from '@/components/banners/BannerStack.vue';
Expand All @@ -26,11 +26,11 @@ const initialState = {
users: {
'aaa-bbb': {
id: 'aaa-bbb',
role: 'global:owner',
role: ROLE.Owner,
},
'bbb-bbb': {
id: 'bbb-bbb',
role: 'global:member',
role: ROLE.Member,
},
},
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { merge } from 'lodash-es';
import { SETTINGS_STORE_DEFAULT_STATE, waitAllPromises } from '@/__tests__/utils';
import { STORES } from '@/constants';
import { ROLE, STORES } from '@/constants';
import { createTestingPinia } from '@pinia/testing';
import { useUIStore } from '@/stores/ui.store';
import CollaborationPane from '@/components//MainHeader/CollaborationPane.vue';
Expand All @@ -13,10 +13,9 @@ const OWNER_USER = {
email: 'owner@user.com',
firstName: 'Owner',
lastName: 'User',
role: 'global:owner',
role: ROLE.Owner,
disabled: false,
isPending: false,
isOwner: true,
fullName: 'Owner User',
};

Expand All @@ -26,10 +25,9 @@ const MEMBER_USER = {
email: 'member@user.com',
firstName: 'Member',
lastName: 'User',
role: 'global:member',
role: ROLE.Member,
disabled: false,
isPending: false,
isOwner: false,
fullName: 'Member User',
};

Expand All @@ -39,10 +37,9 @@ const MEMBER_USER_2 = {
email: 'member2@user.com',
firstName: 'Another Member',
lastName: 'User',
role: 'global:member',
role: ROLE.Member,
disabled: false,
isPending: false,
isOwner: false,
fullName: 'Another Member User',
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import PersonalizationModal from '@/components/PersonalizationModal.vue';
import { createTestingPinia } from '@pinia/testing';
import userEvent from '@testing-library/user-event';
import { PERSONALIZATION_MODAL_KEY, STORES, VIEWS } from '@/constants';
import { PERSONALIZATION_MODAL_KEY, ROLE, STORES, VIEWS } from '@/constants';
import { retry } from '@/__tests__/utils';
import { createComponentRenderer } from '@/__tests__/render';
import { fireEvent } from '@testing-library/vue';
Expand Down Expand Up @@ -31,7 +31,7 @@ const pinia = createTestingPinia({
isDefaultUser: false,
isPendingUser: false,
hasRecoveryCodesLeft: true,
isOwner: true,
role: ROLE.Owner,
mfaEnabled: false,
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { render } from '@testing-library/vue';
import V1Banner from '../V1Banner.vue';
import { createPinia, setActivePinia } from 'pinia';
import { useUsersStore } from '@/stores/users.store';
import { ROLE } from '@/constants';
import type { IUser } from '@/Interface';

describe('V1 Banner', () => {
let pinia: ReturnType<typeof createPinia>;
Expand All @@ -22,8 +24,8 @@ describe('V1 Banner', () => {

it('should render banner with dismiss call if user is owner', () => {
vi.spyOn(usersStore, 'currentUser', 'get').mockReturnValue({
role: 'global:owner',
});
role: ROLE.Owner,
} as IUser);

const { container } = render(V1Banner);
expect(container).toMatchSnapshot();
Expand Down
7 changes: 7 additions & 0 deletions packages/editor-ui/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -752,3 +752,10 @@ export const TEMPLATES_URLS = {
BASE_WEBSITE_URL: 'https://n8n.io/workflows',
UTM_QUERY: 'utm_source=n8n_app&utm_medium=template_library',
};

export const ROLE = {
Owner: 'global:owner',
Member: 'global:member',
Admin: 'global:admin',
Default: 'default', // default user with no email when setting up instance
} as const;
3 changes: 2 additions & 1 deletion packages/editor-ui/src/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { IUser, ICredentialsResponse, IWorkflowDb } from '@/Interface';
import { EnterpriseEditionFeature, PLACEHOLDER_EMPTY_WORKFLOW_ID } from '@/constants';
import { useSettingsStore } from '@/stores/settings.store';
import { hasPermission } from './rbac/permissions';
import { isUserGlobalOwner } from './utils/userUtils';

/**
* Old permissions implementation
Expand Down Expand Up @@ -43,7 +44,7 @@ export const parsePermissionsTable = (
table: IPermissionsTable,
): IPermissions => {
const genericTable: IPermissionsTable = [
{ name: UserRole.InstanceOwner, test: () => !!user?.isOwner },
{ name: UserRole.InstanceOwner, test: () => (user ? isUserGlobalOwner(user) : false) },
];

return [...genericTable, ...table].reduce(
Expand Down
4 changes: 2 additions & 2 deletions packages/editor-ui/src/rbac/checks/__tests__/hasRole.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useUsersStore } from '@/stores/users.store';
import { hasRole } from '@/rbac/checks';
import { ROLE } from '@/utils/userUtils';
import { ROLE } from '@/constants';

vi.mock('@/stores/users.store', () => ({
useUsersStore: vi.fn(),
Expand All @@ -12,7 +12,7 @@ describe('Checks', () => {
vi.mocked(useUsersStore).mockReturnValue({
currentUser: {
isDefaultUser: false,
role: 'global:owner',
role: ROLE.Owner,
},
} as ReturnType<typeof useUsersStore>);

Expand Down
2 changes: 1 addition & 1 deletion packages/editor-ui/src/rbac/checks/hasRole.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useUsersStore } from '@/stores/users.store';
import type { RBACPermissionCheck, RolePermissionOptions } from '@/types/rbac';
import { ROLE } from '@/utils/userUtils';
import { ROLE } from '@/constants';
import type { IRole } from '@/Interface';

export const hasRole: RBACPermissionCheck<RolePermissionOptions> = (checkRoles) => {
Expand Down
7 changes: 3 additions & 4 deletions packages/editor-ui/src/rbac/middleware/__tests__/role.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { roleMiddleware } from '@/rbac/middleware/role';
import { useUsersStore } from '@/stores/users.store';
import { ROLE } from '@/utils/userUtils';
import type { IUser } from '@/Interface';
import type { RouteLocationNormalized } from 'vue-router';
import { VIEWS } from '@/constants';
import { VIEWS, ROLE } from '@/constants';

vi.mock('@/stores/users.store', () => ({
useUsersStore: vi.fn(),
Expand All @@ -15,7 +14,7 @@ describe('Middleware', () => {
vi.mocked(useUsersStore).mockReturnValue({
currentUser: {
isDefaultUser: false,
role: 'global:owner',
role: ROLE.Owner,
} as IUser,
} as ReturnType<typeof useUsersStore>);

Expand Down Expand Up @@ -54,7 +53,7 @@ describe('Middleware', () => {
vi.mocked(useUsersStore).mockReturnValue({
currentUser: {
isDefaultUser: false,
role: 'global:owner',
role: ROLE.Owner,
} as IUser,
} as ReturnType<typeof useUsersStore>);

Expand Down
11 changes: 6 additions & 5 deletions packages/editor-ui/src/stores/__tests__/ui.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
getNotTrialingUserResponse,
} from './utils/cloudStoreUtils';
import type { IRole } from '@/Interface';
import { ROLE } from '@/constants';

let uiStore: ReturnType<typeof useUIStore>;
let settingsStore: ReturnType<typeof useSettingsStore>;
Expand All @@ -33,7 +34,7 @@ function setUser(role: IRole) {
}

function setupOwnerAndCloudDeployment() {
setUser('global:owner');
setUser(ROLE.Owner);
settingsStore.setSettings(
merge({}, SETTINGS_STORE_DEFAULT_STATE.settings, {
n8nMetadata: {
Expand Down Expand Up @@ -75,27 +76,27 @@ describe('UI store', () => {
[
'default',
'production',
'global:owner',
ROLE.Owner,
'https://n8n.io/pricing?utm_campaign=utm-test-campaign&source=test_source',
],
[
'default',
'development',
'global:owner',
ROLE.Owner,
'https://n8n.io/pricing?utm_campaign=utm-test-campaign&source=test_source',
],
[
'cloud',
'production',
'global:owner',
ROLE.Owner,
`https://app.n8n.cloud/login?code=123&returnPath=${encodeURIComponent(
'/account/change-plan',
)}&utm_campaign=utm-test-campaign&source=test_source`,
],
[
'cloud',
'production',
'global:member',
ROLE.Member,
'https://n8n.io/pricing?utm_campaign=utm-test-campaign&source=test_source',
],
])(
Expand Down
8 changes: 3 additions & 5 deletions packages/editor-ui/src/stores/cloudPlan.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,9 @@ export const useCloudPlanStore = defineStore(STORES.CLOUD_PLAN, () => {
const getUserCloudAccount = async () => {
if (!hasCloudPlan.value) throw new Error('User does not have a cloud plan');
try {
if (hasPermission(['instanceOwner'])) {
await usersStore.fetchUserCloudAccount();
if (!usersStore.currentUserCloudInfo?.confirmed && !userIsTrialing.value) {
useUIStore().pushBannerToStack('EMAIL_CONFIRMATION');
}
await usersStore.fetchUserCloudAccount();
if (!usersStore.currentUserCloudInfo?.confirmed && !userIsTrialing.value) {
useUIStore().pushBannerToStack('EMAIL_CONFIRMATION');
}
} catch (error) {
throw new Error(error.message);
Expand Down
1 change: 0 additions & 1 deletion packages/editor-ui/src/stores/ui.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,6 @@ export const useUIStore = defineStore(STORES.UI, {
let linkUrl = '';

const searchParams = new URLSearchParams();
const { isInstanceOwner } = useUsersStore();

if (deploymentType === 'cloud' && hasPermission(['instanceOwner'])) {
const adminPanelHost = new URL(window.location.href).host.split('.').slice(1).join('.');
Expand Down
Loading
Loading