mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-10 06:34:05 -08:00
fix(editor): Remove "move" action from workflow and credential on community plan (#10057)
This commit is contained in:
parent
f876f9ec8b
commit
5a9a2713b4
|
@ -114,6 +114,8 @@ export class E2EController {
|
||||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||||
license.getFeatureValue<NumericLicenseFeature> = (feature: NumericLicenseFeature) =>
|
license.getFeatureValue<NumericLicenseFeature> = (feature: NumericLicenseFeature) =>
|
||||||
this.numericFeatures[feature] ?? UNLIMITED_LICENSE_QUOTA;
|
this.numericFeatures[feature] ?? UNLIMITED_LICENSE_QUOTA;
|
||||||
|
|
||||||
|
license.getPlanName = () => 'Enterprise';
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/reset', { skipAuth: true })
|
@Post('/reset', { skipAuth: true })
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import { setActivePinia } from 'pinia';
|
import { setActivePinia } from 'pinia';
|
||||||
|
import { within } from '@testing-library/vue';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
import { createTestingPinia } from '@pinia/testing';
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
import { createComponentRenderer } from '@/__tests__/render';
|
import { createComponentRenderer } from '@/__tests__/render';
|
||||||
import CredentialCard from '@/components/CredentialCard.vue';
|
import CredentialCard from '@/components/CredentialCard.vue';
|
||||||
import type { ICredentialsResponse } from '@/Interface';
|
import type { ICredentialsResponse } from '@/Interface';
|
||||||
import type { ProjectSharingData } from '@/types/projects.types';
|
import type { ProjectSharingData } from '@/types/projects.types';
|
||||||
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
|
|
||||||
const renderComponent = createComponentRenderer(CredentialCard);
|
const renderComponent = createComponentRenderer(CredentialCard);
|
||||||
|
|
||||||
|
@ -19,9 +22,12 @@ const createCredential = (overrides = {}): ICredentialsResponse => ({
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('CredentialCard', () => {
|
describe('CredentialCard', () => {
|
||||||
|
let settingsStore: ReturnType<typeof useSettingsStore>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const pinia = createTestingPinia();
|
const pinia = createTestingPinia();
|
||||||
setActivePinia(pinia);
|
setActivePinia(pinia);
|
||||||
|
settingsStore = useSettingsStore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render name and home project name', () => {
|
it('should render name and home project name', () => {
|
||||||
|
@ -55,4 +61,28 @@ describe('CredentialCard', () => {
|
||||||
expect(heading).toHaveTextContent(data.name);
|
expect(heading).toHaveTextContent(data.name);
|
||||||
expect(badge).toHaveTextContent('John Doe');
|
expect(badge).toHaveTextContent('John Doe');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should show Move action only if there is resource permission and not on community plan', async () => {
|
||||||
|
vi.spyOn(settingsStore, 'isCommunityPlan', 'get').mockReturnValue(false);
|
||||||
|
|
||||||
|
const data = createCredential({
|
||||||
|
scopes: ['credential:move'],
|
||||||
|
});
|
||||||
|
const { getByTestId } = renderComponent({ props: { data } });
|
||||||
|
const cardActions = getByTestId('credential-card-actions');
|
||||||
|
|
||||||
|
expect(cardActions).toBeInTheDocument();
|
||||||
|
|
||||||
|
const cardActionsOpener = within(cardActions).getByRole('button');
|
||||||
|
expect(cardActionsOpener).toBeInTheDocument();
|
||||||
|
|
||||||
|
const controllingId = cardActionsOpener.getAttribute('aria-controls');
|
||||||
|
|
||||||
|
await userEvent.click(cardActions);
|
||||||
|
const actions = document.querySelector(`#${controllingId}`);
|
||||||
|
if (!actions) {
|
||||||
|
throw new Error('Actions menu not found');
|
||||||
|
}
|
||||||
|
expect(actions).toHaveTextContent('Move');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { useProjectsStore } from '@/stores/projects.store';
|
||||||
import ProjectCardBadge from '@/components/Projects/ProjectCardBadge.vue';
|
import ProjectCardBadge from '@/components/Projects/ProjectCardBadge.vue';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import { ResourceType } from '@/utils/projects.utils';
|
import { ResourceType } from '@/utils/projects.utils';
|
||||||
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
|
|
||||||
const CREDENTIAL_LIST_ITEM_ACTIONS = {
|
const CREDENTIAL_LIST_ITEM_ACTIONS = {
|
||||||
OPEN: 'open',
|
OPEN: 'open',
|
||||||
|
@ -45,6 +46,7 @@ const message = useMessage();
|
||||||
const uiStore = useUIStore();
|
const uiStore = useUIStore();
|
||||||
const credentialsStore = useCredentialsStore();
|
const credentialsStore = useCredentialsStore();
|
||||||
const projectsStore = useProjectsStore();
|
const projectsStore = useProjectsStore();
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
|
||||||
const resourceTypeLabel = computed(() => locale.baseText('generic.credential').toLowerCase());
|
const resourceTypeLabel = computed(() => locale.baseText('generic.credential').toLowerCase());
|
||||||
const credentialType = computed(() => credentialsStore.getCredentialTypeByName(props.data.type));
|
const credentialType = computed(() => credentialsStore.getCredentialTypeByName(props.data.type));
|
||||||
|
@ -64,7 +66,7 @@ const actions = computed(() => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (credentialPermissions.value.move) {
|
if (credentialPermissions.value.move && !settingsStore.isCommunityPlan) {
|
||||||
items.push({
|
items.push({
|
||||||
label: locale.baseText('credentials.item.move'),
|
label: locale.baseText('credentials.item.move'),
|
||||||
value: CREDENTIAL_LIST_ITEM_ACTIONS.MOVE,
|
value: CREDENTIAL_LIST_ITEM_ACTIONS.MOVE,
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { VIEWS } from '@/constants';
|
||||||
import WorkflowCard from '@/components/WorkflowCard.vue';
|
import WorkflowCard from '@/components/WorkflowCard.vue';
|
||||||
import type { IWorkflowDb } from '@/Interface';
|
import type { IWorkflowDb } from '@/Interface';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
|
|
||||||
vi.mock('vue-router', () => {
|
vi.mock('vue-router', () => {
|
||||||
const push = vi.fn();
|
const push = vi.fn();
|
||||||
|
@ -39,12 +40,14 @@ describe('WorkflowCard', () => {
|
||||||
let pinia: ReturnType<typeof createPinia>;
|
let pinia: ReturnType<typeof createPinia>;
|
||||||
let windowOpenSpy: MockInstance;
|
let windowOpenSpy: MockInstance;
|
||||||
let router: ReturnType<typeof useRouter>;
|
let router: ReturnType<typeof useRouter>;
|
||||||
|
let settingsStore: ReturnType<typeof useSettingsStore>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
pinia = createPinia();
|
pinia = createPinia();
|
||||||
setActivePinia(pinia);
|
setActivePinia(pinia);
|
||||||
router = useRouter();
|
router = useRouter();
|
||||||
windowOpenSpy = vi.spyOn(window, 'open');
|
settingsStore = useSettingsStore();
|
||||||
|
windowOpenSpy = vi.spyOn(window, 'open').mockImplementation(() => null);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
@ -95,10 +98,11 @@ describe('WorkflowCard', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const actions = document.querySelector(`#${controllingId}`);
|
const actions = document.querySelector(`#${controllingId}`);
|
||||||
await waitFor(() => {
|
if (!actions) {
|
||||||
expect(actions).toBeInTheDocument();
|
throw new Error('Actions menu not found');
|
||||||
});
|
}
|
||||||
await userEvent.click(actions!.querySelectorAll('li')[0]);
|
await userEvent.click(actions.querySelectorAll('li')[0]);
|
||||||
|
expect(actions).not.toHaveTextContent('Move');
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(router.push).toHaveBeenCalledWith({
|
expect(router.push).toHaveBeenCalledWith({
|
||||||
name: VIEWS.WORKFLOW,
|
name: VIEWS.WORKFLOW,
|
||||||
|
@ -138,4 +142,28 @@ describe('WorkflowCard', () => {
|
||||||
expect(heading).toHaveTextContent(data.name);
|
expect(heading).toHaveTextContent(data.name);
|
||||||
expect(badge).toHaveTextContent('John Doe');
|
expect(badge).toHaveTextContent('John Doe');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should show Move action only if there is resource permission and not on community plan', async () => {
|
||||||
|
vi.spyOn(settingsStore, 'isCommunityPlan', 'get').mockReturnValue(false);
|
||||||
|
|
||||||
|
const data = createWorkflow({
|
||||||
|
scopes: ['workflow:move'],
|
||||||
|
});
|
||||||
|
const { getByTestId } = renderComponent({ props: { data } });
|
||||||
|
const cardActions = getByTestId('workflow-card-actions');
|
||||||
|
|
||||||
|
expect(cardActions).toBeInTheDocument();
|
||||||
|
|
||||||
|
const cardActionsOpener = within(cardActions).getByRole('button');
|
||||||
|
expect(cardActionsOpener).toBeInTheDocument();
|
||||||
|
|
||||||
|
const controllingId = cardActionsOpener.getAttribute('aria-controls');
|
||||||
|
|
||||||
|
await userEvent.click(cardActions);
|
||||||
|
const actions = document.querySelector(`#${controllingId}`);
|
||||||
|
if (!actions) {
|
||||||
|
throw new Error('Actions menu not found');
|
||||||
|
}
|
||||||
|
expect(actions).toHaveTextContent('Move');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -95,7 +95,7 @@ const actions = computed(() => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (workflowPermissions.value.move) {
|
if (workflowPermissions.value.move && !settingsStore.isCommunityPlan) {
|
||||||
items.push({
|
items.push({
|
||||||
label: locale.baseText('workflows.item.move'),
|
label: locale.baseText('workflows.item.move'),
|
||||||
value: WORKFLOW_LIST_ITEM_ACTIONS.MOVE,
|
value: WORKFLOW_LIST_ITEM_ACTIONS.MOVE,
|
||||||
|
|
|
@ -58,9 +58,8 @@ export function useDebugInfo() {
|
||||||
: store.databaseType,
|
: store.databaseType,
|
||||||
executionMode: store.isQueueModeEnabled ? 'scaling' : 'regular',
|
executionMode: store.isQueueModeEnabled ? 'scaling' : 'regular',
|
||||||
concurrency: store.settings.concurrency,
|
concurrency: store.settings.concurrency,
|
||||||
license:
|
license: store.isCommunityPlan
|
||||||
store.planName === 'Community'
|
? 'community'
|
||||||
? (store.planName.toLowerCase() as 'community')
|
|
||||||
: store.settings.license.environment === 'production'
|
: store.settings.license.environment === 'production'
|
||||||
? 'enterprise (production)'
|
? 'enterprise (production)'
|
||||||
: 'enterprise (sandbox)',
|
: 'enterprise (sandbox)',
|
||||||
|
|
|
@ -77,10 +77,13 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
|
||||||
return this.settings.databaseType;
|
return this.settings.databaseType;
|
||||||
},
|
},
|
||||||
planName(): string {
|
planName(): string {
|
||||||
return this.settings.license.planName ?? 'Community';
|
return this.settings.license?.planName ?? 'Community';
|
||||||
|
},
|
||||||
|
isCommunityPlan(): boolean {
|
||||||
|
return this.planName.toLowerCase() === 'community';
|
||||||
},
|
},
|
||||||
consumerId(): string {
|
consumerId(): string {
|
||||||
return this.settings.license.consumerId;
|
return this.settings.license?.consumerId ?? 'unknown';
|
||||||
},
|
},
|
||||||
binaryDataMode(): 'default' | 'filesystem' | 's3' {
|
binaryDataMode(): 'default' | 'filesystem' | 's3' {
|
||||||
return this.settings.binaryDataMode;
|
return this.settings.binaryDataMode;
|
||||||
|
|
Loading…
Reference in a new issue