import {DiscoverSavedQueryFixture} from 'sentry-fixture/discover';
import {OrganizationFixture} from 'sentry-fixture/organization';
import {RouterFixture} from 'sentry-fixture/routerFixture';
import {initializeOrg} from 'sentry-test/initializeOrg';
import {
render,
screen,
userEvent,
waitFor,
within,
} from 'sentry-test/reactTestingLibrary';
import {openAddToDashboardModal} from 'sentry/actionCreators/modal';
import {browserHistory} from 'sentry/utils/browserHistory';
import {DisplayModes, SavedQueryDatasets} from 'sentry/utils/discover/types';
import {DashboardWidgetSource, DisplayType} from 'sentry/views/dashboards/types';
import QueryList from 'sentry/views/discover/queryList';
jest.mock('sentry/actionCreators/modal');
describe('Discover > QueryList', function () {
let location,
savedQueries,
organization,
deleteMock,
duplicateMock,
queryChangeMock,
updateHomepageMock,
eventsStatsMock,
wrapper;
const {router} = initializeOrg();
beforeAll(async function () {
await import('sentry/components/modals/widgetBuilder/addToDashboardModal');
});
beforeEach(function () {
organization = OrganizationFixture({
features: ['discover-basic', 'discover-query'],
});
savedQueries = [
DiscoverSavedQueryFixture(),
DiscoverSavedQueryFixture({name: 'saved query 2', id: '2'}),
];
eventsStatsMock = MockApiClient.addMockResponse({
url: '/organizations/org-slug/events-stats/',
method: 'GET',
statusCode: 200,
body: {data: []},
});
deleteMock = MockApiClient.addMockResponse({
url: '/organizations/org-slug/discover/saved/2/',
method: 'DELETE',
statusCode: 200,
body: {},
});
duplicateMock = MockApiClient.addMockResponse({
url: '/organizations/org-slug/discover/saved/',
method: 'POST',
body: {
id: '3',
name: 'Saved query copy',
},
});
updateHomepageMock = MockApiClient.addMockResponse({
url: '/organizations/org-slug/discover/homepage/',
method: 'PUT',
statusCode: 204,
});
location = {
pathname: '/organizations/org-slug/discover/queries/',
query: {cursor: '0:1:1', statsPeriod: '14d'},
};
queryChangeMock = jest.fn();
});
afterEach(() => {
jest.clearAllMocks();
wrapper?.unmount();
wrapper = null;
});
it('renders an empty list', function () {
render(
);
expect(screen.getByText('No saved queries match that filter')).toBeInTheDocument();
});
it('renders pre-built queries and saved ones', async function () {
render(
);
await waitFor(() => {
expect(screen.getAllByTestId(/card-.*/)).toHaveLength(5);
});
expect(eventsStatsMock).toHaveBeenCalledWith(
'/organizations/org-slug/events-stats/',
expect.objectContaining({
query: {
environment: [],
interval: '30m',
partial: '1',
project: [],
query: '',
referrer: 'api.discover.default-chart',
statsPeriod: '14d',
yAxis: ['count()'],
},
})
);
});
it('renders pre-built queries with dataset', async function () {
organization = OrganizationFixture({
features: [
'discover-basic',
'discover-query',
'performance-view',
'performance-discover-dataset-selector',
],
});
render(
,
{router}
);
await waitFor(() => {
expect(screen.getAllByTestId(/card-.*/)).toHaveLength(5);
});
expect(eventsStatsMock).toHaveBeenCalledWith(
'/organizations/org-slug/events-stats/',
expect.objectContaining({
query: expect.objectContaining({
dataset: 'transactions',
query: '',
referrer: 'api.discover.homepage.prebuilt',
statsPeriod: '24h',
yAxis: 'count()',
}),
})
);
expect(eventsStatsMock).toHaveBeenCalledWith(
'/organizations/org-slug/events-stats/',
expect.objectContaining({
query: expect.objectContaining({
dataset: 'errors',
environment: [],
field: ['url', 'count()', 'count_unique(issue)'],
query: 'has:url',
referrer: 'api.discover.homepage.prebuilt',
statsPeriod: '24h',
topEvents: 5,
yAxis: 'count()',
}),
})
);
await userEvent.click(screen.getAllByTestId(/card-*/).at(0)!);
expect(router.push).toHaveBeenLastCalledWith({
pathname: '/organizations/org-slug/discover/results/',
query: expect.objectContaining({queryDataset: 'error-events'}),
});
});
it('passes dataset to the query if flag is enabled', async function () {
const org = OrganizationFixture({
features: [
'discover-basic',
'discover-query',
'performance-discover-dataset-selector',
],
});
render(
);
await waitFor(() => {
expect(screen.getAllByTestId(/card-.*/)).toHaveLength(5);
});
expect(eventsStatsMock).toHaveBeenCalledWith(
'/organizations/org-slug/events-stats/',
expect.objectContaining({
query: {
environment: [],
interval: '30m',
partial: '1',
project: [],
query: '',
referrer: 'api.discover.default-chart',
statsPeriod: '14d',
yAxis: ['count()'],
dataset: 'transactions',
},
})
);
});
it('can duplicate and trigger change callback', async function () {
render(
);
const card = screen.getAllByTestId(/card-*/).at(0)!;
const withinCard = within(card!);
expect(withinCard.getByText('Saved query #1')).toBeInTheDocument();
await userEvent.click(withinCard.getByTestId('menu-trigger'));
await userEvent.click(withinCard.getByText('Duplicate Query'));
await waitFor(() => {
expect(browserHistory.push).toHaveBeenCalledWith({
pathname: location.pathname,
query: {},
});
});
expect(duplicateMock).toHaveBeenCalled();
expect(queryChangeMock).toHaveBeenCalled();
});
it('can delete and trigger change callback', async function () {
render(
);
const card = screen.getAllByTestId(/card-*/).at(1);
const withinCard = within(card!);
await userEvent.click(withinCard.getByTestId('menu-trigger'));
await userEvent.click(withinCard.getByText('Delete Query'));
await waitFor(() => {
expect(queryChangeMock).toHaveBeenCalled();
});
expect(deleteMock).toHaveBeenCalled();
});
it('redirects to Discover on card click', async function () {
render(
,
{router}
);
await userEvent.click(screen.getAllByTestId(/card-*/).at(0)!);
expect(router.push).toHaveBeenLastCalledWith({
pathname: '/organizations/org-slug/discover/results/',
query: {id: '1', statsPeriod: '14d'},
});
});
it('can redirect on last query deletion', async function () {
render(
,
{router}
);
const card = screen.getAllByTestId(/card-*/).at(0)!;
const withinCard = within(card!);
await userEvent.click(withinCard.getByTestId('menu-trigger'));
await userEvent.click(withinCard.getByText('Delete Query'));
expect(deleteMock).toHaveBeenCalled();
expect(queryChangeMock).not.toHaveBeenCalled();
await waitFor(() => {
expect(browserHistory.push).toHaveBeenCalledWith({
pathname: location.pathname,
query: {cursor: undefined, statsPeriod: '14d'},
});
});
});
it('renders Add to Dashboard in context menu', async function () {
const featuredOrganization = OrganizationFixture({
features: ['dashboards-edit'],
});
render(
);
const card = screen.getAllByTestId(/card-*/).at(0)!;
const withinCard = within(card!);
await userEvent.click(withinCard.getByTestId('menu-trigger'));
expect(
screen.getByRole('menuitemradio', {name: 'Add to Dashboard'})
).toBeInTheDocument();
expect(
screen.getByRole('menuitemradio', {name: 'Set as Default'})
).toBeInTheDocument();
expect(
screen.getByRole('menuitemradio', {name: 'Duplicate Query'})
).toBeInTheDocument();
expect(screen.getByRole('menuitemradio', {name: 'Delete Query'})).toBeInTheDocument();
});
it('only renders Delete Query and Duplicate Query in context menu', async function () {
render(
);
const card = screen.getAllByTestId(/card-*/).at(0)!;
const withinCard = within(card!);
await userEvent.click(withinCard.getByTestId('menu-trigger'));
expect(
screen.queryByRole('menuitemradio', {name: 'Add to Dashboard'})
).not.toBeInTheDocument();
expect(
screen.getByRole('menuitemradio', {name: 'Set as Default'})
).toBeInTheDocument();
expect(
screen.getByRole('menuitemradio', {name: 'Duplicate Query'})
).toBeInTheDocument();
expect(screen.getByRole('menuitemradio', {name: 'Delete Query'})).toBeInTheDocument();
});
it('passes yAxis from the savedQuery to MiniGraph', async function () {
const featuredOrganization = OrganizationFixture({
features: ['dashboards-edit'],
});
const yAxis = ['count()', 'failure_count()'];
const savedQueryWithMultiYAxis = {
...savedQueries.slice(1)[0],
yAxis,
};
render(
);
const chart = await screen.findByTestId('area-chart');
expect(chart).toBeInTheDocument();
expect(eventsStatsMock).toHaveBeenCalledWith(
'/organizations/org-slug/events-stats/',
expect.objectContaining({
query: expect.objectContaining({
yAxis: ['count()', 'failure_count()'],
}),
})
);
});
it('Set as Default updates the homepage query', async function () {
render(
);
await userEvent.click(screen.getByTestId('menu-trigger'));
await userEvent.click(screen.getByText('Set as Default'));
expect(updateHomepageMock).toHaveBeenCalledWith(
'/organizations/org-slug/discover/homepage/',
expect.objectContaining({
data: expect.objectContaining({fields: ['test'], range: '14d'}),
})
);
});
describe('Add to Dashboard modal', () => {
it('opens a modal with the correct params for Top 5 chart', async function () {
const featuredOrganization = OrganizationFixture({
features: ['dashboards-edit'],
});
render(
);
const contextMenu = await screen.findByTestId('menu-trigger');
expect(contextMenu).toBeInTheDocument();
expect(screen.queryByTestId('add-to-dashboard')).not.toBeInTheDocument();
await userEvent.click(contextMenu);
const addToDashboardMenuItem = await screen.findByTestId('add-to-dashboard');
await userEvent.click(addToDashboardMenuItem);
await waitFor(() => {
expect(openAddToDashboardModal).toHaveBeenCalledWith(
expect.objectContaining({
widget: {
title: 'Saved query #1',
displayType: DisplayType.AREA,
limit: 5,
queries: [
{
aggregates: ['count()'],
columns: ['test'],
conditions: '',
fields: ['test', 'count()', 'count()'],
name: '',
orderby: 'test',
},
],
},
widgetAsQueryParams: expect.objectContaining({
defaultTableColumns: ['test', 'count()'],
defaultTitle: 'Saved query #1',
defaultWidgetQuery:
'name=&aggregates=count()&columns=test&fields=test%2Ccount()%2Ccount()&conditions=&orderby=test',
displayType: DisplayType.AREA,
source: DashboardWidgetSource.DISCOVERV2,
}),
})
);
});
});
it('opens a modal with the correct params for other chart', async function () {
const featuredOrganization = OrganizationFixture({
features: ['dashboards-edit'],
});
render(
);
const contextMenu = await screen.findByTestId('menu-trigger');
expect(contextMenu).toBeInTheDocument();
expect(screen.queryByTestId('add-to-dashboard')).not.toBeInTheDocument();
await userEvent.click(contextMenu);
const addToDashboardMenuItem = await screen.findByTestId('add-to-dashboard');
await userEvent.click(addToDashboardMenuItem);
await waitFor(() => {
expect(openAddToDashboardModal).toHaveBeenCalledWith(
expect.objectContaining({
widget: {
title: 'Saved query #1',
displayType: DisplayType.LINE,
queries: [
{
aggregates: ['count()'],
columns: [],
conditions: '',
fields: ['count()'],
name: '',
// Orderby gets dropped because ordering only applies to
// Top-N and tables
orderby: '',
},
],
},
widgetAsQueryParams: expect.objectContaining({
defaultTableColumns: ['test', 'count()'],
defaultTitle: 'Saved query #1',
defaultWidgetQuery:
'name=&aggregates=count()&columns=&fields=count()&conditions=&orderby=',
displayType: DisplayType.LINE,
source: DashboardWidgetSource.DISCOVERV2,
}),
})
);
});
});
});
it('passes dataset to open modal', async function () {
const featuredOrganization = OrganizationFixture({
features: ['dashboards-edit', 'performance-discover-dataset-selector'],
});
render(
);
const contextMenu = await screen.findByTestId('menu-trigger');
expect(contextMenu).toBeInTheDocument();
expect(screen.queryByTestId('add-to-dashboard')).not.toBeInTheDocument();
await userEvent.click(contextMenu);
const addToDashboardMenuItem = await screen.findByTestId('add-to-dashboard');
await userEvent.click(addToDashboardMenuItem);
await waitFor(() => {
expect(openAddToDashboardModal).toHaveBeenCalledWith(
expect.objectContaining({
widget: {
displayType: 'line',
interval: undefined,
limit: undefined,
queries: [
{
aggregates: ['count()'],
columns: [],
conditions: '',
fields: ['count()'],
name: '',
orderby: '',
},
],
title: 'Saved query #1',
widgetType: 'transaction-like',
},
widgetAsQueryParams: expect.objectContaining({
cursor: '0:1:1',
dataset: 'transaction-like',
defaultTableColumns: ['test', 'count()'],
defaultTitle: 'Saved query #1',
defaultWidgetQuery:
'name=&aggregates=count()&columns=&fields=count()&conditions=&orderby=',
displayType: 'line',
source: 'discoverv2',
statsPeriod: '14d',
}),
})
);
});
});
});