import {Fragment, useCallback, useMemo, useState} from 'react'; import styled from '@emotion/styled'; import {Button} from 'sentry/components/button'; import SearchBar from 'sentry/components/events/searchBar'; import SelectField from 'sentry/components/forms/fields/selectField'; import Form, {type FormProps} from 'sentry/components/forms/form'; import FormField from 'sentry/components/forms/formField'; import type FormModel from 'sentry/components/forms/model'; import {IconAdd, IconClose} from 'sentry/icons'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {MetricType} from 'sentry/types/metrics'; import type {Project} from 'sentry/types/project'; import {DiscoverDatasets} from 'sentry/utils/discover/types'; import useOrganization from 'sentry/utils/useOrganization'; import {useSpanFieldSupportedTags} from 'sentry/views/performance/utils/useSpanFieldSupportedTags'; export interface FormData { conditions: string[]; spanAttribute: string | null; tags: string[]; type: MetricType | null; } interface Props extends Omit { initialData: FormData; project: Project; isEdit?: boolean; onSubmit?: ( data: FormData, onSubmitSuccess: (data: FormData) => void, onSubmitError: (error: any) => void, event: React.FormEvent, model: FormModel ) => void; } const ListItemDetails = styled('span')` color: ${p => p.theme.subText}; font-size: ${p => p.theme.fontSizeSmall}; text-align: right; line-height: 1.2; `; const TYPE_OPTIONS = [ { label: t('Counter'), value: 'c', trailingItems: [{t('count')}], }, { label: t('Set'), value: 's', trailingItems: [ {t('count_unique')}, ], }, { label: t('Distribution'), value: 'd', trailingItems: [ {t('count, avg, sum, min, max, percentiles')} , ], }, ]; export function MetricsExtractionRuleForm({isEdit, project, onSubmit, ...props}: Props) { const [customAttributes, setCustomeAttributes] = useState(() => { const {spanAttribute, tags} = props.initialData; return [...new Set(spanAttribute ? [...tags, spanAttribute] : tags)]; }); const organization = useOrganization(); const tags = useSpanFieldSupportedTags({projects: [parseInt(project.id, 10)]}); // TODO(aknaus): Make this nicer const supportedTags = useMemo(() => { const copy = {...tags}; delete copy.has; return copy; }, [tags]); const attributeOptions = useMemo(() => { let keys = Object.keys(supportedTags); if (customAttributes.length) { keys = [...new Set(keys.concat(customAttributes))]; } return keys .map(key => ({ label: key, value: key, })) .sort((a, b) => a.label.localeCompare(b.label)); }, [customAttributes, supportedTags]); const handleSubmit = useCallback( ( data: Record, onSubmitSuccess: (data: Record) => void, onSubmitError: (error: any) => void, event: React.FormEvent, model: FormModel ) => { onSubmit?.(data as FormData, onSubmitSuccess, onSubmitError, event, model); }, [onSubmit] ); return (
{({model}) => ( `Custom: "${value}"`} onCreateOption={value => { setCustomeAttributes(curr => [...curr, value]); model.setValue('spanAttribute', value); }} required /> `Custom: "${value}"`} onCreateOption={value => { setCustomeAttributes(curr => [...curr, value]); const currentTags = model.getValue('tags') as string[]; model.setValue('tags', [...currentTags, value]); }} /> {({onChange, initialData, value}) => { const conditions = (value || initialData) as string[]; return ( 1}> {conditions.map((query, index) => ( {index !== 0 && {t('or')}} onChange(conditions.toSpliced(index, 1, queryString), {}) } placeholder={t('Search for span attributes')} organization={organization} metricAlert={false} supportedTags={supportedTags} dataset={DiscoverDatasets.SPANS_INDEXED} projectIds={[parseInt(project.id, 10)]} hasRecentSearches={false} onBlur={(queryString: string) => onChange(conditions.toSpliced(index, 1, queryString), {}) } /> {value.length > 1 && ( ); }} )}
); } const ConditionsWrapper = styled('div')<{hasDelete: boolean}>` display: grid; gap: ${space(1)}; ${p => p.hasDelete ? ` grid-template-columns: 1fr min-content; ` : ` grid-template-columns: 1fr; `} `; const SearchWrapper = styled('div')<{hasPrefix: boolean}>` display: grid; gap: ${space(1)}; ${p => p.hasPrefix ? ` grid-template-columns: max-content 1fr; ` : ` grid-template-columns: 1fr; `} `; const ConditionLetter = styled('div')` background-color: ${p => p.theme.purple100}; border-radius: ${p => p.theme.borderRadius}; text-align: center; padding: 0 ${space(2)}; color: ${p => p.theme.purple400}; white-space: nowrap; font-weight: ${p => p.theme.fontWeightBold}; align-content: center; `; const ConditionsButtonBar = styled('div')` margin-top: ${space(1)}; `;