import {RouteComponentProps} from 'react-router'; import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator'; 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 LoadingIndicator from 'sentry/components/loadingIndicator'; import {Panel, PanelBody, PanelHeader} from 'sentry/components/panels'; import PluginList from 'sentry/components/pluginList'; import TextCopyInput from 'sentry/components/textCopyInput'; import {t, tct} from 'sentry/locale'; import {Organization, Plugin, Project} from 'sentry/types'; import getDynamicText from 'sentry/utils/getDynamicText'; import routeTitleGen from 'sentry/utils/routeTitle'; import withPlugins from 'sentry/utils/withPlugins'; import AsyncView from 'sentry/views/asyncView'; import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader'; const TOKEN_PLACEHOLDER = 'YOUR_TOKEN'; const WEBHOOK_PLACEHOLDER = 'YOUR_WEBHOOK_URL'; type Props = { organization: Organization; plugins: {loading: boolean; plugins: Plugin[]}; project: Project; } & RouteComponentProps<{projectId: string}, {}>; type State = { data: { token: string; webhookUrl: string; } | null; } & AsyncView['state']; const placeholderData = { token: TOKEN_PLACEHOLDER, webhookUrl: WEBHOOK_PLACEHOLDER, }; class ProjectReleaseTracking extends AsyncView<Props, State> { getTitle() { const {projectId} = this.props.params; return routeTitleGen(t('Releases'), projectId, false); } getEndpoints(): ReturnType<AsyncView['getEndpoints']> { const {organization} = this.props; const {projectId} = this.props.params; // Allow 403s return [ [ 'data', `/projects/${organization.slug}/${projectId}/releases/token/`, {}, {allowError: err => err && err.status === 403}, ], ]; } handleRegenerateToken = () => { const {organization} = this.props; const {projectId} = this.props.params; this.api.request(`/projects/${organization.slug}/${projectId}/releases/token/`, { method: 'POST', data: {project: projectId}, success: data => { this.setState({ data: { token: data.token, webhookUrl: data.webhookUrl, }, }); 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')); }, }); }; getReleaseWebhookIntructions() { const {webhookUrl} = this.state.data || placeholderData; return ( 'curl ' + webhookUrl + ' \\' + '\n ' + '-X POST \\' + '\n ' + "-H 'Content-Type: application/json' \\" + '\n ' + '-d \'{"version": "abcdefg"}\'' ); } renderBody() { const {organization, project, plugins} = this.props; const hasWrite = organization.access.includes('project:write'); if (plugins.loading) { return <LoadingIndicator />; } const pluginList = plugins.plugins.filter( (p: Plugin) => p.type === 'release-tracking' && p.hasConfiguration ); let {token, webhookUrl} = this.state.data || placeholderData; token = getDynamicText({value: token, fixed: '__TOKEN__'}); webhookUrl = getDynamicText({value: webhookUrl, fixed: '__WEBHOOK_URL__'}); return ( <div> <SettingsPageHeader title={t('Release Tracking')} /> {!hasWrite && ( <Alert type="warning"> {t( 'You do not have sufficient permissions to access Release tokens, placeholders are displayed below.' )} </Alert> )} <p> {t( 'Configure release tracking for this project to automatically record new releases of your application.' )} </p> <Panel> <PanelHeader>{t('Client Configuration')}</PanelHeader> <PanelBody withPadding> <p> {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: ( <ExternalLink href="https://docs.sentry.io/platform-redirect/?next=/configuration/releases/"> our docs </ExternalLink> ), release: <code>release</code>, } )} </p> <p> {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." )} </p> <p> {t( 'In addition you may configure a release hook (or use our API) to push a release and include additional metadata with it.' )} </p> </PanelBody> </Panel> <Panel> <PanelHeader>{t('Deploy Token')}</PanelHeader> <PanelBody> <FieldGroup label={t('Token')} help={t('A unique secret which is used to generate deploy hook URLs')} > <TextCopyInput>{token}</TextCopyInput> </FieldGroup> <FieldGroup label={t('Regenerate Token')} help={t( 'If a service becomes compromised, you should regenerate the token and re-configure any deploy hooks with the newly generated URL.' )} > <div> <Confirm disabled={!hasWrite} priority="danger" onConfirm={this.handleRegenerateToken} message={t( 'Are you sure you want to regenerate your token? Your current token will no longer be usable.' )} > <Button priority="danger" disabled={!hasWrite}> {t('Regenerate Token')} </Button> </Confirm> </div> </FieldGroup> </PanelBody> </Panel> <Panel> <PanelHeader>{t('Webhook')}</PanelHeader> <PanelBody withPadding> <p> {t( 'If you simply want to integrate with an existing system, sometimes its easiest just to use a webhook.' )} </p> <AutoSelectText> <pre>{webhookUrl}</pre> </AutoSelectText> <p> {t( 'The release webhook accepts the same parameters as the "Create a new Release" API endpoint.' )} </p> {getDynamicText({ value: ( <AutoSelectText> <pre>{this.getReleaseWebhookIntructions()}</pre> </AutoSelectText> ), fixed: ( <pre> {`curl __WEBHOOK_URL__ \\ -X POST \\ -H 'Content-Type: application/json' \\ -d \'{"version": "abcdefg"}\'`} </pre> ), })} </PanelBody> </Panel> <PluginList organization={organization} project={project} pluginList={pluginList} /> <Panel> <PanelHeader>{t('API')}</PanelHeader> <PanelBody withPadding> <p> {t( 'You can notify Sentry when you release new versions of your application via our HTTP API.' )} </p> <p> {tct('See the [link:releases documentation] for more information.', { link: <ExternalLink href="https://docs.sentry.io/workflow/releases/" />, })} </p> </PanelBody> </Panel> </div> ); } } export default withPlugins(ProjectReleaseTracking); // Export for tests export {ProjectReleaseTracking};