import {Fragment} from 'react'; import styled from '@emotion/styled'; import {BarChart} from 'sentry/components/charts/barChart'; import type {LineChartSeries} from 'sentry/components/charts/lineChart'; import {LineChart} from 'sentry/components/charts/lineChart'; import {DateTime} from 'sentry/components/dateTime'; import Link from 'sentry/components/links/link'; import Panel from 'sentry/components/panels/panel'; import PanelBody from 'sentry/components/panels/panelBody'; import PanelFooter from 'sentry/components/panels/panelFooter'; import PanelHeader from 'sentry/components/panels/panelHeader'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {SentryApp} from 'sentry/types/integrations'; import type {RouteComponentProps} from 'sentry/types/legacyReactRouter'; import type {Organization} from 'sentry/types/organization'; import withOrganization from 'sentry/utils/withOrganization'; import DeprecatedAsyncView from 'sentry/views/deprecatedAsyncView'; import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader'; import RequestLog from './requestLog'; type Props = RouteComponentProps<{appSlug: string}, {}> & { organization: Organization; }; type State = DeprecatedAsyncView['state'] & { app: SentryApp; interactions: { componentInteractions: { [key: string]: [number, number][]; }; views: [number, number][]; }; stats: { installStats: [number, number][]; totalInstalls: number; totalUninstalls: number; uninstallStats: [number, number][]; }; }; class SentryApplicationDashboard extends DeprecatedAsyncView { getEndpoints(): ReturnType { const {appSlug} = this.props.params; // Default time range for now: 90 days ago to now const now = Math.floor(new Date().getTime() / 1000); const ninety_days_ago = 3600 * 24 * 90; return [ [ 'stats', `/sentry-apps/${appSlug}/stats/`, {query: {since: now - ninety_days_ago, until: now}}, ], [ 'interactions', `/sentry-apps/${appSlug}/interaction/`, {query: {since: now - ninety_days_ago, until: now}}, ], ['app', `/sentry-apps/${appSlug}/`], ]; } getTitle() { return t('Integration Dashboard'); } renderInstallData() { const {app, stats} = this.state; const {totalUninstalls, totalInstalls} = stats; return (
{t('Installation & Interaction Data')}
{app.datePublished ? ( {t('Date published')} ) : null} {t('Total installs')}

{totalInstalls}

{t('Total uninstalls')}

{totalUninstalls}

{this.renderInstallCharts()}
); } renderInstallCharts() { const {installStats, uninstallStats} = this.state.stats; const installSeries = { data: installStats.map(point => ({ name: point[0] * 1000, value: point[1], })), seriesName: t('installed'), }; const uninstallSeries = { data: uninstallStats.map(point => ({ name: point[0] * 1000, value: point[1], })), seriesName: t('uninstalled'), }; return ( {t('Installations/Uninstallations over Last 90 Days')} ); } renderIntegrationViews() { const {views} = this.state.interactions; const {organization} = this.props; const {appSlug} = this.props.params; return ( {t('Integration Views')} {t('Integration views are measured through views on the ')} {t('external installation page')} {t(' and views on the Learn More/Install modal on the ')} {t('integrations page')} ); } renderComponentInteractions() { const {componentInteractions} = this.state.interactions; const componentInteractionsDetails = { 'stacktrace-link': t( 'Each link click or context menu open counts as one interaction' ), 'issue-link': t('Each open of the issue link modal counts as one interaction'), }; return ( {t('Component Interactions')} {Object.keys(componentInteractions).map( (component, idx) => componentInteractionsDetails[component] && ( {`${component}: `} {componentInteractionsDetails[component]}
) )}
); } renderBody() { const {app} = this.state; return (
{app.status === 'published' && this.renderInstallData()} {app.status === 'published' && this.renderIntegrationViews()} {app.schema.elements && this.renderComponentInteractions()}
); } } export default withOrganization(SentryApplicationDashboard); type InteractionsChartProps = { data: { [key: string]: [number, number][]; }; }; function InteractionsChart({data}: InteractionsChartProps) { const elementInteractionsSeries: LineChartSeries[] = Object.keys(data).map( (key: string) => { const seriesData = data[key].map(point => ({ value: point[1], name: point[0] * 1000, })); return { seriesName: key, data: seriesData, }; } ); return ( ); } const Row = styled('div')` display: flex; `; const StatsSection = styled('div')` margin-right: ${space(4)}; `; const StatsHeader = styled('h6')` margin-bottom: ${space(1)}; font-size: 12px; text-transform: uppercase; color: ${p => p.theme.subText}; `; const StyledFooter = styled('div')` padding: ${space(1.5)}; `; const ChartWrapper = styled('div')` padding-top: ${space(3)}; `;