From 0094d8029c37949a1ae0e99e627516d1bbeee48d Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Mon, 24 Nov 2025 08:10:22 +0100 Subject: [PATCH 01/13] slop --- .../dataScrubbing/modals/form/index.tsx | 39 +++++++++++++++++++ .../dataScrubbing/modals/modalManager.tsx | 1 + 2 files changed, 40 insertions(+) diff --git a/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx b/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx index 76d4d418eabc5b..ef5f2263b68b1e 100644 --- a/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx +++ b/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx @@ -5,6 +5,7 @@ import sortBy from 'lodash/sortBy'; import {Alert} from 'sentry/components/core/alert'; import {Button} from 'sentry/components/core/button'; +import {Checkbox} from 'sentry/components/core/checkbox'; import {Input} from 'sentry/components/core/input'; import FieldGroup from 'sentry/components/forms/fieldGroup'; import RadioField from 'sentry/components/forms/fields/radioField'; @@ -217,6 +218,20 @@ class Form extends Component>, State> { onBlur={onValidate('pattern')} id="regex-matches" /> + + + onChange( + 'patternReplacement' as any, + e.target.checked ? 'first_capture' : 'entire' + ) + } + /> + + {t('Only replace first capture match')} + + )} @@ -384,6 +399,20 @@ class Form extends Component>, State> { onBlur={onValidate('pattern')} id="regex-matches" /> + + + onChange( + 'patternReplacement' as any, + e.target.checked ? 'first_capture' : 'entire' + ) + } + /> + + {t('Only replace first capture match')} + + )} @@ -474,3 +503,13 @@ const Toggle = styled(Button)` align-items: center; } `; + +const PatternOptions = styled('div')` + display: flex; + align-items: center; + margin-top: ${space(1)}; +`; + +const PatternOptionLabel = styled('span')` + margin-left: ${space(1)}; +`; diff --git a/static/app/views/settings/components/dataScrubbing/modals/modalManager.tsx b/static/app/views/settings/components/dataScrubbing/modals/modalManager.tsx index 1e9b52a96c5784..d164c6bf097fcc 100644 --- a/static/app/views/settings/components/dataScrubbing/modals/modalManager.tsx +++ b/static/app/views/settings/components/dataScrubbing/modals/modalManager.tsx @@ -118,6 +118,7 @@ class ModalManager extends Component { source: initialState?.source ?? '', placeholder: initialState?.placeholder ?? '', pattern: initialState?.pattern ?? '', + patternReplacement: (initialState as any)?.patternReplacement ?? 'entire', }; } From de26d7075aa6a0c4a35955c463b4b9fb7f2294ca Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Mon, 24 Nov 2025 08:41:03 +0100 Subject: [PATCH 02/13] rudimentary form --- .../dataScrubbing/modals/form/index.tsx | 53 ++++++------------- .../dataScrubbing/modals/modalManager.tsx | 2 +- .../components/dataScrubbing/types.tsx | 1 + 3 files changed, 17 insertions(+), 39 deletions(-) diff --git a/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx b/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx index ef5f2263b68b1e..44e4a1b09ec4ed 100644 --- a/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx +++ b/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx @@ -218,20 +218,13 @@ class Form extends Component>, State> { onBlur={onValidate('pattern')} id="regex-matches" /> - - - onChange( - 'patternReplacement' as any, - e.target.checked ? 'first_capture' : 'entire' - ) - } - /> - - {t('Only replace first capture match')} - - + + + + {t('Only replace first capture match')} )} @@ -399,20 +392,14 @@ class Form extends Component>, State> { onBlur={onValidate('pattern')} id="regex-matches" /> - - - onChange( - 'patternReplacement' as any, - e.target.checked ? 'first_capture' : 'entire' - ) - } - /> - - {t('Only replace first capture match')} - - + onChange('replaceCaptured', e.target.checked.toString())} + /> + + {t('Only replace first capture match')} )} @@ -503,13 +490,3 @@ const Toggle = styled(Button)` align-items: center; } `; - -const PatternOptions = styled('div')` - display: flex; - align-items: center; - margin-top: ${space(1)}; -`; - -const PatternOptionLabel = styled('span')` - margin-left: ${space(1)}; -`; diff --git a/static/app/views/settings/components/dataScrubbing/modals/modalManager.tsx b/static/app/views/settings/components/dataScrubbing/modals/modalManager.tsx index d164c6bf097fcc..ae83d582b9f95d 100644 --- a/static/app/views/settings/components/dataScrubbing/modals/modalManager.tsx +++ b/static/app/views/settings/components/dataScrubbing/modals/modalManager.tsx @@ -118,7 +118,7 @@ class ModalManager extends Component { source: initialState?.source ?? '', placeholder: initialState?.placeholder ?? '', pattern: initialState?.pattern ?? '', - patternReplacement: (initialState as any)?.patternReplacement ?? 'entire', + replaceCaptured: initialState?.replaceCaptured ?? 'false', }; } diff --git a/static/app/views/settings/components/dataScrubbing/types.tsx b/static/app/views/settings/components/dataScrubbing/types.tsx index f4addc5fc95930..8c33aa735489c4 100644 --- a/static/app/views/settings/components/dataScrubbing/types.tsx +++ b/static/app/views/settings/components/dataScrubbing/types.tsx @@ -75,6 +75,7 @@ export type RuleDefault = RuleBase & { type RulePattern = RuleBase & { pattern: string; + replaceCaptured: string; type: RuleType.PATTERN; } & Pick; From 44e7bc51843836975a4cc31733c8fdc30dcda253 Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Mon, 24 Nov 2025 09:54:31 +0100 Subject: [PATCH 03/13] works --- .../components/dataScrubbing/convertRelayPiiConfig.tsx | 8 ++++++++ .../components/dataScrubbing/modals/form/index.tsx | 10 ++++++++++ .../components/dataScrubbing/modals/modalManager.tsx | 2 +- .../settings/components/dataScrubbing/submitRules.tsx | 2 ++ .../views/settings/components/dataScrubbing/types.tsx | 1 + 5 files changed, 22 insertions(+), 1 deletion(-) diff --git a/static/app/views/settings/components/dataScrubbing/convertRelayPiiConfig.tsx b/static/app/views/settings/components/dataScrubbing/convertRelayPiiConfig.tsx index 7bd47c19904c08..18ec16bbbed3da 100644 --- a/static/app/views/settings/components/dataScrubbing/convertRelayPiiConfig.tsx +++ b/static/app/views/settings/components/dataScrubbing/convertRelayPiiConfig.tsx @@ -58,6 +58,10 @@ export function convertRelayPiiConfig(relayPiiConfig?: string | null): Rule[] { source, placeholder: redaction?.text, pattern: resolvedRule.pattern, + replaceCaptured: ( + resolvedRule.replaceGroups?.length === 1 && + resolvedRule.replaceGroups?.at(0) === 1 + ).toString(), }); } else { convertedRules.push({ @@ -75,6 +79,10 @@ export function convertRelayPiiConfig(relayPiiConfig?: string | null): Rule[] { type: RuleType.PATTERN, source, pattern: resolvedRule.pattern, + replaceCaptured: ( + resolvedRule.replaceGroups?.length === 1 && + resolvedRule.replaceGroups?.at(0) === 1 + ).toString(), }); } else { convertedRules.push({id, method, type, source}); diff --git a/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx b/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx index 44e4a1b09ec4ed..05532e6c9352d0 100644 --- a/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx +++ b/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx @@ -400,6 +400,16 @@ class Form extends Component>, State> { /> {t('Only replace first capture match')} + {/* + onChange( + 'replaceCaptured' as any, + e.target.checked ? 'first_capture' : 'entire' + ) + } + /> + {t('Only replace first capture match')} */} )} diff --git a/static/app/views/settings/components/dataScrubbing/modals/modalManager.tsx b/static/app/views/settings/components/dataScrubbing/modals/modalManager.tsx index ae83d582b9f95d..dc8cae06924151 100644 --- a/static/app/views/settings/components/dataScrubbing/modals/modalManager.tsx +++ b/static/app/views/settings/components/dataScrubbing/modals/modalManager.tsx @@ -118,7 +118,7 @@ class ModalManager extends Component { source: initialState?.source ?? '', placeholder: initialState?.placeholder ?? '', pattern: initialState?.pattern ?? '', - replaceCaptured: initialState?.replaceCaptured ?? 'false', + replaceCaptured: initialState?.replaceCaptured?.toString() ?? 'false', }; } diff --git a/static/app/views/settings/components/dataScrubbing/submitRules.tsx b/static/app/views/settings/components/dataScrubbing/submitRules.tsx index b864bdef4e17ab..ae0215de9062fa 100644 --- a/static/app/views/settings/components/dataScrubbing/submitRules.tsx +++ b/static/app/views/settings/components/dataScrubbing/submitRules.tsx @@ -12,6 +12,7 @@ function getSubmitFormatRule(rule: Rule): PiiConfig { method: rule.method, text: rule?.placeholder, }, + replaceGroups: rule.replaceCaptured === 'true' ? [1] : undefined, }; } @@ -22,6 +23,7 @@ function getSubmitFormatRule(rule: Rule): PiiConfig { redaction: { method: rule.method, }, + replaceGroups: rule.replaceCaptured === 'true' ? [1] : undefined, }; } diff --git a/static/app/views/settings/components/dataScrubbing/types.tsx b/static/app/views/settings/components/dataScrubbing/types.tsx index 8c33aa735489c4..b08f3a9cb4f386 100644 --- a/static/app/views/settings/components/dataScrubbing/types.tsx +++ b/static/app/views/settings/components/dataScrubbing/types.tsx @@ -123,6 +123,7 @@ type PiiConfigPattern = { method: RulePattern['method']; }; type: RulePattern['type']; + replaceGroups?: number[]; }; type PiiConfigReplaceAndPattern = Omit & From 2e48cd6c25fc159a6951edc3baacf7567ab91f7b Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Mon, 24 Nov 2025 12:07:38 +0100 Subject: [PATCH 04/13] repeat in 2nd function, fix test --- .../dataScrubbing/modals/edit.spec.tsx | 1 + .../dataScrubbing/modals/form/index.tsx | 45 +++++++++---------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/static/app/views/settings/components/dataScrubbing/modals/edit.spec.tsx b/static/app/views/settings/components/dataScrubbing/modals/edit.spec.tsx index b74f603dc0aa7e..9c4710d36bfbcf 100644 --- a/static/app/views/settings/components/dataScrubbing/modals/edit.spec.tsx +++ b/static/app/views/settings/components/dataScrubbing/modals/edit.spec.tsx @@ -189,6 +189,7 @@ describe('Edit Modal', () => { method: 'mask', pattern: '', placeholder: '', + replaceCaptured: 'false', type: 'anything', source: valueSuggestions[2]!.value, }, diff --git a/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx b/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx index 05532e6c9352d0..286f9f58d157b7 100644 --- a/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx +++ b/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx @@ -218,13 +218,16 @@ class Form extends Component>, State> { onBlur={onValidate('pattern')} id="regex-matches" /> - - - - {t('Only replace first capture match')} + + onChange('replaceCaptured', e.target.checked.toString())} + /> +   + {t('Only replace first capture match')} + )} @@ -392,24 +395,16 @@ class Form extends Component>, State> { onBlur={onValidate('pattern')} id="regex-matches" /> - onChange('replaceCaptured', e.target.checked.toString())} - /> - - {t('Only replace first capture match')} - {/* - onChange( - 'replaceCaptured' as any, - e.target.checked ? 'first_capture' : 'entire' - ) - } - /> - {t('Only replace first capture match')} */} + + onChange('replaceCaptured', e.target.checked.toString())} + /> +   + {t('Only replace first capture match')} + )} From be7834502c4e4286887ea9e3330c6b25476240a1 Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Mon, 24 Nov 2025 12:47:26 +0100 Subject: [PATCH 05/13] layout --- .../dataScrubbing/modals/form/index.tsx | 131 +++++++++--------- 1 file changed, 69 insertions(+), 62 deletions(-) diff --git a/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx b/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx index 286f9f58d157b7..61f8c7085841cb 100644 --- a/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx +++ b/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx @@ -3,6 +3,8 @@ import {css} from '@emotion/react'; import styled from '@emotion/styled'; import sortBy from 'lodash/sortBy'; +import {Flex} from '@sentry/scraps/layout'; + import {Alert} from 'sentry/components/core/alert'; import {Button} from 'sentry/components/core/button'; import {Checkbox} from 'sentry/components/core/checkbox'; @@ -351,76 +353,81 @@ class Form extends Component>, State> { )} - - - ({ - label: getRuleLabel(value), - value, - }))} - value={type} - onChange={value => onChange('type', value?.value)} - /> - - {values.type === RuleType.PATTERN && ( + + - ({ + label: getRuleLabel(value), + value, + }))} + value={type} + onChange={value => onChange('type', value?.value)} /> - - onChange('replaceCaptured', e.target.checked.toString())} - /> -   - {t('Only replace first capture match')} - - )} - - - {displayEventId ? ( - - {t('Hide event ID field')} - - - ) : ( - - {t('Use event ID for auto-completion')} - - - )} - + {values.type === RuleType.PATTERN && ( + + + + + + onChange('replaceCaptured', e.target.checked.toString()) + } + /> + {t('Only replace first capture match')} + + + + )} + + + {displayEventId ? ( + + {t('Hide event ID field')} + + + ) : ( + + {t('Use event ID for auto-completion')} + + + )} + + {displayEventId && ( From d2391b0450b8962140729d34a88e3b451a13ac7a Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Mon, 24 Nov 2025 12:53:45 +0100 Subject: [PATCH 06/13] layout --- .../dataScrubbing/modals/form/index.tsx | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx b/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx index 61f8c7085841cb..93f9c2c90a1986 100644 --- a/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx +++ b/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx @@ -211,25 +211,30 @@ class Form extends Component>, State> { required showHelpInTooltip > - - - onChange('replaceCaptured', e.target.checked.toString())} + + -   - {t('Only replace first capture match')} - + + + onChange('replaceCaptured', e.target.checked.toString()) + } + /> + + + )} @@ -408,7 +413,9 @@ class Form extends Component>, State> { onChange('replaceCaptured', e.target.checked.toString()) } /> - {t('Only replace first capture match')} + From f978f3603d8db387285875c75c6a7cf859a8ff39 Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Mon, 24 Nov 2025 13:44:58 +0100 Subject: [PATCH 07/13] format label --- .../components/dataScrubbing/modals/form/index.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx b/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx index 93f9c2c90a1986..537cea65727c7e 100644 --- a/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx +++ b/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx @@ -230,9 +230,9 @@ class Form extends Component>, State> { onChange('replaceCaptured', e.target.checked.toString()) } /> - + @@ -413,9 +413,9 @@ class Form extends Component>, State> { onChange('replaceCaptured', e.target.checked.toString()) } /> - + @@ -483,6 +483,11 @@ const RegularExpression = styled(Input)` font-family: ${p => p.theme.text.familyMono}; `; +const ReplaceCapturedLabel = styled('label')` + font-weight: normal; + margin-bottom: 0; +`; + const DatasetRadioField = styled(RadioField)` padding: 0px; #dataset { From 0658fdde2ecca98c28dec2596366a80d48c673e3 Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Mon, 24 Nov 2025 15:59:56 +0100 Subject: [PATCH 08/13] Check for capture groups --- .../dataScrubbing/modals/form/index.tsx | 57 ++++++++++--------- .../dataScrubbing/modals/modalManager.tsx | 6 +- .../components/dataScrubbing/modals/utils.tsx | 5 ++ 3 files changed, 41 insertions(+), 27 deletions(-) diff --git a/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx b/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx index 537cea65727c7e..8f2e2710ce40d3 100644 --- a/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx +++ b/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx @@ -17,6 +17,7 @@ import {space} from 'sentry/styles/space'; import type {Organization} from 'sentry/types/organization'; import type {Project} from 'sentry/types/project'; import withOrganization from 'sentry/utils/withOrganization'; +import {hasCaptureGroups} from 'sentry/views/settings/components/dataScrubbing/modals/utils'; import { AllowedDataScrubbingDatasets, MethodType, @@ -221,19 +222,21 @@ class Form extends Component>, State> { onBlur={onValidate('pattern')} id="regex-matches" /> - - - onChange('replaceCaptured', e.target.checked.toString()) - } - /> - - {t('Only replace first capture match')} - - + {hasCaptureGroups(values.pattern) && ( + + + onChange('replaceCaptured', e.target.checked.toString()) + } + /> + + {t('Only replace first capture match')} + + + )} )} @@ -404,19 +407,21 @@ class Form extends Component>, State> { onBlur={onValidate('pattern')} id="regex-matches" /> - - - onChange('replaceCaptured', e.target.checked.toString()) - } - /> - - {t('Only replace first capture match')} - - + {hasCaptureGroups(values.pattern) && ( + + + onChange('replaceCaptured', e.target.checked.toString()) + } + /> + + {t('Only replace first capture match')} + + + )} )} diff --git a/static/app/views/settings/components/dataScrubbing/modals/modalManager.tsx b/static/app/views/settings/components/dataScrubbing/modals/modalManager.tsx index dc8cae06924151..c51e08da11d8a0 100644 --- a/static/app/views/settings/components/dataScrubbing/modals/modalManager.tsx +++ b/static/app/views/settings/components/dataScrubbing/modals/modalManager.tsx @@ -29,7 +29,7 @@ import { import Form from './form'; import handleError, {ErrorType} from './handleError'; import Modal from './modal'; -import {useSourceGroupData} from './utils'; +import {hasCaptureGroups, useSourceGroupData} from './utils'; type FormProps = React.ComponentProps; type Values = FormProps['values']; @@ -249,6 +249,10 @@ class ModalManager extends Component { values.pattern = ''; } + if (values.type === RuleType.PATTERN && !hasCaptureGroups(values.pattern)) { + values.replaceCaptured = 'false'; + } + if (values.method !== MethodType.REPLACE && values.placeholder) { values.placeholder = ''; } diff --git a/static/app/views/settings/components/dataScrubbing/modals/utils.tsx b/static/app/views/settings/components/dataScrubbing/modals/utils.tsx index 6a1fbd80a9b3e4..3c19ae8aaa2702 100644 --- a/static/app/views/settings/components/dataScrubbing/modals/utils.tsx +++ b/static/app/views/settings/components/dataScrubbing/modals/utils.tsx @@ -42,3 +42,8 @@ export function useSourceGroupData() { saveToSourceGroupData, }; } + +export function hasCaptureGroups(pattern: string) { + const m = pattern.match(/\(.*\)/); + return m !== null && m.length > 0; +} From bba2b100af62441aea8c6f1fdabe0abea8c300ee Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Mon, 24 Nov 2025 16:02:42 +0100 Subject: [PATCH 09/13] fix: Back to disable --- .../dataScrubbing/modals/form/index.tsx | 58 +++++++++---------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx b/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx index 8f2e2710ce40d3..686a0e65bf7f62 100644 --- a/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx +++ b/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx @@ -222,21 +222,20 @@ class Form extends Component>, State> { onBlur={onValidate('pattern')} id="regex-matches" /> - {hasCaptureGroups(values.pattern) && ( - - - onChange('replaceCaptured', e.target.checked.toString()) - } - /> - - {t('Only replace first capture match')} - - - )} + + + onChange('replaceCaptured', e.target.checked.toString()) + } + /> + + {t('Only replace first capture match')} + + )} @@ -407,21 +406,20 @@ class Form extends Component>, State> { onBlur={onValidate('pattern')} id="regex-matches" /> - {hasCaptureGroups(values.pattern) && ( - - - onChange('replaceCaptured', e.target.checked.toString()) - } - /> - - {t('Only replace first capture match')} - - - )} + + + onChange('replaceCaptured', e.target.checked.toString()) + } + /> + + {t('Only replace first capture match')} + + )} From 4c14f647a7e011e1bb649a7279203c33e51b1bc0 Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Mon, 24 Nov 2025 16:42:47 +0100 Subject: [PATCH 10/13] fix: clear replaceCaptured --- .../settings/components/dataScrubbing/modals/modalManager.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/static/app/views/settings/components/dataScrubbing/modals/modalManager.tsx b/static/app/views/settings/components/dataScrubbing/modals/modalManager.tsx index c51e08da11d8a0..eb10389fe25bb8 100644 --- a/static/app/views/settings/components/dataScrubbing/modals/modalManager.tsx +++ b/static/app/views/settings/components/dataScrubbing/modals/modalManager.tsx @@ -245,8 +245,9 @@ class ModalManager extends Component { [field]: value, }; - if (values.type !== RuleType.PATTERN && values.pattern) { + if (values.type !== RuleType.PATTERN) { values.pattern = ''; + values.replaceCaptured = 'false'; } if (values.type === RuleType.PATTERN && !hasCaptureGroups(values.pattern)) { From 151b0866c03ea75a0c284d9cad37fbebae586468 Mon Sep 17 00:00:00 2001 From: Priscila Oliveira Date: Tue, 25 Nov 2025 13:13:01 +0100 Subject: [PATCH 11/13] create ReplaceCapturedCheckbox component --- .../dataScrubbing/modals/form/index.tsx | 207 +++++++++--------- 1 file changed, 106 insertions(+), 101 deletions(-) diff --git a/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx b/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx index 686a0e65bf7f62..9ee17be7a68734 100644 --- a/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx +++ b/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx @@ -4,6 +4,7 @@ import styled from '@emotion/styled'; import sortBy from 'lodash/sortBy'; import {Flex} from '@sentry/scraps/layout'; +import {Tooltip} from '@sentry/scraps/tooltip'; import {Alert} from 'sentry/components/core/alert'; import {Button} from 'sentry/components/core/button'; @@ -64,6 +65,33 @@ type State = { displayEventId: boolean; }; +function ReplaceCapturedCheckbox({ + values, + onChange, +}: { + onChange: (field: 'replaceCaptured', value: string) => void; + values: Values; +}) { + const disabled = !hasCaptureGroups(values.pattern); + return ( + + + onChange('replaceCaptured', e.target.checked.toString())} + /> + + + + ); +} + class Form extends Component>, State> { state: State = { displayEventId: !!this.props.eventId?.value, @@ -212,31 +240,16 @@ class Form extends Component>, State> { required showHelpInTooltip > - - - - - onChange('replaceCaptured', e.target.checked.toString()) - } - /> - - {t('Only replace first capture match')} - - - + + )} @@ -360,84 +373,67 @@ class Form extends Component>, State> { )} - - + + + ({ + label: getRuleLabel(value), + value, + }))} + value={type} + onChange={value => onChange('type', value?.value)} + /> + + {values.type === RuleType.PATTERN && ( - ({ - label: getRuleLabel(value), - value, - }))} - value={type} - onChange={value => onChange('type', value?.value)} + + - {values.type === RuleType.PATTERN && ( - - - - - - onChange('replaceCaptured', e.target.checked.toString()) - } - /> - - {t('Only replace first capture match')} - - - - - )} - - - {displayEventId ? ( - - {t('Hide event ID field')} - - - ) : ( - - {t('Use event ID for auto-completion')} - - - )} - - + )} + + + {displayEventId ? ( + + {t('Hide event ID field')} + + + ) : ( + + {t('Use event ID for auto-completion')} + + + )} + {displayEventId && ( @@ -464,7 +460,7 @@ const FieldContainer = styled('div')<{hasTwoColumns: boolean}>` @media (min-width: ${p => p.theme.breakpoints.sm}) { gap: ${space(2)}; ${p => p.hasTwoColumns && `grid-template-columns: 1fr 1fr;`} - margin-bottom: ${p => (p.hasTwoColumns ? 0 : space(2))}; + margin-bottom: ${p => p.theme.space.xl}; } `; @@ -484,11 +480,7 @@ const SourceGroup = styled('div')<{isExpanded?: boolean}>` const RegularExpression = styled(Input)` font-family: ${p => p.theme.text.familyMono}; -`; - -const ReplaceCapturedLabel = styled('label')` - font-weight: normal; - margin-bottom: 0; + margin-bottom: ${p => p.theme.space.md}; `; const DatasetRadioField = styled(RadioField)` @@ -517,3 +509,16 @@ const Toggle = styled(Button)` align-items: center; } `; + +const ReplaceCapturedCheckboxContainer = styled(Flex)<{disabled: boolean}>` + label { + font-weight: normal; + margin-bottom: 0; + line-height: 1rem; + ${p => + p.disabled && + css` + color: ${p.theme.disabled}; + `} + } +`; From 63ef4bc0507329926973e8951dbc20ca1901d15d Mon Sep 17 00:00:00 2001 From: Priscila Oliveira Date: Tue, 25 Nov 2025 13:14:58 +0100 Subject: [PATCH 12/13] prefer label than wrapper --- .../dataScrubbing/modals/form/index.tsx | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx b/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx index 9ee17be7a68734..3267429b6e13a4 100644 --- a/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx +++ b/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx @@ -78,7 +78,7 @@ function ReplaceCapturedCheckbox({ title={disabled ? t('This rule does not contain capture groups') : undefined} disabled={!disabled} > - + onChange('replaceCaptured', e.target.checked.toString())} /> - - + + {t('Only replace first capture match')} + + ); } @@ -510,15 +512,13 @@ const Toggle = styled(Button)` } `; -const ReplaceCapturedCheckboxContainer = styled(Flex)<{disabled: boolean}>` - label { - font-weight: normal; - margin-bottom: 0; - line-height: 1rem; - ${p => - p.disabled && - css` - color: ${p.theme.disabled}; - `} - } +const ReplaceCapturedLabel = styled('label')<{disabled: boolean}>` + font-weight: normal; + margin-bottom: 0; + line-height: 1rem; + ${p => + p.disabled && + css` + color: ${p.theme.disabled}; + `} `; From 2508b4cf131c38e7f011e818daff486ad6c63b87 Mon Sep 17 00:00:00 2001 From: Priscila Oliveira Date: Tue, 25 Nov 2025 13:39:48 +0100 Subject: [PATCH 13/13] update types --- .../dataScrubbing/convertRelayPiiConfig.tsx | 10 ++++------ .../components/dataScrubbing/modals/edit.spec.tsx | 2 +- .../components/dataScrubbing/modals/form/index.tsx | 10 +++++----- .../dataScrubbing/modals/modalManager.tsx | 13 +++++++++---- .../components/dataScrubbing/submitRules.tsx | 4 ++-- .../settings/components/dataScrubbing/types.tsx | 9 +++++++-- 6 files changed, 28 insertions(+), 20 deletions(-) diff --git a/static/app/views/settings/components/dataScrubbing/convertRelayPiiConfig.tsx b/static/app/views/settings/components/dataScrubbing/convertRelayPiiConfig.tsx index 18ec16bbbed3da..192bb6f147c581 100644 --- a/static/app/views/settings/components/dataScrubbing/convertRelayPiiConfig.tsx +++ b/static/app/views/settings/components/dataScrubbing/convertRelayPiiConfig.tsx @@ -58,10 +58,9 @@ export function convertRelayPiiConfig(relayPiiConfig?: string | null): Rule[] { source, placeholder: redaction?.text, pattern: resolvedRule.pattern, - replaceCaptured: ( + replaceCaptured: resolvedRule.replaceGroups?.length === 1 && - resolvedRule.replaceGroups?.at(0) === 1 - ).toString(), + resolvedRule.replaceGroups?.at(0) === 1, }); } else { convertedRules.push({ @@ -79,10 +78,9 @@ export function convertRelayPiiConfig(relayPiiConfig?: string | null): Rule[] { type: RuleType.PATTERN, source, pattern: resolvedRule.pattern, - replaceCaptured: ( + replaceCaptured: resolvedRule.replaceGroups?.length === 1 && - resolvedRule.replaceGroups?.at(0) === 1 - ).toString(), + resolvedRule.replaceGroups?.at(0) === 1, }); } else { convertedRules.push({id, method, type, source}); diff --git a/static/app/views/settings/components/dataScrubbing/modals/edit.spec.tsx b/static/app/views/settings/components/dataScrubbing/modals/edit.spec.tsx index 9c4710d36bfbcf..2947696979de5f 100644 --- a/static/app/views/settings/components/dataScrubbing/modals/edit.spec.tsx +++ b/static/app/views/settings/components/dataScrubbing/modals/edit.spec.tsx @@ -189,7 +189,7 @@ describe('Edit Modal', () => { method: 'mask', pattern: '', placeholder: '', - replaceCaptured: 'false', + replaceCaptured: false, type: 'anything', source: valueSuggestions[2]!.value, }, diff --git a/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx b/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx index 3267429b6e13a4..3f8ad2fa5f541a 100644 --- a/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx +++ b/static/app/views/settings/components/dataScrubbing/modals/form/index.tsx @@ -51,7 +51,7 @@ type Props = { errors: Partial; eventId: EventId; onAttributeError: (message: string) => void; - onChange: (field: K, value: string) => void; + onChange: (field: K, value: V[K]) => void; onChangeDataset: (dataset: AllowedDataScrubbingDatasets) => void; onUpdateEventId: (eventId: string) => void; onValidate: (field: K) => () => void; @@ -69,7 +69,7 @@ function ReplaceCapturedCheckbox({ values, onChange, }: { - onChange: (field: 'replaceCaptured', value: string) => void; + onChange: (field: 'replaceCaptured', value: boolean) => void; values: Values; }) { const disabled = !hasCaptureGroups(values.pattern); @@ -82,9 +82,9 @@ function ReplaceCapturedCheckbox({ onChange('replaceCaptured', e.target.checked.toString())} + onChange={e => onChange('replaceCaptured', e.target.checked)} /> {t('Only replace first capture match')} @@ -102,7 +102,7 @@ class Form extends Component>, State> { handleChange = (field: K) => (event: React.ChangeEvent) => { - this.props.onChange(field, event.target.value); + this.props.onChange(field, event.target.value as Values[K]); }; handleToggleEventId = () => { diff --git a/static/app/views/settings/components/dataScrubbing/modals/modalManager.tsx b/static/app/views/settings/components/dataScrubbing/modals/modalManager.tsx index eb10389fe25bb8..9fe5fbdadf59fd 100644 --- a/static/app/views/settings/components/dataScrubbing/modals/modalManager.tsx +++ b/static/app/views/settings/components/dataScrubbing/modals/modalManager.tsx @@ -118,7 +118,7 @@ class ModalManager extends Component { source: initialState?.source ?? '', placeholder: initialState?.placeholder ?? '', pattern: initialState?.pattern ?? '', - replaceCaptured: initialState?.replaceCaptured?.toString() ?? 'false', + replaceCaptured: initialState?.replaceCaptured ?? false, }; } @@ -247,11 +247,11 @@ class ModalManager extends Component { if (values.type !== RuleType.PATTERN) { values.pattern = ''; - values.replaceCaptured = 'false'; + values.replaceCaptured = false; } if (values.type === RuleType.PATTERN && !hasCaptureGroups(values.pattern)) { - values.replaceCaptured = 'false'; + values.replaceCaptured = false; } if (values.method !== MethodType.REPLACE && values.placeholder) { @@ -297,7 +297,12 @@ class ModalManager extends Component { handleValidate = (field: K) => () => { - const isFieldValueEmpty = !this.state.values[field].trim(); + const value = this.state.values[field]; + if (typeof value !== 'string') { + return; + } + + const isFieldValueEmpty = !value.trim(); const fieldErrorAlreadyExist = this.state.errors[field]; diff --git a/static/app/views/settings/components/dataScrubbing/submitRules.tsx b/static/app/views/settings/components/dataScrubbing/submitRules.tsx index ae0215de9062fa..8c8dc9affea2d4 100644 --- a/static/app/views/settings/components/dataScrubbing/submitRules.tsx +++ b/static/app/views/settings/components/dataScrubbing/submitRules.tsx @@ -12,7 +12,7 @@ function getSubmitFormatRule(rule: Rule): PiiConfig { method: rule.method, text: rule?.placeholder, }, - replaceGroups: rule.replaceCaptured === 'true' ? [1] : undefined, + replaceGroups: rule.replaceCaptured ? [1] : undefined, }; } @@ -23,7 +23,7 @@ function getSubmitFormatRule(rule: Rule): PiiConfig { redaction: { method: rule.method, }, - replaceGroups: rule.replaceCaptured === 'true' ? [1] : undefined, + replaceGroups: rule.replaceCaptured ? [1] : undefined, }; } diff --git a/static/app/views/settings/components/dataScrubbing/types.tsx b/static/app/views/settings/components/dataScrubbing/types.tsx index b08f3a9cb4f386..3150b68b825905 100644 --- a/static/app/views/settings/components/dataScrubbing/types.tsx +++ b/static/app/views/settings/components/dataScrubbing/types.tsx @@ -75,7 +75,7 @@ export type RuleDefault = RuleBase & { type RulePattern = RuleBase & { pattern: string; - replaceCaptured: string; + replaceCaptured: boolean; type: RuleType.PATTERN; } & Pick; @@ -95,7 +95,12 @@ export type EventId = { value: string; }; -export type EditableRule = Omit, string>, 'id'>; +export type EditableRule = Omit< + { + [K in KeysOfUnion]: K extends 'replaceCaptured' ? boolean : string; + }, + 'id' +>; export type AttributeResults = Record< AllowedDataScrubbingDatasets,