import {Fragment, useState} from 'react'; import { addErrorMessage, addLoadingMessage, addSuccessMessage, } from 'sentry/actionCreators/indicator'; import {hasEveryAccess} from 'sentry/components/acl/access'; 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 Pagination from 'sentry/components/pagination'; import Panel from 'sentry/components/panels/panel'; import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; import {IconAdd, IconFlag} from 'sentry/icons'; import {t, tct} from 'sentry/locale'; import type {Project, ProjectKey} from 'sentry/types/project'; import {useApiQuery, useMutation} from 'sentry/utils/queryClient'; import useApi from 'sentry/utils/useApi'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; import {useParams} from 'sentry/utils/useParams'; import {useRoutes} from 'sentry/utils/useRoutes'; import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader'; import TextBlock from 'sentry/views/settings/components/text/textBlock'; import PermissionAlert from 'sentry/views/settings/project/permissionAlert'; import KeyRow from './keyRow'; type Props = { project: Project; }; function ProjectKeys({project}: Props) { const params = useParams<{projectId: string}>(); const {projectId} = params; const location = useLocation(); const organization = useOrganization(); const api = useApi({persistInFlight: true}); const routes = useRoutes(); const [keyListState, setKeyListState] = useState(undefined); const { data: fetchedKeyList, isPending, isError, refetch, getResponseHeader, } = useApiQuery([`/projects/${organization.slug}/${projectId}/keys/`], { staleTime: 0, }); /** * Optimistically remove key */ const handleRemoveKeyMutation = useMutation({ mutationFn: (data: ProjectKey) => { return api.requestPromise( `/projects/${organization.slug}/${projectId}/keys/${data.id}/`, { method: 'DELETE', } ); }, onMutate: (data: ProjectKey) => { addLoadingMessage(t('Revoking key\u2026')); setKeyListState(keyList.filter(key => key.id !== data.id)); }, onSuccess: () => { addSuccessMessage(t('Revoked key')); }, onError: () => { setKeyListState([...keyList]); addErrorMessage(t('Unable to revoke key')); }, }); const handleToggleKeyMutation = useMutation({ mutationFn: ({isActive, data}: {data: ProjectKey; isActive: boolean}) => { return api.requestPromise( `/projects/${organization.slug}/${projectId}/keys/${data.id}/`, { method: 'PUT', data: {isActive}, } ); }, onMutate: ({data}: {data: ProjectKey}) => { addLoadingMessage(t('Saving changes\u2026')); setKeyListState( keyList.map(key => { if (key.id === data.id) { return { ...key, isActive: !data.isActive, }; } return key; }) ); }, onSuccess: ({isActive}: {isActive: boolean}) => { addSuccessMessage(isActive ? t('Enabled key') : t('Disabled key')); }, onError: ({isActive}: {isActive: boolean}) => { addErrorMessage(isActive ? t('Error enabling key') : t('Error disabling key')); setKeyListState([...keyList]); }, }); const handleCreateKeyMutation = useMutation({ mutationFn: () => { return api.requestPromise(`/projects/${organization.slug}/${projectId}/keys/`, { method: 'POST', }); }, onSuccess: (updatedKey: ProjectKey) => { setKeyListState([...keyList, updatedKey]); addSuccessMessage(t('Created a new key.')); }, onError: () => { addErrorMessage(t('Unable to create new key. Please try again.')); }, }); if (isPending) { return ; } if (isError) { return ; } const keyList = keyListState ? keyListState : fetchedKeyList; const renderEmpty = () => { return ( } description={t('There are no keys active for this project.')} /> ); }; const renderResults = () => { const hasAccess = hasEveryAccess(['project:write'], {organization, project}); return ( {keyList.map(key => ( handleToggleKeyMutation.mutate({isActive, data}) } onRemove={data => handleRemoveKeyMutation.mutate(data)} routes={routes} location={location} params={params} /> ))} ); }; const isEmpty = !keyList.length; const hasAccess = hasEveryAccess(['project:write'], {organization, project}); return (
handleCreateKeyMutation.mutate()} size="sm" priority="primary" icon={} disabled={!hasAccess} > {t('Generate New Key')} } /> {tct( `To send data to Sentry you will need to configure an SDK with a client key (usually referred to as the [code:SENTRY_DSN] value). For more information on integrating Sentry with your application take a look at our [link:documentation].`, { link: ( ), code: , } )} {isEmpty ? renderEmpty() : renderResults()}
); } export default ProjectKeys;