apiTokens.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  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. mutationFn: (token: InternalAppApiToken) => {
  41. return api.requestPromise('/api-tokens/', {
  42. method: 'DELETE',
  43. data: {tokenId: token.id},
  44. });
  45. },
  46. onMutate: token => {
  47. addLoadingMessage();
  48. queryClient.cancelQueries({queryKey: API_TOKEN_QUERY_KEY});
  49. const previous = getApiQueryData<InternalAppApiToken[]>(
  50. queryClient,
  51. API_TOKEN_QUERY_KEY
  52. );
  53. setApiQueryData<InternalAppApiToken[]>(
  54. queryClient,
  55. API_TOKEN_QUERY_KEY,
  56. oldTokenList => {
  57. return oldTokenList?.filter(tk => tk.id !== token.id);
  58. }
  59. );
  60. return {previous};
  61. },
  62. onSuccess: _data => {
  63. addSuccessMessage(t('Removed token'));
  64. },
  65. onError: (_error, _variables, context) => {
  66. addErrorMessage(t('Unable to remove token. Please try again.'));
  67. if (context?.previous) {
  68. setApiQueryData<InternalAppApiToken[]>(
  69. queryClient,
  70. API_TOKEN_QUERY_KEY,
  71. context.previous
  72. );
  73. }
  74. },
  75. onSettled: () => {
  76. queryClient.invalidateQueries({queryKey: API_TOKEN_QUERY_KEY});
  77. },
  78. });
  79. if (isPending) {
  80. return <LoadingIndicator />;
  81. }
  82. if (isError) {
  83. return <LoadingError onRetry={refetch} />;
  84. }
  85. const isEmpty = !Array.isArray(tokenList) || tokenList.length === 0;
  86. const action = (
  87. <LinkButton
  88. priority="primary"
  89. size="sm"
  90. to="/settings/account/api/auth-tokens/new-token/"
  91. >
  92. {t('Create New Token')}
  93. </LinkButton>
  94. );
  95. return (
  96. <SentryDocumentTitle title={PAGE_TITLE}>
  97. <SettingsPageHeader title={PAGE_TITLE} action={action} />
  98. <TextBlock>
  99. {t(
  100. "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."
  101. )}
  102. </TextBlock>
  103. <TextBlock>
  104. {tct(
  105. 'For more information on how to use the web API, see our [link:documentation].',
  106. {
  107. link: <ExternalLink href="https://docs.sentry.io/api/" />,
  108. }
  109. )}
  110. </TextBlock>
  111. <Panel>
  112. <PanelHeader>{t('Auth Token')}</PanelHeader>
  113. <PanelBody>
  114. {isEmpty && (
  115. <EmptyMessage>
  116. {t("You haven't created any authentication tokens yet.")}
  117. </EmptyMessage>
  118. )}
  119. {tokenList?.map(token => (
  120. <ApiTokenRow key={token.id} token={token} onRemove={deleteToken} canEdit />
  121. ))}
  122. </PanelBody>
  123. </Panel>
  124. </SentryDocumentTitle>
  125. );
  126. }
  127. export default ApiTokens;