Skip to content

Commit

Permalink
feat(dashboards): Add equations for Big Number widget (#77790)
Browse files Browse the repository at this point in the history
#68167

Added equation functionality to Big Number Widget Builder. This PR is
for the frontend changes only:

<img width="887" alt="Screenshot 2024-09-20 at 10 05 42 AM"
src="https://github.com/user-attachments/assets/d244183e-63fa-43aa-90ef-5ac0f3e9fd96">

---------

Co-authored-by: harshithadurai <harshi.durai@esentry.io>
Co-authored-by: Nar Saynorath <nar.saynorath@sentry.io>
  • Loading branch information
3 people committed Sep 20, 2024
1 parent 993e47d commit 386e546
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 24 deletions.
2 changes: 2 additions & 0 deletions static/app/views/dashboards/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ export type WidgetQuery = {
isHidden?: boolean | null;
// Contains the on-demand entries for the widget query.
onDemand?: WidgetQueryOnDemand[];
// Aggregate selected for the Big Number widget builder
selectedAggregate?: number;
};

export type Widget = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ interface Props {
aggregates: QueryFieldValue[];
dataSet: DataSet;
displayType: DisplayType;
onYAxisChange: (newFields: QueryFieldValue[]) => void;
onYAxisChange: (newFields: QueryFieldValue[], newSelectedAggregate?: number) => void;
organization: Organization;
tags: TagCollection;
widgetType: WidgetType;
queryErrors?: Record<string, any>[];
selectedAggregate?: number;
}

export function YAxisStep({
Expand All @@ -28,6 +29,7 @@ export function YAxisStep({
onYAxisChange,
tags,
widgetType,
selectedAggregate,
}: Props) {
return (
<BuildStep
Expand All @@ -51,6 +53,7 @@ export function YAxisStep({
onChange={onYAxisChange}
tags={tags}
errors={queryErrors}
selectedAggregate={selectedAggregate}
/>
</BuildStep>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import styled from '@emotion/styled';

import Feature from 'sentry/components/acl/feature';
import ButtonBar from 'sentry/components/buttonBar';
import {RadioLineItem} from 'sentry/components/forms/controls/radioGroup';
import FieldGroup from 'sentry/components/forms/fieldGroup';
import Radio from 'sentry/components/radio';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import type {TagCollection} from 'sentry/types/group';
Expand All @@ -25,11 +28,12 @@ interface Props {
/**
* Fired when aggregates are added/removed/modified/reordered.
*/
onChange: (aggregates: QueryFieldValue[]) => void;
onChange: (aggregates: QueryFieldValue[], selectedAggregate?: number) => void;
tags: TagCollection;
widgetType: Widget['widgetType'];
errors?: Record<string, any>;
noFieldsMessage?: string;
selectedAggregate?: number;
}

export function YAxisSelector({
Expand All @@ -40,20 +44,26 @@ export function YAxisSelector({
onChange,
errors,
noFieldsMessage,
selectedAggregate,
}: Props) {
const organization = useOrganization();
const datasetConfig = getDatasetConfig(widgetType);

const {customMeasurements} = useCustomMeasurements();

function handleAddOverlay(event: React.MouseEvent) {
function handleAddFields(event: React.MouseEvent) {
event.preventDefault();

const newAggregates = [
...aggregates,
{kind: FieldValueKind.FIELD, field: ''} as QueryFieldValue,
];
onChange(newAggregates);
if (
organization.features.includes('dashboards-bignumber-equations') &&
displayType === DisplayType.BIG_NUMBER
) {
onChange(newAggregates, newAggregates.length - 1);
} else onChange(newAggregates);
}

function handleAddEquation(event: React.MouseEvent) {
Expand All @@ -63,15 +73,27 @@ export function YAxisSelector({
...aggregates,
{kind: FieldValueKind.EQUATION, field: ''} as QueryFieldValue,
];
onChange(newAggregates);
if (
organization.features.includes('dashboards-bignumber-equations') &&
displayType === DisplayType.BIG_NUMBER
) {
const newSelectedAggregate = newAggregates.length - 1;
onChange(newAggregates, newSelectedAggregate);
} else onChange(newAggregates);
}

function handleRemoveQueryField(event: React.MouseEvent, fieldIndex: number) {
event.preventDefault();

const newAggregates = [...aggregates];
newAggregates.splice(fieldIndex, 1);
onChange(newAggregates);
if (
organization.features.includes('dashboards-bignumber-equations') &&
displayType === DisplayType.BIG_NUMBER
) {
const newSelectedAggregate = newAggregates.length - 1;
onChange(newAggregates, newSelectedAggregate);
} else onChange(newAggregates);
}

function handleChangeQueryField(value: QueryFieldValue, fieldIndex: number) {
Expand All @@ -80,13 +102,19 @@ export function YAxisSelector({
onChange(newAggregates);
}

function handleSelectField(newSelectedAggregate: number) {
onChange(aggregates, newSelectedAggregate);
}

const fieldError = errors?.find(error => error?.aggregates)?.aggregates;
const canDelete = aggregates.length > 1;

const hideAddYAxisButtons =
(DisplayType.BIG_NUMBER === displayType && aggregates.length === 1) ||
([DisplayType.LINE, DisplayType.AREA, DisplayType.BAR].includes(displayType) &&
aggregates.length === 3);
aggregates.length === 3) ||
(organization.features.includes('dashboards-bignumber-equations')
? displayType === DisplayType.BIG_NUMBER && widgetType === WidgetType.RELEASE
: DisplayType.BIG_NUMBER === displayType && aggregates.length === 1);

let injectedFunctions: Set<string> = new Set();

Expand All @@ -112,6 +140,17 @@ export function YAxisSelector({
<FieldGroup inline={false} flexibleControlStateSize error={fieldError} stacked>
{aggregates.map((fieldValue, i) => (
<QueryFieldWrapper key={`${fieldValue}:${i}`}>
{aggregates.length > 1 && displayType === DisplayType.BIG_NUMBER && (
<Feature features="dashboards-bignumber-equations">
<RadioLineItem index={i} role="radio" aria-label="aggregate-selector">
<Radio
checked={i === selectedAggregate}
onChange={() => handleSelectField(i)}
aria-label={'field' + i}
/>
</RadioLineItem>
</Feature>
)}
<QueryField
fieldValue={fieldValue}
fieldOptions={fieldOptions}
Expand All @@ -136,7 +175,15 @@ export function YAxisSelector({

{!hideAddYAxisButtons && (
<Actions gap={1}>
<AddButton title={t('Add Overlay')} onAdd={handleAddOverlay} />
<AddButton
title={
displayType === DisplayType.BIG_NUMBER &&
organization.features.includes('dashboards-bignumber-equations')
? t('Add Field')
: t('Add Overlay')
}
onAdd={handleAddFields}
/>
{datasetConfig.enableEquations && (
<AddButton title={t('Add an Equation')} onAdd={handleAddEquation} />
)}
Expand Down
33 changes: 22 additions & 11 deletions static/app/views/dashboards/widgetBuilder/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {FieldValue} from 'sentry/components/forms/types';
import {t} from 'sentry/locale';
import type {SelectValue} from 'sentry/types/core';
import type {TagCollection} from 'sentry/types/group';
import type {OrganizationSummary} from 'sentry/types/organization';
import type {Organization, OrganizationSummary} from 'sentry/types/organization';
import {defined} from 'sentry/utils';
import {
aggregateFunctionOutputType,
Expand Down Expand Up @@ -148,9 +148,11 @@ export function normalizeQueries({
displayType,
queries,
widgetType,
organization,
}: {
displayType: DisplayType;
queries: Widget['queries'];
organization?: Organization;
widgetType?: Widget['widgetType'];
}): Widget['queries'] {
const isTimeseriesChart = getIsTimeseriesChart(displayType);
Expand Down Expand Up @@ -294,16 +296,25 @@ export function normalizeQueries({
}

if (DisplayType.BIG_NUMBER === displayType) {
// For world map chart, cap fields of the queries to only one field.
queries = queries.map(query => {
return {
...query,
fields: query.aggregates.slice(0, 1),
aggregates: query.aggregates.slice(0, 1),
orderby: '',
columns: [],
};
});
if (organization?.features.includes('dashboards-bignumber-equations')) {
queries = queries.map(query => {
return {
...query,
orderby: '',
columns: [],
};
});
} else {
queries = queries.map(query => {
return {
...query,
fields: query.aggregates.slice(0, 1),
aggregates: query.aggregates.slice(0, 1),
orderby: '',
columns: [],
};
});
}
}

return queries;
Expand Down
33 changes: 33 additions & 0 deletions static/app/views/dashboards/widgetBuilder/widgetBuilder.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,39 @@ describe('WidgetBuilder', function () {
expect(handleSave).toHaveBeenCalledTimes(1);
});

it('can add additional fields and equation for Big Number with selection', async function () {
renderTestComponent({
query: {
displayType: DisplayType.BIG_NUMBER,
},
orgFeatures: [...defaultOrgFeatures, 'dashboards-bignumber-equations'],
});

// Add new field
await userEvent.click(screen.getByLabelText('Add Field'));
expect(screen.getByText('(Required)')).toBeInTheDocument();
await selectEvent.select(screen.getByText('(Required)'), ['count_unique(…)']);
expect(screen.getByRole('radio', {name: 'field1'})).toBeChecked();

// Add another new field
await userEvent.click(screen.getByLabelText('Add Field'));
expect(screen.getByText('(Required)')).toBeInTheDocument();
await selectEvent.select(screen.getByText('(Required)'), ['eps()']);
expect(screen.getByRole('radio', {name: 'field2'})).toBeChecked();

// Add an equation
await userEvent.click(screen.getByLabelText('Add an Equation'));
expect(screen.getByPlaceholderText('Equation')).toBeInTheDocument();
expect(screen.getByRole('radio', {name: 'field3'})).toBeChecked();
await userEvent.click(screen.getByPlaceholderText('Equation'));
await userEvent.paste('eps() + 100');

// Check if right value is displayed from equation
await userEvent.click(screen.getByPlaceholderText('Equation'));
await userEvent.paste('2 * 100');
expect(screen.getByText('200')).toBeInTheDocument();
});

it('can add equation fields', async function () {
const handleSave = jest.fn();

Expand Down
22 changes: 19 additions & 3 deletions static/app/views/dashboards/widgetBuilder/widgetBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ function WidgetBuilder({
displayType: newDisplayType,
queries: widgetFromDashboard.queries,
widgetType: widgetFromDashboard.widgetType ?? defaultWidgetType,
organization: organization,
}).map(query => ({
...query,
// Use the last aggregate because that's where the y-axis is stored
Expand All @@ -339,6 +340,7 @@ function WidgetBuilder({
displayType: newDisplayType,
queries: widgetFromDashboard.queries,
widgetType: widgetFromDashboard.widgetType ?? defaultWidgetType,
organization: organization,
});
}

Expand Down Expand Up @@ -437,6 +439,7 @@ function WidgetBuilder({
displayType: newDisplayType,
queries: [{...getDatasetConfig(defaultWidgetType).defaultWidgetQuery}],
widgetType: defaultWidgetType,
organization: organization,
})
);
set(newState, 'dataSet', defaultDataset);
Expand All @@ -448,6 +451,7 @@ function WidgetBuilder({
displayType: newDisplayType,
queries: prevState.queries,
widgetType: DATA_SET_TO_WIDGET_TYPE[prevState.dataSet],
organization: organization,
});

if (newDisplayType === DisplayType.TOP_N) {
Expand Down Expand Up @@ -675,7 +679,10 @@ function WidgetBuilder({
return handleColumnFieldChange;
}

function handleYAxisChange(newFields: QueryFieldValue[]) {
function handleYAxisChange(
newFields: QueryFieldValue[],
newSelectedAggregate?: number
) {
const fieldStrings = newFields.map(generateFieldAsString);
const newState = cloneDeep(state);

Expand Down Expand Up @@ -704,6 +711,12 @@ function WidgetBuilder({
return newQuery;
});

if (
organization.features.includes('dashboards-bignumber-equations') &&
defined(newSelectedAggregate)
)
newQueries[0].selectedAggregate = newSelectedAggregate;

set(newState, 'queries', newQueries);
set(newState, 'userHasModified', true);

Expand Down Expand Up @@ -1242,10 +1255,13 @@ function WidgetBuilder({
displayType={state.displayType}
widgetType={widgetType}
queryErrors={state.errors?.queries}
onYAxisChange={newFields => {
handleYAxisChange(newFields);
onYAxisChange={(newFields, newSelectedField) => {
handleYAxisChange(newFields, newSelectedField);
}}
aggregates={explodedAggregates}
selectedAggregate={
state.queries[0].selectedAggregate
}
tags={tags}
organization={organization}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export function WidgetLibrary({
displayType,
queries: widget.queries,
widgetType: widget.widgetType,
organization: organization,
});

const newWidget = {
Expand Down
11 changes: 10 additions & 1 deletion static/app/views/dashboards/widgetCard/chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import type {
} from 'sentry/types/echarts';
import type {InjectedRouter} from 'sentry/types/legacyReactRouter';
import type {Organization} from 'sentry/types/organization';
import {defined} from 'sentry/utils';
import {
axisLabelFormatter,
axisLabelFormatterUsingAggregateOutputType,
Expand Down Expand Up @@ -206,7 +207,15 @@ class WidgetCardChart extends Component<WidgetCardChartProps> {
const tableMeta = {...result.meta};
const fields = Object.keys(tableMeta);

const field = fields[0];
let field = fields[0];

if (
organization.features.includes('dashboards-bignumber-equations') &&
defined(widget.queries[0].selectedAggregate)
) {
const index = widget.queries[0].selectedAggregate;
field = widget.queries[0].aggregates[index];
}

// Change tableMeta for the field from integer to string since we will be rendering with toLocaleString
const shouldExpandInteger = !!expandNumbers && tableMeta[field] === 'integer';
Expand Down

0 comments on commit 386e546

Please sign in to comment.