From 7635131bd396252f51d29e7407099eafa92a304f Mon Sep 17 00:00:00 2001 From: Stephen Wright Date: Mon, 11 May 2026 13:02:59 +0100 Subject: [PATCH] feat(editor): Show locked state and permission notice on data redaction workflow settings (#30022) Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> --- .../frontend/@n8n/i18n/src/locales/en.json | 4 + .../app/components/RedactionMembersModal.vue | 114 +++++++++++++ .../app/components/WorkflowSettings.test.ts | 19 ++- .../src/app/components/WorkflowSettings.vue | 160 +++++++++++++----- 4 files changed, 244 insertions(+), 53 deletions(-) create mode 100644 packages/frontend/editor-ui/src/app/components/RedactionMembersModal.vue diff --git a/packages/frontend/@n8n/i18n/src/locales/en.json b/packages/frontend/@n8n/i18n/src/locales/en.json index 157b27fef37..b709c60ff25 100644 --- a/packages/frontend/@n8n/i18n/src/locales/en.json +++ b/packages/frontend/@n8n/i18n/src/locales/en.json @@ -3816,6 +3816,10 @@ "workflowSettings.redactionOptions.redact": "Redact", "workflowSettings.helpTexts.redactProductionData": "Controls whether execution data from production (non-manually triggered) executions is redacted.", "workflowSettings.helpTexts.redactManualData": "Controls whether execution data from manually triggered executions is redacted.", + "workflowSettings.redactionPermissionNotice": "You don't have permission to change data redaction settings.", + "workflowSettings.redactionPermissionNotice.viewUsers": "View users with access", + "workflowSettings.redactionMembersModal.title": "Users who can edit data redaction settings", + "workflowSettings.redactionMembersModal.description": "These users can change data redaction settings. Contact one of them to request a change.", "workflowSettings.saveManualExecutions": "Save manual executions", "workflowSettings.saveManualOptions.defaultSave": "Default - {defaultValue}", "workflowSettings.saveManualOptions.doNotSave": "Do not save", diff --git a/packages/frontend/editor-ui/src/app/components/RedactionMembersModal.vue b/packages/frontend/editor-ui/src/app/components/RedactionMembersModal.vue new file mode 100644 index 00000000000..aaebeeffa40 --- /dev/null +++ b/packages/frontend/editor-ui/src/app/components/RedactionMembersModal.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/packages/frontend/editor-ui/src/app/components/WorkflowSettings.test.ts b/packages/frontend/editor-ui/src/app/components/WorkflowSettings.test.ts index 8ac22edeeed..877f35bcf72 100644 --- a/packages/frontend/editor-ui/src/app/components/WorkflowSettings.test.ts +++ b/packages/frontend/editor-ui/src/app/components/WorkflowSettings.test.ts @@ -968,11 +968,11 @@ describe('WorkflowSettingsVue', () => { }); describe('Redaction Policy', () => { - it('should not render redaction policy when env feature flag is missing', async () => { - const { queryByTestId } = createComponent({ pinia }); + it('should show locked redaction policy when licensed but user lacks updateRedactionSetting scope', async () => { + const { getByTestId } = createComponent({ pinia }); await flushPromises(); - expect(queryByTestId('workflow-settings-redaction-policy')).not.toBeInTheDocument(); + expect(getByTestId('workflow-settings-redaction-policy')).toBeInTheDocument(); }); it('should not render redaction policy when redaction module is inactive', async () => { @@ -995,13 +995,20 @@ describe('WorkflowSettingsVue', () => { expect(queryByTestId('workflow-settings-redaction-policy')).not.toBeInTheDocument(); }); - it('should not render redaction policy when user lacks updateRedactionSetting scope', async () => { + it('should disable redaction dropdowns when user lacks updateRedactionSetting scope', async () => { vi.spyOn(settingsStore, 'isModuleActive').mockReturnValue(true); - const { queryByTestId } = createComponent({ pinia }); + const { getByTestId } = createComponent({ pinia }); await flushPromises(); - expect(queryByTestId('workflow-settings-redaction-policy')).not.toBeInTheDocument(); + const productionCombobox = within( + getByTestId('workflow-settings-redact-production-select'), + ).getByRole('combobox'); + const manualCombobox = within( + getByTestId('workflow-settings-redact-manual-select'), + ).getByRole('combobox'); + expect(productionCombobox).toBeDisabled(); + expect(manualCombobox).toBeDisabled(); }); it('should render redaction policy when module is active and user has scope', async () => { diff --git a/packages/frontend/editor-ui/src/app/components/WorkflowSettings.vue b/packages/frontend/editor-ui/src/app/components/WorkflowSettings.vue index 2edd826f1b5..289eeb6045e 100644 --- a/packages/frontend/editor-ui/src/app/components/WorkflowSettings.vue +++ b/packages/frontend/editor-ui/src/app/components/WorkflowSettings.vue @@ -45,6 +45,7 @@ import { useTelemetry } from '@/app/composables/useTelemetry'; import { useDebounce } from '@/app/composables/useDebounce'; import { injectWorkflowDocumentStore } from '@/app/stores/workflowDocument.store'; import { useMcp } from '@/features/ai/mcpAccess/composables/useMcp'; +import RedactionMembersModal from '@/app/components/RedactionMembersModal.vue'; import { useGlobalLinkActions } from '@/app/composables/useGlobalLinkActions'; import { usePageRedirectionHelper } from '@/app/composables/usePageRedirectionHelper'; import { useNodeCreatorStore } from '@/features/shared/nodeCreator/nodeCreator.store'; @@ -202,12 +203,14 @@ const isDataRedactionLicensed = computed( () => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.DataRedaction], ); -const isRedactionSettingVisible = computed( - () => - settingsStore.isModuleActive('redaction') && - (isDataRedactionLicensed.value ? workflowPermissions.value.updateRedactionSetting : true), +const isRedactionSettingVisible = computed(() => settingsStore.isModuleActive('redaction')); + +const isRedactionSettingLocked = computed( + () => isDataRedactionLicensed.value && !workflowPermissions.value.updateRedactionSetting, ); +const redactionMembersModalOpen = ref(false); + function goToDataRedactionUpgrade() { void pageRedirectionHelper.goToUpgrade('workflow-settings', 'upgrade-data-redaction'); } @@ -1171,10 +1174,19 @@ onBeforeUnmount(() => { :span="10" :class="[ $style['setting-name'], - { [$style['setting-name--disabled']]: !isDataRedactionLicensed }, + { + [$style['setting-name--disabled']]: + !isDataRedactionLicensed || isRedactionSettingLocked, + }, ]" > {{ i18n.baseText('workflowSettings.redactProductionData') }} + { - - - + + + - - + + + + @@ -1226,10 +1257,14 @@ onBeforeUnmount(() => { :span="10" :class="[ $style['setting-name'], - { [$style['setting-name--disabled']]: !isDataRedactionLicensed }, + { + [$style['setting-name--disabled']]: + !isDataRedactionLicensed || isRedactionSettingLocked, + }, ]" > {{ i18n.baseText('workflowSettings.redactManualData') }} + { - - - + + + - - + + + + @@ -1494,6 +1548,12 @@ onBeforeUnmount(() => { @click="saveSettings" /> + @@ -1545,6 +1605,12 @@ onBeforeUnmount(() => { opacity: 0.5; } +.permission-notice-link { + color: var(--color-foreground-xlight); + text-decoration: underline; + cursor: pointer; +} + .upgrade-badge { cursor: pointer; }