import {Fragment, memo, useEffect, useMemo, useState} from 'react'; import styled from '@emotion/styled'; import {Button, ButtonProps} from 'sentry/components/button'; import {CompactSelect} from 'sentry/components/compactSelect'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import PageFilterBar from 'sentry/components/organizations/pageFilterBar'; import Tag from 'sentry/components/tag'; import {IconCheckmark, IconClose, IconLightning, IconReleases} from 'sentry/icons'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {MetricMeta, MRI} from 'sentry/types'; import { getReadableMetricType, isAllowedOp, isCustomMetric, isMeasurement, isTransactionDuration, MetricDisplayType, MetricsQuery, MetricsQuerySubject, MetricWidgetQueryParams, } from 'sentry/utils/metrics'; import {useMetricsMeta} from 'sentry/utils/metrics/useMetricsMeta'; import {useMetricsTags} from 'sentry/utils/metrics/useMetricsTags'; import {MetricSearchBar} from 'sentry/views/ddm/metricSearchBar'; import {formatMRI} from '../../../../utils/metrics/mri'; type InlineEditorProps = { displayType: MetricDisplayType; isEdit: boolean; metricsQuery: MetricsQuerySubject; onCancel: () => void; onChange: (data: Partial) => void; onSubmit: () => void; projects: number[]; powerUserMode?: boolean; size?: 'xs' | 'sm'; }; const isShownByDefault = (metric: MetricMeta) => isMeasurement(metric) || isCustomMetric(metric) || isTransactionDuration(metric); export const InlineEditor = memo(function InlineEditor({ metricsQuery, projects, displayType, onChange, onCancel, onSubmit, isEdit, size = 'sm', }: InlineEditorProps) { const {data: meta, isLoading: isMetaLoading} = useMetricsMeta(projects); const {data: tags = []} = useMetricsTags(metricsQuery.mri, projects); const displayedMetrics = useMemo(() => { 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]); 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 [loading, setIsLoading] = useState(false); useEffect(() => { if (loading && !isEdit) { setIsLoading(false); } }, [isEdit, loading]); return ( ({ label: 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, size, trailingItems: () => ( {getReadableMetricType(metric.type)} {metric.unit} ), }))} value={metricsQuery.mri} onChange={option => { const availableOps = meta .find(metric => metric.mri === option.value) ?.operations.filter(isAllowedOp) ?? []; // @ts-expect-error .op is an operation 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, size, 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}); }} /> id.toString())} mri={metricsQuery.mri} disabled={!metricsQuery.mri} onChange={query => { onChange({query}); }} query={metricsQuery.query} /> { onSubmit(); setIsLoading(true); }} aria-label="apply" />