Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {FieldValueKind} from 'sentry/views/discover/table/types';
import {AddButton} from './addButton';
import {DeleteButton} from './deleteButton';

export const MAX_NUM_Y_AXES = 3;

interface Props {
aggregates: QueryFieldValue[];
displayType: DisplayType;
Expand Down Expand Up @@ -107,7 +109,7 @@ export function YAxisSelector({

const hideAddYAxisButtons =
([DisplayType.LINE, DisplayType.AREA, DisplayType.BAR].includes(displayType) &&
aggregates.length === 3) ||
aggregates.length === MAX_NUM_Y_AXES) ||
(displayType === DisplayType.BIG_NUMBER && widgetType === WidgetType.RELEASE);

let injectedFunctions: Set<string> = new Set();
Expand Down
1 change: 1 addition & 0 deletions static/app/views/discover/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,7 @@ export function constructAddQueryToDashboardLink({
defaultTitle,
displayType: displayType === DisplayType.TOP_N ? DisplayType.AREA : displayType,
dataset: widgetType,
field: eventView.getFields(),
limit:
displayType === DisplayType.TOP_N
? Number(eventView.topEvents) || TOP_N
Expand Down
4 changes: 4 additions & 0 deletions static/app/views/explore/charts/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import usePageFilters from 'sentry/utils/usePageFilters';
import useProjects from 'sentry/utils/useProjects';
import {formatVersion} from 'sentry/utils/versions/formatVersion';
import {Dataset} from 'sentry/views/alerts/rules/metric/types';
import {AddToDashboardButton} from 'sentry/views/explore/components/addToDashboardButton';
import {useChartInterval} from 'sentry/views/explore/hooks/useChartInterval';
import {useDataset} from 'sentry/views/explore/hooks/useDataset';
import {useVisualizes} from 'sentry/views/explore/hooks/useVisualizes';
Expand Down Expand Up @@ -265,6 +266,9 @@ export function ExploreCharts({query, setError}: ExploreChartsProps) {
/>
</Tooltip>
</Feature>
<Feature features="organizations:dashboards-eap">
<AddToDashboardButton visualizeIndex={index} />
</Feature>
</ChartHeader>
<Chart
height={CHART_HEIGHT}
Expand Down
267 changes: 267 additions & 0 deletions static/app/views/explore/components/addToDashboardButton.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';

import {openAddToDashboardModal} from 'sentry/actionCreators/modal';
import {DisplayType, WidgetType} from 'sentry/views/dashboards/types';
import {AddToDashboardButton} from 'sentry/views/explore/components/addToDashboardButton';
import {useResultMode} from 'sentry/views/explore/hooks/useResultsMode';
import {useVisualizes} from 'sentry/views/explore/hooks/useVisualizes';
import {ChartType} from 'sentry/views/insights/common/components/chart';

jest.mock('sentry/actionCreators/modal');
jest.mock('sentry/views/explore/hooks/useVisualizes');
jest.mock('sentry/views/explore/hooks/useResultsMode');

describe('AddToDashboardButton', () => {
beforeEach(() => {
jest.clearAllMocks();

jest.mocked(useVisualizes).mockReturnValue([
[
{
yAxes: ['avg(span.duration)'],
chartType: ChartType.LINE,
label: 'Custom Explore Widget',
},
],
jest.fn(),
]);

jest.mocked(useResultMode).mockReturnValue(['samples', jest.fn()]);
});

it('renders', async () => {
render(<AddToDashboardButton visualizeIndex={0} />);
await userEvent.hover(screen.getByLabelText('Add to Dashboard'));
expect(await screen.findByText('Add to Dashboard')).toBeInTheDocument();
});

it('opens the dashboard modal with the correct query for samples mode', async () => {
render(<AddToDashboardButton visualizeIndex={0} />);
await userEvent.click(screen.getByLabelText('Add to Dashboard'));

// The table columns are encoded as the fields for the defaultWidgetQuery
expect(openAddToDashboardModal).toHaveBeenCalledWith(
expect.objectContaining({
// For Add + Stay on Page
widget: {
title: 'Custom Explore Widget',
displayType: DisplayType.LINE,
interval: undefined,
limit: undefined,
widgetType: WidgetType.SPANS,
queries: [
{
aggregates: ['avg(span.duration)'],
columns: [],
fields: ['avg(span.duration)'],
conditions: '',
orderby: '-timestamp',
name: '',
},
],
},

// For Open in Widget Builder
widgetAsQueryParams: expect.objectContaining({
dataset: WidgetType.SPANS,
defaultTableColumns: [
'span_id',
'project',
'span.op',
'span.description',
'span.duration',
'timestamp',
],
defaultTitle: 'Custom Explore Widget',
defaultWidgetQuery:
'name=&aggregates=avg(span.duration)&columns=&fields=avg(span.duration)&conditions=&orderby=-timestamp',
displayType: DisplayType.LINE,
field: [
'span_id',
'project',
'span.op',
'span.description',
'span.duration',
'timestamp',
],
}),
})
);
});

it('opens the dashboard modal with the correct query based on the visualize index', async () => {
// Mock a second visualize object
jest.mocked(useVisualizes).mockReturnValue([
[
{
yAxes: ['avg(span.duration)'],
chartType: ChartType.LINE,
label: 'Custom Explore Widget',
},
{
yAxes: ['max(span.duration)'],
chartType: ChartType.LINE,
label: 'Custom Explore Widget',
},
],
jest.fn(),
]);

render(<AddToDashboardButton visualizeIndex={1} />);
await userEvent.click(screen.getByLabelText('Add to Dashboard'));

// The group by and the yAxes are encoded as the fields for the defaultTableQuery
expect(openAddToDashboardModal).toHaveBeenCalledWith(
expect.objectContaining({
// For Add + Stay on Page
widget: {
title: 'Custom Explore Widget',
displayType: DisplayType.LINE,
interval: undefined,
limit: undefined,
widgetType: WidgetType.SPANS,
queries: [
{
aggregates: ['max(span.duration)'],
columns: [],
fields: ['max(span.duration)'],
conditions: '',
orderby: '-timestamp',
name: '',
},
],
},

// For Open in Widget Builder
widgetAsQueryParams: expect.objectContaining({
dataset: WidgetType.SPANS,
defaultTableColumns: [
'span_id',
'project',
'span.op',
'span.description',
'span.duration',
'timestamp',
],
defaultTitle: 'Custom Explore Widget',
defaultWidgetQuery:
'name=&aggregates=max(span.duration)&columns=&fields=max(span.duration)&conditions=&orderby=-timestamp',
displayType: DisplayType.LINE,
field: [
'span_id',
'project',
'span.op',
'span.description',
'span.duration',
'timestamp',
],
}),
})
);
});

it('uses the yAxes for the aggregate mode', async () => {
jest.mocked(useResultMode).mockReturnValue(['aggregate', jest.fn()]);

render(<AddToDashboardButton visualizeIndex={0} />);
await userEvent.click(screen.getByLabelText('Add to Dashboard'));

expect(openAddToDashboardModal).toHaveBeenCalledWith(
expect.objectContaining({
// For Add + Stay on Page
widget: {
title: 'Custom Explore Widget',
displayType: DisplayType.LINE,
interval: undefined,
limit: undefined,
widgetType: WidgetType.SPANS,
queries: [
{
aggregates: ['avg(span.duration)'],
columns: [],
fields: ['avg(span.duration)'],
conditions: '',
orderby: '-avg(span.duration)',
name: '',
},
],
},

// For Open in Widget Builder
widgetAsQueryParams: expect.objectContaining({
dataset: WidgetType.SPANS,
defaultTableColumns: ['avg(span.duration)'],
defaultTitle: 'Custom Explore Widget',
defaultWidgetQuery:
'name=&aggregates=avg(span.duration)&columns=&fields=avg(span.duration)&conditions=&orderby=-avg(span.duration)',
displayType: DisplayType.LINE,
field: ['avg(span.duration)'],
}),
})
);
});

it('takes the first 3 yAxes', async () => {
jest.mocked(useResultMode).mockReturnValue(['aggregate', jest.fn()]);
jest.mocked(useVisualizes).mockReturnValue([
[
{
yAxes: [
'avg(span.duration)',
'max(span.duration)',
'min(span.duration)',
'p90(span.duration)',
],
chartType: ChartType.LINE,
label: 'Custom Explore Widget',
},
],
jest.fn(),
]);

render(<AddToDashboardButton visualizeIndex={0} />);
await userEvent.click(screen.getByLabelText('Add to Dashboard'));

expect(openAddToDashboardModal).toHaveBeenCalledWith(
expect.objectContaining({
// For Add + Stay on Page
widget: {
title: 'Custom Explore Widget',
displayType: DisplayType.LINE,
interval: undefined,
limit: undefined,
widgetType: WidgetType.SPANS,
queries: [
{
aggregates: [
'avg(span.duration)',
'max(span.duration)',
'min(span.duration)',
],
columns: [],
fields: ['avg(span.duration)', 'max(span.duration)', 'min(span.duration)'],
conditions: '',
orderby: '-avg(span.duration)',
name: '',
},
],
},

// For Open in Widget Builder
widgetAsQueryParams: expect.objectContaining({
dataset: WidgetType.SPANS,
defaultTableColumns: [
'avg(span.duration)',
'max(span.duration)',
'min(span.duration)',
],
defaultTitle: 'Custom Explore Widget',
defaultWidgetQuery:
'name=&aggregates=avg(span.duration)%2Cmax(span.duration)%2Cmin(span.duration)&columns=&fields=avg(span.duration)%2Cmax(span.duration)%2Cmin(span.duration)&conditions=&orderby=-avg(span.duration)',
displayType: DisplayType.LINE,
field: ['avg(span.duration)', 'max(span.duration)', 'min(span.duration)'],
}),
})
);
});
});
Loading
Loading