|
@@ -1,6 +1,8 @@
|
|
|
import {Fragment, useCallback, useId, useMemo, useState} from 'react';
|
|
|
import styled from '@emotion/styled';
|
|
|
+import {Observer} from 'mobx-react';
|
|
|
|
|
|
+import Alert from 'sentry/components/alert';
|
|
|
import {Button} from 'sentry/components/button';
|
|
|
import SearchBar from 'sentry/components/events/searchBar';
|
|
|
import SelectField from 'sentry/components/forms/fields/selectField';
|
|
@@ -9,7 +11,7 @@ import FormField from 'sentry/components/forms/formField';
|
|
|
import type FormModel from 'sentry/components/forms/model';
|
|
|
import ExternalLink from 'sentry/components/links/externalLink';
|
|
|
import {Tooltip} from 'sentry/components/tooltip';
|
|
|
-import {IconAdd, IconClose, IconWarning} from 'sentry/icons';
|
|
|
+import {IconAdd, IconClose, IconQuestion, IconWarning} from 'sentry/icons';
|
|
|
import {t, tct} from 'sentry/locale';
|
|
|
import {space} from 'sentry/styles/space';
|
|
|
import type {SelectValue} from 'sentry/types/core';
|
|
@@ -194,7 +196,7 @@ export function MetricsExtractionRuleForm({
|
|
|
}: Props) {
|
|
|
const organization = useOrganization();
|
|
|
|
|
|
- const [customAttributes, setCustomeAttributes] = useState<string[]>(() => {
|
|
|
+ const [customAttributes, setCustomAttributes] = useState<string[]>(() => {
|
|
|
const {spanAttribute, tags} = props.initialData;
|
|
|
return [...new Set(spanAttribute ? [...tags, spanAttribute] : tags)];
|
|
|
});
|
|
@@ -265,7 +267,7 @@ export function MetricsExtractionRuleForm({
|
|
|
|
|
|
const unitOptions = useMemo(() => {
|
|
|
const options: SelectValue<string>[] = SUPPORTED_UNITS.map(unit => ({
|
|
|
- label: unit,
|
|
|
+ label: unit + (unit === 'none' ? '' : 's'),
|
|
|
value: unit,
|
|
|
}));
|
|
|
if (customUnit) {
|
|
@@ -308,89 +310,123 @@ export function MetricsExtractionRuleForm({
|
|
|
<Form onSubmit={onSubmit && handleSubmit} {...props}>
|
|
|
{({model}) => (
|
|
|
<Fragment>
|
|
|
- <SelectField
|
|
|
- name="spanAttribute"
|
|
|
- options={attributeOptions}
|
|
|
- disabled={isEdit}
|
|
|
- label={t('Measure')}
|
|
|
- help={tct(
|
|
|
- 'Define the span attribute you want to track. Learn how to instrument custom attributes in [link:our docs].',
|
|
|
- {
|
|
|
- // TODO(telemetry-experience): add the correct link here once we have it!!!
|
|
|
- link: (
|
|
|
- <ExternalLink href="https://docs.sentry.io/product/explore/metrics/" />
|
|
|
- ),
|
|
|
+ <SpanAttributeUnitWrapper>
|
|
|
+ <SelectField
|
|
|
+ inline={false}
|
|
|
+ stacked
|
|
|
+ name="spanAttribute"
|
|
|
+ options={attributeOptions}
|
|
|
+ disabled={isEdit}
|
|
|
+ label={
|
|
|
+ <TooltipIconLabel
|
|
|
+ label={t('Measure')}
|
|
|
+ help={tct(
|
|
|
+ 'Define the span attribute you want to track. Learn how to instrument custom attributes in [link:our docs].',
|
|
|
+ {
|
|
|
+ // TODO(telemetry-experience): add the correct link here once we have it!!!
|
|
|
+ link: (
|
|
|
+ <ExternalLink href="https://docs.sentry.io/product/explore/metrics/" />
|
|
|
+ ),
|
|
|
+ }
|
|
|
+ )}
|
|
|
+ />
|
|
|
}
|
|
|
- )}
|
|
|
- placeholder={t('Select a span attribute')}
|
|
|
- creatable
|
|
|
- formatCreateLabel={value => `Custom: "${value}"`}
|
|
|
- onCreateOption={value => {
|
|
|
- setCustomeAttributes(curr => [...curr, value]);
|
|
|
- model.setValue('spanAttribute', value);
|
|
|
- }}
|
|
|
- onChange={value => {
|
|
|
- if (value in FIXED_UNITS_BY_ATTRIBUTE) {
|
|
|
- model.setValue('unit', FIXED_UNITS_BY_ATTRIBUTE[value]);
|
|
|
- setIsUnitDisabled(true);
|
|
|
- } else {
|
|
|
- setIsUnitDisabled(false);
|
|
|
+ placeholder={t('Select span attribute')}
|
|
|
+ creatable
|
|
|
+ formatCreateLabel={value => `Custom: "${value}"`}
|
|
|
+ onCreateOption={value => {
|
|
|
+ setCustomAttributes(curr => [...curr, value]);
|
|
|
+ model.setValue('spanAttribute', value);
|
|
|
+ }}
|
|
|
+ onChange={value => {
|
|
|
+ model.setValue('spanAttribute', value);
|
|
|
+ if (value in FIXED_UNITS_BY_ATTRIBUTE) {
|
|
|
+ model.setValue('unit', FIXED_UNITS_BY_ATTRIBUTE[value]);
|
|
|
+ setIsUnitDisabled(true);
|
|
|
+ } else {
|
|
|
+ setIsUnitDisabled(false);
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ required
|
|
|
+ />
|
|
|
+ <StyledFieldConnector>in</StyledFieldConnector>
|
|
|
+ <SelectField
|
|
|
+ inline={false}
|
|
|
+ stacked
|
|
|
+ allowEmpty
|
|
|
+ name="unit"
|
|
|
+ options={unitOptions}
|
|
|
+ disabled={isUnitDisabled}
|
|
|
+ label={
|
|
|
+ <ExternalLink href="https://docs.sentry.io/product/explore/metrics/">
|
|
|
+ {t('Create Custom Attribute?')}
|
|
|
+ </ExternalLink>
|
|
|
}
|
|
|
- }}
|
|
|
- required
|
|
|
- />
|
|
|
- <SelectField
|
|
|
- name="unit"
|
|
|
- options={unitOptions}
|
|
|
- disabled={isUnitDisabled}
|
|
|
- label={t('Unit')}
|
|
|
- placeholder={t('Select a unit')}
|
|
|
- creatable
|
|
|
- formatCreateLabel={value => `Custom: "${value}"`}
|
|
|
- onCreateOption={value => {
|
|
|
- setCustomUnit(value);
|
|
|
- model.setValue('unit', value);
|
|
|
- }}
|
|
|
- required
|
|
|
- />
|
|
|
+ placeholder={t('Select unit')}
|
|
|
+ creatable
|
|
|
+ formatCreateLabel={value => `Custom: "${value}"`}
|
|
|
+ onCreateOption={value => {
|
|
|
+ setCustomUnit(value);
|
|
|
+ model.setValue('unit', value);
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </SpanAttributeUnitWrapper>
|
|
|
+
|
|
|
<SelectField
|
|
|
+ inline={false}
|
|
|
+ stacked
|
|
|
name="aggregates"
|
|
|
required
|
|
|
options={AGGREGATE_OPTIONS}
|
|
|
- label={t('Aggregate')}
|
|
|
+ label={
|
|
|
+ <TooltipIconLabel
|
|
|
+ label={t('Aggregate')}
|
|
|
+ help={tct(
|
|
|
+ 'Select the aggregations you want to store. For more information, read [link:our docs]',
|
|
|
+ {
|
|
|
+ // TODO(telemetry-experience): add the correct link here once we have it!!!
|
|
|
+ link: (
|
|
|
+ <ExternalLink href="https://docs.sentry.io/product/explore/metrics/" />
|
|
|
+ ),
|
|
|
+ }
|
|
|
+ )}
|
|
|
+ />
|
|
|
+ }
|
|
|
multiple
|
|
|
- help={tct(
|
|
|
- 'Select the aggregations you want to store. For more information, read [link:our docs]',
|
|
|
- {
|
|
|
- // TODO(telemetry-experience): add the correct link here once we have it!!!
|
|
|
- link: (
|
|
|
- <ExternalLink href="https://docs.sentry.io/product/explore/metrics/" />
|
|
|
- ),
|
|
|
- }
|
|
|
- )}
|
|
|
/>
|
|
|
<SelectField
|
|
|
+ inline={false}
|
|
|
+ stacked
|
|
|
name="tags"
|
|
|
options={tagOptions}
|
|
|
- label={t('Group and filter by')}
|
|
|
multiple
|
|
|
placeholder={t('Select tags')}
|
|
|
- help={t(
|
|
|
- 'Select the tags that can be used to group and filter the metric. Tag values have to be non-numeric.'
|
|
|
- )}
|
|
|
+ label={
|
|
|
+ <TooltipIconLabel
|
|
|
+ label={t('Group and filter by')}
|
|
|
+ help={t(
|
|
|
+ 'Select the tags that can be used to group and filter the metric. Tag values have to be non-numeric.'
|
|
|
+ )}
|
|
|
+ />
|
|
|
+ }
|
|
|
creatable
|
|
|
formatCreateLabel={value => `Custom: "${value}"`}
|
|
|
onCreateOption={value => {
|
|
|
- setCustomeAttributes(curr => [...curr, value]);
|
|
|
+ setCustomAttributes(curr => [...curr, value]);
|
|
|
const currentTags = model.getValue('tags') as string[];
|
|
|
model.setValue('tags', [...currentTags, value]);
|
|
|
}}
|
|
|
/>
|
|
|
<FormField
|
|
|
- label={t('Filters')}
|
|
|
- help={t(
|
|
|
- 'Define filters to narrow down the metric to a specific set of spans.'
|
|
|
- )}
|
|
|
+ stacked
|
|
|
+ label={
|
|
|
+ <TooltipIconLabel
|
|
|
+ label={t('Filters')}
|
|
|
+ help={t(
|
|
|
+ 'Define filters to narrow down the metric to a specific set of spans.'
|
|
|
+ )}
|
|
|
+ />
|
|
|
+ }
|
|
|
name="conditions"
|
|
|
inline={false}
|
|
|
hasControlState={false}
|
|
@@ -457,7 +493,7 @@ export function MetricsExtractionRuleForm({
|
|
|
handleChange(queryString, index);
|
|
|
}
|
|
|
}}
|
|
|
- placeholder={t('Search for span attributes')}
|
|
|
+ placeholder={t('Add span attributes')}
|
|
|
organization={organization}
|
|
|
supportedTags={supportedTags}
|
|
|
dataset={DiscoverDatasets.SPANS_INDEXED}
|
|
@@ -491,19 +527,84 @@ export function MetricsExtractionRuleForm({
|
|
|
);
|
|
|
}}
|
|
|
</FormField>
|
|
|
+ <Observer>
|
|
|
+ {() =>
|
|
|
+ model.formChanged ? (
|
|
|
+ <Alert
|
|
|
+ type="info"
|
|
|
+ showIcon
|
|
|
+ expand={
|
|
|
+ <Fragment>
|
|
|
+ <b>{t('Why that?')}</b>
|
|
|
+ <p>
|
|
|
+ {t(
|
|
|
+ 'Well, it’s because we’ll only collect data once you’ve created a metric and not before. Likewise, if you deleted an existing metric, then we’ll stop collecting data for that metric.'
|
|
|
+ )}
|
|
|
+ </p>
|
|
|
+ </Fragment>
|
|
|
+ }
|
|
|
+ >
|
|
|
+ {t('Hey, we’ll need a moment to collect data that matches the above.')}
|
|
|
+ </Alert>
|
|
|
+ ) : null
|
|
|
+ }
|
|
|
+ </Observer>
|
|
|
</Fragment>
|
|
|
)}
|
|
|
</Form>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
+function TooltipIconLabel({label, help}) {
|
|
|
+ return (
|
|
|
+ <TooltipIconLabelWrapper>
|
|
|
+ {label}
|
|
|
+ <Tooltip title={help}>
|
|
|
+ <IconQuestion size="sm" color="gray200" />
|
|
|
+ </Tooltip>
|
|
|
+ </TooltipIconLabelWrapper>
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+const TooltipIconLabelWrapper = styled('span')`
|
|
|
+ display: inline-flex;
|
|
|
+ font-weight: bold;
|
|
|
+ color: ${p => p.theme.gray300};
|
|
|
+ gap: ${space(0.5)};
|
|
|
+
|
|
|
+ & > span {
|
|
|
+ margin-top: 1px;
|
|
|
+ }
|
|
|
+
|
|
|
+ & > span:hover {
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+`;
|
|
|
+
|
|
|
+const StyledFieldConnector = styled('div')`
|
|
|
+ color: ${p => p.theme.gray300};
|
|
|
+ padding-bottom: ${space(1)};
|
|
|
+`;
|
|
|
+
|
|
|
+const SpanAttributeUnitWrapper = styled('div')`
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-end;
|
|
|
+
|
|
|
+ gap: ${space(1)};
|
|
|
+ padding-bottom: ${space(2)};
|
|
|
+
|
|
|
+ & > div:first-child {
|
|
|
+ flex: 1;
|
|
|
+ padding-bottom: 0;
|
|
|
+ }
|
|
|
+`;
|
|
|
+
|
|
|
function SearchBarWithId(props: React.ComponentProps<typeof SearchBar>) {
|
|
|
const id = useId();
|
|
|
return <SearchBar id={id} {...props} />;
|
|
|
}
|
|
|
|
|
|
const ConditionsWrapper = styled('div')<{hasDelete: boolean}>`
|
|
|
- padding: ${space(1)} 0;
|
|
|
display: grid;
|
|
|
align-items: center;
|
|
|
gap: ${space(1)};
|