codeowners.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. import {Component, Fragment} from 'react';
  2. import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
  3. import type {Client} from 'sentry/api';
  4. import {Button} from 'sentry/components/button';
  5. import Confirm from 'sentry/components/confirm';
  6. import {IconDelete, IconSync} from 'sentry/icons';
  7. import {t} from 'sentry/locale';
  8. import type {CodeOwner, CodeownersFile, Organization, Project} from 'sentry/types';
  9. import withApi from 'sentry/utils/withApi';
  10. import RulesPanel from 'sentry/views/settings/project/projectOwnership/rulesPanel';
  11. type Props = {
  12. api: Client;
  13. codeowners: CodeOwner[];
  14. disabled: boolean;
  15. onDelete: (data: CodeOwner) => void;
  16. onUpdate: (data: CodeOwner) => void;
  17. organization: Organization;
  18. project: Project;
  19. };
  20. class CodeOwnersPanel extends Component<Props> {
  21. handleDelete = async (codeowner: CodeOwner) => {
  22. const {api, organization, project, onDelete} = this.props;
  23. const endpoint = `/api/0/projects/${organization.slug}/${project.slug}/codeowners/${codeowner.id}/`;
  24. try {
  25. await api.requestPromise(endpoint, {
  26. method: 'DELETE',
  27. });
  28. onDelete(codeowner);
  29. addSuccessMessage(t('Deletion successful'));
  30. } catch {
  31. // no 4xx errors should happen on delete
  32. addErrorMessage(t('An error occurred'));
  33. }
  34. };
  35. handleSync = async (codeowner: CodeOwner) => {
  36. const {api, organization, project, onUpdate} = this.props;
  37. try {
  38. const codeownerFile: CodeownersFile = await api.requestPromise(
  39. `/organizations/${organization.slug}/code-mappings/${codeowner.codeMappingId}/codeowners/`,
  40. {
  41. method: 'GET',
  42. }
  43. );
  44. const data = await api.requestPromise(
  45. `/projects/${organization.slug}/${project.slug}/codeowners/${codeowner.id}/`,
  46. {
  47. method: 'PUT',
  48. data: {raw: codeownerFile.raw},
  49. }
  50. );
  51. onUpdate({...codeowner, ...data});
  52. addSuccessMessage(t('CODEOWNERS file sync successful.'));
  53. } catch (_err) {
  54. addErrorMessage(t('An error occurred trying to sync CODEOWNERS file.'));
  55. }
  56. };
  57. render() {
  58. const {codeowners, disabled} = this.props;
  59. return codeowners.map(codeowner => {
  60. const {dateUpdated, provider, codeMapping, ownershipSyntax} = codeowner;
  61. return (
  62. <Fragment key={codeowner.id}>
  63. <RulesPanel
  64. data-test-id="codeowners-panel"
  65. type="codeowners"
  66. raw={ownershipSyntax || ''}
  67. dateUpdated={dateUpdated}
  68. provider={provider}
  69. repoName={codeMapping?.repoName}
  70. controls={[
  71. <Button
  72. key="sync"
  73. icon={<IconSync />}
  74. size="xs"
  75. onClick={() => this.handleSync(codeowner)}
  76. disabled={disabled}
  77. aria-label={t('Sync')}
  78. />,
  79. <Confirm
  80. onConfirm={() => this.handleDelete(codeowner)}
  81. message={t('Are you sure you want to remove this CODEOWNERS file?')}
  82. key="confirm-delete"
  83. disabled={disabled}
  84. >
  85. <Button
  86. key="delete"
  87. icon={<IconDelete />}
  88. aria-label={t('Delete')}
  89. size="xs"
  90. />
  91. </Confirm>,
  92. ]}
  93. />
  94. </Fragment>
  95. );
  96. });
  97. }
  98. }
  99. export default withApi(CodeOwnersPanel);