index.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import {Fragment, useMemo} from 'react';
  2. import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
  3. import {openAddTempestCredentialsModal} from 'sentry/actionCreators/modal';
  4. import Alert from 'sentry/components/alert';
  5. import {Button} from 'sentry/components/button';
  6. import Form from 'sentry/components/forms/form';
  7. import JsonForm from 'sentry/components/forms/jsonForm';
  8. import List from 'sentry/components/list';
  9. import ListItem from 'sentry/components/list/listItem';
  10. import {PanelTable} from 'sentry/components/panels/panelTable';
  11. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  12. import {Tooltip} from 'sentry/components/tooltip';
  13. import {t} from 'sentry/locale';
  14. import type {Organization} from 'sentry/types/organization';
  15. import type {Project} from 'sentry/types/project';
  16. import {handleXhrErrorResponse} from 'sentry/utils/handleXhrErrorResponse';
  17. import {useMutation} from 'sentry/utils/queryClient';
  18. import type RequestError from 'sentry/utils/requestError/requestError';
  19. import {hasTempestAccess} from 'sentry/utils/tempest/features';
  20. import useApi from 'sentry/utils/useApi';
  21. import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
  22. import {useFetchTempestCredentials} from 'sentry/views/settings/project/tempest/hooks/useFetchTempestCredentials';
  23. import {MessageType} from 'sentry/views/settings/project/tempest/types';
  24. import {useHasTempestWriteAccess} from 'sentry/views/settings/project/tempest/utils/access';
  25. import {CredentialRow} from './CredentialRow';
  26. interface Props {
  27. organization: Organization;
  28. project: Project;
  29. }
  30. export default function TempestSettings({organization, project}: Props) {
  31. const hasWriteAccess = useHasTempestWriteAccess();
  32. const {
  33. data: tempestCredentials,
  34. isLoading,
  35. invalidateCredentialsCache,
  36. } = useFetchTempestCredentials(organization, project);
  37. const api = useApi();
  38. const {mutate: handleRemoveCredential, isPending: isRemoving} = useMutation<
  39. {},
  40. RequestError,
  41. {id: number}
  42. >({
  43. mutationFn: ({id}) =>
  44. api.requestPromise(
  45. `/projects/${organization.slug}/${project.slug}/tempest-credentials/${id}/`,
  46. {
  47. method: 'DELETE',
  48. }
  49. ),
  50. onSuccess: () => {
  51. addSuccessMessage(t('Removed the credentials.'));
  52. invalidateCredentialsCache();
  53. },
  54. onError: error => {
  55. const message = t('Failed to remove the credentials.');
  56. handleXhrErrorResponse(message, error);
  57. addErrorMessage(message);
  58. },
  59. });
  60. const credentialErrors = useMemo(() => {
  61. return tempestCredentials?.filter(
  62. credential => credential.messageType === MessageType.ERROR && credential.message
  63. );
  64. }, [tempestCredentials]);
  65. if (!hasTempestAccess(organization)) {
  66. return <Alert type="warning">{t("You don't have access to this feature")}</Alert>;
  67. }
  68. return (
  69. <Fragment>
  70. <SentryDocumentTitle title={t('PlayStation')} />
  71. <SettingsPageHeader
  72. title={t('PlayStation')}
  73. action={addNewCredentials(hasWriteAccess, organization, project)}
  74. />
  75. {credentialErrors && credentialErrors?.length > 0 && (
  76. <Alert type="error" showIcon>
  77. {t('There was a problem with following credentials:')}
  78. <List symbol="bullet">
  79. {credentialErrors.map(credential => (
  80. <ListItem key={credential.id}>
  81. {credential.clientId} - {credential.message}
  82. </ListItem>
  83. ))}
  84. </List>
  85. </Alert>
  86. )}
  87. <Form
  88. apiMethod="PUT"
  89. apiEndpoint={`/projects/${organization.slug}/${project.slug}/`}
  90. initialData={{
  91. tempestFetchScreenshots: project?.tempestFetchScreenshots,
  92. }}
  93. saveOnBlur
  94. hideFooter
  95. >
  96. <JsonForm
  97. forms={[
  98. {
  99. title: t('General Settings'),
  100. fields: [
  101. {
  102. name: 'tempestFetchScreenshots',
  103. type: 'boolean',
  104. label: t('Attach Screenshots'),
  105. help: t('Attach screenshots to issues.'),
  106. },
  107. ],
  108. },
  109. ]}
  110. />
  111. </Form>
  112. <PanelTable
  113. headers={[
  114. t('Client ID'),
  115. t('Client Secret'),
  116. t('Created At'),
  117. t('Created By'),
  118. '',
  119. ]}
  120. isLoading={isLoading}
  121. isEmpty={!tempestCredentials?.length}
  122. >
  123. {tempestCredentials?.map(credential => (
  124. <CredentialRow
  125. key={credential.id}
  126. credential={credential}
  127. isRemoving={isRemoving}
  128. removeCredential={hasWriteAccess ? handleRemoveCredential : undefined}
  129. />
  130. ))}
  131. </PanelTable>
  132. </Fragment>
  133. );
  134. }
  135. const addNewCredentials = (
  136. hasWriteAccess: boolean,
  137. organization: Organization,
  138. project: Project
  139. ) => (
  140. <Tooltip
  141. title={t('You must be an organization admin to add new credentials.')}
  142. disabled={hasWriteAccess}
  143. >
  144. <Button
  145. priority="primary"
  146. size="sm"
  147. data-test-id="create-new-credentials"
  148. disabled={!hasWriteAccess}
  149. onClick={() => openAddTempestCredentialsModal({organization, project})}
  150. >
  151. {t('Add Credentials')}
  152. </Button>
  153. </Tooltip>
  154. );