import {Fragment, useCallback} from 'react'; import type {RouteComponentProps} from 'react-router'; import styled from '@emotion/styled'; import {LinkButton} from 'sentry/components/button'; import MiniBarChart from 'sentry/components/charts/miniBarChart'; import EmptyMessage from 'sentry/components/emptyMessage'; import FieldGroup from 'sentry/components/forms/fieldGroup'; import Panel from 'sentry/components/panels/panel'; import PanelBody from 'sentry/components/panels/panelBody'; import PanelHeader from 'sentry/components/panels/panelHeader'; import {PanelTable} from 'sentry/components/panels/panelTable'; import Placeholder from 'sentry/components/placeholder'; import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; import {CHART_PALETTE} from 'sentry/constants/chartPalette'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type { MetricsOperation, MetricType, MRI, Organization, Project, } from 'sentry/types'; import {getDdmUrl} from 'sentry/utils/metrics'; import {getReadableMetricType} from 'sentry/utils/metrics/formatters'; import {formatMRI, formatMRIField, MRIToField, parseMRI} from 'sentry/utils/metrics/mri'; import {MetricDisplayType} from 'sentry/utils/metrics/types'; import {useBlockMetric} from 'sentry/utils/metrics/useBlockMetric'; import {useMetricsQuery} from 'sentry/utils/metrics/useMetricsQuery'; import {useMetricsTags} from 'sentry/utils/metrics/useMetricsTags'; import routeTitleGen from 'sentry/utils/routeTitle'; import {CodeLocations} from 'sentry/views/ddm/codeLocations'; import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader'; import {useAccess} from 'sentry/views/settings/projectMetrics/access'; import {BlockButton} from 'sentry/views/settings/projectMetrics/blockButton'; import {TextAlignRight} from 'sentry/views/starfish/components/textAlign'; import {useProjectMetric} from '../../../utils/metrics/useMetricsMeta'; function getSettingsOperationForType(type: MetricType): MetricsOperation { switch (type) { case 'c': return 'sum'; case 's': return 'count_unique'; case 'd': return 'count'; case 'g': return 'count'; default: return 'sum'; } } type Props = { organization: Organization; project: Project; } & RouteComponentProps<{mri: MRI; projectId: string}, {}>; function ProjectMetricsDetails({project, params, organization}: Props) { const {mri} = params; const projectId = parseInt(project.id, 10); const projectIds = [projectId]; const { data: {blockingStatus}, } = useProjectMetric(mri, projectId); const {data: tagsData = []} = useMetricsTags(mri, {projects: projectIds}, false); const isBlockedMetric = blockingStatus?.isBlocked ?? false; const blockMetricMutation = useBlockMetric(project); const {hasAccess} = useAccess({access: ['project:write']}); const {type, name, unit} = parseMRI(mri) ?? {}; const operation = getSettingsOperationForType(type ?? 'c'); const {data: metricsData, isLoading} = useMetricsQuery( [{mri, op: operation, name: 'query'}], { datetime: { period: '30d', start: '', end: '', utc: false, }, environments: [], projects: projectIds, }, {interval: '1d'} ); const field = MRIToField(mri, operation); const series = [ { seriesName: formatMRIField(field) ?? 'Metric', data: metricsData?.intervals.map((interval, index) => ({ name: interval, value: metricsData.data[0]?.[0]?.series[index] ?? 0, })) ?? [], }, ]; const isChartEmpty = series[0].data.every(({value}) => value === 0); const handleMetricBlockToggle = useCallback(() => { const operationType = isBlockedMetric ? 'unblockMetric' : 'blockMetric'; blockMetricMutation.mutate({operationType, mri}); }, [blockMetricMutation, mri, isBlockedMetric]); const handleMetricTagBlockToggle = useCallback( (tag: string) => { const currentlyBlockedTags = blockingStatus?.blockedTags ?? []; const isBlockedTag = currentlyBlockedTags.includes(tag); const operationType = isBlockedTag ? 'unblockTags' : 'blockTags'; blockMetricMutation.mutate({operationType, mri, tags: [tag]}); }, [blockMetricMutation, mri, blockingStatus?.blockedTags] ); const tags = tagsData.sort((a, b) => a.key.localeCompare(b.key)); return ( {t('Open in Metrics')} } /> {t('Metric Details')} {name}
{getReadableMetricType(type)}
{unit}
{t('Activity in the last 30 days (by day)')} {isLoading && } {!isLoading && ( )} {!isLoading && isChartEmpty && ( )} {t('Tags')}, {t('Actions')} , ]} emptyMessage={t('There are no tags for this metric.')} isEmpty={tags.length === 0} isLoading={isLoading} > {tags.map(({key}) => { const isBlockedTag = blockingStatus?.blockedTags?.includes(key) ?? false; return (
{key}
handleMetricTagBlockToggle(key)} aria-label={t('Block tag')} message={ isBlockedTag ? t('Are you sure you want to unblock this tag?') : t( 'Are you sure you want to block this tag? It will no longer be ingested, and will not be available for use in Metrics, Alerts, or Dashboards.' ) } />
); })}
{t('Code Location')}
); } const TableHeading = styled('div')` color: ${p => p.theme.textColor}; `; const MetricName = styled('div')` word-break: break-word; `; const Title = styled('div')` flex: 1; margin-right: ${space(1)}; `; const Controls = styled('div')` display: grid; align-items: center; gap: ${space(1)}; grid-auto-flow: column; `; export default ProjectMetricsDetails;