import {Fragment, useCallback, useEffect, useMemo, useState} from 'react'; import styled from '@emotion/styled'; import {CompactSelect} from 'sentry/components/compactSelect'; import SearchBar from 'sentry/components/events/searchBar'; import PageFilterBar from 'sentry/components/organizations/pageFilterBar'; import Tag from 'sentry/components/tag'; import {IconLightning, IconReleases} from 'sentry/icons'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {MetricsTag, SavedSearchType, TagCollection} from 'sentry/types'; import { defaultMetricDisplayType, getReadableMetricType, getUseCaseFromMri, isAllowedOp, MetricDisplayType, MetricsQuery, useMetricsMeta, useMetricsTags, } from 'sentry/utils/metrics'; 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 {MetricWidgetProps} from 'sentry/views/ddm/widget'; type QueryBuilderProps = { displayType: MetricDisplayType; // TODO(ddm): move display type out of the query builder metricsQuery: Pick; onChange: (data: Partial) => void; projects: number[]; powerUserMode?: boolean; }; export function QueryBuilder({ metricsQuery, projects, displayType, powerUserMode, onChange, }: QueryBuilderProps) { const meta = useMetricsMeta(projects); 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); if (!meta) { return null; } return ( mriMode ? true : metric.mri.includes(':custom/') || metric.mri === metricsQuery.mri ) .map(metric => ({ label: mriMode ? metric.mri : metric.name, value: metric.mri, trailingItems: mriMode ? undefined : ( {getReadableMetricType(metric.type)} {metric.unit} ), }))} value={metricsQuery.mri} onChange={option => { const availableOps = meta[option.value]?.operations.filter(isAllowedOp); const selectedOp = availableOps.includes(metricsQuery.op ?? '') ? metricsQuery.op : availableOps[0]; onChange({ mri: option.value, op: selectedOp, groupBy: undefined, focusedSeries: undefined, displayType: getWidgetDisplayType(option.value, selectedOp), }); }} /> ({ label: op, value: op, })) ?? [] } disabled={!metricsQuery.mri} value={metricsQuery.op} onChange={option => onChange({ op: option.value, }) } /> ({ label: tag.key, value: tag.key, trailingItems: ( {tag.key === 'release' && } {tag.key === 'transaction' && } ), }))} disabled={!metricsQuery.mri} value={metricsQuery.groupBy} onChange={options => onChange({ groupBy: options.map(o => o.value), focusedSeries: undefined, }) } /> { onChange({displayType: value}); }} /> onChange({query})} query={metricsQuery.query} /> ); } type MetricSearchBarProps = { mri: string; onChange: (value: string) => void; tags: MetricsTag[]; disabled?: boolean; query?: string; }; function MetricSearchBar({tags, mri, disabled, onChange, query}: MetricSearchBarProps) { const org = useOrganization(); const api = useApi(); const {selection} = usePageFilters(); const supportedTags: TagCollection = useMemo( () => tags.reduce((acc, tag) => ({...acc, [tag.key]: tag}), {}), [tags] ); // TODO(ddm): try to use useApiQuery here const getTagValues = useCallback( async tag => { const tagsValues = await api.requestPromise( `/organizations/${org.slug}/metrics/tags/${tag.key}/`, { query: { metric: mri, useCase: getUseCaseFromMri(mri), project: selection.projects, }, } ); return tagsValues.filter(tv => tv.value !== '').map(tv => tv.value); }, [api, mri, org.slug, selection.projects] ); const handleChange = useCallback( (value: string, {validSearch} = {validSearch: true}) => { if (validSearch) { onChange(value); } }, [onChange] ); return ( ); } function getWidgetDisplayType( mri: MetricsQuery['mri'], op: MetricsQuery['op'] ): MetricDisplayType { if (mri?.startsWith('c') || op === 'count') { return MetricDisplayType.BAR; } return MetricDisplayType.LINE; } const QueryBuilderWrapper = styled('div')` display: flex; flex-direction: column; `; const QueryBuilderRow = styled('div')` padding: ${space(1)}; padding-bottom: 0; `; const WideSearchBar = styled(SearchBar)` width: 100%; opacity: ${p => (p.disabled ? '0.6' : '1')}; `; const WrapPageFilterBar = styled(PageFilterBar)` max-width: max-content; height: auto; flex-wrap: wrap; `;