|
1 |
| -import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator'; |
2 |
| -import DeprecatedAsyncComponent from 'sentry/components/deprecatedAsyncComponent'; |
| 1 | +import { |
| 2 | + addErrorMessage, |
| 3 | + addLoadingMessage, |
| 4 | + addSuccessMessage, |
| 5 | +} from 'sentry/actionCreators/indicator'; |
| 6 | +import LoadingError from 'sentry/components/loadingError'; |
3 | 7 | import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
|
4 | 8 | import {t} from 'sentry/locale';
|
5 |
| -import type {RouteComponentProps} from 'sentry/types/legacyReactRouter'; |
6 |
| -import type {Organization} from 'sentry/types/organization'; |
7 |
| -import {browserHistory} from 'sentry/utils/browserHistory'; |
8 |
| -import recreateRoute from 'sentry/utils/recreateRoute'; |
9 |
| -import withOrganization from 'sentry/utils/withOrganization'; |
| 9 | +import { |
| 10 | + setApiQueryData, |
| 11 | + useApiQuery, |
| 12 | + useMutation, |
| 13 | + useQueryClient, |
| 14 | +} from 'sentry/utils/queryClient'; |
| 15 | +import useApi from 'sentry/utils/useApi'; |
| 16 | +import {useNavigate} from 'sentry/utils/useNavigate'; |
| 17 | +import useOrganization from 'sentry/utils/useOrganization'; |
10 | 18 |
|
11 | 19 | import OrganizationApiKeysList from './organizationApiKeysList';
|
12 | 20 | import type {DeprecatedApiKey} from './types';
|
13 | 21 |
|
14 |
| -type Props = RouteComponentProps<{}, {}> & { |
15 |
| - organization: Organization; |
16 |
| -}; |
17 |
| - |
18 |
| -type State = { |
19 |
| - keys: DeprecatedApiKey[]; |
20 |
| -} & DeprecatedAsyncComponent['state']; |
21 |
| - |
22 | 22 | /**
|
23 | 23 | * API Keys are deprecated, but there may be some legacy customers that still use it
|
24 | 24 | */
|
25 |
| -class OrganizationApiKeys extends DeprecatedAsyncComponent<Props, State> { |
26 |
| - getEndpoints(): ReturnType<DeprecatedAsyncComponent['getEndpoints']> { |
27 |
| - const {organization} = this.props; |
28 |
| - return [['keys', `/organizations/${organization.slug}/api-keys/`]]; |
29 |
| - } |
30 |
| - |
31 |
| - handleRemove = async (id: string) => { |
32 |
| - const {organization} = this.props; |
33 |
| - const oldKeys = [...this.state.keys]; |
| 25 | +function OrganizationApiKeys() { |
| 26 | + const api = useApi(); |
| 27 | + const organization = useOrganization(); |
| 28 | + const navigate = useNavigate(); |
| 29 | + const queryClient = useQueryClient(); |
| 30 | + const { |
| 31 | + data: apiKeys = [], |
| 32 | + isPending, |
| 33 | + isError, |
| 34 | + refetch, |
| 35 | + } = useApiQuery<DeprecatedApiKey[]>([`/organizations/${organization.slug}/api-keys/`], { |
| 36 | + staleTime: 0, |
| 37 | + }); |
34 | 38 |
|
35 |
| - this.setState(state => ({ |
36 |
| - keys: state.keys.filter(({id: existingId}) => existingId !== id), |
37 |
| - })); |
38 |
| - |
39 |
| - try { |
40 |
| - await this.api.requestPromise( |
41 |
| - `/organizations/${organization.slug}/api-keys/${id}/`, |
| 39 | + const removeMutation = useMutation({ |
| 40 | + mutationFn: ({removedId}: {removedId: string}) => { |
| 41 | + return api.requestPromise( |
| 42 | + `/organizations/${organization.slug}/api-keys/${removedId}/`, |
42 | 43 | {
|
43 | 44 | method: 'DELETE',
|
44 | 45 | data: {},
|
45 | 46 | }
|
46 | 47 | );
|
47 |
| - } catch { |
48 |
| - this.setState({keys: oldKeys, busy: false}); |
49 |
| - addErrorMessage(t('Error removing key')); |
50 |
| - } |
51 |
| - }; |
52 |
| - |
53 |
| - handleAddApiKey = async () => { |
54 |
| - this.setState({ |
55 |
| - busy: true, |
56 |
| - }); |
57 |
| - const {organization} = this.props; |
| 48 | + }, |
| 49 | + onMutate: () => { |
| 50 | + addLoadingMessage(t('Removing API key')); |
| 51 | + }, |
| 52 | + onSuccess: (_data, {removedId}) => { |
| 53 | + setApiQueryData<DeprecatedApiKey[]>( |
| 54 | + queryClient, |
| 55 | + [`/organizations/${organization.slug}/api-keys/`], |
| 56 | + oldData => { |
| 57 | + if (!oldData) { |
| 58 | + return oldData; |
| 59 | + } |
58 | 60 |
|
59 |
| - try { |
60 |
| - const data = await this.api.requestPromise( |
61 |
| - `/organizations/${organization.slug}/api-keys/`, |
62 |
| - { |
63 |
| - method: 'POST', |
64 |
| - data: {}, |
| 61 | + return oldData.filter(({id}) => id !== removedId); |
65 | 62 | }
|
66 | 63 | );
|
| 64 | + }, |
| 65 | + onError: () => { |
| 66 | + addErrorMessage(t('Error removing key')); |
| 67 | + }, |
| 68 | + }); |
67 | 69 |
|
68 |
| - if (data) { |
69 |
| - this.setState({busy: false}); |
70 |
| - browserHistory.push( |
71 |
| - recreateRoute(`${data.id}/`, { |
72 |
| - params: {orgId: organization.slug}, |
73 |
| - routes: this.props.routes, |
74 |
| - }) |
75 |
| - ); |
76 |
| - addSuccessMessage(t('Created a new API key "%s"', data.label)); |
| 70 | + const addMutation = useMutation({ |
| 71 | + mutationFn: (): Promise<DeprecatedApiKey> => { |
| 72 | + return api.requestPromise(`/organizations/${organization.slug}/api-keys/`, { |
| 73 | + method: 'POST', |
| 74 | + data: {}, |
| 75 | + }); |
| 76 | + }, |
| 77 | + onSuccess: data => { |
| 78 | + if (!data) { |
| 79 | + return; |
77 | 80 | }
|
78 |
| - } catch { |
79 |
| - this.setState({busy: false}); |
80 |
| - } |
81 |
| - }; |
82 | 81 |
|
83 |
| - renderLoading() { |
84 |
| - return this.renderBody(); |
85 |
| - } |
86 |
| - |
87 |
| - renderBody() { |
88 |
| - const {organization} = this.props; |
89 |
| - const params = {orgId: organization.slug}; |
| 82 | + navigate(`/settings/${organization.slug}/api-keys/${data.id}/`); |
| 83 | + addSuccessMessage(t('Created a new API key "%s"', data.label)); |
| 84 | + }, |
| 85 | + onError: () => { |
| 86 | + addErrorMessage(t('Error creating key')); |
| 87 | + }, |
| 88 | + }); |
90 | 89 |
|
91 |
| - return ( |
92 |
| - <SentryDocumentTitle title={t('Api Keys')} orgSlug={organization.slug}> |
93 |
| - <OrganizationApiKeysList |
94 |
| - {...this.props} |
95 |
| - params={params} |
96 |
| - loading={this.state.loading} |
97 |
| - busy={this.state.busy} |
98 |
| - keys={this.state.keys} |
99 |
| - onRemove={this.handleRemove} |
100 |
| - onAddApiKey={this.handleAddApiKey} |
101 |
| - /> |
102 |
| - </SentryDocumentTitle> |
103 |
| - ); |
| 90 | + if (isError) { |
| 91 | + return <LoadingError onRetry={refetch} />; |
104 | 92 | }
|
| 93 | + |
| 94 | + return ( |
| 95 | + <SentryDocumentTitle title={t('Api Keys')} orgSlug={organization.slug}> |
| 96 | + <OrganizationApiKeysList |
| 97 | + organization={organization} |
| 98 | + loading={isPending} |
| 99 | + busy={addMutation.isPending} |
| 100 | + keys={apiKeys} |
| 101 | + onRemove={id => removeMutation.mutateAsync({removedId: id})} |
| 102 | + onAddApiKey={addMutation.mutateAsync} |
| 103 | + /> |
| 104 | + </SentryDocumentTitle> |
| 105 | + ); |
105 | 106 | }
|
106 | 107 |
|
107 |
| -export default withOrganization(OrganizationApiKeys); |
| 108 | +export default OrganizationApiKeys; |
0 commit comments