projectServiceHooks.tsx 4.9 KB

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