projectServiceHooks.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. import {Fragment} from 'react';
  2. import {RouteComponentProps} from 'react-router';
  3. import {
  4. addErrorMessage,
  5. addLoadingMessage,
  6. clearIndicators,
  7. } from 'sentry/actionCreators/indicator';
  8. import Button from 'sentry/components/button';
  9. import EmptyMessage from 'sentry/components/emptyMessage';
  10. import Field from 'sentry/components/forms/field';
  11. import Link from 'sentry/components/links/link';
  12. import {Panel, PanelAlert, PanelBody, PanelHeader} from 'sentry/components/panels';
  13. import Switch from 'sentry/components/switchButton';
  14. import Truncate from 'sentry/components/truncate';
  15. import {IconAdd} from 'sentry/icons';
  16. import {t} from 'sentry/locale';
  17. import {Organization, ServiceHook} from 'sentry/types';
  18. import withOrganization from 'sentry/utils/withOrganization';
  19. import AsyncView from 'sentry/views/asyncView';
  20. import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
  21. type RowProps = {
  22. hook: ServiceHook;
  23. onToggleActive: () => void;
  24. orgId: string;
  25. projectId: string;
  26. };
  27. function ServiceHookRow({orgId, projectId, hook, onToggleActive}: RowProps) {
  28. return (
  29. <Field
  30. label={
  31. <Link
  32. data-test-id="project-service-hook"
  33. to={`/settings/${orgId}/projects/${projectId}/hooks/${hook.id}/`}
  34. >
  35. <Truncate value={hook.url} />
  36. </Link>
  37. }
  38. help={
  39. <small>
  40. {hook.events && hook.events.length !== 0 ? (
  41. hook.events.join(', ')
  42. ) : (
  43. <em>{t('no events configured')}</em>
  44. )}
  45. </small>
  46. }
  47. >
  48. <Switch isActive={hook.status === 'active'} size="lg" toggle={onToggleActive} />
  49. </Field>
  50. );
  51. }
  52. type Props = RouteComponentProps<{orgId: string; projectId: string}, {}> & {
  53. organization: Organization;
  54. };
  55. type State = {
  56. hookList: null | ServiceHook[];
  57. } & AsyncView['state'];
  58. class ProjectServiceHooks extends AsyncView<Props, State> {
  59. getEndpoints(): ReturnType<AsyncView['getEndpoints']> {
  60. const {orgId, projectId} = this.props.params;
  61. return [['hookList', `/projects/${orgId}/${projectId}/hooks/`]];
  62. }
  63. onToggleActive = (hook: ServiceHook) => {
  64. const {orgId, projectId} = this.props.params;
  65. const {hookList} = this.state;
  66. if (!hookList) {
  67. return;
  68. }
  69. addLoadingMessage(t('Saving changes\u2026'));
  70. this.api.request(`/projects/${orgId}/${projectId}/hooks/${hook.id}/`, {
  71. method: 'PUT',
  72. data: {
  73. isActive: hook.status !== 'active',
  74. },
  75. success: data => {
  76. clearIndicators();
  77. this.setState({
  78. hookList: hookList.map(h => {
  79. if (h.id === data.id) {
  80. return {
  81. ...h,
  82. ...data,
  83. };
  84. }
  85. return h;
  86. }),
  87. });
  88. },
  89. error: () => {
  90. addErrorMessage(t('Unable to remove application. Please try again.'));
  91. },
  92. });
  93. };
  94. renderEmpty() {
  95. return (
  96. <EmptyMessage>
  97. {t('There are no service hooks associated with this project.')}
  98. </EmptyMessage>
  99. );
  100. }
  101. renderResults() {
  102. const {orgId, projectId} = this.props.params;
  103. return (
  104. <Fragment>
  105. <PanelHeader key="header">{t('Service Hook')}</PanelHeader>
  106. <PanelBody key="body">
  107. <PanelAlert type="info" showIcon>
  108. {t(
  109. 'Service Hooks are an early adopter preview feature and will change in the future.'
  110. )}
  111. </PanelAlert>
  112. {this.state.hookList?.map(hook => (
  113. <ServiceHookRow
  114. key={hook.id}
  115. orgId={orgId}
  116. projectId={projectId}
  117. hook={hook}
  118. onToggleActive={this.onToggleActive.bind(this, hook)}
  119. />
  120. ))}
  121. </PanelBody>
  122. </Fragment>
  123. );
  124. }
  125. renderBody() {
  126. const {hookList} = this.state;
  127. const body =
  128. hookList && hookList.length > 0 ? this.renderResults() : this.renderEmpty();
  129. const {orgId, projectId} = this.props.params;
  130. const access = new Set(this.props.organization.access);
  131. return (
  132. <Fragment>
  133. <SettingsPageHeader
  134. title={t('Service Hooks')}
  135. action={
  136. access.has('project:write') ? (
  137. <Button
  138. data-test-id="new-service-hook"
  139. to={`/settings/${orgId}/projects/${projectId}/hooks/new/`}
  140. size="sm"
  141. priority="primary"
  142. icon={<IconAdd size="xs" isCircled />}
  143. >
  144. {t('Create New Hook')}
  145. </Button>
  146. ) : null
  147. }
  148. />
  149. <Panel>{body}</Panel>
  150. </Fragment>
  151. );
  152. }
  153. }
  154. export default withOrganization(ProjectServiceHooks);