rules.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. import {forwardRef} from 'react';
  2. import styled from '@emotion/styled';
  3. import Button from 'sentry/components/button';
  4. import ConfirmDelete from 'sentry/components/confirmDelete';
  5. import TextOverflow from 'sentry/components/textOverflow';
  6. import {IconDelete, IconEdit} from 'sentry/icons';
  7. import {t} from 'sentry/locale';
  8. import space from 'sentry/styles/space';
  9. import {MethodType, Rule, RuleType} from './types';
  10. import {getMethodLabel, getRuleLabel} from './utils';
  11. type Props = {
  12. rules: Array<Rule>;
  13. disabled?: boolean;
  14. onDeleteRule?: (id: Rule['id']) => () => void;
  15. onEditRule?: (id: Rule['id']) => () => void;
  16. };
  17. const getListItemDescription = (rule: Rule) => {
  18. const {method, type, source} = rule;
  19. const methodLabel = getMethodLabel(method);
  20. const typeLabel = getRuleLabel(type);
  21. const descriptionDetails: Array<string> = [];
  22. descriptionDetails.push(`[${methodLabel.label}]`);
  23. descriptionDetails.push(
  24. rule.type === RuleType.PATTERN ? `[${rule.pattern}]` : `[${typeLabel}]`
  25. );
  26. if (rule.method === MethodType.REPLACE && rule.placeholder) {
  27. descriptionDetails.push(`with [${rule.placeholder}]`);
  28. }
  29. return `${descriptionDetails.join(' ')} ${t('from')} [${source}]`;
  30. };
  31. const Rules = forwardRef(function RulesList(
  32. {rules, onEditRule, onDeleteRule, disabled}: Props,
  33. ref: React.Ref<HTMLUListElement>
  34. ) {
  35. return (
  36. <List ref={ref} isDisabled={disabled} data-test-id="advanced-data-scrubbing-rules">
  37. {rules.map(rule => {
  38. const {id} = rule;
  39. const ruleDescription = getListItemDescription(rule);
  40. return (
  41. <ListItem key={id}>
  42. <TextOverflow>{ruleDescription}</TextOverflow>
  43. {onEditRule && (
  44. <Button
  45. aria-label={t('Edit Rule')}
  46. size="sm"
  47. onClick={onEditRule(id)}
  48. icon={<IconEdit />}
  49. disabled={disabled}
  50. title={
  51. disabled ? t('You do not have permission to edit rules') : undefined
  52. }
  53. />
  54. )}
  55. {onDeleteRule && (
  56. <ConfirmDelete
  57. message={t('Are you sure you wish to delete this rule?')}
  58. priority="danger"
  59. onConfirm={() => {
  60. onDeleteRule(id)();
  61. }}
  62. confirmInput={ruleDescription}
  63. disabled={disabled}
  64. stopPropagation
  65. >
  66. <Button
  67. aria-label={t('Delete Rule')}
  68. size="sm"
  69. icon={<IconDelete />}
  70. disabled={disabled}
  71. title={
  72. disabled ? t('You do not have permission to delete rules') : undefined
  73. }
  74. />
  75. </ConfirmDelete>
  76. )}
  77. </ListItem>
  78. );
  79. })}
  80. </List>
  81. );
  82. });
  83. export default Rules;
  84. const List = styled('ul')<{
  85. isDisabled?: boolean;
  86. }>`
  87. list-style: none;
  88. margin: 0;
  89. padding: 0;
  90. margin-bottom: 0 !important;
  91. ${p =>
  92. p.isDisabled &&
  93. `
  94. color: ${p.theme.gray200};
  95. background: ${p.theme.backgroundSecondary};
  96. `}
  97. `;
  98. const ListItem = styled('li')`
  99. display: grid;
  100. grid-template-columns: auto max-content max-content;
  101. grid-column-gap: ${space(1)};
  102. align-items: center;
  103. padding: ${space(1)} ${space(2)};
  104. border-bottom: 1px solid ${p => p.theme.border};
  105. &:hover {
  106. background-color: ${p => p.theme.backgroundSecondary};
  107. }
  108. &:last-child {
  109. border-bottom: 0;
  110. }
  111. `;