import {Fragment, useMemo, useState} from 'react'; import type {RouteComponentProps} from 'react-router'; import {browserHistory} from 'react-router'; import styled from '@emotion/styled'; import * as Sentry from '@sentry/react'; import debounce from 'lodash/debounce'; import {Button} from 'sentry/components/button'; import ExternalLink from 'sentry/components/links/externalLink'; import Link from 'sentry/components/links/link'; import {PanelTable} from 'sentry/components/panels/panelTable'; import SearchBar from 'sentry/components/searchBar'; import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; import {TabList, TabPanels, Tabs} from 'sentry/components/tabs'; import {Tag} from 'sentry/components/tag'; import {DEFAULT_DEBOUNCE_DURATION} from 'sentry/constants'; import {t, tct} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {MetricMeta, Organization, Project} from 'sentry/types'; import {METRICS_DOCS_URL} from 'sentry/utils/metrics/constants'; import {getReadableMetricType} from 'sentry/utils/metrics/formatters'; import {formatMRI} from 'sentry/utils/metrics/mri'; import {useBlockMetric} from 'sentry/utils/metrics/useBlockMetric'; import {useMetricsMeta} from 'sentry/utils/metrics/useMetricsMeta'; import {middleEllipsis} from 'sentry/utils/middleEllipsis'; import {decodeScalar} from 'sentry/utils/queryString'; import routeTitleGen from 'sentry/utils/routeTitle'; import {useMetricsOnboardingSidebar} from 'sentry/views/ddm/ddmOnboarding/useMetricsOnboardingSidebar'; import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader'; import TextBlock from 'sentry/views/settings/components/text/textBlock'; import PermissionAlert from 'sentry/views/settings/project/permissionAlert'; import {useAccess} from 'sentry/views/settings/projectMetrics/access'; import {BlockButton} from 'sentry/views/settings/projectMetrics/blockButton'; type Props = { organization: Organization; project: Project; } & RouteComponentProps<{projectId: string}, {}>; enum BlockingStatusTab { ACTIVE = 'active', BLOCKED = 'blocked', } function ProjectMetrics({project, location}: Props) { const {data: meta, isLoading} = useMetricsMeta( {projects: [parseInt(project.id, 10)]}, ['custom'], false ); const query = decodeScalar(location.query.query, '').trim(); const {activateSidebar} = useMetricsOnboardingSidebar(); const [selectedTab, setSelectedTab] = useState(BlockingStatusTab.ACTIVE); const debouncedSearch = useMemo( () => debounce( (searchQuery: string) => browserHistory.replace({ pathname: location.pathname, query: {...location.query, query: searchQuery}, }), DEFAULT_DEBOUNCE_DURATION ), [location.pathname, location.query] ); const metrics = meta.filter( ({mri, type, unit}) => mri.includes(query) || getReadableMetricType(type).includes(query) || unit.includes(query) ); return ( { Sentry.metrics.increment('ddm.add_custom_metric', 1, { tags: { referrer: 'settings', }, }); activateSidebar(); }} size="sm" > {t('Add Metric')} } /> {tct( `Metrics are numerical values that can track anything about your environment over time, from latency to error rates to user signups. To learn more about metrics, [link:read the docs].`, { link: , } )} {t('Active')} {t('Blocked')} !blockingStatus[0]?.isBlocked )} isLoading={isLoading} query={query} project={project} /> blockingStatus[0]?.isBlocked)} isLoading={isLoading} query={query} project={project} /> ); } interface MetricsTableProps { isLoading: boolean; metrics: MetricMeta[]; project: Project; query: string; } function MetricsTable({metrics, isLoading, query, project}: MetricsTableProps) { const blockMetricMutation = useBlockMetric(project); const {hasAccess} = useAccess({access: ['project:write']}); return ( {' '} {t('Type')} , {t('Unit')} , {t('Actions')} , ]} emptyMessage={ query ? t('No metrics match the query.') : t('There are no custom metrics to display.') } isEmpty={metrics.length === 0} isLoading={isLoading} > {metrics.map(({mri, type, unit, blockingStatus}) => { const isBlocked = blockingStatus[0]?.isBlocked; return ( {middleEllipsis(formatMRI(mri), 65, /\.|-|_/)} {getReadableMetricType(type)} {unit} { blockMetricMutation.mutate({ mri, operationType: isBlocked ? 'unblockMetric' : 'blockMetric', }); }} /> ); })} ); } const TabPanelsWrapper = styled(TabPanels)` margin-top: ${space(2)}; `; const SearchWrapper = styled('div')` margin-bottom: ${space(2)}; `; const StyledPanelTable = styled(PanelTable)` grid-template-columns: 1fr repeat(3, minmax(115px, min-content)); `; const Cell = styled('div')<{right?: boolean}>` display: flex; align-items: center; align-self: stretch; justify-content: ${p => (p.right ? 'flex-end' : 'flex-start')}; `; export default ProjectMetrics;