|
@@ -0,0 +1,121 @@
|
|
|
+import {createContext, Fragment, useContext, useState} from 'react';
|
|
|
+import styled from '@emotion/styled';
|
|
|
+
|
|
|
+import {addErrorMessage} from 'sentry/actionCreators/indicator';
|
|
|
+import {t} from 'sentry/locale';
|
|
|
+import ConfigStore from 'sentry/stores/configStore';
|
|
|
+import {useLegacyStore} from 'sentry/stores/useLegacyStore';
|
|
|
+import {OrgAuthToken} from 'sentry/types';
|
|
|
+import {handleXhrErrorResponse} from 'sentry/utils/handleXhrErrorResponse';
|
|
|
+import {useMutation} from 'sentry/utils/queryClient';
|
|
|
+import RequestError from 'sentry/utils/requestError/requestError';
|
|
|
+import useApi from 'sentry/utils/useApi';
|
|
|
+import useOrganization from 'sentry/utils/useOrganization';
|
|
|
+
|
|
|
+type OrgAuthTokenWithToken = OrgAuthToken & {token: string};
|
|
|
+
|
|
|
+const AuthTokenGeneratorContext = createContext<{
|
|
|
+ generateAuthToken: () => void;
|
|
|
+ isLoading: boolean;
|
|
|
+ authToken?: string;
|
|
|
+}>({
|
|
|
+ isLoading: false,
|
|
|
+ generateAuthToken: () => {},
|
|
|
+});
|
|
|
+
|
|
|
+interface AuthTokenGeneratorProviderProps {
|
|
|
+ children: React.ReactNode;
|
|
|
+ projectSlug: string;
|
|
|
+}
|
|
|
+
|
|
|
+export function AuthTokenGeneratorProvider({
|
|
|
+ children,
|
|
|
+ projectSlug,
|
|
|
+}: AuthTokenGeneratorProviderProps) {
|
|
|
+ const api = useApi();
|
|
|
+ const organization = useOrganization();
|
|
|
+ const [authToken, setAuthToken] = useState<string>();
|
|
|
+ const config = useLegacyStore(ConfigStore);
|
|
|
+
|
|
|
+ const {mutate: generateAuthToken, isLoading} = useMutation<
|
|
|
+ OrgAuthTokenWithToken,
|
|
|
+ RequestError
|
|
|
+ >({
|
|
|
+ mutationFn: () => {
|
|
|
+ const currentDate = new Date().toISOString().slice(0, 10);
|
|
|
+ const name = `Generated by ${config.user.name} for ${projectSlug} on ${currentDate}`;
|
|
|
+ return api.requestPromise(`/organizations/${organization.slug}/org-auth-tokens/`, {
|
|
|
+ method: 'POST',
|
|
|
+ data: {
|
|
|
+ name,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ onSuccess: (token: OrgAuthTokenWithToken) => {
|
|
|
+ setAuthToken(token.token);
|
|
|
+ },
|
|
|
+ onError: error => {
|
|
|
+ const message = t('Failed to create a new auth token.');
|
|
|
+ handleXhrErrorResponse(message, error);
|
|
|
+ addErrorMessage(message);
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ return (
|
|
|
+ <AuthTokenGeneratorContext.Provider value={{authToken, isLoading, generateAuthToken}}>
|
|
|
+ {children}
|
|
|
+ </AuthTokenGeneratorContext.Provider>
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+export function AuthTokenGenerator() {
|
|
|
+ const {authToken, isLoading, generateAuthToken} = useContext(AuthTokenGeneratorContext);
|
|
|
+
|
|
|
+ function handleClick() {
|
|
|
+ generateAuthToken();
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleKeyDown(event: React.KeyboardEvent<HTMLButtonElement>) {
|
|
|
+ if (['Enter', 'Space'].includes(event.key)) {
|
|
|
+ generateAuthToken();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (authToken) {
|
|
|
+ return <Fragment>{authToken}</Fragment>;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isLoading) {
|
|
|
+ return <Wrapper isInteractive={false}>{t('Generating token...')}</Wrapper>;
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <Wrapper
|
|
|
+ isInteractive
|
|
|
+ role="button"
|
|
|
+ tabIndex={0}
|
|
|
+ onClick={handleClick}
|
|
|
+ onKeyDown={handleKeyDown}
|
|
|
+ >
|
|
|
+ {t('Click to generate token')}
|
|
|
+ </Wrapper>
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+const Wrapper = styled('span')<{isInteractive: boolean}>`
|
|
|
+ background: var(--prism-highlight-accent);
|
|
|
+ border-radius: 4px;
|
|
|
+ border: none;
|
|
|
+ padding: 0px 2px;
|
|
|
+ margin: 0 4px;
|
|
|
+
|
|
|
+ ${p =>
|
|
|
+ p.isInteractive &&
|
|
|
+ `
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: var(--prism-highlight-background);
|
|
|
+ }`}
|
|
|
+`;
|