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 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 PanelFooter from 'sentry/components/panels/panelFooter'; import PanelHeader from 'sentry/components/panels/panelHeader'; import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {SentryApp} from 'sentry/types/integrations'; import {useApiQuery} from 'sentry/utils/queryClient'; import useOrganization from 'sentry/utils/useOrganization'; import {useParams} from 'sentry/utils/useParams'; import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader'; import RequestLog from './requestLog'; type Interactions = { componentInteractions: { [key: string]: Array<[number, number]>; }; views: Array<[number, number]>; }; type Stats = { installStats: Array<[number, number]>; totalInstalls: number; totalUninstalls: number; uninstallStats: Array<[number, number]>; }; function SentryApplicationDashboard() { const organization = useOrganization(); const {appSlug} = useParams<{appSlug: string}>(); // 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; const { data: app, isPending: isAppPending, isError: isAppError, } = useApiQuery([`/sentry-apps/${appSlug}/`], {staleTime: 0}); const { data: interactions, isPending: isInteractionsPending, isError: isInteractionsError, } = useApiQuery( [ `/sentry-apps/${appSlug}/interaction/`, {query: {since: now - ninety_days_ago, until: now}}, ], {staleTime: 0} ); const { data: stats, isPending: isStatsPending, isError: isStatsError, } = useApiQuery( [ `/sentry-apps/${appSlug}/stats/`, {query: {since: now - ninety_days_ago, until: now}}, ], {staleTime: 0} ); if (isAppPending || isStatsPending || isInteractionsPending) { return ; } if (isAppError || isStatsError || isInteractionsError) { return ; } const {installStats, uninstallStats, totalUninstalls, totalInstalls} = stats; const {views, componentInteractions} = interactions; const renderInstallData = () => { return (
{t('Installation & Interaction Data')}
{app.datePublished ? ( {t('Date published')} ) : null} {t('Total installs')}

{totalInstalls}

{t('Total uninstalls')}

{totalUninstalls}

{renderInstallCharts()}
); }; const renderInstallCharts = () => { 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')} ); }; const renderIntegrationViews = () => { 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')} ); }; const renderComponentInteractions = () => { 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 as keyof typeof componentInteractionsDetails ] && ( {`${component}: `} { componentInteractionsDetails[ component as keyof typeof componentInteractionsDetails ] }
) )}
); }; return (
{app.status === 'published' && renderInstallData()} {app.status === 'published' && renderIntegrationViews()} {app.schema.elements && renderComponentInteractions()}
); } export default SentryApplicationDashboard; type InteractionsChartProps = { data: { [key: string]: Array<[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)}; `;