import {Fragment} from 'react'; import {browserHistory} from 'react-router'; import { addErrorMessage, addLoadingMessage, clearIndicators, } from 'sentry/actionCreators/indicator'; import {Button} from 'sentry/components/button'; import MiniBarChart from 'sentry/components/charts/miniBarChart'; import DeprecatedAsyncComponent from 'sentry/components/deprecatedAsyncComponent'; import EmptyMessage from 'sentry/components/emptyMessage'; import ErrorBoundary from 'sentry/components/errorBoundary'; import FieldGroup from 'sentry/components/forms/fieldGroup'; import Panel from 'sentry/components/panels/panel'; import PanelAlert from 'sentry/components/panels/panelAlert'; import PanelBody from 'sentry/components/panels/panelBody'; import PanelHeader from 'sentry/components/panels/panelHeader'; import TextCopyInput from 'sentry/components/textCopyInput'; import {t} from 'sentry/locale'; import {Organization, ServiceHook} from 'sentry/types'; import getDynamicText from 'sentry/utils/getDynamicText'; import {normalizeUrl} from 'sentry/utils/withDomainRequired'; import DeprecatedAsyncView from 'sentry/views/deprecatedAsyncView'; import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader'; import ServiceHookSettingsForm from 'sentry/views/settings/project/serviceHookSettingsForm'; type Params = { hookId: string; projectId: string; }; type StatsProps = { organization: Organization; params: Params; }; type StatsState = { stats: {total: number; ts: number}[] | null; } & DeprecatedAsyncComponent['state']; class HookStats extends DeprecatedAsyncComponent { getEndpoints(): ReturnType { const until = Math.floor(new Date().getTime() / 1000); const since = until - 3600 * 24 * 30; const {organization} = this.props; const {hookId, projectId} = this.props.params; return [ [ 'stats', `/projects/${organization.slug}/${projectId}/hooks/${hookId}/stats/`, { query: { since, until, resolution: '1d', }, }, ], ]; } renderBody() { const {stats} = this.state; if (stats === null) { return null; } let emptyStats = true; const series = { seriesName: t('Events'), data: stats.map(p => { if (p.total) { emptyStats = false; } return { name: p.ts * 1000, value: p.total, }; }), }; return ( {t('Events in the last 30 days (by day)')} {!emptyStats ? ( ) : ( )} ); } } type Props = { organization: Organization; params: Params; }; type State = { hook: ServiceHook | null; } & DeprecatedAsyncView['state']; export default class ProjectServiceHookDetails extends DeprecatedAsyncView { getEndpoints(): ReturnType { const {organization} = this.props; const {projectId, hookId} = this.props.params; return [['hook', `/projects/${organization.slug}/${projectId}/hooks/${hookId}/`]]; } onDelete = () => { const {organization} = this.props; const {projectId, hookId} = this.props.params; addLoadingMessage(t('Saving changes\u2026')); this.api.request(`/projects/${organization.slug}/${projectId}/hooks/${hookId}/`, { method: 'DELETE', success: () => { clearIndicators(); browserHistory.push( normalizeUrl(`/settings/${organization.slug}/projects/${projectId}/hooks/`) ); }, error: () => { addErrorMessage(t('Unable to remove application. Please try again.')); }, }); }; renderBody() { const {organization, params} = this.props; const {projectId, hookId} = params; const {hook} = this.state; if (!hook) { return null; } return ( {t('Event Validation')} Sentry will send the X-ServiceHook-Signature header built using{' '} HMAC(SHA256, [secret], [payload]). You should always verify this signature before trusting the information provided in the webhook. {getDynamicText({ value: hook.secret, fixed: 'a dynamic secret value', })} {t('Delete Hook')}
); } }