apiTokens.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import {
  2. addErrorMessage,
  3. addLoadingMessage,
  4. addSuccessMessage,
  5. } from 'sentry/actionCreators/indicator';
  6. import {LinkButton} from 'sentry/components/button';
  7. import EmptyMessage from 'sentry/components/emptyMessage';
  8. import ExternalLink from 'sentry/components/links/externalLink';
  9. import LoadingError from 'sentry/components/loadingError';
  10. import LoadingIndicator from 'sentry/components/loadingIndicator';
  11. import Panel from 'sentry/components/panels/panel';
  12. import PanelBody from 'sentry/components/panels/panelBody';
  13. import PanelHeader from 'sentry/components/panels/panelHeader';
  14. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  15. import {t, tct} from 'sentry/locale';
  16. import type {InternalAppApiToken} from 'sentry/types/user';
  17. import {
  18. getApiQueryData,
  19. setApiQueryData,
  20. useApiQuery,
  21. useMutation,
  22. useQueryClient,
  23. } from 'sentry/utils/queryClient';
  24. import useApi from 'sentry/utils/useApi';
  25. import ApiTokenRow from 'sentry/views/settings/account/apiTokenRow';
  26. import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
  27. import TextBlock from 'sentry/views/settings/components/text/textBlock';
  28. const PAGE_TITLE = t('User Auth Tokens');
  29. const API_TOKEN_QUERY_KEY = ['/api-tokens/'] as const;
  30. export function ApiTokens() {
  31. const api = useApi();
  32. const queryClient = useQueryClient();
  33. const {
  34. data: tokenList,
  35. isPending,
  36. isError,
  37. refetch,
  38. } = useApiQuery<InternalAppApiToken[]>(API_TOKEN_QUERY_KEY, {staleTime: 0});
  39. const {mutate: deleteToken} = useMutation(
  40. (token: InternalAppApiToken) => {
  41. return api.requestPromise('/api-tokens/', {
  42. method: 'DELETE',
  43. data: {tokenId: token.id},
  44. });
  45. },
  46. {
  47. onMutate: token => {
  48. addLoadingMessage();
  49. queryClient.cancelQueries(API_TOKEN_QUERY_KEY);
  50. const previous = getApiQueryData<InternalAppApiToken[]>(
  51. queryClient,
  52. API_TOKEN_QUERY_KEY
  53. );
  54. setApiQueryData<InternalAppApiToken[]>(
  55. queryClient,
  56. API_TOKEN_QUERY_KEY,
  57. oldTokenList => {
  58. return oldTokenList?.filter(tk => tk.id !== token.id);
  59. }
  60. );
  61. return {previous};
  62. },
  63. onSuccess: _data => {
  64. addSuccessMessage(t('Removed token'));
  65. },
  66. onError: (_error, _variables, context) => {
  67. addErrorMessage(t('Unable to remove token. Please try again.'));
  68. if (context?.previous) {
  69. setApiQueryData<InternalAppApiToken[]>(
  70. queryClient,
  71. API_TOKEN_QUERY_KEY,
  72. context.previous
  73. );
  74. }
  75. },
  76. onSettled: () => {
  77. queryClient.invalidateQueries(API_TOKEN_QUERY_KEY);
  78. },
  79. }
  80. );
  81. if (isPending) {
  82. return <LoadingIndicator />;
  83. }
  84. if (isError) {
  85. return <LoadingError onRetry={refetch} />;
  86. }
  87. const isEmpty = !Array.isArray(tokenList) || tokenList.length === 0;
  88. const action = (
  89. <LinkButton
  90. priority="primary"
  91. size="sm"
  92. to="/settings/account/api/auth-tokens/new-token/"
  93. >
  94. {t('Create New Token')}
  95. </LinkButton>
  96. );
  97. return (
  98. <SentryDocumentTitle title={PAGE_TITLE}>
  99. <SettingsPageHeader title={PAGE_TITLE} action={action} />
  100. <TextBlock>
  101. {t(
  102. "Authentication tokens allow you to perform actions against the Sentry API on behalf of your account. They're the easiest way to get started using the API."
  103. )}
  104. </TextBlock>
  105. <TextBlock>
  106. {tct(
  107. 'For more information on how to use the web API, see our [link:documentation].',
  108. {
  109. link: <ExternalLink href="https://docs.sentry.io/api/" />,
  110. }
  111. )}
  112. </TextBlock>
  113. <Panel>
  114. <PanelHeader>{t('Auth Token')}</PanelHeader>
  115. <PanelBody>
  116. {isEmpty && (
  117. <EmptyMessage>
  118. {t("You haven't created any authentication tokens yet.")}
  119. </EmptyMessage>
  120. )}
  121. {tokenList?.map(token => (
  122. <ApiTokenRow key={token.id} token={token} onRemove={deleteToken} canEdit />
  123. ))}
  124. </PanelBody>
  125. </Panel>
  126. </SentryDocumentTitle>
  127. );
  128. }
  129. export default ApiTokens;