123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- import {useCallback, useEffect, useState} from 'react';
- import styled from '@emotion/styled';
- import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
- import {openModal} from 'sentry/actionCreators/modal';
- import {Button} from 'sentry/components/button';
- import EmptyMessage from 'sentry/components/emptyMessage';
- import ExternalLink from 'sentry/components/links/externalLink';
- import Panel from 'sentry/components/panels/panel';
- import PanelAlert from 'sentry/components/panels/panelAlert';
- import PanelBody from 'sentry/components/panels/panelBody';
- import PanelHeader from 'sentry/components/panels/panelHeader';
- import {IconWarning} from 'sentry/icons';
- import {t, tct} from 'sentry/locale';
- import {space} from 'sentry/styles/space';
- import type {Organization} from 'sentry/types/organization';
- import type {Project} from 'sentry/types/project';
- import {defined} from 'sentry/utils';
- import useApi from 'sentry/utils/useApi';
- import {useNavigate} from 'sentry/utils/useNavigate';
- import {useParams} from 'sentry/utils/useParams';
- import Add from './modals/add';
- import Edit from './modals/edit';
- import {convertRelayPiiConfig} from './convertRelayPiiConfig';
- import {OrganizationRules} from './organizationRules';
- import Rules from './rules';
- import submitRules from './submitRules';
- import type {Rule} from './types';
- const ADVANCED_DATASCRUBBING_LINK =
- 'https://docs.sentry.io/product/data-management-settings/scrubbing/advanced-datascrubbing/';
- type Props = {
- endpoint: string;
- organization: Organization;
- additionalContext?: React.ReactNode;
- disabled?: boolean;
- onSubmitSuccess?: (data: {relayPiiConfig: string}) => void;
- project?: Project;
- relayPiiConfig?: string | null;
- };
- export function DataScrubbing({
- project,
- endpoint,
- organization,
- disabled,
- onSubmitSuccess,
- additionalContext,
- relayPiiConfig,
- }: Props) {
- const api = useApi();
- const [rules, setRules] = useState<Rule[]>([]);
- const navigate = useNavigate();
- const params = useParams();
- const successfullySaved = useCallback(
- (response: {relayPiiConfig: string}, successMessage: string) => {
- setRules(convertRelayPiiConfig(response.relayPiiConfig));
- addSuccessMessage(successMessage);
- onSubmitSuccess?.(response);
- },
- [onSubmitSuccess]
- );
- const handleCloseModal = useCallback(() => {
- const path = project?.slug
- ? `/settings/${organization.slug}/projects/${project.slug}/security-and-privacy/`
- : `/settings/${organization.slug}/security-and-privacy/`;
- navigate(path);
- }, [navigate, organization.slug, project?.slug]);
- useEffect(() => {
- if (
- !defined(params.scrubbingId) ||
- !rules.some(rule => String(rule.id) === params.scrubbingId)
- ) {
- return;
- }
- openModal(
- modalProps => (
- <Edit
- {...modalProps}
- rule={rules[params.scrubbingId]}
- projectId={project?.id}
- savedRules={rules}
- api={api}
- endpoint={endpoint}
- orgSlug={organization.slug}
- onSubmitSuccess={response => {
- return successfullySaved(
- response,
- t('Successfully updated data scrubbing rule')
- );
- }}
- />
- ),
- {onClose: handleCloseModal}
- );
- }, [
- params.scrubbingId,
- rules,
- project?.id,
- api,
- endpoint,
- organization.slug,
- successfullySaved,
- handleCloseModal,
- ]);
- useEffect(() => {
- function loadRules() {
- try {
- setRules(convertRelayPiiConfig(relayPiiConfig));
- } catch {
- addErrorMessage(t('Unable to load data scrubbing rules'));
- }
- }
- loadRules();
- }, [relayPiiConfig]);
- function handleEdit(id: Rule['id']) {
- const path = project
- ? `/settings/${organization.slug}/projects/${project.slug}/security-and-privacy/advanced-data-scrubbing/${id}/`
- : `/settings/${organization.slug}/security-and-privacy/advanced-data-scrubbing/${id}/`;
- navigate(path);
- }
- function handleAdd() {
- openModal(modalProps => (
- <Add
- {...modalProps}
- projectId={project?.id}
- savedRules={rules}
- api={api}
- endpoint={endpoint}
- orgSlug={organization.slug}
- onSubmitSuccess={response => {
- return successfullySaved(response, t('Successfully added data scrubbing rule'));
- }}
- />
- ));
- }
- async function handleDelete(id: Rule['id']) {
- try {
- const filteredRules = rules.filter(rule => rule.id !== id);
- const data = await submitRules(api, endpoint, filteredRules);
- if (data?.relayPiiConfig) {
- setRules(convertRelayPiiConfig(data.relayPiiConfig));
- addSuccessMessage(t('Successfully deleted data scrubbing rule'));
- }
- } catch {
- addErrorMessage(t('An unknown error occurred while deleting data scrubbing rule'));
- }
- }
- return (
- <Panel data-test-id="advanced-data-scrubbing">
- <PanelHeader>
- <div>{t('Advanced Data Scrubbing')}</div>
- </PanelHeader>
- <PanelAlert type="info">
- {additionalContext}{' '}
- {tct(
- 'The new rules will only apply to upcoming events. For more details, see [linkToDocs].',
- {
- linkToDocs: (
- <ExternalLink href={ADVANCED_DATASCRUBBING_LINK}>
- {t('full documentation on data scrubbing')}
- </ExternalLink>
- ),
- }
- )}
- </PanelAlert>
- <PanelBody>
- {project && <OrganizationRules organization={organization} />}
- {!rules.length ? (
- <EmptyMessage
- icon={<IconWarning size="xl" />}
- description={t('You have no data scrubbing rules')}
- />
- ) : (
- <Rules
- rules={rules}
- onDeleteRule={handleDelete}
- onEditRule={handleEdit}
- disabled={disabled}
- />
- )}
- <PanelAction>
- <Button href={ADVANCED_DATASCRUBBING_LINK} external>
- {t('Read Docs')}
- </Button>
- <Button disabled={disabled} onClick={handleAdd} priority="primary">
- {t('Add Rule')}
- </Button>
- </PanelAction>
- </PanelBody>
- </Panel>
- );
- }
- const PanelAction = styled('div')`
- padding: ${space(1)} ${space(2)};
- position: relative;
- display: grid;
- gap: ${space(1)};
- grid-template-columns: auto auto;
- justify-content: flex-end;
- border-top: 1px solid ${p => p.theme.border};
- `;
|