Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ function computeEquationReferencedLabels(
return [];
}
const labelSet = new Set(Object.keys(referenceMap));
const unresolvedText = unresolveExpression(visualize.expression.text, referenceMap);
const unresolvedText =
visualize.internalExpression ??
unresolveExpression(visualize.expression.text, referenceMap);
return extractReferenceLabels(new Expression(unresolvedText, labelSet));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ export function MetricToolbar({
) => {
if (isVisualizeEquation(visualize)) {
setVisualize(
visualize.replace({yAxis: `${EQUATION_PREFIX}${resolvedExpression.text}`})
visualize.replace({
yAxis: `${EQUATION_PREFIX}${resolvedExpression.text}`,
internalExpression: internalText,
})
);
const labelSet = new Set(Object.keys(referenceMap));
const expr = new Expression(internalText, labelSet);
Expand Down Expand Up @@ -118,6 +121,7 @@ export function MetricToolbar({
referenceMap={referenceMap}
handleExpressionChange={handleExpressionChange}
disabled={disabled}
storedInternalExpression={visualize.internalExpression}
/>
) : null}
<Flex flex="1 1 100%" minWidth="0">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ function computeEquationReferencedLabels(
return [];
}
const labelSet = new Set(Object.keys(referenceMap));
const unresolvedText = unresolveExpression(visualize.expression.text, referenceMap);
const unresolvedText =
visualize.internalExpression ??
unresolveExpression(visualize.expression.text, referenceMap);
return extractReferenceLabels(new Expression(unresolvedText, labelSet));
}

Expand Down Expand Up @@ -366,7 +368,10 @@ function MetricToolbar({
) => {
if (isVisualizeEquation(visualize)) {
setVisualize(
visualize.replace({yAxis: `${EQUATION_PREFIX}${resolvedExpression.text}`})
visualize.replace({
yAxis: `${EQUATION_PREFIX}${resolvedExpression.text}`,
internalExpression: internalText,
})
);
// Report the user's typed labels (pre-resolve) so identical rows don't
// collapse the lock onto whichever label sorts first in the map.
Expand Down Expand Up @@ -425,6 +430,7 @@ function MetricToolbar({
expression={visualize.expression.text}
referenceMap={referenceMap}
handleExpressionChange={handleExpressionChange}
storedInternalExpression={visualize.internalExpression}
/>
<Filter
traceMetric={traceMetric}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,23 @@ export function EquationBuilder({
handleExpressionChange,
onReferenceLabelsChange,
disabled,
storedInternalExpression,
}: {
expression: string;
handleExpressionChange: (resolved: Expression, internalText: string) => void;
disabled?: boolean;
onReferenceLabelsChange?: (labels: string[]) => void;
referenceMap?: Record<string, string>;
storedInternalExpression?: string;
}) {
const [_, startTransition] = useTransition();
const references = useMemo(
() => new Set(Object.keys(referenceMap ?? {})),
[referenceMap]
);

const internalExpression = unresolveExpression(expression, referenceMap);
const internalExpression =
storedInternalExpression ?? unresolveExpression(expression, referenceMap);

// Report which labels this equation references after unresolving.
// Cleans up on unmount so deleted equations don't block metric deletion.
Expand Down
150 changes: 150 additions & 0 deletions static/app/views/explore/metrics/equationBuilder/utils.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,156 @@ describe('syncEquationMetricQueries', () => {
);
});

it('correctly updates equations when two references resolve to the same value', () => {
const metricQueries: BaseMetricQuery[] = [
{
label: 'A',
metric: {name: 'metricA', type: 'distribution', unit: 'none'},
queryParams: new ReadableQueryParams({
extrapolate: true,
mode: Mode.SAMPLES,
query: '',
cursor: '',
fields: ['id', 'timestamp'],
sortBys: [],
aggregateCursor: '',
aggregateFields: [
new VisualizeFunction('sum(value,metricA,distribution,none)'),
],
aggregateSortBys: [],
}),
},
{
label: 'B',
metric: {name: 'metricA', type: 'distribution', unit: 'none'},
queryParams: new ReadableQueryParams({
extrapolate: true,
mode: Mode.SAMPLES,
query: '',
cursor: '',
fields: ['id', 'timestamp'],
sortBys: [],
aggregateCursor: '',
aggregateFields: [
new VisualizeFunction('sum(value,metricA,distribution,none)'),
],
aggregateSortBys: [],
}),
},
{
label: 'ƒ1',
metric: {name: '', type: ''},
queryParams: new ReadableQueryParams({
extrapolate: true,
mode: Mode.AGGREGATE,
query: '',
cursor: '',
fields: ['id', 'timestamp'],
sortBys: [],
aggregateCursor: '',
aggregateFields: [
new VisualizeEquation(
'equation|sum(value,metricA,distribution,none) + sum(value,metricA,distribution,none)',
{internalExpression: 'A + B'}
),
],
aggregateSortBys: [],
}),
},
];

const updatedQueries = syncEquationMetricQueries(
metricQueries,
{
A: 'sum(value,metricA,distribution,none)',
B: 'sum(value,metricA,distribution,none)',
},
{
A: 'sum(value,metricA,distribution,none)',
B: 'avg(value,metricA,distribution,none)',
}
);

expect(updatedQueries[2]!.queryParams.visualizes[0]!.yAxis).toBe(
'equation|sum(value,metricA,distribution,none) + avg(value,metricA,distribution,none)'
);
});

it('correctly updates equations with non-alphabetical label order in expression', () => {
const metricQueries: BaseMetricQuery[] = [
{
label: 'A',
metric: {name: 'metricA', type: 'distribution', unit: 'none'},
queryParams: new ReadableQueryParams({
extrapolate: true,
mode: Mode.SAMPLES,
query: '',
cursor: '',
fields: ['id', 'timestamp'],
sortBys: [],
aggregateCursor: '',
aggregateFields: [
new VisualizeFunction('sum(value,metricA,distribution,none)'),
],
aggregateSortBys: [],
}),
},
{
label: 'B',
metric: {name: 'metricA', type: 'distribution', unit: 'none'},
queryParams: new ReadableQueryParams({
extrapolate: true,
mode: Mode.SAMPLES,
query: '',
cursor: '',
fields: ['id', 'timestamp'],
sortBys: [],
aggregateCursor: '',
aggregateFields: [
new VisualizeFunction('sum(value,metricA,distribution,none)'),
],
aggregateSortBys: [],
}),
},
{
label: 'ƒ1',
metric: {name: '', type: ''},
queryParams: new ReadableQueryParams({
extrapolate: true,
mode: Mode.AGGREGATE,
query: '',
cursor: '',
fields: ['id', 'timestamp'],
sortBys: [],
aggregateCursor: '',
aggregateFields: [
new VisualizeEquation(
'equation|sum(value,metricA,distribution,none) + sum(value,metricA,distribution,none)',
{internalExpression: 'B + A'}
),
],
aggregateSortBys: [],
}),
},
];

const updatedQueries = syncEquationMetricQueries(
metricQueries,
{
A: 'sum(value,metricA,distribution,none)',
B: 'sum(value,metricA,distribution,none)',
},
{
A: 'sum(value,metricA,distribution,none)',
B: 'avg(value,metricA,distribution,none)',
}
);

expect(updatedQueries[2]!.queryParams.visualizes[0]!.yAxis).toBe(
'equation|avg(value,metricA,distribution,none) + sum(value,metricA,distribution,none)'
);
});

it('updates every equation when a referenced metric changes', () => {
const metricQueries: BaseMetricQuery[] = [
{
Expand Down
10 changes: 5 additions & 5 deletions static/app/views/explore/metrics/equationBuilder/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,10 @@ export function syncEquationMetricQueries(
return metricQuery;
}

const internalExpression = unresolveExpression(
visualize.expression.text,
previousReferenceMap
);
const expression = new Expression(internalExpression, previousReferences);
const internalExpressionText =
visualize.internalExpression ??
unresolveExpression(visualize.expression.text, previousReferenceMap);
const expression = new Expression(internalExpressionText, previousReferences);

if (!expression.isValid) {
return metricQuery;
Expand All @@ -118,6 +117,7 @@ export function syncEquationMetricQueries(

return field.replace({
yAxis: `${EQUATION_PREFIX}${resolvedExpression.text}`,
internalExpression: visualize.internalExpression,
});
}),
}),
Expand Down
5 changes: 4 additions & 1 deletion static/app/views/explore/metrics/metricPanel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,10 @@ export function MetricPanel({

const [title, setTitle] = useState<string | undefined>(() => {
if (isVisualizeEquation(visualize)) {
return unresolveExpression(visualize.expression.text, referenceMap);
return (
visualize.internalExpression ??
unresolveExpression(visualize.expression.text, referenceMap)
);
}
return;
});
Expand Down
8 changes: 7 additions & 1 deletion static/app/views/explore/metrics/metricToolbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,12 @@ export function MetricToolbar({

const handleExpressionChange = useCallback(
(newExpression: Expression, internalText: string) => {
setVisualize(visualize.replace({yAxis: `${EQUATION_PREFIX}${newExpression.text}`}));
setVisualize(
visualize.replace({
yAxis: `${EQUATION_PREFIX}${newExpression.text}`,
internalExpression: internalText,
})
);
onTitleChange?.(internalText);
},
[setVisualize, visualize, onTitleChange]
Expand Down Expand Up @@ -159,6 +164,7 @@ export function MetricToolbar({
handleExpressionChange={handleExpressionChange}
onReferenceLabelsChange={handleReferenceLabelsChange}
disabled={hasUnresolvedMetrics}
storedInternalExpression={visualize.internalExpression}
/>
</Flex>
<Flex flex="9 1 0" minWidth={0}>
Expand Down
23 changes: 23 additions & 0 deletions static/app/views/explore/queryParams/visualize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export const MAX_VISUALIZES = 8;

interface VisualizeOptions {
chartType?: ChartType;
// The internal expression is used to store the reference format for
// equations so we can properly update the correct references when
// dependencies change
internalExpression?: string;
visible?: boolean;
}

Expand All @@ -38,8 +42,10 @@ export abstract class Visualize {
chartType,
visible,
yAxis,
internalExpression,
}: {
chartType?: ChartType;
internalExpression?: string;
visible?: boolean;
yAxis?: string;
}): Visualize;
Expand All @@ -66,6 +72,7 @@ export abstract class Visualize {
return new VisualizeEquation(yAxis, {
chartType: json.chartType,
visible: json.visible,
internalExpression: json.internalExpression,
});
}
return new VisualizeFunction(yAxis, {
Expand Down Expand Up @@ -98,6 +105,7 @@ export class VisualizeFunction extends Visualize {
yAxis,
}: {
chartType?: ChartType;
internalExpression?: string;
visible?: boolean;
yAxis?: string;
}): VisualizeFunction {
Expand All @@ -111,32 +119,46 @@ export class VisualizeFunction extends Visualize {
export class VisualizeEquation extends Visualize {
readonly kind = 'equation';
readonly expression: Expression;
readonly internalExpression?: string;

constructor(yAxis: string, options?: VisualizeOptions) {
super(yAxis, options);
this.expression = new Expression(stripEquationPrefix(yAxis));
this.internalExpression = options?.internalExpression;
}

clone(): Visualize {
return new VisualizeEquation(this.yAxis, {
chartType: this.selectedChartType,
internalExpression: this.internalExpression,
});
}

replace({
chartType,
visible,
yAxis,
internalExpression,
}: {
chartType?: ChartType;
internalExpression?: string;
visible?: boolean;
yAxis?: string;
}): Visualize {
return new VisualizeEquation(yAxis ?? this.yAxis, {
chartType: chartType ?? this.selectedChartType,
visible: visible ?? this.visible,
internalExpression: internalExpression ?? this.internalExpression,
});
}

override serialize(): BaseVisualize {
const json = super.serialize();
if (this.internalExpression) {
json.internalExpression = this.internalExpression;
}
return json;
}
}

export function getVisualizesFromLocation(
Expand Down Expand Up @@ -188,6 +210,7 @@ export function isVisualizeEquation(
export interface BaseVisualize {
yAxes: readonly string[];
chartType?: ChartType;
internalExpression?: string;
visible?: boolean;
}

Expand Down
Loading