import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator'; import {hasEveryAccess} from 'sentry/components/acl/access'; import {Alert} from 'sentry/components/alert'; import AutoSelectText from 'sentry/components/autoSelectText'; import {Button} from 'sentry/components/button'; import Confirm from 'sentry/components/confirm'; import FieldGroup from 'sentry/components/forms/fieldGroup'; 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 PluginList from 'sentry/components/pluginList'; import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; import TextCopyInput from 'sentry/components/textCopyInput'; import {t, tct} from 'sentry/locale'; import type {Plugin} from 'sentry/types/integrations'; import type {Organization} from 'sentry/types/organization'; import type {Project} from 'sentry/types/project'; import getDynamicText from 'sentry/utils/getDynamicText'; import { type ApiQueryKey, setApiQueryData, useApiQuery, useQueryClient, } from 'sentry/utils/queryClient'; import useApi from 'sentry/utils/useApi'; import {useParams} from 'sentry/utils/useParams'; import withPlugins from 'sentry/utils/withPlugins'; import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader'; import TextBlock from 'sentry/views/settings/components/text/textBlock'; type Props = { organization: Organization; plugins: {loading: boolean; plugins: Plugin[]}; project: Project; }; type TokenResponse = { token: string; webhookUrl: string; }; const TOKEN_PLACEHOLDER = 'YOUR_TOKEN'; const WEBHOOK_PLACEHOLDER = 'YOUR_WEBHOOK_URL'; const placeholderData = { token: TOKEN_PLACEHOLDER, webhookUrl: WEBHOOK_PLACEHOLDER, }; function getReleaseTokenQueryKey( organization: Organization, projectId: string ): ApiQueryKey { return [`/projects/${organization.slug}/${projectId}/releases/token/`]; } function ProjectReleaseTracking({organization, project, plugins}: Props) { const api = useApi({persistInFlight: true}); const {projectId} = useParams<{projectId: string}>(); const queryClient = useQueryClient(); const { data: releaseTokenData = placeholderData, isFetching, isError, error, } = useApiQuery(getReleaseTokenQueryKey(organization, projectId), { staleTime: 0, retry: false, }); const handleRegenerateToken = () => { api.request(`/projects/${organization.slug}/${projectId}/releases/token/`, { method: 'POST', data: {project: projectId}, success: data => { setApiQueryData( queryClient, getReleaseTokenQueryKey(organization, projectId), data ); addSuccessMessage( t( 'Your deploy token has been regenerated. You will need to update any existing deploy hooks.' ) ); }, error: () => { addErrorMessage(t('Unable to regenerate deploy token, please try again')); }, }); }; function getReleaseWebhookIntructions() { return ( 'curl ' + releaseTokenData?.webhookUrl + ' \\' + '\n ' + '-X POST \\' + '\n ' + "-H 'Content-Type: application/json' \\" + '\n ' + '-d \'{"version": "abcdefg"}\'' ); } if (isError && error?.status !== 403) { return ; } // Using isFetching instead of isPending to avoid showing loading indicator when 403 if (isFetching || plugins.loading) { return ; } const pluginList = plugins.plugins.filter( (p: Plugin) => p.type === 'release-tracking' && p.hasConfiguration ); const hasWrite = hasEveryAccess(['project:write'], {organization, project}); return (
{t( 'Configure release tracking for this project to automatically record new releases of your application.' )} {!hasWrite && ( {t( 'You do not have sufficient permissions to access Release tokens, placeholders are displayed below.' )} )} {t('Client Configuration')}

{tct( 'Start by binding the [release] attribute in your application, take a look at [link] to see how to configure this for the SDK you are using.', { link: ( our docs ), release: release, } )}

{t( "This will annotate each event with the version of your application, as well as automatically create a release entity in the system the first time it's seen." )}

{t( 'In addition you may configure a release hook (or use our API) to push a release and include additional metadata with it.' )}

{t('Deploy Token')} {releaseTokenData.token}
{t('Webhook')}

{t( 'If you simply want to integrate with an existing system, sometimes its easiest just to use a webhook.' )}

{releaseTokenData?.webhookUrl}

{t( 'The release webhook accepts the same parameters as the "Create a new Release" API endpoint.' )}

{getDynamicText({ value: (
{getReleaseWebhookIntructions()}
), fixed: (
                {`curl __WEBHOOK_URL__ \\
-X POST \\
-H 'Content-Type: application/json' \\
-d \'{"version": "abcdefg"}\'`}
              
), })}
{t('API')}

{t( 'You can notify Sentry when you release new versions of your application via our HTTP API.' )}

{tct('See the [link:releases documentation] for more information.', { link: , })}

); } export default withPlugins(ProjectReleaseTracking); // Export for tests export {ProjectReleaseTracking};