import {Fragment, memo, useCallback, useEffect, useMemo, useState} from 'react'; import styled from '@emotion/styled'; import {navigateTo} from 'sentry/actionCreators/navigation'; import {Button} from 'sentry/components/button'; import {HeaderTitle} from 'sentry/components/charts/styles'; import {CompactSelect} from 'sentry/components/compactSelect'; import PageFilterBar from 'sentry/components/organizations/pageFilterBar'; import {BooleanOperator} from 'sentry/components/searchSyntax/parser'; import SmartSearchBar, {SmartSearchBarProps} from 'sentry/components/smartSearchBar'; import Tag from 'sentry/components/tag'; import TextOverflow from 'sentry/components/textOverflow'; import {IconLightning, IconReleases, IconSettings} from 'sentry/icons'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {MetricMeta, MRI, SavedSearchType, TagCollection} from 'sentry/types'; import { defaultMetricDisplayType, getReadableMetricType, isAllowedOp, isCustomMetric, isMeasurement, isTransactionDuration, MetricDisplayType, MetricsQuery, MetricsQuerySubject, MetricWidgetQueryParams, stringifyMetricWidget, } from 'sentry/utils/metrics'; import {formatMRI, getUseCaseFromMRI} from 'sentry/utils/metrics/mri'; import {useMetricsMeta} from 'sentry/utils/metrics/useMetricsMeta'; import {useMetricsTags} from 'sentry/utils/metrics/useMetricsTags'; import useApi from 'sentry/utils/useApi'; import useKeyPress from 'sentry/utils/useKeyPress'; import useOrganization from 'sentry/utils/useOrganization'; import usePageFilters from 'sentry/utils/usePageFilters'; import useRouter from 'sentry/utils/useRouter'; type QueryBuilderProps = { displayType: MetricDisplayType; isEdit: boolean; // TODO(ddm): move display type out of the query builder metricsQuery: MetricsQuerySubject; onChange: (data: Partial) => void; projects: number[]; powerUserMode?: boolean; }; const isShownByDefault = (metric: MetricMeta) => isMeasurement(metric) || isCustomMetric(metric) || isTransactionDuration(metric); function stopPropagation(e: React.MouseEvent) { e.stopPropagation(); } export const QueryBuilder = memo(function QueryBuilder({ metricsQuery, projects, displayType, powerUserMode, onChange, isEdit, }: QueryBuilderProps) { const {data: meta, isLoading: isMetaLoading} = useMetricsMeta(projects); const router = useRouter(); const mriModeKeyPressed = useKeyPress('`', undefined, true); const [mriMode, setMriMode] = useState(powerUserMode); // power user mode that shows raw MRI instead of metrics names useEffect(() => { if (mriModeKeyPressed && !powerUserMode) { setMriMode(!mriMode); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [mriModeKeyPressed, powerUserMode]); const {data: tags = []} = useMetricsTags(metricsQuery.mri, projects); const displayedMetrics = useMemo(() => { if (mriMode) { return meta; } const isSelected = (metric: MetricMeta) => metric.mri === metricsQuery.mri; return meta .filter(metric => isShownByDefault(metric) || isSelected(metric)) .sort(metric => (isSelected(metric) ? -1 : 1)); }, [meta, metricsQuery.mri, mriMode]); const selectedMeta = useMemo(() => { return meta.find(metric => metric.mri === metricsQuery.mri); }, [meta, metricsQuery.mri]); // Reset the query data if the selected metric is no longer available useEffect(() => { if ( metricsQuery.mri && !isMetaLoading && !displayedMetrics.find(metric => metric.mri === metricsQuery.mri) ) { onChange({mri: '' as MRI, op: '', groupBy: []}); } }, [isMetaLoading, displayedMetrics, metricsQuery.mri, onChange]); const stringifiedMetricWidget = stringifyMetricWidget(metricsQuery); if (!isEdit) { return ( {metricsQuery.title || stringifiedMetricWidget} ); } return ( ({ label: mriMode ? metric.mri : formatMRI(metric.mri), // enable search by mri, name, unit (millisecond), type (c:), and readable type (counter) textValue: `${metric.mri}${getReadableMetricType(metric.type)}`, value: metric.mri, trailingItems: mriMode ? undefined : ({isFocused}) => ( {isFocused && isCustomMetric({mri: metric.mri}) && (