organizationRules.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. import {Component, createRef} from 'react';
  2. import styled from '@emotion/styled';
  3. import {addErrorMessage} from 'sentry/actionCreators/indicator';
  4. import {Button} from 'sentry/components/button';
  5. import {IconChevron} from 'sentry/icons';
  6. import {t} from 'sentry/locale';
  7. import {space} from 'sentry/styles/space';
  8. import {Organization} from 'sentry/types';
  9. import {convertRelayPiiConfig} from 'sentry/views/settings/components/dataScrubbing/convertRelayPiiConfig';
  10. import Rules from './rules';
  11. import {Rule} from './types';
  12. type Props = {
  13. organization: Organization;
  14. };
  15. type State = {
  16. isCollapsed: boolean;
  17. rules: Rule[];
  18. contentHeight?: string;
  19. };
  20. export class OrganizationRules extends Component<Props, State> {
  21. state: State = {
  22. isCollapsed: true,
  23. rules: [],
  24. };
  25. componentDidMount() {
  26. this.loadRules();
  27. }
  28. componentDidUpdate(prevProps: Props) {
  29. if (
  30. prevProps.organization.relayPiiConfig !== this.props.organization.relayPiiConfig
  31. ) {
  32. this.loadRules();
  33. return;
  34. }
  35. this.loadContentHeight();
  36. }
  37. rulesRef = createRef<HTMLUListElement>();
  38. loadContentHeight() {
  39. if (!this.state.contentHeight) {
  40. const contentHeight = this.rulesRef.current?.offsetHeight;
  41. if (contentHeight) {
  42. this.setState({contentHeight: `${contentHeight}px`});
  43. }
  44. }
  45. }
  46. handleToggleCollapsed = () => {
  47. this.setState(prevState => ({
  48. isCollapsed: !prevState.isCollapsed,
  49. }));
  50. };
  51. loadRules() {
  52. try {
  53. this.setState({
  54. rules: convertRelayPiiConfig(this.props.organization.relayPiiConfig),
  55. });
  56. } catch {
  57. addErrorMessage(t('Unable to load data scrubbing rules'));
  58. }
  59. }
  60. render() {
  61. const {isCollapsed, contentHeight, rules} = this.state;
  62. if (rules.length === 0) {
  63. return (
  64. <Wrapper>
  65. {t('There are no data scrubbing rules at the organization level')}
  66. </Wrapper>
  67. );
  68. }
  69. return (
  70. <Wrapper isCollapsed={isCollapsed} contentHeight={contentHeight}>
  71. <Header onClick={this.handleToggleCollapsed}>
  72. <div>{t('Organization Rules')}</div>
  73. <Button
  74. title={
  75. isCollapsed
  76. ? t('Expand Organization Rules')
  77. : t('Collapse Organization Rules')
  78. }
  79. icon={<IconChevron size="xs" direction={isCollapsed ? 'down' : 'up'} />}
  80. size="xs"
  81. aria-label={t('Toggle Organization Rules')}
  82. />
  83. </Header>
  84. <Content>
  85. <Rules rules={rules} ref={this.rulesRef} disabled />
  86. </Content>
  87. </Wrapper>
  88. );
  89. }
  90. }
  91. const Content = styled('div')`
  92. transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
  93. height: 0;
  94. overflow: hidden;
  95. `;
  96. const Header = styled('div')`
  97. cursor: pointer;
  98. display: grid;
  99. grid-template-columns: 1fr auto;
  100. align-items: center;
  101. border-bottom: 1px solid ${p => p.theme.border};
  102. padding: ${space(1)} ${space(2)};
  103. `;
  104. const Wrapper = styled('div')<{contentHeight?: string; isCollapsed?: boolean}>`
  105. color: ${p => p.theme.gray200};
  106. background: ${p => p.theme.backgroundSecondary};
  107. ${p => !p.contentHeight && `padding: ${space(1)} ${space(2)}`};
  108. ${p => !p.isCollapsed && ` border-bottom: 1px solid ${p.theme.border}`};
  109. ${p =>
  110. !p.isCollapsed &&
  111. p.contentHeight &&
  112. `
  113. ${Content} {
  114. height: ${p.contentHeight};
  115. }
  116. `}
  117. `;