sentryAppUpdateModal.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import {Fragment, useState} from 'react';
  2. import {addLoadingMessage, clearIndicators} from 'sentry/actionCreators/indicator';
  3. import type {ModalRenderProps} from 'sentry/actionCreators/modal';
  4. import NumberField from 'sentry/components/forms/fields/numberField';
  5. import SelectField from 'sentry/components/forms/fields/selectField';
  6. import Form from 'sentry/components/forms/form';
  7. import LoadingError from 'sentry/components/loadingError';
  8. import LoadingIndicator from 'sentry/components/loadingIndicator';
  9. import {tct} from 'sentry/locale';
  10. import type {IntegrationFeature} from 'sentry/types/integrations';
  11. import {useApiQuery, useMutation} from 'sentry/utils/queryClient';
  12. import type RequestError from 'sentry/utils/requestError/requestError';
  13. import useApi from 'sentry/utils/useApi';
  14. const fieldProps = {
  15. stacked: true,
  16. inline: false,
  17. flexibleControlStateSize: true,
  18. } as const;
  19. type Props = ModalRenderProps & {
  20. onAction: (data: any) => void;
  21. sentryAppData: any;
  22. };
  23. type SubmitQueryVariables = {
  24. data: Record<string, any>;
  25. onSubmitError: (error: any) => void;
  26. onSubmitSuccess: (response: Record<string, any>) => void;
  27. };
  28. type SubmitQueryResponse = Record<string, any>;
  29. // See Django reference for PositiveSmallIntegerField
  30. // (https://docs.djangoproject.com/en/3.2/ref/models/fields/#positivesmallintegerfield)
  31. const POPULARITY_MIN = 0;
  32. const POPULARITY_MAX = 32767;
  33. function SentryAppUpdateModal(props: Props) {
  34. const api = useApi({persistInFlight: true});
  35. const {sentryAppData, closeModal, Header, Body} = props;
  36. const [popularityError, setPopularityError] = useState(false);
  37. const onPopularityChange = (value: any) => {
  38. const popularity = parseInt(value, 10);
  39. const hasError =
  40. isNaN(popularity) || popularity < POPULARITY_MIN || popularity > POPULARITY_MAX;
  41. if (hasError) {
  42. setPopularityError(true);
  43. }
  44. };
  45. const onSubmitMutation = useMutation<
  46. SubmitQueryResponse,
  47. RequestError,
  48. SubmitQueryVariables
  49. >({
  50. mutationFn: ({data}: SubmitQueryVariables) => {
  51. return api.requestPromise(`/sentry-apps/${sentryAppData.slug}/`, {
  52. method: 'PUT',
  53. data,
  54. });
  55. },
  56. onMutate: () => {
  57. addLoadingMessage('Saving changes\u2026');
  58. },
  59. onSuccess: (data: Record<string, any>, {onSubmitSuccess}) => {
  60. clearIndicators();
  61. onSubmitSuccess(data);
  62. },
  63. onError: (err: RequestError, {onSubmitError}) => {
  64. clearIndicators();
  65. onSubmitError(err);
  66. },
  67. });
  68. const {
  69. data: featureData,
  70. isPending,
  71. isError,
  72. refetch,
  73. } = useApiQuery<IntegrationFeature[]>([`/integration-features/`], {
  74. staleTime: 0,
  75. });
  76. if (isPending) {
  77. return <LoadingIndicator />;
  78. }
  79. if (isError) {
  80. return <LoadingError onRetry={refetch} />;
  81. }
  82. const getFeatures = (): Array<[number, string]> => {
  83. if (!featureData) {
  84. return [];
  85. }
  86. return featureData.map(({featureId, featureGate}) => [
  87. featureId,
  88. featureGate.replace(/(^integrations-)/, ''),
  89. ]);
  90. };
  91. const getInitialData = () => {
  92. return {
  93. ...sentryAppData,
  94. features: sentryAppData?.featureData?.map(({featureId}: any) => featureId),
  95. };
  96. };
  97. return (
  98. <Fragment>
  99. <Header>Update Sentry App</Header>
  100. <Body>
  101. <Form
  102. submitDisabled={popularityError}
  103. onSubmit={(data, onSubmitSuccess, onSubmitError) =>
  104. onSubmitMutation.mutate({data, onSubmitSuccess, onSubmitError})
  105. }
  106. onSubmitSuccess={() => {
  107. closeModal();
  108. }}
  109. initialData={getInitialData()}
  110. >
  111. <NumberField
  112. {...fieldProps}
  113. name="popularity"
  114. label="New popularity"
  115. help={tct(
  116. 'Higher values will be more prominent on the integration directory. Only values between [POPULARITY_MIN] and [POPULARITY_MAX] are permitted.',
  117. {
  118. POPULARITY_MIN,
  119. POPULARITY_MAX,
  120. }
  121. )}
  122. onChange={onPopularityChange}
  123. defaultValue={sentryAppData.popularity}
  124. />
  125. <SelectField
  126. {...fieldProps}
  127. multiple
  128. name="features"
  129. label="Features"
  130. help="What features does this integration have?"
  131. choices={getFeatures()}
  132. required
  133. />
  134. </Form>
  135. </Body>
  136. </Fragment>
  137. );
  138. }
  139. export default SentryAppUpdateModal;