feat(editor): Show data redaction scope dropdown to unlicensed users (#30966)
Some checks are pending
CI: Master (Build, Test, Lint) / Build for Github Cache (push) Waiting to run
CI: Master (Build, Test, Lint) / Unit tests (22.22.3) (push) Waiting to run
CI: Master (Build, Test, Lint) / Unit tests (24.15.0) (push) Waiting to run
CI: Master (Build, Test, Lint) / Lint (push) Waiting to run
CI: Master (Build, Test, Lint) / Performance (push) Waiting to run
CI: Master (Build, Test, Lint) / Notify Slack on failure (push) Blocked by required conditions

This commit is contained in:
Csaba Tuncsik 2026-05-28 06:28:27 +02:00 committed by GitHub
parent 6d4f917671
commit 25a836dfb7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 120 additions and 15 deletions

View File

@ -202,27 +202,54 @@ function goToUpgrade() {
</div>
</div>
<div
v-if="enforced && isLicensed"
v-if="!isLicensed || enforced"
:class="$style.settingsContainer"
data-test-id="redaction-enforcement-scope-row"
>
<div :class="$style.settingsContainerInfo">
<N8nText :bold="true">{{
i18n.baseText('settings.security.dataRedaction.scope.title')
}}</N8nText>
<N8nText :bold="true"
>{{ i18n.baseText('settings.security.dataRedaction.scope.title') }}
<N8nBadge v-if="!isLicensed" class="ml-4xs">{{
i18n.baseText('generic.upgrade')
}}</N8nBadge>
</N8nText>
<N8nText size="small" color="text-light">{{
i18n.baseText('settings.security.dataRedaction.scope.description')
}}</N8nText>
</div>
<div :class="$style.settingsContainerAction">
<N8nSelect2
:model-value="dropdownFloor"
:items="floorOptions"
size="medium"
:disabled="props.managedByEnv || isSaving"
data-test-id="redaction-enforcement-scope-select"
@update:model-value="onSelectFloor"
/>
<EnterpriseEdition :features="[EnterpriseEditionFeature.DataRedaction]">
<N8nSelect2
:model-value="dropdownFloor"
:items="floorOptions"
size="medium"
:disabled="props.managedByEnv || isSaving"
data-test-id="redaction-enforcement-scope-select"
@update:model-value="onSelectFloor"
/>
<template #fallback>
<N8nTooltip>
<N8nSelect2
:model-value="dropdownFloor"
:items="floorOptions"
size="medium"
:disabled="true"
data-test-id="redaction-enforcement-scope-select"
/>
<template #content>
<I18nT :keypath="TOOLTIP_KEY" tag="span" scope="global">
<template #action>
<a @click="goToUpgrade">
{{
i18n.baseText('settings.security.dataRedaction.unlicensed_tooltip.link')
}}
</a>
</template>
</I18nT>
</template>
</N8nTooltip>
</template>
</EnterpriseEdition>
</div>
</div>
<div :class="$style.settingsCountRow" data-test-id="redaction-enforcement-summary">

View File

@ -661,7 +661,7 @@ describe('SecuritySettings', () => {
expect(getByTestId('enable-redaction-enforcement')).toHaveClass('is-disabled');
});
it('should not render scope dropdown when unlicensed even if enforced=true', async () => {
it('should render disabled scope dropdown when unlicensed even if enforced=true', async () => {
enableRedactionEnforcementFlag(true);
settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.DataRedaction] = false;
getSecuritySettings.mockResolvedValue({
@ -669,13 +669,91 @@ describe('SecuritySettings', () => {
redactionEnforcement: { floor: 'production' },
});
const { getByTestId, queryByTestId } = renderView();
const { getByTestId } = renderView();
await waitFor(() => {
expect(getByTestId('enable-redaction-enforcement')).toBeInTheDocument();
});
expect(queryByTestId('redaction-enforcement-scope-row')).not.toBeInTheDocument();
expect(getByTestId('redaction-enforcement-scope-row')).toBeInTheDocument();
expect(getByTestId('redaction-enforcement-scope-select')).toHaveAttribute('data-disabled');
});
it('should render scope dropdown with Upgrade badge when unlicensed and not enforced', async () => {
enableRedactionEnforcementFlag(true);
settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.DataRedaction] = false;
const { getAllByText, getByTestId } = renderView();
await waitFor(() => {
expect(getByTestId('enable-redaction-enforcement')).toBeInTheDocument();
});
expect(getByTestId('redaction-enforcement-scope-row')).toBeInTheDocument();
expect(getByTestId('redaction-enforcement-scope-select')).toHaveAttribute('data-disabled');
// One badge on the toggle row, one on the scope row.
expect(getAllByText('Upgrade').length).toBeGreaterThanOrEqual(2);
// Summary reflects stored floor='off' while the disabled dropdown previews 'production'.
expect(getByTestId('redaction-enforcement-summary')).toHaveTextContent('No executions');
});
it('should not call updateSecuritySettings when clicking disabled scope dropdown', async () => {
enableRedactionEnforcementFlag(true);
settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.DataRedaction] = false;
getSecuritySettings.mockResolvedValue({
...defaultSettings,
redactionEnforcement: { floor: 'production' },
});
const { getByTestId } = renderView();
await waitFor(() => {
expect(getByTestId('redaction-enforcement-scope-select')).toBeInTheDocument();
});
await userEvent.click(getByTestId('redaction-enforcement-scope-select'));
expect(updateSecuritySettings).not.toHaveBeenCalled();
});
it('should show stored scope value when unlicensed with floor=production (downgrade)', async () => {
enableRedactionEnforcementFlag(true);
settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.DataRedaction] = false;
getSecuritySettings.mockResolvedValue({
...defaultSettings,
redactionEnforcement: { floor: 'production' },
});
const { getByTestId } = renderView();
await waitFor(() => {
expect(getByTestId('redaction-enforcement-scope-row')).toBeInTheDocument();
});
expect(getByTestId('redaction-enforcement-scope-select')).toHaveAttribute('data-disabled');
expect(getByTestId('redaction-enforcement-summary')).toHaveTextContent(
'Production executions',
);
});
it('should show stored scope value when unlicensed with floor=all (downgrade)', async () => {
enableRedactionEnforcementFlag(true);
settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.DataRedaction] = false;
getSecuritySettings.mockResolvedValue({
...defaultSettings,
redactionEnforcement: { floor: 'all' },
});
const { getByTestId } = renderView();
await waitFor(() => {
expect(getByTestId('redaction-enforcement-scope-row')).toBeInTheDocument();
});
expect(getByTestId('redaction-enforcement-scope-select')).toHaveAttribute('data-disabled');
expect(getByTestId('redaction-enforcement-summary')).toHaveTextContent(
'Manual and production executions',
);
});
it('should disable toggle when managed by env', async () => {