index.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. import {useCallback, useEffect, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
  4. import {openModal} from 'sentry/actionCreators/modal';
  5. import {Button} from 'sentry/components/button';
  6. import EmptyMessage from 'sentry/components/emptyMessage';
  7. import ExternalLink from 'sentry/components/links/externalLink';
  8. import Panel from 'sentry/components/panels/panel';
  9. import PanelAlert from 'sentry/components/panels/panelAlert';
  10. import PanelBody from 'sentry/components/panels/panelBody';
  11. import PanelHeader from 'sentry/components/panels/panelHeader';
  12. import {IconWarning} from 'sentry/icons';
  13. import {t, tct} from 'sentry/locale';
  14. import {space} from 'sentry/styles/space';
  15. import {Organization, Project} from 'sentry/types';
  16. import {defined} from 'sentry/utils';
  17. import useApi from 'sentry/utils/useApi';
  18. import {useNavigate} from 'sentry/utils/useNavigate';
  19. import {useParams} from 'sentry/utils/useParams';
  20. import Add from './modals/add';
  21. import Edit from './modals/edit';
  22. import {convertRelayPiiConfig} from './convertRelayPiiConfig';
  23. import {OrganizationRules} from './organizationRules';
  24. import Rules from './rules';
  25. import submitRules from './submitRules';
  26. import {Rule} from './types';
  27. const ADVANCED_DATASCRUBBING_LINK =
  28. 'https://docs.sentry.io/product/data-management-settings/scrubbing/advanced-datascrubbing/';
  29. type Props = {
  30. endpoint: string;
  31. organization: Organization;
  32. additionalContext?: React.ReactNode;
  33. disabled?: boolean;
  34. onSubmitSuccess?: (data: {relayPiiConfig: string}) => void;
  35. project?: Project;
  36. relayPiiConfig?: string | null;
  37. };
  38. export function DataScrubbing({
  39. project,
  40. endpoint,
  41. organization,
  42. disabled,
  43. onSubmitSuccess,
  44. additionalContext,
  45. relayPiiConfig,
  46. }: Props) {
  47. const api = useApi();
  48. const [rules, setRules] = useState<Rule[]>([]);
  49. const navigate = useNavigate();
  50. const params = useParams();
  51. const successfullySaved = useCallback(
  52. (response: {relayPiiConfig: string}, successMessage: string) => {
  53. setRules(convertRelayPiiConfig(response.relayPiiConfig));
  54. addSuccessMessage(successMessage);
  55. onSubmitSuccess?.(response);
  56. },
  57. [onSubmitSuccess]
  58. );
  59. const handleCloseModal = useCallback(() => {
  60. const path = project?.slug
  61. ? `/settings/${organization.slug}/projects/${project.slug}/security-and-privacy/`
  62. : `/settings/${organization.slug}/security-and-privacy/`;
  63. navigate(path);
  64. }, [navigate, organization.slug, project?.slug]);
  65. useEffect(() => {
  66. if (
  67. !defined(params.scrubbingId) ||
  68. !rules.some(rule => String(rule.id) === params.scrubbingId)
  69. ) {
  70. return;
  71. }
  72. openModal(
  73. modalProps => (
  74. <Edit
  75. {...modalProps}
  76. rule={rules[params.scrubbingId]}
  77. projectId={project?.id}
  78. savedRules={rules}
  79. api={api}
  80. endpoint={endpoint}
  81. orgSlug={organization.slug}
  82. onSubmitSuccess={response => {
  83. return successfullySaved(
  84. response,
  85. t('Successfully updated data scrubbing rule')
  86. );
  87. }}
  88. />
  89. ),
  90. {onClose: handleCloseModal}
  91. );
  92. }, [
  93. params.scrubbingId,
  94. rules,
  95. project?.id,
  96. api,
  97. endpoint,
  98. organization.slug,
  99. successfullySaved,
  100. handleCloseModal,
  101. ]);
  102. useEffect(() => {
  103. function loadRules() {
  104. try {
  105. setRules(convertRelayPiiConfig(relayPiiConfig));
  106. } catch {
  107. addErrorMessage(t('Unable to load data scrubbing rules'));
  108. }
  109. }
  110. loadRules();
  111. }, [relayPiiConfig]);
  112. function handleEdit(id: Rule['id']) {
  113. const path = project
  114. ? `/settings/${organization.slug}/projects/${project.slug}/security-and-privacy/advanced-data-scrubbing/${id}/`
  115. : `/settings/${organization.slug}/security-and-privacy/advanced-data-scrubbing/${id}/`;
  116. navigate(path);
  117. }
  118. function handleAdd() {
  119. openModal(modalProps => (
  120. <Add
  121. {...modalProps}
  122. projectId={project?.id}
  123. savedRules={rules}
  124. api={api}
  125. endpoint={endpoint}
  126. orgSlug={organization.slug}
  127. onSubmitSuccess={response => {
  128. return successfullySaved(response, t('Successfully added data scrubbing rule'));
  129. }}
  130. />
  131. ));
  132. }
  133. async function handleDelete(id: Rule['id']) {
  134. try {
  135. const filteredRules = rules.filter(rule => rule.id !== id);
  136. const data = await submitRules(api, endpoint, filteredRules);
  137. if (data?.relayPiiConfig) {
  138. setRules(convertRelayPiiConfig(data.relayPiiConfig));
  139. addSuccessMessage(t('Successfully deleted data scrubbing rule'));
  140. }
  141. } catch {
  142. addErrorMessage(t('An unknown error occurred while deleting data scrubbing rule'));
  143. }
  144. }
  145. return (
  146. <Panel data-test-id="advanced-data-scrubbing">
  147. <PanelHeader>
  148. <div>{t('Advanced Data Scrubbing')}</div>
  149. </PanelHeader>
  150. <PanelAlert type="info">
  151. {additionalContext}{' '}
  152. {tct(
  153. 'The new rules will only apply to upcoming events. For more details, see [linkToDocs].',
  154. {
  155. linkToDocs: (
  156. <ExternalLink href={ADVANCED_DATASCRUBBING_LINK}>
  157. {t('full documentation on data scrubbing')}
  158. </ExternalLink>
  159. ),
  160. }
  161. )}
  162. </PanelAlert>
  163. <PanelBody>
  164. {project && <OrganizationRules organization={organization} />}
  165. {!rules.length ? (
  166. <EmptyMessage
  167. icon={<IconWarning size="xl" />}
  168. description={t('You have no data scrubbing rules')}
  169. />
  170. ) : (
  171. <Rules
  172. rules={rules}
  173. onDeleteRule={handleDelete}
  174. onEditRule={handleEdit}
  175. disabled={disabled}
  176. />
  177. )}
  178. <PanelAction>
  179. <Button href={ADVANCED_DATASCRUBBING_LINK} external>
  180. {t('Read Docs')}
  181. </Button>
  182. <Button disabled={disabled} onClick={handleAdd} priority="primary">
  183. {t('Add Rule')}
  184. </Button>
  185. </PanelAction>
  186. </PanelBody>
  187. </Panel>
  188. );
  189. }
  190. const PanelAction = styled('div')`
  191. padding: ${space(1)} ${space(2)};
  192. position: relative;
  193. display: grid;
  194. gap: ${space(1)};
  195. grid-template-columns: auto auto;
  196. justify-content: flex-end;
  197. border-top: 1px solid ${p => p.theme.border};
  198. `;