import {Fragment, useState} from 'react'; import styled from '@emotion/styled'; import {LinkButton} from 'sentry/components/button'; import {EventDataSection} from 'sentry/components/events/eventDataSection'; import KeyValueList from 'sentry/components/events/interfaces/keyValueList'; import type { MetricsSummary, MetricsSummaryItem, } from 'sentry/components/events/interfaces/spans/types'; import Link from 'sentry/components/links/link'; import {normalizeDateTimeString} from 'sentry/components/organizations/pageFilters/parse'; import Pill from 'sentry/components/pill'; import Pills from 'sentry/components/pills'; import TextOverflow from 'sentry/components/textOverflow'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {MRI, Organization} from 'sentry/types'; import {getDdmUrl, getDefaultMetricOp} from 'sentry/utils/metrics'; import {hasCustomMetrics} from 'sentry/utils/metrics/features'; import { formatMetricUsingUnit, getReadableMetricType, } from 'sentry/utils/metrics/formatters'; import {formatMRI, parseMRI} from 'sentry/utils/metrics/mri'; import {MetricDisplayType} from 'sentry/utils/metrics/types'; import useOrganization from 'sentry/utils/useOrganization'; function flattenMetricsSummary( metricsSummary: MetricsSummary ): {item: MetricsSummaryItem; key: string; mri: MRI}[] { return ( Object.entries(metricsSummary) as [ keyof MetricsSummary, MetricsSummary[keyof MetricsSummary], ][] ).flatMap(([mri, items]) => (items || []).map((item, index) => ({item, mri, key: `${mri}${index}`})) ); } function tagToQuery(tagKey: string, tagValue: string) { return `${tagKey}:"${tagValue}"`; } const HALF_HOUR_IN_MS = 30 * 60 * 1000; export function CustomMetricsEventData({ metricsSummary, startTimestamp, }: { startTimestamp: number; metricsSummary?: MetricsSummary; }) { const organization = useOrganization(); const metricsSummaryEntries = metricsSummary ? flattenMetricsSummary(metricsSummary) : []; const widgetStart = new Date(startTimestamp * 1000 - HALF_HOUR_IN_MS); const widgetEnd = new Date(startTimestamp * 1000 + HALF_HOUR_IN_MS); if (!hasCustomMetrics(organization) || metricsSummaryEntries.length === 0) { return null; } return ( {metricsSummaryEntries.map(({mri, item, key}) => { return ( {formatMRI(mri)}, actionButton: ( tagToQuery(tagKey, tagValue)) .join(' '), }, ], })} > {t('Open in Metrics')} ), }, { key: 'stats', subject: t('Stats'), value: , }, item.tags && Object.keys(item.tags).length > 0 ? { key: 'tags', subject: t('Tags'), value: ( ), } : null, ].filter((row): row is Exclude => Boolean(row))} /> ); })} ); } function MetricStats({mri, item}: {item: MetricsSummaryItem; mri: MRI}) { const parsedMRI = parseMRI(mri); const unit = parsedMRI?.unit ?? 'none'; const type = parsedMRI?.type ?? 'c'; const typeLine = t(`Type: %s`, getReadableMetricType(type)); // We use formatMetricUsingUnit with unit 'none' to ensure uniform number formatting const countLine = t(`Count: %s`, formatMetricUsingUnit(item.count, 'none')); // For counters the other stats offer little value, so we only show type and count if (type === 'c' || !item.count) { return (
        {typeLine}
        
{countLine}
); } // If there is only one value, min, max, avg and sum are all the same if (item.count <= 1) { return (
        {typeLine}
        
{t('Value: %s', formatMetricUsingUnit(item.sum, unit))}
); } return (
      {typeLine}
      
{countLine}
{t('Sum: %s', formatMetricUsingUnit(item.sum, unit))}
{t('Min: %s', formatMetricUsingUnit(item.min, unit))}
{t('Max: %s', formatMetricUsingUnit(item.max, unit))}
{t( 'Avg: %s', formatMetricUsingUnit(item.sum && item.count && item.sum / item.count, unit) )}
); } function Tags({ tags, organization, mri, }: { mri: MRI; organization: Organization; tags: Record; }) { const [showingAll, setShowingAll] = useState(false); const renderedTags = Object.entries(tags).slice(0, showingAll ? undefined : 5); const renderText = showingAll ? t('Show less') : t('Show more') + '...'; return ( {renderedTags.map(([tagKey, tagValue]) => ( {tagValue} ))} {Object.entries(tags).length > 5 && ( setShowingAll(prev => !prev)}>{renderText} )} ); } const StyledPills = styled(Pills)` padding-top: ${space(1)}; `; const StyledPill = styled(Pill)` width: min-content; `; const ShowMore = styled('a')` white-space: nowrap; align-self: center; margin-bottom: ${space(1)}; padding: ${space(0.5)} ${space(0.5)}; `;