123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584 |
- import {OrganizationFixture} from 'sentry-fixture/organization';
- import {ProjectFixture} from 'sentry-fixture/project';
- import {RouterFixture} from 'sentry-fixture/routerFixture';
- import {
- render,
- screen,
- userEvent,
- waitFor,
- within,
- } from 'sentry-test/reactTestingLibrary';
- import {openAddToDashboardModal} from 'sentry/actionCreators/modal';
- import ProjectsStore from 'sentry/stores/projectsStore';
- import {DiscoverDatasets} from 'sentry/utils/discover/types';
- import {
- PageParamsProvider,
- useExploreDataset,
- useExploreFields,
- useExploreGroupBys,
- useExploreMode,
- useExplorePageParams,
- useExploreSortBys,
- useExploreVisualizes,
- } from 'sentry/views/explore/contexts/pageParamsContext';
- import {Mode} from 'sentry/views/explore/contexts/pageParamsContext/mode';
- import {ExploreToolbar} from 'sentry/views/explore/toolbar';
- import {ChartType} from 'sentry/views/insights/common/components/chart';
- import {SpanTagsProvider} from '../contexts/spanTagsContext';
- jest.mock('sentry/actionCreators/modal');
- describe('ExploreToolbar', function () {
- const organization = OrganizationFixture({
- features: ['alerts-eap', 'dashboards-eap', 'dashboards-edit'],
- });
- beforeEach(function () {
- // without this the `CompactSelect` component errors with a bunch of async updates
- jest.spyOn(console, 'error').mockImplementation();
- const project = ProjectFixture({
- id: '1',
- slug: 'proj-slug',
- organization,
- });
- ProjectsStore.loadInitialData([project]);
- MockApiClient.addMockResponse({
- url: `/organizations/${organization.slug}/spans/fields/`,
- method: 'GET',
- body: [],
- });
- });
- it('should not render dataset selector', function () {
- function Component() {
- return <ExploreToolbar />;
- }
- render(
- <PageParamsProvider>
- <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
- <Component />
- </SpanTagsProvider>
- </PageParamsProvider>,
- {disableRouterMocks: true}
- );
- const section = screen.queryByTestId('section-dataset');
- expect(section).not.toBeInTheDocument();
- });
- it('allows changing datasets', async function () {
- let dataset: any;
- function Component() {
- dataset = useExploreDataset();
- return <ExploreToolbar extras={['dataset toggle']} />;
- }
- render(
- <PageParamsProvider>
- <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
- <Component />
- </SpanTagsProvider>
- </PageParamsProvider>,
- {disableRouterMocks: true}
- );
- const section = screen.getByTestId('section-dataset');
- const eapSpans = within(section).getByRole('radio', {name: 'EAP Spans'});
- const rpcSpans = within(section).getByRole('radio', {name: 'EAP RPC Spans'});
- const indexedSpans = within(section).getByRole('radio', {name: 'Indexed Spans'});
- expect(eapSpans).toBeChecked();
- expect(rpcSpans).not.toBeChecked();
- expect(indexedSpans).not.toBeChecked();
- expect(dataset).toEqual(DiscoverDatasets.SPANS_EAP);
- await userEvent.click(rpcSpans);
- expect(eapSpans).not.toBeChecked();
- expect(rpcSpans).toBeChecked();
- expect(indexedSpans).not.toBeChecked();
- expect(dataset).toEqual(DiscoverDatasets.SPANS_EAP_RPC);
- await userEvent.click(indexedSpans);
- expect(eapSpans).not.toBeChecked();
- expect(rpcSpans).not.toBeChecked();
- expect(indexedSpans).toBeChecked();
- expect(dataset).toEqual(DiscoverDatasets.SPANS_INDEXED);
- });
- it('allows changing mode', async function () {
- let mode: any;
- function Component() {
- mode = useExploreMode();
- return <ExploreToolbar extras={['dataset toggle']} />;
- }
- render(
- <PageParamsProvider>
- <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
- <Component />
- </SpanTagsProvider>
- </PageParamsProvider>,
- {disableRouterMocks: true}
- );
- const section = screen.getByTestId('section-mode');
- const samples = within(section).getByRole('radio', {name: 'Samples'});
- const aggregates = within(section).getByRole('radio', {name: 'Aggregates'});
- expect(samples).toBeChecked();
- expect(aggregates).not.toBeChecked();
- expect(mode).toEqual(Mode.SAMPLES);
- await userEvent.click(aggregates);
- expect(samples).not.toBeChecked();
- expect(aggregates).toBeChecked();
- expect(mode).toEqual(Mode.AGGREGATE);
- await userEvent.click(samples);
- expect(samples).toBeChecked();
- expect(aggregates).not.toBeChecked();
- expect(mode).toEqual(Mode.SAMPLES);
- });
- it('inserts group bys from aggregate mode as fields in samples mode', async function () {
- let fields, groupBys;
- function Component() {
- fields = useExploreFields();
- groupBys = useExploreGroupBys();
- return <ExploreToolbar extras={['dataset toggle']} />;
- }
- render(
- <PageParamsProvider>
- <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
- <Component />
- </SpanTagsProvider>
- </PageParamsProvider>,
- {disableRouterMocks: true}
- );
- const section = screen.getByTestId('section-mode');
- const samples = within(section).getByRole('radio', {name: 'Samples'});
- const aggregates = within(section).getByRole('radio', {name: 'Aggregates'});
- expect(fields).toEqual([
- 'id',
- 'span.op',
- 'span.description',
- 'span.duration',
- 'transaction',
- 'timestamp',
- ]); // default
- // Add a group by, and leave one unselected
- await userEvent.click(aggregates);
- const groupBy = screen.getByTestId('section-group-by');
- await userEvent.click(within(groupBy).getByRole('button', {name: 'span.op'}));
- await userEvent.click(within(groupBy).getByRole('option', {name: 'release'}));
- expect(groupBys).toEqual(['release']);
- await userEvent.click(within(groupBy).getByRole('button', {name: 'Add Group'}));
- expect(groupBys).toEqual(['release', '']);
- await userEvent.click(samples);
- expect(fields).toEqual([
- 'id',
- 'span.op',
- 'span.description',
- 'span.duration',
- 'transaction',
- 'timestamp',
- 'release',
- ]); // default
- });
- it('allows changing visualizes', async function () {
- let visualizes: any;
- function Component() {
- visualizes = useExploreVisualizes();
- return <ExploreToolbar />;
- }
- render(
- <PageParamsProvider>
- <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
- <Component />
- </SpanTagsProvider>
- </PageParamsProvider>,
- {disableRouterMocks: true}
- );
- const section = screen.getByTestId('section-visualizes');
- // this is the default
- expect(visualizes).toEqual([
- {
- chartType: ChartType.LINE,
- label: 'A',
- yAxes: ['avg(span.duration)'],
- },
- ]);
- // try changing the field
- await userEvent.click(within(section).getByRole('button', {name: 'span.duration'}));
- await userEvent.click(within(section).getByRole('option', {name: 'span.self_time'}));
- expect(visualizes).toEqual([
- {
- chartType: ChartType.LINE,
- label: 'A',
- yAxes: ['avg(span.self_time)'],
- },
- ]);
- // try changing the aggregate
- await userEvent.click(within(section).getByRole('button', {name: 'avg'}));
- await userEvent.click(within(section).getByRole('option', {name: 'count'}));
- expect(visualizes).toEqual([
- {
- chartType: ChartType.LINE,
- label: 'A',
- yAxes: ['count(span.self_time)'],
- },
- ]);
- // try adding an overlay
- await userEvent.click(within(section).getByRole('button', {name: 'Add Series'}));
- await userEvent.click(within(section).getByRole('button', {name: 'span.duration'}));
- await userEvent.click(within(section).getByRole('option', {name: 'span.self_time'}));
- expect(visualizes).toEqual([
- {
- chartType: ChartType.LINE,
- label: 'A',
- yAxes: ['count(span.self_time)', 'avg(span.self_time)'],
- },
- ]);
- // try adding a new chart
- await userEvent.click(within(section).getByRole('button', {name: 'Add Chart'}));
- expect(visualizes).toEqual([
- {
- chartType: ChartType.LINE,
- label: 'A',
- yAxes: ['count(span.self_time)', 'avg(span.self_time)'],
- },
- {
- chartType: ChartType.LINE,
- label: 'B',
- yAxes: ['avg(span.duration)'],
- },
- ]);
- // delete first overlay
- await userEvent.click(within(section).getAllByLabelText('Remove Overlay')[0]!);
- expect(visualizes).toEqual([
- {
- chartType: ChartType.LINE,
- label: 'A',
- yAxes: ['avg(span.self_time)'],
- },
- {
- chartType: ChartType.LINE,
- label: 'B',
- yAxes: ['avg(span.duration)'],
- },
- ]);
- // delete second chart
- await userEvent.click(within(section).getAllByLabelText('Remove Overlay')[1]!);
- expect(visualizes).toEqual([
- {
- chartType: ChartType.LINE,
- label: 'A',
- yAxes: ['avg(span.self_time)'],
- },
- ]);
- // only one left so cant be deleted
- expect(within(section).getByLabelText('Remove Overlay')).toBeDisabled();
- });
- it('allows changing group bys', async function () {
- let groupBys: any;
- function Component() {
- groupBys = useExploreGroupBys();
- return <ExploreToolbar />;
- }
- render(
- <PageParamsProvider>
- <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
- <Component />
- </SpanTagsProvider>
- </PageParamsProvider>,
- {disableRouterMocks: true}
- );
- expect(screen.queryByTestId('section-group-by')).not.toBeInTheDocument();
- // click the aggregates mode to enable
- await userEvent.click(
- within(screen.getByTestId('section-mode')).getByRole('radio', {
- name: 'Aggregates',
- })
- );
- const section = screen.getByTestId('section-group-by');
- expect(within(section).getByRole('button', {name: 'span.op'})).toBeEnabled();
- await userEvent.click(within(section).getByRole('button', {name: 'span.op'}));
- const groupByOptions1 = await within(section).findAllByRole('option');
- expect(groupByOptions1.length).toBeGreaterThan(0);
- await userEvent.click(within(section).getByRole('option', {name: 'project'}));
- expect(groupBys).toEqual(['project']);
- await userEvent.click(within(section).getByRole('button', {name: 'Add Group'}));
- expect(groupBys).toEqual(['project', '']);
- await userEvent.click(within(section).getByRole('button', {name: 'None'}));
- const groupByOptions2 = await within(section).findAllByRole('option');
- expect(groupByOptions2.length).toBeGreaterThan(0);
- await userEvent.click(
- within(section).getByRole('option', {name: 'span.description'})
- );
- expect(groupBys).toEqual(['project', 'span.description']);
- await userEvent.click(within(section).getAllByLabelText('Remove Column')[0]!);
- expect(groupBys).toEqual(['span.description']);
- // only 1 left but it's not empty
- expect(within(section).getByLabelText('Remove Column')).toBeEnabled();
- await userEvent.click(within(section).getByLabelText('Remove Column'));
- expect(groupBys).toEqual(['']);
- // last one and it's empty
- expect(within(section).getByLabelText('Remove Column')).toBeDisabled();
- });
- it('allows changing sort by', async function () {
- let sortBys: any;
- function Component() {
- sortBys = useExploreSortBys();
- return <ExploreToolbar />;
- }
- render(
- <PageParamsProvider>
- <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
- <Component />
- </SpanTagsProvider>
- </PageParamsProvider>,
- {disableRouterMocks: true}
- );
- const section = screen.getByTestId('section-sort-by');
- // this is the default
- expect(within(section).getByRole('button', {name: 'timestamp'})).toBeInTheDocument();
- expect(within(section).getByRole('button', {name: 'Desc'})).toBeInTheDocument();
- expect(sortBys).toEqual([{field: 'timestamp', kind: 'desc'}]);
- // check the default field options
- const fields = [
- 'id',
- 'span.description',
- 'span.duration',
- 'span.op',
- 'timestamp',
- 'transaction',
- ];
- await userEvent.click(within(section).getByRole('button', {name: 'timestamp'}));
- const fieldOptions = await within(section).findAllByRole('option');
- expect(fieldOptions).toHaveLength(fields.length);
- fieldOptions.forEach((option, i) => {
- expect(option).toHaveTextContent(fields[i]!);
- });
- // try changing the field
- await userEvent.click(within(section).getByRole('option', {name: 'span.op'}));
- expect(within(section).getByRole('button', {name: 'span.op'})).toBeInTheDocument();
- expect(within(section).getByRole('button', {name: 'Desc'})).toBeInTheDocument();
- expect(sortBys).toEqual([{field: 'span.op', kind: 'desc'}]);
- // check the kind options
- await userEvent.click(within(section).getByRole('button', {name: 'Desc'}));
- const kindOptions = await within(section).findAllByRole('option');
- expect(kindOptions).toHaveLength(2);
- expect(kindOptions[0]).toHaveTextContent('Desc');
- expect(kindOptions[1]).toHaveTextContent('Asc');
- // try changing the kind
- await userEvent.click(within(section).getByRole('option', {name: 'Asc'}));
- expect(within(section).getByRole('button', {name: 'span.op'})).toBeInTheDocument();
- expect(within(section).getByRole('button', {name: 'Asc'})).toBeInTheDocument();
- expect(sortBys).toEqual([{field: 'span.op', kind: 'asc'}]);
- });
- it('takes you to suggested query', async function () {
- let pageParams: any;
- function Component() {
- pageParams = useExplorePageParams();
- return <ExploreToolbar />;
- }
- render(
- <PageParamsProvider>
- <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
- <Component />
- </SpanTagsProvider>
- </PageParamsProvider>,
- {disableRouterMocks: true}
- );
- const section = screen.getByTestId('section-suggested-queries');
- await userEvent.click(within(section).getByText('Slowest Ops'));
- expect(pageParams).toEqual(
- expect.objectContaining({
- fields: [
- 'id',
- 'project',
- 'span.op',
- 'span.description',
- 'span.duration',
- 'timestamp',
- ],
- groupBys: ['span.op'],
- mode: Mode.AGGREGATE,
- query: '',
- sortBys: [{field: 'avg(span.duration)', kind: 'desc'}],
- visualizes: [
- {
- chartType: ChartType.LINE,
- label: 'A',
- yAxes: ['avg(span.duration)'],
- },
- {
- chartType: ChartType.LINE,
- label: 'B',
- yAxes: ['p50(span.duration)'],
- },
- ],
- })
- );
- });
- it('opens the right alert', async function () {
- const router = RouterFixture({
- location: {
- pathname: '/traces/',
- query: {
- visualize: encodeURIComponent('{"chartType":1,"yAxes":["avg(span.duration)"]}'),
- },
- },
- });
- function Component() {
- return <ExploreToolbar />;
- }
- render(
- <PageParamsProvider>
- <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
- <Component />
- </SpanTagsProvider>
- </PageParamsProvider>,
- {router, organization}
- );
- const section = screen.getByTestId('section-save-as');
- await userEvent.click(within(section).getByText(/Save as/));
- await userEvent.hover(within(section).getByText('An Alert for'));
- await userEvent.click(screen.getByText('avg(span.duration)'));
- expect(router.push).toHaveBeenCalledWith({
- pathname: '/organizations/org-slug/alerts/new/metric/',
- query: expect.objectContaining({
- aggregate: 'avg(span.duration)',
- dataset: 'events_analytics_platform',
- }),
- });
- });
- it('add to dashboard options correctly', async function () {
- const router = RouterFixture({
- location: {
- pathname: '/traces/',
- query: {
- visualize: encodeURIComponent('{"chartType":1,"yAxes":["avg(span.duration)"]}'),
- },
- },
- });
- function Component() {
- return <ExploreToolbar />;
- }
- render(
- <PageParamsProvider>
- <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
- <Component />
- </SpanTagsProvider>
- </PageParamsProvider>,
- {router, organization}
- );
- const section = screen.getByTestId('section-save-as');
- await userEvent.click(within(section).getByText(/Save as/));
- await userEvent.click(within(section).getByText('A Dashboard widget'));
- await waitFor(() => {
- expect(openAddToDashboardModal).toHaveBeenCalledWith(
- expect.objectContaining({
- widget: expect.objectContaining({
- displayType: 'line',
- queries: [
- {
- aggregates: ['avg(span.duration)'],
- columns: [],
- conditions: '',
- fields: ['avg(span.duration)'],
- name: '',
- orderby: '-timestamp',
- },
- ],
- title: 'Custom Widget',
- widgetType: 'spans',
- }),
- widgetAsQueryParams: expect.objectContaining({
- dataset: 'spans',
- defaultTableColumns: [
- 'id',
- 'span.op',
- 'span.description',
- 'span.duration',
- 'transaction',
- 'timestamp',
- ],
- defaultTitle: 'Custom Widget',
- defaultWidgetQuery:
- 'name=&aggregates=avg(span.duration)&columns=&fields=avg(span.duration)&conditions=&orderby=-timestamp',
- displayType: 'line',
- end: undefined,
- field: [
- 'id',
- 'span.op',
- 'span.description',
- 'span.duration',
- 'transaction',
- 'timestamp',
- ],
- limit: undefined,
- source: 'traceExplorer',
- start: undefined,
- statsPeriod: '14d',
- }),
- })
- );
- });
- });
- });
|