Просмотр исходного кода

Revert "ref(tsc): Convert ApiTokens to FC (#68277)"

This reverts commit 25880782e391a88a2d2123185a25c81660a29ded.

Co-authored-by: ArthurKnaus <7033940+ArthurKnaus@users.noreply.github.com>
getsentry-bot 11 месяцев назад
Родитель
Сommit
8508942d53

+ 17 - 38
static/app/views/settings/account/apiTokens.spec.tsx

@@ -1,75 +1,54 @@
 import {ApiTokenFixture} from 'sentry-fixture/apiToken';
+import {OrganizationFixture} from 'sentry-fixture/organization';
 
-import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
+import {fireEvent, render, screen} from 'sentry-test/reactTestingLibrary';
 
 import {ApiTokens} from 'sentry/views/settings/account/apiTokens';
 
+const organization = OrganizationFixture();
+
 describe('ApiTokens', function () {
   beforeEach(function () {
     MockApiClient.clearMockResponses();
   });
 
-  it('renders empty result', async function () {
+  it('renders empty result', function () {
     MockApiClient.addMockResponse({
       url: '/api-tokens/',
       body: null,
     });
 
-    render(<ApiTokens />);
-
-    expect(
-      await screen.findByText("You haven't created any authentication tokens yet.")
-    ).toBeInTheDocument();
+    render(<ApiTokens organization={organization} />);
   });
 
-  it('renders with result', async function () {
-    const token1 = ApiTokenFixture({id: '1', name: 'token1'});
-    const token2 = ApiTokenFixture({id: '2', name: 'token2'});
-
+  it('renders with result', function () {
     MockApiClient.addMockResponse({
       url: '/api-tokens/',
-      body: [token1, token2],
+      body: [ApiTokenFixture()],
     });
 
-    render(<ApiTokens />);
-
-    expect(await screen.findByText('token1')).toBeInTheDocument();
-    expect(await screen.findByText('token2')).toBeInTheDocument();
+    render(<ApiTokens organization={organization} />);
   });
 
-  it('can delete token', async function () {
+  it('can delete token', function () {
     MockApiClient.addMockResponse({
       url: '/api-tokens/',
       body: [ApiTokenFixture()],
     });
 
-    const deleteTokenMock = MockApiClient.addMockResponse({
+    const mock = MockApiClient.addMockResponse({
       url: '/api-tokens/',
       method: 'DELETE',
     });
+    expect(mock).not.toHaveBeenCalled();
 
-    render(<ApiTokens />);
-
-    const removeButton = await screen.findByRole('button', {name: 'Remove'});
-    expect(removeButton).toBeInTheDocument();
-    expect(deleteTokenMock).not.toHaveBeenCalled();
-
-    // mock response for refetch after delete
-    MockApiClient.addMockResponse({
-      url: '/api-tokens/',
-      body: [],
-    });
-
-    userEvent.click(removeButton);
+    render(<ApiTokens organization={organization} />);
 
-    // Wait for list to update
-    expect(
-      await screen.findByText("You haven't created any authentication tokens yet.")
-    ).toBeInTheDocument();
+    fireEvent.click(screen.getByLabelText('Remove'));
 
-    // Should have called delete
-    expect(deleteTokenMock).toHaveBeenCalledTimes(1);
-    expect(deleteTokenMock).toHaveBeenCalledWith(
+    // Should be loading
+    expect(mock).toHaveBeenCalledTimes(1);
+    expect(mock).toHaveBeenCalledWith(
       '/api-tokens/',
       expect.objectContaining({
         method: 'DELETE',

+ 106 - 127
static/app/views/settings/account/apiTokens.tsx

@@ -7,148 +7,127 @@ import AlertLink from 'sentry/components/alertLink';
 import {Button} from 'sentry/components/button';
 import EmptyMessage from 'sentry/components/emptyMessage';
 import ExternalLink from 'sentry/components/links/externalLink';
-import LoadingError from 'sentry/components/loadingError';
-import LoadingIndicator from 'sentry/components/loadingIndicator';
 import Panel from 'sentry/components/panels/panel';
 import PanelBody from 'sentry/components/panels/panelBody';
 import PanelHeader from 'sentry/components/panels/panelHeader';
-import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
 import {t, tct} from 'sentry/locale';
-import type {InternalAppApiToken} from 'sentry/types';
-import {
-  getApiQueryData,
-  setApiQueryData,
-  useApiQuery,
-  useMutation,
-  useQueryClient,
-} from 'sentry/utils/queryClient';
-import useApi from 'sentry/utils/useApi';
-import useOrganization from 'sentry/utils/useOrganization';
+import type {InternalAppApiToken, Organization} from 'sentry/types';
+import withOrganization from 'sentry/utils/withOrganization';
+import DeprecatedAsyncView from 'sentry/views/deprecatedAsyncView';
 import ApiTokenRow from 'sentry/views/settings/account/apiTokenRow';
 import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
 import TextBlock from 'sentry/views/settings/components/text/textBlock';
 
-const PAGE_TITLE = t('User Auth Tokens');
-const API_TOKEN_QUERY_KEY = ['/api-tokens/'] as const;
-
-export function ApiTokens() {
-  const api = useApi();
-  const organization = useOrganization();
-  const queryClient = useQueryClient();
-
-  const {
-    data: tokenList,
-    isLoading,
-    isError,
-    refetch,
-  } = useApiQuery<InternalAppApiToken[]>(API_TOKEN_QUERY_KEY, {staleTime: 0});
-
-  const {mutate: deleteToken} = useMutation(
-    (token: InternalAppApiToken) => {
-      return api.requestPromise('/api-tokens/', {
-        method: 'DELETE',
-        data: {tokenId: token.id},
-      });
-    },
-    {
-      onMutate: token => {
-        addLoadingMessage();
-        queryClient.cancelQueries(API_TOKEN_QUERY_KEY);
+type Props = {
+  organization: Organization;
+} & DeprecatedAsyncView['props'];
 
-        const previous = getApiQueryData<InternalAppApiToken[]>(
-          queryClient,
-          API_TOKEN_QUERY_KEY
-        );
-
-        setApiQueryData<InternalAppApiToken[]>(
-          queryClient,
-          API_TOKEN_QUERY_KEY,
-          oldTokenList => {
-            return oldTokenList?.filter(tk => tk.id !== token.id);
-          }
-        );
-
-        return {previous};
-      },
-      onSuccess: _data => {
-        addSuccessMessage(t('Removed token'));
-      },
-      onError: (_error, _variables, context) => {
-        addErrorMessage(t('Unable to remove token. Please try again.'));
-
-        if (context?.previous) {
-          setApiQueryData<InternalAppApiToken[]>(
-            queryClient,
-            API_TOKEN_QUERY_KEY,
-            context.previous
-          );
-        }
-      },
-      onSettled: () => {
-        queryClient.invalidateQueries(API_TOKEN_QUERY_KEY);
-      },
-    }
-  );
+type State = {
+  tokenList: InternalAppApiToken[] | null;
+} & DeprecatedAsyncView['state'];
 
-  if (isLoading) {
-    return <LoadingIndicator />;
+export class ApiTokens extends DeprecatedAsyncView<Props, State> {
+  getTitle() {
+    return t('User Auth Tokens');
   }
 
-  if (isError) {
-    return <LoadingError onRetry={refetch} />;
+  getDefaultState() {
+    return {
+      ...super.getDefaultState(),
+      tokenList: [],
+    };
   }
 
-  const isEmpty = !Array.isArray(tokenList) || tokenList.length === 0;
-
-  const action = (
-    <Button
-      priority="primary"
-      size="sm"
-      to="/settings/account/api/auth-tokens/new-token/"
-      data-test-id="create-token"
-    >
-      {t('Create New Token')}
-    </Button>
-  );
-
-  return (
-    <SentryDocumentTitle title={PAGE_TITLE}>
-      <SettingsPageHeader title={PAGE_TITLE} action={action} />
-      <AlertLink to={`/settings/${organization?.slug ?? ''}/auth-tokens/`}>
-        {t(
-          "User Auth Tokens are tied to the logged in user, meaning they'll stop working if the user leaves the organization! We suggest using Organization Auth Tokens to create/manage tokens tied to the organization instead."
-        )}
-      </AlertLink>
-      <TextBlock>
-        {t(
-          "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."
-        )}
-      </TextBlock>
-      <TextBlock>
-        {tct(
-          'For more information on how to use the web API, see our [link:documentation].',
-          {
-            link: <ExternalLink href="https://docs.sentry.io/api/" />,
-          }
-        )}
-      </TextBlock>
-      <Panel>
-        <PanelHeader>{t('Auth Token')}</PanelHeader>
+  getEndpoints(): ReturnType<DeprecatedAsyncView['getEndpoints']> {
+    return [['tokenList', '/api-tokens/']];
+  }
 
-        <PanelBody>
-          {isEmpty && (
-            <EmptyMessage>
-              {t("You haven't created any authentication tokens yet.")}
-            </EmptyMessage>
+  handleRemoveToken = (token: InternalAppApiToken) => {
+    addLoadingMessage();
+    const oldTokenList = this.state.tokenList;
+
+    this.setState(
+      state => ({
+        tokenList: state.tokenList?.filter(tk => tk.id !== token.id) ?? [],
+      }),
+      async () => {
+        try {
+          await this.api.requestPromise('/api-tokens/', {
+            method: 'DELETE',
+            data: {tokenId: token.id},
+          });
+
+          addSuccessMessage(t('Removed token'));
+        } catch (_err) {
+          addErrorMessage(t('Unable to remove token. Please try again.'));
+          this.setState({
+            tokenList: oldTokenList,
+          });
+        }
+      }
+    );
+  };
+
+  renderBody() {
+    const {organization} = this.props;
+    const {tokenList} = this.state;
+
+    const isEmpty = !Array.isArray(tokenList) || tokenList.length === 0;
+
+    const action = (
+      <Button
+        priority="primary"
+        size="sm"
+        to="/settings/account/api/auth-tokens/new-token/"
+        data-test-id="create-token"
+      >
+        {t('Create New Token')}
+      </Button>
+    );
+
+    return (
+      <div>
+        <SettingsPageHeader title={this.getTitle()} action={action} />
+        <AlertLink to={`/settings/${organization?.slug ?? ''}/auth-tokens/`}>
+          {t(
+            "User Auth Tokens are tied to the logged in user, meaning they'll stop working if the user leaves the organization! We suggest using Organization Auth Tokens to create/manage tokens tied to the organization instead."
           )}
-
-          {tokenList?.map(token => (
-            <ApiTokenRow key={token.id} token={token} onRemove={deleteToken} />
-          ))}
-        </PanelBody>
-      </Panel>
-    </SentryDocumentTitle>
-  );
+        </AlertLink>
+        <TextBlock>
+          {t(
+            "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."
+          )}
+        </TextBlock>
+        <TextBlock>
+          {tct(
+            'For more information on how to use the web API, see our [link:documentation].',
+            {
+              link: <ExternalLink href="https://docs.sentry.io/api/" />,
+            }
+          )}
+        </TextBlock>
+        <Panel>
+          <PanelHeader>{t('Auth Token')}</PanelHeader>
+
+          <PanelBody>
+            {isEmpty && (
+              <EmptyMessage>
+                {t("You haven't created any authentication tokens yet.")}
+              </EmptyMessage>
+            )}
+
+            {tokenList?.map(token => (
+              <ApiTokenRow
+                key={token.id}
+                token={token}
+                onRemove={this.handleRemoveToken}
+              />
+            ))}
+          </PanelBody>
+        </Panel>
+      </div>
+    );
+  }
 }
 
-export default ApiTokens;
+export default withOrganization(ApiTokens);