Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion packages/frontend/@n8n/i18n/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2354,8 +2354,9 @@
"projectRoles.workflow:read": "View",
"projectRoles.workflow:read.tooltip": "View workflows within the project",
"projectRoles.workflow:execute": "Execute",
"projectRoles.workflow:execute.tooltip": "Execute workflows within the project",
"projectRoles.workflow:update": "Edit",
"projectRoles.workflow:update.tooltip": "Edit workflow content and execute workflows",
"projectRoles.workflow:update.tooltip": "Edit workflow content",
"projectRoles.workflow:create": "Create",
"projectRoles.workflow:create.tooltip": "Create new workflows",
"projectRoles.workflow:share": "Share",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,116 @@ describe('ProjectRoleView', () => {
});
});

describe('workflow:execute scope dependency', () => {
it('should render workflow:execute checkbox in the UI', async () => {
const { getByTestId } = renderComponent();

await waitFor(() =>
expect(getByTestId('scope-checkbox-workflow:execute')).toBeInTheDocument(),
);
});

it('should auto-check workflow:read when workflow:execute is checked and workflow:read is unchecked', async () => {
const { getByTestId } = renderComponent();

await waitFor(() =>
expect(getByTestId('scope-checkbox-workflow:execute')).toBeInTheDocument(),
);

const executeCheckbox = getByTestId('scope-checkbox-workflow:execute');
const readCheckbox = getByTestId('scope-checkbox-workflow:read');

// workflow:read starts checked (it's in defaultScopes), uncheck it first
await userEvent.click(readCheckbox);
expect(readCheckbox).not.toBeChecked();
expect(executeCheckbox).not.toBeChecked();

// Now check workflow:execute — should auto-check workflow:read
await userEvent.click(executeCheckbox);
expect(executeCheckbox).toBeChecked();
expect(readCheckbox).toBeChecked();
});

it('should not double-add workflow:read when workflow:execute is checked and workflow:read is already checked', async () => {
const { getByTestId } = renderComponent();

await waitFor(() =>
expect(getByTestId('scope-checkbox-workflow:execute')).toBeInTheDocument(),
);

const executeCheckbox = getByTestId('scope-checkbox-workflow:execute');
const readCheckbox = getByTestId('scope-checkbox-workflow:read');

// workflow:read is already checked (it's in defaultScopes)
expect(readCheckbox).toBeChecked();

await userEvent.click(executeCheckbox);
expect(executeCheckbox).toBeChecked();
// workflow:read should still be checked, not toggled off
expect(readCheckbox).toBeChecked();
});

it('should auto-uncheck workflow:execute when workflow:read is unchecked', async () => {
const { getByTestId } = renderComponent();

await waitFor(() =>
expect(getByTestId('scope-checkbox-workflow:execute')).toBeInTheDocument(),
);

const executeCheckbox = getByTestId('scope-checkbox-workflow:execute');
const readCheckbox = getByTestId('scope-checkbox-workflow:read');

// First enable workflow:execute
await userEvent.click(executeCheckbox);
expect(executeCheckbox).toBeChecked();
expect(readCheckbox).toBeChecked();

// Uncheck workflow:read — should auto-uncheck workflow:execute
await userEvent.click(readCheckbox);
expect(readCheckbox).not.toBeChecked();
expect(executeCheckbox).not.toBeChecked();
});

it('should not affect workflow:execute when workflow:read is unchecked and workflow:execute was not checked', async () => {
const { getByTestId } = renderComponent();

await waitFor(() =>
expect(getByTestId('scope-checkbox-workflow:execute')).toBeInTheDocument(),
);

const executeCheckbox = getByTestId('scope-checkbox-workflow:execute');
const readCheckbox = getByTestId('scope-checkbox-workflow:read');

// workflow:execute is not checked; uncheck workflow:read
expect(executeCheckbox).not.toBeChecked();
await userEvent.click(readCheckbox);
expect(readCheckbox).not.toBeChecked();
expect(executeCheckbox).not.toBeChecked();
});

it('should NOT auto-toggle workflow:execute when workflow:update is toggled', async () => {
const { getByTestId } = renderComponent();

await waitFor(() =>
expect(getByTestId('scope-checkbox-workflow:update')).toBeInTheDocument(),
);

const updateCheckbox = getByTestId('scope-checkbox-workflow:update');
const executeCheckbox = getByTestId('scope-checkbox-workflow:execute');

expect(executeCheckbox).not.toBeChecked();

await userEvent.click(updateCheckbox);
expect(updateCheckbox).toBeChecked();
// workflow:execute must remain unchanged
expect(executeCheckbox).not.toBeChecked();

await userEvent.click(updateCheckbox);
expect(updateCheckbox).not.toBeChecked();
expect(executeCheckbox).not.toBeChecked();
});
});

describe('External Secrets Scopes', () => {
it('should not render externalSecretsProvider scope type when forProjects is off', () => {
const { queryByText } = renderComponent();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ const scopes = SCOPES;

function toggleScope(scope: string) {
const index = form.value.scopes.indexOf(scope);
const isBeingAdded = index === -1;

if (index !== -1) {
form.value.scopes.splice(index, 1);
} else {
Expand All @@ -155,8 +157,17 @@ function toggleScope(scope: string) {
toggleScope(scope.replace(':read', ':list'));
}

if (scope === 'workflow:update') {
toggleScope('workflow:execute');
// Dependency: workflow:execute requires workflow:read
if (scope === 'workflow:execute' && isBeingAdded) {
if (!form.value.scopes.includes('workflow:read')) {
toggleScope('workflow:read');
}
}

if (scope === 'workflow:read' && !isBeingAdded) {
if (form.value.scopes.includes('workflow:execute')) {
toggleScope('workflow:execute');
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const UI_OPERATIONS = {
folder: ['read', 'update', 'create', 'move', 'delete'],
workflow: [
'read',
'execute',
'update',
'create',
'publish',
Expand Down
Loading