|
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