import selectEvent from 'react-select-event';
import {urlEncode} from '@sentry/utils';

import {initializeOrg} from 'sentry-test/initializeOrg';
import {mountGlobalModal} from 'sentry-test/modal';
import {act, render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';

import * as indicators from 'sentry/actionCreators/indicator';
import * as modals from 'sentry/actionCreators/modal';
import TagStore from 'sentry/stores/tagStore';
import {TOP_N} from 'sentry/utils/discover/types';
import {
  DashboardDetails,
  DashboardWidgetSource,
  DisplayType,
  Widget,
  WidgetType,
} from 'sentry/views/dashboardsV2/types';
import WidgetBuilder, {WidgetBuilderProps} from 'sentry/views/dashboardsV2/widgetBuilder';

const defaultOrgFeatures = [
  'performance-view',
  'new-widget-builder-experience-design',
  'dashboards-edit',
  'global-views',
  'dashboards-mep',
];

// Mocking worldMapChart to avoid act warnings
jest.mock('sentry/components/charts/worldMapChart');

function mockDashboard(dashboard: Partial<DashboardDetails>): DashboardDetails {
  return {
    id: '1',
    title: 'Dashboard',
    createdBy: undefined,
    dateCreated: '2020-01-01T00:00:00.000Z',
    widgets: [],
    projects: [],
    filters: {},
    ...dashboard,
  };
}

function renderTestComponent({
  dashboard,
  query,
  orgFeatures,
  onSave,
  params,
}: {
  dashboard?: WidgetBuilderProps['dashboard'];
  onSave?: WidgetBuilderProps['onSave'];
  orgFeatures?: string[];
  params?: Partial<WidgetBuilderProps['params']>;
  query?: Record<string, any>;
} = {}) {
  const {organization, router, routerContext} = initializeOrg({
    ...initializeOrg(),
    organization: {
      features: orgFeatures ?? defaultOrgFeatures,
    },
    router: {
      location: {
        query: {
          source: DashboardWidgetSource.DASHBOARDS,
          ...query,
        },
      },
    },
  });

  render(
    <WidgetBuilder
      route={{}}
      router={router}
      routes={router.routes}
      routeParams={router.params}
      location={router.location}
      dashboard={{
        id: 'new',
        title: 'Dashboard',
        createdBy: undefined,
        dateCreated: '2020-01-01T00:00:00.000Z',
        widgets: [],
        projects: [],
        filters: {},
        ...dashboard,
      }}
      onSave={onSave ?? jest.fn()}
      params={{
        orgId: organization.slug,
        dashboardId: dashboard?.id ?? 'new',
        ...params,
      }}
    />,
    {
      context: routerContext,
      organization,
    }
  );

  return {router};
}

describe('WidgetBuilder', function () {
  const untitledDashboard: DashboardDetails = {
    id: '1',
    title: 'Untitled Dashboard',
    createdBy: undefined,
    dateCreated: '2020-01-01T00:00:00.000Z',
    widgets: [],
    projects: [],
    filters: {},
  };

  const testDashboard: DashboardDetails = {
    id: '2',
    title: 'Test Dashboard',
    createdBy: undefined,
    dateCreated: '2020-01-01T00:00:00.000Z',
    widgets: [],
    projects: [],
    filters: {},
  };

  let eventsStatsMock: jest.Mock | undefined;
  let eventsv2Mock: jest.Mock | undefined;
  let eventsMock: jest.Mock | undefined;
  let sessionsDataMock: jest.Mock | undefined;
  let metricsDataMock: jest.Mock | undefined;
  let tagsMock: jest.Mock | undefined;
  let measurementsMetaMock: jest.Mock | undefined;

  beforeEach(function () {
    MockApiClient.addMockResponse({
      url: '/organizations/org-slug/dashboards/',
      body: [
        {...untitledDashboard, widgetDisplay: [DisplayType.TABLE]},
        {...testDashboard, widgetDisplay: [DisplayType.AREA]},
      ],
    });

    MockApiClient.addMockResponse({
      url: '/organizations/org-slug/dashboards/widgets/',
      method: 'POST',
      statusCode: 200,
      body: [],
    });

    eventsv2Mock = MockApiClient.addMockResponse({
      url: '/organizations/org-slug/eventsv2/',
      method: 'GET',
      statusCode: 200,
      body: {
        meta: {},
        data: [],
      },
    });

    eventsMock = MockApiClient.addMockResponse({
      url: '/organizations/org-slug/events/',
      method: 'GET',
      statusCode: 200,
      body: {
        meta: {fields: {}},
        data: [],
      },
    });

    MockApiClient.addMockResponse({
      url: '/organizations/org-slug/projects/',
      method: 'GET',
      body: [],
    });

    MockApiClient.addMockResponse({
      url: '/organizations/org-slug/recent-searches/',
      method: 'GET',
      body: [],
    });

    MockApiClient.addMockResponse({
      url: '/organizations/org-slug/recent-searches/',
      method: 'POST',
      body: [],
    });

    MockApiClient.addMockResponse({
      url: '/organizations/org-slug/issues/',
      method: 'GET',
      body: [],
    });

    eventsStatsMock = MockApiClient.addMockResponse({
      url: '/organizations/org-slug/events-stats/',
      body: [],
    });

    MockApiClient.addMockResponse({
      url: '/organizations/org-slug/tags/event.type/values/',
      body: [{count: 2, name: 'Nvidia 1080ti'}],
    });

    MockApiClient.addMockResponse({
      url: '/organizations/org-slug/events-geo/',
      body: {data: [], meta: {}},
    });

    MockApiClient.addMockResponse({
      url: '/organizations/org-slug/users/',
      body: [],
    });

    sessionsDataMock = MockApiClient.addMockResponse({
      method: 'GET',
      url: '/organizations/org-slug/sessions/',
      body: TestStubs.SessionsField({
        field: `sum(session)`,
      }),
    });

    metricsDataMock = MockApiClient.addMockResponse({
      method: 'GET',
      url: '/organizations/org-slug/metrics/data/',
      body: TestStubs.MetricsField({
        field: 'sum(sentry.sessions.session)',
      }),
    });

    tagsMock = MockApiClient.addMockResponse({
      url: '/organizations/org-slug/tags/',
      method: 'GET',
      body: TestStubs.Tags(),
    });

    measurementsMetaMock = MockApiClient.addMockResponse({
      url: '/organizations/org-slug/measurements-meta/',
      method: 'GET',
      body: {},
    });

    MockApiClient.addMockResponse({
      url: '/organizations/org-slug/tags/is/values/',
      method: 'GET',
      body: [],
    });

    TagStore.reset();
  });

  afterEach(function () {
    MockApiClient.clearMockResponses();
    jest.clearAllMocks();
    jest.useRealTimers();
  });

  describe('with eventsv2', function () {
    it('no feature access', function () {
      renderTestComponent({orgFeatures: []});

      expect(
        screen.getByText("You don't have access to this feature")
      ).toBeInTheDocument();
    });

    it('widget not found', function () {
      const widget: Widget = {
        displayType: DisplayType.AREA,
        interval: '1d',
        queries: [
          {
            name: 'Known Users',
            fields: [],
            columns: [],
            aggregates: [],
            conditions: '',
            orderby: '-time',
          },
          {
            name: 'Anonymous Users',
            fields: [],
            columns: [],
            aggregates: [],
            conditions: '',
            orderby: '-time',
          },
        ],
        title: 'Transactions',
        id: '1',
      };

      const dashboard = mockDashboard({widgets: [widget]});

      renderTestComponent({
        dashboard,
        orgFeatures: ['new-widget-builder-experience-design', 'dashboards-edit'],
        params: {
          widgetIndex: '2', // Out of bounds, only one widget
        },
      });

      expect(
        screen.getByText('The widget you want to edit was not found.')
      ).toBeInTheDocument();
    });

    it('renders a widget not found message if the widget index url is not an integer', function () {
      const widget: Widget = {
        displayType: DisplayType.AREA,
        interval: '1d',
        queries: [
          {
            name: 'Known Users',
            fields: [],
            columns: [],
            aggregates: [],
            conditions: '',
            orderby: '-time',
          },
        ],
        title: 'Transactions',
        id: '1',
      };

      const dashboard = mockDashboard({widgets: [widget]});

      renderTestComponent({
        dashboard,
        orgFeatures: ['new-widget-builder-experience-design', 'dashboards-edit'],
        params: {
          widgetIndex: '0.5', // Invalid index
        },
      });

      expect(
        screen.getByText('The widget you want to edit was not found.')
      ).toBeInTheDocument();
    });

    it('renders', async function () {
      renderTestComponent();

      // Header - Breadcrumbs
      expect(await screen.findByRole('link', {name: 'Dashboards'})).toHaveAttribute(
        'href',
        '/organizations/org-slug/dashboards/'
      );
      expect(screen.getByRole('link', {name: 'Dashboard'})).toHaveAttribute(
        'href',
        '/organizations/org-slug/dashboards/new/'
      );
      expect(screen.getByText('Widget Builder')).toBeInTheDocument();

      // Header - Widget Title
      expect(screen.getByRole('heading', {name: 'Custom Widget'})).toBeInTheDocument();

      // Footer - Actions
      expect(screen.getByLabelText('Cancel')).toBeInTheDocument();
      expect(screen.getByLabelText('Add Widget')).toBeInTheDocument();

      // Content - Step 1
      expect(
        screen.getByRole('heading', {name: 'Choose your dataset'})
      ).toBeInTheDocument();
      expect(screen.getByLabelText('Select Errors and Transactions')).toBeChecked();

      // Content - Step 2
      expect(
        screen.getByRole('heading', {name: 'Choose your visualization'})
      ).toBeInTheDocument();

      // Content - Step 3
      expect(
        screen.getByRole('heading', {name: 'Choose your columns'})
      ).toBeInTheDocument();

      // Content - Step 4
      expect(
        screen.getByRole('heading', {name: 'Filter your results'})
      ).toBeInTheDocument();

      // Content - Step 5
      expect(screen.getByRole('heading', {name: 'Sort by a column'})).toBeInTheDocument();
    });

    it('has links back to the new dashboard if creating', async function () {
      // Dashboard has undefined dashboardId when creating from a new dashboard
      // because of route setup
      renderTestComponent({params: {dashboardId: undefined}});

      expect(await screen.findByRole('link', {name: 'Dashboard'})).toHaveAttribute(
        'href',
        '/organizations/org-slug/dashboards/new/'
      );

      expect(screen.getByLabelText('Cancel')).toHaveAttribute(
        'href',
        '/organizations/org-slug/dashboards/new/'
      );
    });

    it('renders new design', async function () {
      renderTestComponent({
        orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
      });

      // Switch to line chart for time series
      userEvent.click(await screen.findByText('Table'));
      userEvent.click(screen.getByText('Line Chart'));

      // Header - Breadcrumbs
      expect(await screen.findByRole('link', {name: 'Dashboards'})).toHaveAttribute(
        'href',
        '/organizations/org-slug/dashboards/'
      );

      expect(screen.getByRole('link', {name: 'Dashboard'})).toHaveAttribute(
        'href',
        '/organizations/org-slug/dashboards/new/'
      );

      expect(screen.getByText('Widget Builder')).toBeInTheDocument();

      // Header - Widget Title
      expect(screen.getByRole('heading', {name: 'Custom Widget'})).toBeInTheDocument();

      // Footer - Actions
      expect(screen.getByLabelText('Cancel')).toBeInTheDocument();
      expect(screen.getByLabelText('Add Widget')).toBeInTheDocument();

      // Content - Step 1
      expect(
        screen.getByRole('heading', {name: 'Choose your dataset'})
      ).toBeInTheDocument();
      expect(screen.getByLabelText('Select Errors and Transactions')).toBeChecked();

      // Content - Step 2
      expect(
        screen.getByRole('heading', {name: 'Choose your visualization'})
      ).toBeInTheDocument();

      // Content - Step 3
      expect(
        screen.getByRole('heading', {name: 'Choose what to plot in the y-axis'})
      ).toBeInTheDocument();

      // Content - Step 4
      expect(
        screen.getByRole('heading', {name: 'Filter your results'})
      ).toBeInTheDocument();

      // Content - Step 5
      expect(
        screen.getByRole('heading', {name: 'Group your results'})
      ).toBeInTheDocument();
    });

    it('can update the title', async function () {
      renderTestComponent({
        query: {source: DashboardWidgetSource.DISCOVERV2},
      });

      const customWidgetLabels = await screen.findAllByText('Custom Widget');
      // EditableText and chart title
      expect(customWidgetLabels).toHaveLength(2);

      userEvent.click(customWidgetLabels[0]);
      userEvent.clear(screen.getByRole('textbox', {name: 'Widget title'}));
      userEvent.paste(
        screen.getByRole('textbox', {name: 'Widget title'}),
        'Unique Users'
      );
      userEvent.keyboard('{enter}');

      expect(screen.queryByText('Custom Widget')).not.toBeInTheDocument();

      expect(screen.getAllByText('Unique Users')).toHaveLength(2);
    });

    it('can add query conditions', async function () {
      const {router} = renderTestComponent({
        query: {source: DashboardWidgetSource.DISCOVERV2},
        dashboard: testDashboard,
      });

      userEvent.type(
        await screen.findByRole('textbox', {name: 'Search events'}),
        'color:blue{enter}'
      );

      userEvent.click(screen.getByText('Add Widget'));

      await waitFor(() => {
        expect(router.push).toHaveBeenCalledWith(
          expect.objectContaining({
            pathname: '/organizations/org-slug/dashboard/2/',
            query: {
              displayType: 'table',
              interval: '5m',
              title: 'Custom Widget',
              queryNames: [''],
              queryConditions: ['color:blue'],
              queryFields: ['count()'],
              queryOrderby: '-count()',
              start: null,
              end: null,
              statsPeriod: '24h',
              utc: null,
              project: [],
              environment: [],
            },
          })
        );
      });
    });

    it('can choose a field', async function () {
      const {router} = renderTestComponent({
        query: {source: DashboardWidgetSource.DISCOVERV2},
        dashboard: testDashboard,
      });

      expect(await screen.findAllByText('Custom Widget')).toHaveLength(2);

      // No delete button as there is only one query.
      expect(screen.queryByLabelText('Remove query')).not.toBeInTheDocument();

      // 1 in the table header, 1 in the column selector, 1 in the sort field
      const countFields = screen.getAllByText('count()');
      expect(countFields).toHaveLength(3);

      await selectEvent.select(countFields[1], ['last_seen()']);

      userEvent.click(screen.getByText('Add Widget'));

      await waitFor(() => {
        expect(router.push).toHaveBeenCalledWith(
          expect.objectContaining({
            pathname: '/organizations/org-slug/dashboard/2/',
            query: {
              displayType: 'table',
              interval: '5m',
              title: 'Custom Widget',
              queryNames: [''],
              queryConditions: [''],
              queryFields: ['last_seen()'],
              queryOrderby: '-last_seen()',
              start: null,
              end: null,
              statsPeriod: '24h',
              utc: null,
              project: [],
              environment: [],
            },
          })
        );
      });
    });

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

      renderTestComponent({onSave: handleSave});

      userEvent.click(await screen.findByText('Table'));

      // Select line chart display
      userEvent.click(screen.getByText('Line Chart'));

      // Click the add overlay button
      userEvent.click(screen.getByLabelText('Add Overlay'));
      await selectEvent.select(screen.getByText('(Required)'), ['count_unique(…)']);

      userEvent.click(screen.getByLabelText('Add Widget'));

      await waitFor(() => {
        expect(handleSave).toHaveBeenCalledWith([
          expect.objectContaining({
            title: 'Custom Widget',
            displayType: DisplayType.LINE,
            interval: '5m',
            widgetType: WidgetType.DISCOVER,
            queries: [
              {
                conditions: '',
                fields: ['count()', 'count_unique(user)'],
                aggregates: ['count()', 'count_unique(user)'],
                fieldAliases: [],
                columns: [],
                orderby: '',
                name: '',
              },
            ],
          }),
        ]);
      });

      expect(handleSave).toHaveBeenCalledTimes(1);
    });

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

      renderTestComponent({onSave: handleSave});
      userEvent.click(await screen.findByText('Table'));

      // Select line chart display
      userEvent.click(screen.getByText('Line Chart'));

      // Click the add an equation button
      userEvent.click(screen.getByLabelText('Add an Equation'));

      expect(screen.getByPlaceholderText('Equation')).toBeInTheDocument();

      userEvent.paste(screen.getByPlaceholderText('Equation'), 'count() + 100');

      userEvent.click(screen.getByLabelText('Add Widget'));

      await waitFor(() => {
        expect(handleSave).toHaveBeenCalledWith([
          expect.objectContaining({
            title: 'Custom Widget',
            displayType: DisplayType.LINE,
            interval: '5m',
            widgetType: WidgetType.DISCOVER,
            queries: [
              {
                name: '',
                fields: ['count()', 'equation|count() + 100'],
                aggregates: ['count()', 'equation|count() + 100'],
                columns: [],
                fieldAliases: [],
                conditions: '',
                orderby: '',
              },
            ],
          }),
        ]);
      });

      expect(handleSave).toHaveBeenCalledTimes(1);
    });

    it('can respond to validation feedback', async function () {
      jest.spyOn(indicators, 'addErrorMessage');

      renderTestComponent();

      userEvent.click(await screen.findByText('Table'));

      const customWidgetLabels = await screen.findAllByText('Custom Widget');
      // EditableText and chart title
      expect(customWidgetLabels).toHaveLength(2);

      userEvent.click(customWidgetLabels[0]);
      userEvent.clear(screen.getByRole('textbox', {name: 'Widget title'}));

      userEvent.keyboard('{enter}');

      expect(indicators.addErrorMessage).toHaveBeenCalledWith('Widget title is required');
    });

    it('sets up widget data in edit correctly', async function () {
      const widget: Widget = {
        id: '1',
        title: 'Errors over time',
        interval: '5m',
        displayType: DisplayType.LINE,
        queries: [
          {
            name: 'errors',
            conditions: 'event.type:error',
            fields: ['count()', 'count_unique(id)'],
            aggregates: ['count()', 'count_unique(id)'],
            columns: [],
            orderby: '',
          },
          {
            name: 'csp',
            conditions: 'event.type:csp',
            fields: ['count()', 'count_unique(id)'],
            aggregates: ['count()', 'count_unique(id)'],
            columns: [],
            orderby: '',
          },
        ],
      };

      const dashboard = mockDashboard({widgets: [widget]});

      renderTestComponent({dashboard, params: {widgetIndex: '0'}});

      await screen.findByText('Line Chart');

      // Should be in edit 'mode'
      expect(await screen.findByText('Update Widget')).toBeInTheDocument();

      // Should set widget data up.
      expect(screen.getByText('Update Widget')).toBeInTheDocument();

      // Filters
      expect(
        screen.getAllByPlaceholderText('Search for events, users, tags, and more')
      ).toHaveLength(2);
      expect(screen.getByText('event.type:csp')).toBeInTheDocument();
      expect(screen.getByText('event.type:error')).toBeInTheDocument();

      // Y-axis
      expect(screen.getAllByRole('button', {name: 'Remove query'})).toHaveLength(2);
      expect(screen.getByText('count()')).toBeInTheDocument();
      expect(screen.getByText('count_unique(…)')).toBeInTheDocument();
      expect(screen.getByText('id')).toBeInTheDocument();

      // Expect events-stats endpoint to be called for each search conditions with
      // the same y-axis parameters
      expect(eventsStatsMock).toHaveBeenNthCalledWith(
        1,
        '/organizations/org-slug/events-stats/',
        expect.objectContaining({
          query: expect.objectContaining({
            query: 'event.type:error',
            yAxis: ['count()', 'count_unique(id)'],
          }),
        })
      );

      expect(eventsStatsMock).toHaveBeenNthCalledWith(
        2,
        '/organizations/org-slug/events-stats/',
        expect.objectContaining({
          query: expect.objectContaining({
            query: 'event.type:csp',
            yAxis: ['count()', 'count_unique(id)'],
          }),
        })
      );
    });

    it('can edit a widget', async function () {
      const widget: Widget = {
        id: '1',
        title: 'Errors over time',
        interval: '5m',
        displayType: DisplayType.LINE,
        queries: [
          {
            name: 'errors',
            conditions: 'event.type:error',
            fields: ['count()', 'count_unique(id)'],
            aggregates: ['count()', 'count_unique(id)'],
            columns: [],
            orderby: '',
          },
          {
            name: 'csp',
            conditions: 'event.type:csp',
            fields: ['count()', 'count_unique(id)'],
            aggregates: ['count()', 'count_unique(id)'],
            columns: [],
            orderby: '',
          },
        ],
      };

      const dashboard = mockDashboard({widgets: [widget]});

      const handleSave = jest.fn();

      renderTestComponent({onSave: handleSave, dashboard, params: {widgetIndex: '0'}});

      await screen.findByText('Line Chart');

      // Should be in edit 'mode'
      expect(await screen.findByText('Update Widget')).toBeInTheDocument();

      const customWidgetLabels = await screen.findAllByText(widget.title);
      // EditableText and chart title
      expect(customWidgetLabels).toHaveLength(2);
      userEvent.click(customWidgetLabels[0]);

      userEvent.clear(screen.getByRole('textbox', {name: 'Widget title'}));
      userEvent.paste(screen.getByRole('textbox', {name: 'Widget title'}), 'New Title');

      userEvent.click(screen.getByRole('button', {name: 'Update Widget'}));

      await waitFor(() => {
        expect(handleSave).toHaveBeenCalledWith([
          expect.objectContaining({
            ...widget,
            title: 'New Title',
          }),
        ]);
      });

      expect(handleSave).toHaveBeenCalledTimes(1);
    });

    it('renders column inputs for table widgets', async function () {
      const widget: Widget = {
        id: '0',
        title: 'sdk usage',
        interval: '5m',
        displayType: DisplayType.TABLE,
        queries: [
          {
            name: 'errors',
            conditions: 'event.type:error',
            fields: ['sdk.name', 'count()'],
            columns: ['sdk.name'],
            aggregates: ['count()'],
            orderby: '',
          },
        ],
      };

      const dashboard = mockDashboard({widgets: [widget]});

      renderTestComponent({dashboard, params: {widgetIndex: '0'}});

      // Should be in edit 'mode'
      expect(await screen.findByText('Update Widget')).toBeInTheDocument();

      // Should set widget data up.
      expect(screen.getByRole('heading', {name: widget.title})).toBeInTheDocument();
      expect(screen.getByText('Table')).toBeInTheDocument();
      expect(screen.getByLabelText('Search events')).toBeInTheDocument();

      // Should have an orderby select
      expect(screen.getByText('Sort by a column')).toBeInTheDocument();

      // Add a column, and choose a value,
      expect(screen.getByLabelText('Add a Column')).toBeInTheDocument();
    });

    it('can save table widgets', async function () {
      const widget: Widget = {
        id: '0',
        title: 'sdk usage',
        interval: '5m',
        displayType: DisplayType.TABLE,
        queries: [
          {
            name: 'errors',
            conditions: 'event.type:error',
            fields: ['sdk.name', 'count()'],
            columns: ['sdk.name'],
            aggregates: ['count()'],
            orderby: '-count()',
          },
        ],
      };

      const dashboard = mockDashboard({widgets: [widget]});

      const handleSave = jest.fn();

      renderTestComponent({dashboard, onSave: handleSave, params: {widgetIndex: '0'}});

      // Should be in edit 'mode'
      expect(await screen.findByText('Update Widget')).toBeInTheDocument();
      // Add a column, and choose a value,
      userEvent.click(screen.getByLabelText('Add a Column'));
      await selectEvent.select(screen.getByText('(Required)'), 'trace');

      // Save widget
      userEvent.click(screen.getByLabelText('Update Widget'));

      await waitFor(() => {
        expect(handleSave).toHaveBeenCalledWith([
          expect.objectContaining({
            id: '0',
            title: 'sdk usage',
            displayType: DisplayType.TABLE,
            interval: '5m',
            queries: [
              {
                name: 'errors',
                conditions: 'event.type:error',
                fields: ['sdk.name', 'count()', 'trace'],
                aggregates: ['count()'],
                columns: ['sdk.name', 'trace'],
                orderby: '-count()',
                fieldAliases: ['', '', ''],
              },
            ],
            widgetType: WidgetType.DISCOVER,
          }),
        ]);
      });

      expect(handleSave).toHaveBeenCalledTimes(1);
    });

    it('should properly query for table fields', async function () {
      const defaultWidgetQuery = {
        name: '',
        fields: ['title', 'count()'],
        columns: ['title'],
        aggregates: ['count()'],
        conditions: '',
        orderby: '',
      };

      const defaultTableColumns = ['title', 'count()', 'count_unique(user)', 'epm()'];

      renderTestComponent({
        query: {
          source: DashboardWidgetSource.DISCOVERV2,
          defaultWidgetQuery: urlEncode(defaultWidgetQuery),
          displayType: DisplayType.LINE,
          defaultTableColumns,
        },
      });

      expect(await screen.findByText('Line Chart')).toBeInTheDocument();
      userEvent.click(screen.getByText('Line Chart'));
      userEvent.click(screen.getByText('Table'));

      await waitFor(() => {
        expect(eventsv2Mock).toHaveBeenLastCalledWith(
          '/organizations/org-slug/eventsv2/',
          expect.objectContaining({
            query: expect.objectContaining({
              field: defaultTableColumns,
            }),
          })
        );
      });
    });

    it('should use defaultWidgetQuery Y-Axis and Conditions if given a defaultWidgetQuery', async function () {
      const defaultWidgetQuery = {
        name: '',
        fields: ['count()', 'failure_count()', 'count_unique(user)'],
        columns: [],
        aggregates: ['count()', 'failure_count()', 'count_unique(user)'],
        conditions: 'tag:value',
        orderby: '',
      };

      renderTestComponent({
        query: {
          source: DashboardWidgetSource.DISCOVERV2,
          defaultWidgetQuery: urlEncode(defaultWidgetQuery),
        },
      });

      expect(await screen.findByText('tag:value')).toBeInTheDocument();

      // Table display, column, and sort field
      expect(screen.getAllByText('count()')).toHaveLength(3);
      // Table display and column
      expect(screen.getAllByText('failure_count()')).toHaveLength(2);
      // Table display
      expect(screen.getByText('count_unique(user)')).toBeInTheDocument();
      // Column
      expect(screen.getByText('count_unique(…)')).toBeInTheDocument();
      // Column
      expect(screen.getByText('user')).toBeInTheDocument();
    });

    it('uses displayType if given a displayType', async function () {
      renderTestComponent({
        query: {
          displayType: DisplayType.BAR,
        },
      });

      expect(await screen.findByText('Bar Chart')).toBeInTheDocument();
    });

    it('deletes the widget when the modal is confirmed', async () => {
      const handleSave = jest.fn();
      const widget: Widget = {
        id: '1',
        title: 'Errors over time',
        interval: '5m',
        displayType: DisplayType.LINE,
        queries: [
          {
            name: 'errors',
            conditions: 'event.type:error',
            fields: ['count()', 'count_unique(id)'],
            aggregates: ['count()', 'count_unique(id)'],
            columns: [],
            orderby: '',
          },
          {
            name: 'csp',
            conditions: 'event.type:csp',
            fields: ['count()', 'count_unique(id)'],
            aggregates: ['count()', 'count_unique(id)'],
            columns: [],
            orderby: '',
          },
        ],
      };

      const dashboard = mockDashboard({widgets: [widget]});

      renderTestComponent({onSave: handleSave, dashboard, params: {widgetIndex: '0'}});

      userEvent.click(await screen.findByText('Delete'));

      await mountGlobalModal();
      userEvent.click(await screen.findByText('Confirm'));

      await waitFor(() => {
        // The only widget was deleted
        expect(handleSave).toHaveBeenCalledWith([]);
      });

      expect(handleSave).toHaveBeenCalledTimes(1);
    });

    it('persists the page filter period when updating a widget', async () => {
      const widget: Widget = {
        id: '1',
        title: 'Errors over time',
        interval: '5m',
        displayType: DisplayType.LINE,
        queries: [
          {
            name: 'errors',
            conditions: 'event.type:error',
            fields: ['count()', 'count_unique(id)'],
            aggregates: ['count()', 'count_unique(id)'],
            columns: [],
            orderby: '',
          },
        ],
      };

      const dashboard = mockDashboard({widgets: [widget]});

      const {router} = renderTestComponent({
        dashboard,
        params: {orgId: 'org-slug', widgetIndex: '0'},
        query: {statsPeriod: '90d'},
      });

      await screen.findByText('Update Widget');
      await screen.findByText('90D');

      expect(screen.getByTestId('page-filter-timerange-selector')).toBeEnabled();

      userEvent.click(screen.getByText('Update Widget'));

      await waitFor(() => {
        expect(router.push).toHaveBeenLastCalledWith(
          expect.objectContaining({
            pathname: '/organizations/org-slug/dashboard/1/',
            query: expect.objectContaining({
              statsPeriod: '90d',
            }),
          })
        );
      });
    });

    it('renders page filters in the filter step', async () => {
      const mockReleases = MockApiClient.addMockResponse({
        url: '/organizations/org-slug/releases/',
        body: [TestStubs.Release()],
      });

      renderTestComponent({
        params: {orgId: 'org-slug'},
        query: {statsPeriod: '90d'},
        orgFeatures: [...defaultOrgFeatures, 'dashboards-top-level-filter'],
      });

      await screen.findByText('90D');
      expect(screen.getByTestId('page-filter-timerange-selector')).toBeDisabled();
      expect(screen.getByTestId('page-filter-environment-selector')).toBeDisabled();
      expect(screen.getByTestId('page-filter-project-selector-loading')).toBeDisabled();

      await waitFor(() => {
        expect(mockReleases).toHaveBeenCalled();
      });

      expect(screen.getByRole('button', {name: /all releases/i})).toBeDisabled();
    });

    it('appends dashboard filters to widget builder fetch data request', async () => {
      MockApiClient.addMockResponse({
        url: '/organizations/org-slug/releases/',
        body: [TestStubs.Release()],
      });

      const mock = MockApiClient.addMockResponse({
        url: '/organizations/org-slug/eventsv2/',
        body: [],
      });

      renderTestComponent({
        dashboard: {
          id: 'new',
          title: 'Dashboard',
          createdBy: undefined,
          dateCreated: '2020-01-01T00:00:00.000Z',
          widgets: [],
          projects: [],
          filters: {release: ['abc@1.2.0']},
        },
        params: {orgId: 'org-slug'},
        query: {statsPeriod: '90d'},
        orgFeatures: [...defaultOrgFeatures, 'dashboards-top-level-filter'],
      });

      await waitFor(() => {
        expect(mock).toHaveBeenCalledWith(
          '/organizations/org-slug/eventsv2/',
          expect.objectContaining({
            query: expect.objectContaining({
              query: ' release:abc@1.2.0 ',
            }),
          })
        );
      });
    });

    it('does not error when query conditions field is blurred', async function () {
      jest.useFakeTimers();
      const widget: Widget = {
        id: '0',
        title: 'sdk usage',
        interval: '5m',
        displayType: DisplayType.BAR,
        queries: [
          {
            name: 'filled in',
            conditions: 'event.type:error',
            fields: ['count()', 'count_unique(id)'],
            aggregates: ['count()', 'count_unique(id)'],
            columns: [],
            orderby: '-count()',
          },
        ],
      };

      const dashboard = mockDashboard({widgets: [widget]});

      const handleSave = jest.fn();

      renderTestComponent({dashboard, onSave: handleSave, params: {widgetIndex: '0'}});

      userEvent.click(await screen.findByLabelText('Add Query'));

      // Triggering the onBlur of the new field should not error
      userEvent.click(
        screen.getAllByPlaceholderText('Search for events, users, tags, and more')[1]
      );
      userEvent.keyboard('{esc}');
      act(() => {
        // Run all timers because the handleBlur contains a setTimeout
        jest.runAllTimers();
      });
    });

    it('does not wipe column changes when filters are modified', async function () {
      jest.useFakeTimers();

      // widgetIndex: undefined means creating a new widget
      renderTestComponent({params: {widgetIndex: undefined}});

      userEvent.click(await screen.findByLabelText('Add a Column'));
      await selectEvent.select(screen.getByText('(Required)'), /project/);

      // Triggering the onBlur of the filter should not error
      userEvent.click(
        screen.getByPlaceholderText('Search for events, users, tags, and more')
      );
      userEvent.keyboard('{enter}');
      act(() => {
        // Run all timers because the handleBlur contains a setTimeout
        jest.runAllTimers();
      });

      expect(await screen.findAllByText('project')).toHaveLength(2);
    });

    it('renders fields with commas properly', async () => {
      const defaultWidgetQuery = {
        conditions: '',
        fields: ['equation|count_if(transaction.duration,equals,300)*2'],
        aggregates: ['equation|count_if(transaction.duration,equals,300)*2'],
        columns: [],
        orderby: '',
        name: '',
      };
      const defaultTableColumns = [
        'count_if(transaction.duration,equals,300)',
        'equation|count_if(transaction.duration,equals,300)*2',
      ];
      renderTestComponent({
        query: {
          source: DashboardWidgetSource.DISCOVERV2,
          defaultWidgetQuery: urlEncode(defaultWidgetQuery),
          defaultTableColumns,
          yAxis: ['equation|count_if(transaction.duration,equals,300)*2'],
        },
      });

      expect(
        await screen.findByText('count_if(transaction.duration,equals,300)*2')
      ).toBeInTheDocument();
    });

    it('sets the correct fields for a top n widget', async () => {
      renderTestComponent({
        orgFeatures: [...defaultOrgFeatures, 'performance-view'],
        query: {
          displayType: DisplayType.TOP_N,
        },
      });

      // Top N now opens as Area Chart
      await screen.findByText('Area Chart');

      // Add a group by
      userEvent.click(screen.getByText('Add Overlay'));
      await selectEvent.select(screen.getByText('Select group'), /project/);

      // Change the y-axis
      await selectEvent.select(screen.getAllByText('count()')[0], 'eps()');

      await waitFor(() => {
        expect(eventsStatsMock).toHaveBeenLastCalledWith(
          '/organizations/org-slug/events-stats/',
          expect.objectContaining({
            query: expect.objectContaining({
              query: '',
              yAxis: ['eps()'],
              field: ['project', 'eps()'],
              topEvents: TOP_N,
              orderby: '-eps()',
            }),
          })
        );
      });
    });

    it('fetches tags when tag store is empty', function () {
      renderTestComponent();
      expect(tagsMock).toHaveBeenCalled();
    });

    it('does not fetch tags when tag store is not empty', function () {
      TagStore.loadTagsSuccess(TestStubs.Tags());
      renderTestComponent();
      expect(tagsMock).not.toHaveBeenCalled();
    });

    it('excludes the Other series when grouping and using multiple y-axes', async function () {
      renderTestComponent({
        orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
        query: {
          displayType: DisplayType.LINE,
        },
      });

      await selectEvent.select(await screen.findByText('Select group'), 'project');

      userEvent.click(screen.getByText('Add Overlay'));
      await selectEvent.select(screen.getByText('(Required)'), /count_unique/);

      await waitFor(() => {
        expect(eventsStatsMock).toBeCalledWith(
          '/organizations/org-slug/events-stats/',
          expect.objectContaining({
            query: expect.objectContaining({excludeOther: '1'}),
          })
        );
      });
    });

    it('excludes the Other series when grouping and using multiple queries', async function () {
      renderTestComponent({
        orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
        query: {
          displayType: DisplayType.LINE,
        },
      });

      await selectEvent.select(await screen.findByText('Select group'), 'project');
      userEvent.click(screen.getByText('Add Query'));

      await waitFor(() => {
        expect(eventsStatsMock).toBeCalledWith(
          '/organizations/org-slug/events-stats/',
          expect.objectContaining({
            query: expect.objectContaining({excludeOther: '1'}),
          })
        );
      });
    });

    it('includes Other series when there is only one query and one y-axis', async function () {
      renderTestComponent({
        orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
        query: {
          displayType: DisplayType.LINE,
        },
      });

      await selectEvent.select(await screen.findByText('Select group'), 'project');

      await waitFor(() => {
        expect(eventsStatsMock).toBeCalledWith(
          '/organizations/org-slug/events-stats/',
          expect.objectContaining({
            query: expect.not.objectContaining({excludeOther: '1'}),
          })
        );
      });
    });

    it('decreases the limit when more y-axes and queries are added', async function () {
      renderTestComponent({
        orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
        query: {
          displayType: DisplayType.LINE,
        },
      });

      await selectEvent.select(await screen.findByText('Select group'), 'project');

      screen.getByText('Limit to 5 results');

      userEvent.click(screen.getByText('Add Query'));
      userEvent.click(screen.getByText('Add Overlay'));

      await screen.findByText('Limit to 2 results');
    });

    it('alerts the user if there are unsaved changes', async function () {
      const {router} = renderTestComponent();

      const alertMock = jest.fn();
      const setRouteLeaveHookMock = jest.spyOn(router, 'setRouteLeaveHook');
      setRouteLeaveHookMock.mockImplementationOnce((_route, _callback) => {
        alertMock();
      });

      const customWidgetLabels = await screen.findAllByText('Custom Widget');
      // EditableText and chart title
      expect(customWidgetLabels).toHaveLength(2);

      // Change title text
      userEvent.click(customWidgetLabels[0]);
      userEvent.clear(screen.getByRole('textbox', {name: 'Widget title'}));
      userEvent.paste(
        screen.getByRole('textbox', {name: 'Widget title'}),
        'Unique Users'
      );
      userEvent.keyboard('{enter}');

      // Click Cancel
      userEvent.click(screen.getByText('Cancel'));

      // Assert an alert was triggered
      expect(alertMock).toHaveBeenCalled();
    });

    it('does not trigger alert dialog if no changes', async function () {
      const {router} = renderTestComponent();

      const alertMock = jest.fn();
      const setRouteLeaveHookMock = jest.spyOn(router, 'setRouteLeaveHook');
      setRouteLeaveHookMock.mockImplementationOnce((_route, _callback) => {
        alertMock();
      });

      await screen.findAllByText('Custom Widget');

      // Click Cancel
      userEvent.click(screen.getByText('Cancel'));

      // Assert an alert was triggered
      expect(alertMock).not.toHaveBeenCalled();
    });

    describe('Sort by selectors', function () {
      it('renders', async function () {
        renderTestComponent({
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
        });

        expect(await screen.findByText('Sort by a column')).toBeInTheDocument();
        expect(
          screen.getByText("Choose one of the columns you've created to sort by.")
        ).toBeInTheDocument();

        // Selector "sortDirection"
        expect(screen.getByText('High to low')).toBeInTheDocument();
        // Selector "sortBy"
        expect(screen.getAllByText('count()')).toHaveLength(3);
      });

      it('ordering by column uses field form when selecting orderby', async function () {
        const widget: Widget = {
          id: '1',
          title: 'Test Widget',
          interval: '5m',
          displayType: DisplayType.TABLE,
          queries: [
            {
              name: 'errors',
              conditions: 'event.type:error',
              fields: ['count()'],
              aggregates: ['count()'],
              columns: ['project'],
              orderby: '-project',
            },
          ],
        };

        const dashboard = mockDashboard({widgets: [widget]});

        renderTestComponent({
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
          dashboard,
          params: {
            widgetIndex: '0',
          },
        });

        await waitFor(async () => {
          expect(await screen.findAllByText('project')).toHaveLength(3);
        });

        await selectEvent.select(screen.getAllByText('project')[2], 'count()');

        await waitFor(() => {
          expect(eventsv2Mock).toHaveBeenCalledWith(
            '/organizations/org-slug/eventsv2/',
            expect.objectContaining({
              query: expect.objectContaining({
                sort: ['-count()'],
              }),
            })
          );
        });
      });

      it('sortBy defaults to the first field value when changing display type to table', async function () {
        const widget: Widget = {
          id: '1',
          title: 'Errors over time',
          interval: '5m',
          displayType: DisplayType.LINE,
          queries: [
            {
              name: 'errors',
              conditions: 'event.type:error',
              fields: ['count()', 'count_unique(id)'],
              aggregates: ['count()', 'count_unique(id)'],
              columns: [],
              orderby: '',
            },
            {
              name: 'csp',
              conditions: 'event.type:csp',
              fields: ['count()', 'count_unique(id)'],
              aggregates: ['count()', 'count_unique(id)'],
              columns: [],
              orderby: '',
            },
          ],
        };

        const dashboard = mockDashboard({widgets: [widget]});

        renderTestComponent({
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
          dashboard,
          params: {
            widgetIndex: '0',
          },
        });

        // Click on the displayType selector
        userEvent.click(await screen.findByText('Line Chart'));

        // Choose the table visualization
        userEvent.click(screen.getByText('Table'));

        expect(await screen.findByText('Sort by a column')).toBeInTheDocument();

        // Selector "sortDirection"
        expect(screen.getByText('High to low')).toBeInTheDocument();

        // Selector "sortBy"
        expect(screen.getAllByText('count()')).toHaveLength(3);
      });

      it('can update selectors values', async function () {
        const handleSave = jest.fn();

        const widget: Widget = {
          id: '1',
          title: 'Errors over time',
          interval: '5m',
          displayType: DisplayType.TABLE,
          queries: [
            {
              name: '',
              conditions: '',
              fields: ['count()', 'count_unique(id)'],
              aggregates: ['count()', 'count_unique(id)'],
              columns: [],
              orderby: '-count()',
            },
          ],
        };

        const dashboard = mockDashboard({widgets: [widget]});

        renderTestComponent({
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
          dashboard,
          onSave: handleSave,
          params: {
            widgetIndex: '0',
          },
        });

        expect(await screen.findByText('Sort by a column')).toBeInTheDocument();

        // Selector "sortDirection"
        expect(screen.getByText('High to low')).toBeInTheDocument();

        // Selector "sortBy"
        expect(screen.getAllByText('count()')).toHaveLength(3);

        await selectEvent.select(screen.getAllByText('count()')[2], 'count_unique(id)');

        // Wait for the Builder update the widget values
        await waitFor(() => {
          expect(screen.getAllByText('count()')).toHaveLength(2);
        });

        // Now count_unique(id) is selected in the "sortBy" selector
        expect(screen.getAllByText('count_unique(id)')).toHaveLength(2);

        await selectEvent.select(screen.getByText('High to low'), 'Low to high');

        // Saves the widget
        userEvent.click(screen.getByText('Update Widget'));

        await waitFor(() => {
          expect(handleSave).toHaveBeenCalledWith([
            expect.objectContaining({
              queries: [expect.objectContaining({orderby: 'count_unique(id)'})],
            }),
          ]);
        });
      });

      it('sortBy defaults to the first field value when coming from discover', async function () {
        const defaultWidgetQuery = {
          name: '',
          fields: ['title', 'count()', 'count_unique(user)', 'epm()', 'count()'],
          columns: ['title'],
          aggregates: ['count()', 'count_unique(user)', 'epm()', 'count()'],
          conditions: 'tag:value',
          orderby: '',
        };

        const {router} = renderTestComponent({
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
          query: {
            source: DashboardWidgetSource.DISCOVERV2,
            defaultWidgetQuery: urlEncode(defaultWidgetQuery),
            displayType: DisplayType.TABLE,
            defaultTableColumns: ['title', 'count()', 'count_unique(user)', 'epm()'],
          },
        });

        expect(await screen.findByText('Sort by a column')).toBeInTheDocument();

        // Selector "sortDirection"
        expect(screen.getByText('Low to high')).toBeInTheDocument();

        // Selector "sortBy"
        expect(screen.getAllByText('title')).toHaveLength(2);

        // Saves the widget
        userEvent.click(screen.getByText('Add Widget'));

        await waitFor(() => {
          expect(router.push).toHaveBeenCalledWith(
            expect.objectContaining({
              query: expect.objectContaining({queryOrderby: 'count()'}),
            })
          );
        });
      });

      it('sortBy is only visible on tabular visualizations or when there is a groupBy value selected on time-series visualizations', async function () {
        renderTestComponent({
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
        });

        // Sort by shall be visible on table visualization
        expect(await screen.findByText('Sort by a column')).toBeInTheDocument();

        // Update visualization to be a time-series
        userEvent.click(screen.getByText('Table'));
        userEvent.click(screen.getByText('Line Chart'));

        // Time-series visualizations display GroupBy step
        expect(await screen.findByText('Group your results')).toBeInTheDocument();

        // Do not show sortBy when empty columns (groupBys) are added
        userEvent.click(screen.getByText('Add Group'));
        expect(screen.getAllByText('Select group')).toHaveLength(2);

        // SortBy step shall not be visible
        expect(screen.queryByText('Sort by a y-axis')).not.toBeInTheDocument();

        // Select GroupBy value
        await selectEvent.select(screen.getAllByText('Select group')[0], 'project');

        // Now that at least one groupBy value is selected, the SortBy step shall be visible
        expect(screen.getByText('Sort by a y-axis')).toBeInTheDocument();

        // Remove selected GroupBy value
        userEvent.click(screen.getAllByLabelText('Remove group')[0]);

        // SortBy step shall no longer be visible
        expect(screen.queryByText('Sort by a y-axis')).not.toBeInTheDocument();
      });

      it('allows for sorting by a custom equation', async function () {
        renderTestComponent({
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
          query: {
            source: DashboardWidgetSource.DASHBOARDS,
            displayType: DisplayType.LINE,
          },
        });

        await selectEvent.select(await screen.findByText('Select group'), 'project');
        expect(screen.getAllByText('count()')).toHaveLength(2);
        await selectEvent.select(screen.getAllByText('count()')[1], 'Custom Equation');
        userEvent.paste(
          screen.getByPlaceholderText('Enter Equation'),
          'count_unique(user) * 2'
        );
        userEvent.keyboard('{enter}');

        await waitFor(() => {
          expect(eventsStatsMock).toHaveBeenCalledWith(
            '/organizations/org-slug/events-stats/',
            expect.objectContaining({
              query: expect.objectContaining({
                field: expect.arrayContaining(['equation|count_unique(user) * 2']),
                orderby: '-equation[0]',
              }),
            })
          );
        });
      }, 10000);

      it('persists the state when toggling between sorting options', async function () {
        renderTestComponent({
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
          query: {
            source: DashboardWidgetSource.DASHBOARDS,
            displayType: DisplayType.LINE,
          },
        });

        await selectEvent.select(await screen.findByText('Select group'), 'project');
        expect(screen.getAllByText('count()')).toHaveLength(2);
        await selectEvent.select(screen.getAllByText('count()')[1], 'Custom Equation');
        userEvent.paste(
          screen.getByPlaceholderText('Enter Equation'),
          'count_unique(user) * 2'
        );
        userEvent.keyboard('{enter}');

        // Switch away from the Custom Equation
        expect(screen.getByText('project')).toBeInTheDocument();
        await selectEvent.select(screen.getByText('Custom Equation'), 'project');
        expect(screen.getAllByText('project')).toHaveLength(2);

        // Switch back, the equation should still be visible
        await selectEvent.select(screen.getAllByText('project')[1], 'Custom Equation');
        expect(screen.getByPlaceholderText('Enter Equation')).toHaveValue(
          'count_unique(user) * 2'
        );
      });

      it('persists the state when updating y-axes', async function () {
        renderTestComponent({
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
          query: {
            source: DashboardWidgetSource.DASHBOARDS,
            displayType: DisplayType.LINE,
          },
        });

        await selectEvent.select(await screen.findByText('Select group'), 'project');
        expect(screen.getAllByText('count()')).toHaveLength(2);
        await selectEvent.select(screen.getAllByText('count()')[1], 'Custom Equation');
        userEvent.paste(
          screen.getByPlaceholderText('Enter Equation'),
          'count_unique(user) * 2'
        );
        userEvent.keyboard('{enter}');

        // Add a y-axis
        userEvent.click(screen.getByText('Add Overlay'));

        // The equation should still be visible
        expect(screen.getByPlaceholderText('Enter Equation')).toHaveValue(
          'count_unique(user) * 2'
        );
      });

      it('displays the custom equation if the widget has it saved', async function () {
        const widget: Widget = {
          id: '1',
          title: 'Test Widget',
          interval: '5m',
          displayType: DisplayType.LINE,
          queries: [
            {
              name: '',
              conditions: '',
              fields: ['count()', 'project'],
              aggregates: ['count()'],
              columns: ['project'],
              orderby: '-equation|count_unique(user) * 2',
            },
          ],
        };

        const dashboard = mockDashboard({widgets: [widget]});
        renderTestComponent({
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
          query: {
            source: DashboardWidgetSource.DASHBOARDS,
            displayType: DisplayType.LINE,
          },
          params: {
            widgetIndex: '0',
          },
          dashboard,
        });

        expect(await screen.findByPlaceholderText('Enter Equation')).toHaveValue(
          'count_unique(user) * 2'
        );
      });

      it('displays Operators in the input dropdown', async function () {
        renderTestComponent({
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
          query: {
            source: DashboardWidgetSource.DASHBOARDS,
            displayType: DisplayType.LINE,
          },
        });

        await selectEvent.select(await screen.findByText('Select group'), 'project');
        expect(screen.getAllByText('count()')).toHaveLength(2);
        await selectEvent.select(screen.getAllByText('count()')[1], 'Custom Equation');
        selectEvent.openMenu(screen.getByPlaceholderText('Enter Equation'));

        expect(screen.getByText('Operators')).toBeInTheDocument();
        expect(screen.queryByText('Fields')).not.toBeInTheDocument();
      });

      it('hides Custom Equation input and resets orderby when switching to table', async function () {
        renderTestComponent({
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
          query: {
            source: DashboardWidgetSource.DASHBOARDS,
            displayType: DisplayType.LINE,
          },
        });

        await selectEvent.select(await screen.findByText('Select group'), 'project');
        expect(screen.getAllByText('count()')).toHaveLength(2);
        await selectEvent.select(screen.getAllByText('count()')[1], 'Custom Equation');
        userEvent.paste(
          screen.getByPlaceholderText('Enter Equation'),
          'count_unique(user) * 2'
        );
        userEvent.keyboard('{enter}');

        // Switch the display type to Table
        userEvent.click(screen.getByText('Line Chart'));
        userEvent.click(screen.getByText('Table'));

        expect(screen.getAllByText('count()')).toHaveLength(2);
        expect(screen.queryByPlaceholderText('Enter Equation')).not.toBeInTheDocument();

        await waitFor(() => {
          expect(eventsv2Mock).toHaveBeenCalledWith(
            '/organizations/org-slug/eventsv2/',
            expect.objectContaining({
              query: expect.objectContaining({
                sort: ['-count()'],
              }),
            })
          );
        });
      });

      it('does not show the Custom Equation input if the only y-axis left is an empty equation', async function () {
        renderTestComponent({
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
          query: {
            source: DashboardWidgetSource.DASHBOARDS,
            displayType: DisplayType.LINE,
          },
        });

        await selectEvent.select(await screen.findByText('Select group'), 'project');
        userEvent.click(screen.getByText('Add an Equation'));
        userEvent.click(screen.getAllByLabelText('Remove this Y-Axis')[0]);

        expect(screen.queryByPlaceholderText('Enter Equation')).not.toBeInTheDocument();
      });

      it('persists a sort by a grouping when changing y-axes', async function () {
        renderTestComponent({
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
          query: {
            source: DashboardWidgetSource.DASHBOARDS,
            displayType: DisplayType.LINE,
          },
        });

        await selectEvent.select(await screen.findByText('Select group'), 'project');
        expect(screen.getAllByText('count()')).toHaveLength(2);

        // Change the sort option to a grouping field, and then change a y-axis
        await selectEvent.select(screen.getAllByText('count()')[1], 'project');
        await selectEvent.select(screen.getAllByText('count()')[0], /count_unique/);

        // project should appear in the group by field, as well as the sort field
        expect(screen.getAllByText('project')).toHaveLength(2);
      });

      it('persists sort by a y-axis when grouping changes', async function () {
        renderTestComponent({
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
          query: {
            source: DashboardWidgetSource.DASHBOARDS,
            displayType: DisplayType.LINE,
          },
        });

        userEvent.click(await screen.findByText('Add Overlay'));
        await selectEvent.select(screen.getByText('Select group'), 'project');

        // Change the sort by to count_unique
        await selectEvent.select(screen.getAllByText('count()')[1], /count_unique/);

        // Change the grouping
        await selectEvent.select(screen.getByText('project'), 'environment');

        // count_unique(user) should still be the sorting field
        expect(screen.getByText(/count_unique/)).toBeInTheDocument();
        expect(screen.getByText('user')).toBeInTheDocument();
      });

      it('does not remove the Custom Equation field if a grouping is updated', async function () {
        renderTestComponent({
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
          query: {
            source: DashboardWidgetSource.DASHBOARDS,
            displayType: DisplayType.LINE,
          },
        });

        await selectEvent.select(await screen.findByText('Select group'), 'project');
        await selectEvent.select(screen.getAllByText('count()')[1], 'Custom Equation');
        userEvent.paste(
          screen.getByPlaceholderText('Enter Equation'),
          'count_unique(user) * 2'
        );
        userEvent.keyboard('{enter}');

        userEvent.click(screen.getByText('Add Group'));
        expect(screen.getByPlaceholderText('Enter Equation')).toHaveValue(
          'count_unique(user) * 2'
        );
      });

      it.each`
        directionPrefix | expectedOrderSelection | displayType
        ${'-'}          | ${'High to low'}       | ${DisplayType.TABLE}
        ${''}           | ${'Low to high'}       | ${DisplayType.TABLE}
        ${'-'}          | ${'High to low'}       | ${DisplayType.LINE}
        ${''}           | ${'Low to high'}       | ${DisplayType.LINE}
      `(
        `opens a widget with the '$expectedOrderSelection' sort order when the widget was saved with that direction`,
        async function ({directionPrefix, expectedOrderSelection}) {
          const widget: Widget = {
            id: '1',
            title: 'Test Widget',
            interval: '5m',
            displayType: DisplayType.LINE,
            queries: [
              {
                name: '',
                conditions: '',
                fields: ['count_unique(user)'],
                aggregates: ['count_unique(user)'],
                columns: ['project'],
                orderby: `${directionPrefix}count_unique(user)`,
              },
            ],
          };

          const dashboard = mockDashboard({widgets: [widget]});

          renderTestComponent({
            orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
            dashboard,
            params: {
              widgetIndex: '0',
            },
          });

          await screen.findByText(expectedOrderSelection);
        }
      );

      it('saved widget with aggregate alias as orderby should persist alias when y-axes change', async function () {
        const widget: Widget = {
          id: '1',
          title: 'Test Widget',
          interval: '5m',
          displayType: DisplayType.TABLE,
          queries: [
            {
              name: '',
              conditions: '',
              fields: ['project', 'count_unique(user)'],
              aggregates: ['count_unique(user)'],
              columns: ['project'],
              orderby: 'count_unique(user)',
            },
          ],
        };

        const dashboard = mockDashboard({widgets: [widget]});

        renderTestComponent({
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
          dashboard,
          params: {
            widgetIndex: '0',
          },
        });

        await screen.findByText('Sort by a column');

        // Assert for length 2 since one in the table header and one in sort by
        expect(screen.getAllByText('count_unique(user)')).toHaveLength(2);

        userEvent.click(screen.getByText('Add a Column'));

        // The sort by should still have count_unique(user)
        await waitFor(() =>
          expect(screen.getAllByText('count_unique(user)')).toHaveLength(2)
        );
      });

      it('will reset the sort field when going from line to table when sorting by a value not in fields', async function () {
        renderTestComponent({
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
          query: {
            displayType: DisplayType.LINE,
          },
        });

        await selectEvent.select(await screen.findByText('Select group'), 'project');
        expect(screen.getAllByText('count()')).toHaveLength(2);
        await selectEvent.select(screen.getAllByText('count()')[1], /count_unique/);

        userEvent.click(screen.getByText('Line Chart'));
        userEvent.click(screen.getByText('Table'));

        // 1 for table header, 1 for column selection, and 1 for sorting
        await waitFor(() => {
          expect(screen.getAllByText('count()')).toHaveLength(3);
        });
      });

      it('equations in y-axis appear in sort by field for grouped timeseries', async function () {
        renderTestComponent({
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
          query: {
            displayType: DisplayType.LINE,
          },
        });

        userEvent.click(await screen.findByText('Add an Equation'));
        userEvent.paste(screen.getByPlaceholderText('Equation'), 'count() * 100');
        userEvent.keyboard('{enter}');

        await selectEvent.select(screen.getByText('Select group'), 'project');
        expect(screen.getAllByText('count()')).toHaveLength(2);
        await selectEvent.select(screen.getAllByText('count()')[1], 'count() * 100');
      });

      it('does not reset the orderby when ordered by an equation in table', async function () {
        const widget: Widget = {
          id: '1',
          title: 'Errors over time',
          interval: '5m',
          displayType: DisplayType.TABLE,
          queries: [
            {
              name: '',
              conditions: '',
              fields: [
                'count()',
                'count_unique(id)',
                'equation|count() + count_unique(id)',
              ],
              aggregates: [
                'count()',
                'count_unique(id)',
                'equation|count() + count_unique(id)',
              ],
              columns: [],
              orderby: '-equation[0]',
            },
          ],
        };

        const dashboard = mockDashboard({widgets: [widget]});

        renderTestComponent({
          dashboard,
          params: {
            widgetIndex: '0',
          },
        });

        await screen.findByText('Sort by a column');

        // 1 in the column selector, 1 in the sort by field
        expect(screen.getAllByText('count() + count_unique(id)')).toHaveLength(2);
      });
    });

    describe('Widget creation coming from other verticals', function () {
      it('redirects correctly when creating a new dashboard', async function () {
        const {router} = renderTestComponent({
          query: {source: DashboardWidgetSource.DISCOVERV2},
        });

        userEvent.click(await screen.findByText('Add Widget'));

        await waitFor(() => {
          expect(router.push).toHaveBeenCalledWith(
            expect.objectContaining({
              pathname: '/organizations/org-slug/dashboards/new/',
              query: {
                displayType: 'table',
                interval: '5m',
                title: 'Custom Widget',
                queryNames: [''],
                queryConditions: [''],
                queryFields: ['count()'],
                queryOrderby: '-count()',
                start: null,
                end: null,
                statsPeriod: '24h',
                utc: null,
                project: [],
                environment: [],
              },
            })
          );
        });
      });

      it('redirects correctly when choosing an existing dashboard', async function () {
        const {router} = renderTestComponent({
          query: {source: DashboardWidgetSource.DISCOVERV2},
          dashboard: testDashboard,
        });

        userEvent.click(await screen.findByText('Add Widget'));

        await waitFor(() => {
          expect(router.push).toHaveBeenCalledWith(
            expect.objectContaining({
              pathname: '/organizations/org-slug/dashboard/2/',
              query: {
                displayType: 'table',
                interval: '5m',
                title: 'Custom Widget',
                queryNames: [''],
                queryConditions: [''],
                queryFields: ['count()'],
                queryOrderby: '-count()',
                start: null,
                end: null,
                statsPeriod: '24h',
                utc: null,
                project: [],
                environment: [],
              },
            })
          );
        });
      });

      it('shows the correct orderby when switching from a line chart to table', async function () {
        const defaultWidgetQuery = {
          name: '',
          fields: ['count_unique(user)'],
          columns: [],
          aggregates: ['count_unique(user)'],
          conditions: '',
          orderby: 'count_unique(user)',
        };

        const defaultTableColumns = ['title', 'count_unique(user)'];

        renderTestComponent({
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
          query: {
            source: DashboardWidgetSource.DISCOVERV2,
            defaultWidgetQuery: urlEncode(defaultWidgetQuery),
            displayType: DisplayType.LINE,
            defaultTableColumns,
          },
        });

        userEvent.click(await screen.findByText('Line Chart'));
        userEvent.click(screen.getByText('Table'));

        expect(screen.getByText('count_unique(user)')).toBeInTheDocument();
        await waitFor(() => {
          expect(eventsv2Mock).toHaveBeenLastCalledWith(
            '/organizations/org-slug/eventsv2/',
            expect.objectContaining({
              query: expect.objectContaining({
                field: defaultTableColumns,
                sort: ['count_unique(user)'],
              }),
            })
          );
        });
      });

      it('does not send request with orderby if a timeseries chart without grouping', async function () {
        const defaultWidgetQuery = {
          name: '',
          fields: ['count_unique(user)'],
          columns: [],
          aggregates: ['count_unique(user)'],
          conditions: '',
          orderby: 'count_unique(user)',
        };

        const defaultTableColumns = ['title', 'count_unique(user)'];

        renderTestComponent({
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
          query: {
            source: DashboardWidgetSource.DISCOVERV2,
            defaultWidgetQuery: urlEncode(defaultWidgetQuery),
            displayType: DisplayType.LINE,
            defaultTableColumns,
          },
        });

        await waitFor(() => {
          expect(eventsStatsMock).toHaveBeenLastCalledWith(
            '/organizations/org-slug/events-stats/',
            expect.objectContaining({
              query: expect.objectContaining({
                orderby: '',
              }),
            })
          );
        });
      });
    });

    it('opens top-N widgets as area display', async function () {
      const widget: Widget = {
        id: '1',
        title: 'Errors over time',
        interval: '5m',
        displayType: DisplayType.TOP_N,
        queries: [
          {
            name: '',
            conditions: '',
            fields: ['count()', 'count_unique(id)'],
            aggregates: ['count()', 'count_unique(id)'],
            columns: [],
            orderby: '-count()',
          },
        ],
      };

      const dashboard = mockDashboard({widgets: [widget]});

      renderTestComponent({
        orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
        dashboard,
        params: {
          widgetIndex: '0',
        },
      });

      expect(await screen.findByText('Area Chart')).toBeInTheDocument();
    });

    it('Update table header values (field alias)', async function () {
      const handleSave = jest.fn();

      renderTestComponent({
        onSave: handleSave,
        orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
      });

      await screen.findByText('Table');

      userEvent.paste(screen.getByPlaceholderText('Alias'), 'First Alias');

      userEvent.click(screen.getByLabelText('Add a Column'));

      userEvent.paste(screen.getAllByPlaceholderText('Alias')[1], 'Second Alias');

      userEvent.click(screen.getByText('Add Widget'));

      await waitFor(() => {
        expect(handleSave).toHaveBeenCalledWith([
          expect.objectContaining({
            queries: [
              expect.objectContaining({fieldAliases: ['First Alias', 'Second Alias']}),
            ],
          }),
        ]);
      });
    });

    it('does not wipe equation aliases when a column alias is updated', async function () {
      renderTestComponent({
        orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
      });

      await screen.findByText('Table');

      userEvent.click(screen.getByText('Add an Equation'));
      userEvent.paste(screen.getAllByPlaceholderText('Alias')[1], 'This should persist');
      userEvent.type(screen.getAllByPlaceholderText('Alias')[0], 'A');

      expect(await screen.findByText('This should persist')).toBeInTheDocument();
    });

    it('does not wipe equation aliases when a column selection is made', async function () {
      renderTestComponent({
        orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
      });

      await screen.findByText('Table');

      userEvent.click(screen.getByText('Add an Equation'));
      userEvent.paste(screen.getAllByPlaceholderText('Alias')[1], 'This should persist');

      // 1 for the table, 1 for the the column selector, 1 for the sort
      await waitFor(() => expect(screen.getAllByText('count()')).toHaveLength(3));
      await selectEvent.select(screen.getAllByText('count()')[1], /count_unique/);

      expect(screen.getByText('This should persist')).toBeInTheDocument();
    });

    it('copies over the orderby from the previous query if adding another', async function () {
      renderTestComponent({
        orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
      });

      userEvent.click(await screen.findByText('Table'));
      userEvent.click(screen.getByText('Line Chart'));
      await selectEvent.select(screen.getByText('Select group'), 'project');
      await selectEvent.select(screen.getAllByText('count()')[1], 'count_unique(…)');

      MockApiClient.clearMockResponses();
      eventsStatsMock = MockApiClient.addMockResponse({
        url: '/organizations/org-slug/events-stats/',
        body: [],
      });

      userEvent.click(screen.getByText('Add Query'));

      // Assert on two calls, one for each query
      const expectedArgs = expect.objectContaining({
        query: expect.objectContaining({
          orderby: '-count_unique(user)',
        }),
      });
      expect(eventsStatsMock).toHaveBeenNthCalledWith(
        1,
        '/organizations/org-slug/events-stats/',
        expectedArgs
      );

      expect(eventsStatsMock).toHaveBeenNthCalledWith(
        2,
        '/organizations/org-slug/events-stats/',
        expectedArgs
      );
    });

    describe('Issue Widgets', function () {
      it('sets widgetType to issues', async function () {
        const handleSave = jest.fn();

        renderTestComponent({onSave: handleSave});

        userEvent.click(
          await screen.findByText('Issues (States, Assignment, Time, etc.)')
        );
        userEvent.click(screen.getByLabelText('Add Widget'));

        await waitFor(() => {
          expect(handleSave).toHaveBeenCalledWith([
            expect.objectContaining({
              title: 'Custom Widget',
              displayType: DisplayType.TABLE,
              interval: '5m',
              widgetType: WidgetType.ISSUE,
              queries: [
                {
                  conditions: '',
                  fields: ['issue', 'assignee', 'title'],
                  columns: ['issue', 'assignee', 'title'],
                  aggregates: [],
                  fieldAliases: [],
                  name: '',
                  orderby: 'date',
                },
              ],
            }),
          ]);
        });

        expect(handleSave).toHaveBeenCalledTimes(1);
      });

      it('render issues dataset disabled when the display type is not set to table', async function () {
        renderTestComponent({
          query: {
            source: DashboardWidgetSource.DISCOVERV2,
          },
        });

        userEvent.click(await screen.findByText('Table'));
        userEvent.click(screen.getByText('Line Chart'));
        expect(
          screen.getByRole('radio', {
            name: 'Select Errors and Transactions',
          })
        ).toBeEnabled();
        expect(
          screen.getByRole('radio', {
            name: 'Select Issues (States, Assignment, Time, etc.)',
          })
        ).toBeDisabled();
      });

      it('disables moving and deleting issue column', async function () {
        renderTestComponent();

        userEvent.click(
          await screen.findByText('Issues (States, Assignment, Time, etc.)')
        );
        expect(screen.getByText('issue')).toBeInTheDocument();
        expect(screen.getByText('assignee')).toBeInTheDocument();
        expect(screen.getByText('title')).toBeInTheDocument();
        expect(screen.getAllByLabelText('Remove column')).toHaveLength(2);
        expect(screen.getAllByLabelText('Drag to reorder')).toHaveLength(3);

        userEvent.click(screen.getAllByLabelText('Remove column')[1]);
        userEvent.click(screen.getAllByLabelText('Remove column')[0]);

        expect(screen.getByText('issue')).toBeInTheDocument();
        expect(screen.queryByText('assignee')).not.toBeInTheDocument();
        expect(screen.queryByText('title')).not.toBeInTheDocument();
        expect(screen.queryByLabelText('Remove column')).not.toBeInTheDocument();
        expect(screen.queryByLabelText('Drag to reorder')).not.toBeInTheDocument();
      });

      it('issue query does not work on default search bar', async function () {
        renderTestComponent();

        const input = (await screen.findByPlaceholderText(
          'Search for events, users, tags, and more'
        )) as HTMLTextAreaElement;
        userEvent.paste(input, 'bookmarks', {
          clipboardData: {getData: () => ''},
        } as unknown as React.ClipboardEvent<HTMLTextAreaElement>);
        input.setSelectionRange(9, 9);

        expect(await screen.findByText('No items found')).toBeInTheDocument();
      });

      it('renders with an issues search bar when selected in dataset selection', async function () {
        renderTestComponent();

        userEvent.click(
          await screen.findByText('Issues (States, Assignment, Time, etc.)')
        );

        const input = (await screen.findByPlaceholderText(
          'Search for issues, status, assigned, and more'
        )) as HTMLTextAreaElement;
        userEvent.paste(input, 'is:', {
          clipboardData: {getData: () => ''},
        } as unknown as React.ClipboardEvent<HTMLTextAreaElement>);
        input.setSelectionRange(3, 3);

        expect(await screen.findByText('resolved')).toBeInTheDocument();
      });

      it('Update table header values (field alias)', async function () {
        const handleSave = jest.fn();

        renderTestComponent({
          onSave: handleSave,
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
        });

        await screen.findByText('Table');

        userEvent.click(screen.getByText('Issues (States, Assignment, Time, etc.)'));

        userEvent.paste(screen.getAllByPlaceholderText('Alias')[0], 'First Alias');

        userEvent.click(screen.getByText('Add Widget'));

        await waitFor(() => {
          expect(handleSave).toHaveBeenCalledWith([
            expect.objectContaining({
              queries: [
                expect.objectContaining({
                  fieldAliases: ['First Alias', '', ''],
                }),
              ],
            }),
          ]);
        });
      });
    });

    describe('Release Widgets', function () {
      const releaseHealthFeatureFlags = [
        ...defaultOrgFeatures,
        'new-widget-builder-experience-design',
        'dashboards-releases',
      ];

      it('does not show the Release Health dataset if there is no dashboards-releases flag', async function () {
        renderTestComponent({
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
        });

        expect(await screen.findByText('Errors and Transactions')).toBeInTheDocument();
        expect(
          screen.queryByText('Releases (sessions, crash rates)')
        ).not.toBeInTheDocument();
      });

      it('shows the Release Health dataset if there is the dashboards-releases flag', async function () {
        renderTestComponent({
          orgFeatures: releaseHealthFeatureFlags,
        });

        expect(await screen.findByText('Errors and Transactions')).toBeInTheDocument();
        expect(screen.getByText('Releases (sessions, crash rates)')).toBeInTheDocument();
      });

      it('maintains the selected dataset when display type is changed', async function () {
        renderTestComponent({
          orgFeatures: releaseHealthFeatureFlags,
        });

        expect(
          await screen.findByText('Releases (sessions, crash rates)')
        ).toBeInTheDocument();

        expect(screen.getByLabelText(/releases/i)).not.toBeChecked();
        userEvent.click(screen.getByLabelText(/releases/i));
        await waitFor(() => expect(screen.getByLabelText(/releases/i)).toBeChecked());

        userEvent.click(screen.getByText('Table'));
        userEvent.click(screen.getByText('Line Chart'));
        await waitFor(() => expect(screen.getByLabelText(/releases/i)).toBeChecked());
      });

      it('displays releases tags', async function () {
        renderTestComponent({
          orgFeatures: releaseHealthFeatureFlags,
        });

        expect(
          await screen.findByText('Releases (sessions, crash rates)')
        ).toBeInTheDocument();

        userEvent.click(screen.getByLabelText(/releases/i));

        expect(screen.getByText('crash_free_rate(…)')).toBeInTheDocument();
        expect(screen.getByText('session')).toBeInTheDocument();

        userEvent.click(screen.getByText('crash_free_rate(…)'));
        expect(screen.getByText('count_unique(…)')).toBeInTheDocument();

        expect(screen.getByText('release')).toBeInTheDocument();
        expect(screen.getByText('environment')).toBeInTheDocument();
        expect(screen.getByText('session.status')).toBeInTheDocument();

        userEvent.click(screen.getByText('count_unique(…)'));
        expect(screen.getByText('user')).toBeInTheDocument();
      });

      it('does not display tags as params', async function () {
        renderTestComponent({
          orgFeatures: releaseHealthFeatureFlags,
        });

        expect(
          await screen.findByText('Releases (sessions, crash rates)')
        ).toBeInTheDocument();

        userEvent.click(screen.getByLabelText(/releases/i));

        expect(screen.getByText('crash_free_rate(…)')).toBeInTheDocument();
        await selectEvent.select(
          screen.getByText('crash_free_rate(…)'),
          'count_unique(…)'
        );

        userEvent.click(screen.getByText('user'));
        expect(screen.queryByText('release')).not.toBeInTheDocument();
        expect(screen.queryByText('environment')).not.toBeInTheDocument();
        expect(screen.queryByText('session.status')).not.toBeInTheDocument();
      });

      it('does not allow sort by when session.status is selected', async function () {
        renderTestComponent({
          orgFeatures: releaseHealthFeatureFlags,
        });

        expect(
          await screen.findByText('Releases (sessions, crash rates)')
        ).toBeInTheDocument();

        userEvent.click(screen.getByLabelText(/releases/i));

        expect(screen.getByText('High to low')).toBeEnabled();
        expect(screen.getByText('crash_free_rate(session)')).toBeInTheDocument();

        userEvent.click(screen.getByLabelText('Add a Column'));
        await selectEvent.select(screen.getByText('(Required)'), 'session.status');

        expect(screen.getByRole('textbox', {name: 'Sort direction'})).toBeDisabled();
        expect(screen.getByRole('textbox', {name: 'Sort by'})).toBeDisabled();
      });

      it('does not allow sort on tags except release', async function () {
        jest.useFakeTimers().setSystemTime(new Date('2022-08-02'));
        renderTestComponent({
          orgFeatures: releaseHealthFeatureFlags,
        });

        expect(
          await screen.findByText('Releases (sessions, crash rates)')
        ).toBeInTheDocument();

        userEvent.click(screen.getByLabelText(/releases/i));

        expect(screen.getByText('High to low')).toBeEnabled();
        expect(screen.getByText('crash_free_rate(session)')).toBeInTheDocument();

        userEvent.click(screen.getByLabelText('Add a Column'));
        await selectEvent.select(screen.getByText('(Required)'), 'release');

        userEvent.click(screen.getByLabelText('Add a Column'));
        await selectEvent.select(screen.getByText('(Required)'), 'environment');

        expect(await screen.findByText('Sort by a column')).toBeInTheDocument();

        // Selector "sortDirection"
        expect(screen.getByText('High to low')).toBeInTheDocument();

        // Selector "sortBy"
        userEvent.click(screen.getAllByText('crash_free_rate(session)')[1]);

        // release exists in sort by selector
        expect(screen.getAllByText('release')).toHaveLength(3);
        // environment does not exist in sort by selector
        expect(screen.getAllByText('environment')).toHaveLength(2);
      });

      it('makes the appropriate sessions call', async function () {
        jest.useFakeTimers().setSystemTime(new Date('2022-08-02'));
        renderTestComponent({
          orgFeatures: releaseHealthFeatureFlags,
        });

        expect(
          await screen.findByText('Releases (sessions, crash rates)')
        ).toBeInTheDocument();

        userEvent.click(screen.getByLabelText(/releases/i));

        userEvent.click(screen.getByText('Table'));
        userEvent.click(screen.getByText('Line Chart'));

        await waitFor(() =>
          expect(metricsDataMock).toHaveBeenLastCalledWith(
            `/organizations/org-slug/metrics/data/`,
            expect.objectContaining({
              query: expect.objectContaining({
                environment: [],
                field: [`session.crash_free_rate`],
                groupBy: [],
                interval: '5m',
                project: [],
                statsPeriod: '24h',
              }),
            })
          )
        );
      });

      it('calls the session endpoint with the right limit', async function () {
        jest.useFakeTimers().setSystemTime(new Date('2022-08-02'));
        renderTestComponent({
          orgFeatures: releaseHealthFeatureFlags,
        });

        expect(
          await screen.findByText('Releases (sessions, crash rates)')
        ).toBeInTheDocument();

        userEvent.click(screen.getByLabelText(/releases/i));

        userEvent.click(screen.getByText('Table'));
        userEvent.click(screen.getByText('Line Chart'));

        await selectEvent.select(await screen.findByText('Select group'), 'project');

        expect(screen.getByText('Limit to 5 results')).toBeInTheDocument();

        await waitFor(() =>
          expect(metricsDataMock).toHaveBeenLastCalledWith(
            `/organizations/org-slug/metrics/data/`,
            expect.objectContaining({
              query: expect.objectContaining({
                environment: [],
                field: ['session.crash_free_rate'],
                groupBy: ['project_id'],
                interval: '5m',
                orderBy: '-session.crash_free_rate',
                per_page: 5,
                project: [],
                statsPeriod: '24h',
              }),
            })
          )
        );
      });

      it('calls sessions api when session.status is selected as a groupby', async function () {
        jest.useFakeTimers().setSystemTime(new Date('2022-08-02'));
        renderTestComponent({
          orgFeatures: releaseHealthFeatureFlags,
        });

        expect(
          await screen.findByText('Releases (sessions, crash rates)')
        ).toBeInTheDocument();

        userEvent.click(screen.getByLabelText(/releases/i));

        userEvent.click(screen.getByText('Table'));
        userEvent.click(screen.getByText('Line Chart'));

        await selectEvent.select(
          await screen.findByText('Select group'),
          'session.status'
        );

        expect(screen.getByText('Limit to 5 results')).toBeInTheDocument();

        await waitFor(() =>
          expect(sessionsDataMock).toHaveBeenLastCalledWith(
            `/organizations/org-slug/sessions/`,
            expect.objectContaining({
              query: expect.objectContaining({
                environment: [],
                field: ['crash_free_rate(session)'],
                groupBy: ['session.status'],
                interval: '5m',
                project: [],
                statsPeriod: '24h',
              }),
            })
          )
        );
      });

      it('displays the correct options for area chart', async function () {
        renderTestComponent({
          orgFeatures: releaseHealthFeatureFlags,
        });

        expect(
          await screen.findByText('Releases (sessions, crash rates)')
        ).toBeInTheDocument();

        // change dataset to releases
        userEvent.click(screen.getByLabelText(/releases/i));

        userEvent.click(screen.getByText('Table'));
        userEvent.click(screen.getByText('Line Chart'));

        expect(screen.getByText('crash_free_rate(…)')).toBeInTheDocument();
        expect(screen.getByText(`session`)).toBeInTheDocument();

        userEvent.click(screen.getByText('crash_free_rate(…)'));
        expect(screen.getByText('count_unique(…)')).toBeInTheDocument();

        userEvent.click(screen.getByText('count_unique(…)'));
        expect(screen.getByText('user')).toBeInTheDocument();
      });

      it('sets widgetType to release', async function () {
        jest.useFakeTimers().setSystemTime(new Date('2022-08-02'));
        renderTestComponent({
          orgFeatures: releaseHealthFeatureFlags,
        });

        userEvent.click(await screen.findByText('Releases (sessions, crash rates)'));

        expect(metricsDataMock).toHaveBeenCalled();
        expect(screen.getByLabelText(/Releases/i)).toBeChecked();
      });

      it('does not display "add an equation" button', async function () {
        const widget: Widget = {
          title: 'Release Widget',
          displayType: DisplayType.TABLE,
          widgetType: WidgetType.RELEASE,
          queries: [
            {
              name: 'errors',
              conditions: 'event.type:error',
              fields: ['sdk.name', 'count()'],
              columns: ['sdk.name'],
              aggregates: ['count()'],
              orderby: '-sdk.name',
            },
          ],
          interval: '1d',
          id: '1',
        };

        const dashboard = mockDashboard({widgets: [widget]});

        renderTestComponent({
          orgFeatures: releaseHealthFeatureFlags,
          dashboard,
          params: {
            widgetIndex: '0',
          },
        });

        // Select line chart display
        userEvent.click(await screen.findByText('Table'));
        userEvent.click(screen.getByText('Line Chart'));

        await waitFor(() =>
          expect(screen.queryByLabelText('Add an Equation')).not.toBeInTheDocument()
        );
      });

      it('render release dataset disabled when the display type is world map', async function () {
        renderTestComponent({
          query: {
            source: DashboardWidgetSource.DISCOVERV2,
          },
          orgFeatures: releaseHealthFeatureFlags,
        });

        userEvent.click(await screen.findByText('Table'));
        userEvent.click(screen.getByText('World Map'));

        await waitFor(() => expect(screen.getByLabelText(/Releases/)).toBeDisabled());

        expect(
          screen.getByRole('radio', {
            name: 'Select Errors and Transactions',
          })
        ).toBeEnabled();
        expect(
          screen.getByRole('radio', {
            name: 'Select Issues (States, Assignment, Time, etc.)',
          })
        ).toBeDisabled();
      });

      it('renders with a release search bar', async function () {
        renderTestComponent({
          orgFeatures: releaseHealthFeatureFlags,
        });

        userEvent.type(
          await screen.findByPlaceholderText('Search for events, users, tags, and more'),
          'session.status:'
        );

        await waitFor(() => {
          expect(screen.getByText("isn't supported here.")).toBeInTheDocument();
        });

        userEvent.click(screen.getByText('Releases (sessions, crash rates)'));
        userEvent.click(
          screen.getByPlaceholderText(
            'Search for release version, session status, and more'
          )
        );
        expect(await screen.findByText('environment')).toBeInTheDocument();
        expect(screen.getByText('project')).toBeInTheDocument();
        expect(screen.getByText('release')).toBeInTheDocument();
      });

      it('adds a function when the only column chosen in a table is a tag', async function () {
        jest.useFakeTimers().setSystemTime(new Date('2022-08-02'));
        renderTestComponent({
          orgFeatures: releaseHealthFeatureFlags,
        });

        userEvent.click(await screen.findByText('Releases (sessions, crash rates)'));

        await selectEvent.select(screen.getByText('crash_free_rate(…)'), 'environment');

        // 1 in the table header, 1 in the column selector, and 1 in the sort by
        expect(screen.getAllByText(/crash_free_rate/)).toHaveLength(3);
        expect(screen.getAllByText('environment')).toHaveLength(2);
      });
    });

    describe('Widget Library', function () {
      it('renders', async function () {
        renderTestComponent();
        expect(await screen.findByText('Widget Library')).toBeInTheDocument();
      });

      it('only opens the modal when the query data is changed', async function () {
        const mockModal = jest.spyOn(modals, 'openWidgetBuilderOverwriteModal');
        renderTestComponent();
        await screen.findByText('Widget Library');

        userEvent.click(screen.getByText('Duration Distribution'));

        // Widget Library, Builder title, and Chart title
        expect(await screen.findAllByText('Duration Distribution')).toHaveLength(3);

        // Confirm modal doesn't open because no changes were made
        expect(mockModal).not.toHaveBeenCalled();

        userEvent.click(screen.getAllByLabelText('Remove this Y-Axis')[0]);
        userEvent.click(screen.getByText('High Throughput Transactions'));

        // Should not have overwritten widget data, and confirm modal should open
        expect(await screen.findAllByText('Duration Distribution')).toHaveLength(3);
        expect(mockModal).toHaveBeenCalled();
      });
    });

    describe('group by field', function () {
      it('does not contain functions as options', async function () {
        renderTestComponent({
          query: {displayType: 'line'},
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
        });

        await screen.findByText('Group your results');

        expect(screen.getByText('Select group')).toBeInTheDocument();

        userEvent.click(screen.getByText('Select group'));

        // Only one f(x) field set in the y-axis selector
        expect(screen.getByText('f(x)')).toBeInTheDocument();
      });

      it('adds more fields when Add Group is clicked', async function () {
        renderTestComponent({
          query: {displayType: 'line'},
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
        });

        await screen.findByText('Group your results');
        userEvent.click(screen.getByText('Add Group'));
        expect(await screen.findAllByText('Select group')).toHaveLength(2);
      });

      it("doesn't reset group by when changing y-axis", async function () {
        renderTestComponent({
          query: {displayType: 'line'},
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
        });

        await selectEvent.select(await screen.findByText('Select group'), 'project');
        userEvent.click(screen.getAllByText('count()')[0], undefined, {skipHover: true});
        userEvent.click(screen.getByText(/count_unique/), undefined, {skipHover: true});

        expect(await screen.findByText('project')).toBeInTheDocument();
      });

      it("doesn't erase the selection when switching to another time series", async function () {
        renderTestComponent({
          query: {displayType: 'line'},
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
        });

        await selectEvent.select(await screen.findByText('Select group'), 'project');

        userEvent.click(screen.getByText('Line Chart'));
        userEvent.click(screen.getByText('Area Chart'));

        expect(await screen.findByText('project')).toBeInTheDocument();
      });

      it('sends a top N request when a grouping is selected', async function () {
        renderTestComponent({
          query: {displayType: 'line'},
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
        });

        userEvent.click(await screen.findByText('Group your results'));
        userEvent.type(screen.getByText('Select group'), 'project{enter}');

        await waitFor(() =>
          expect(eventsStatsMock).toHaveBeenNthCalledWith(
            2,
            '/organizations/org-slug/events-stats/',
            expect.objectContaining({
              query: expect.objectContaining({
                query: '',
                yAxis: ['count()'],
                field: ['project', 'count()'],
                topEvents: TOP_N,
                orderby: '-count()',
              }),
            })
          )
        );
      });

      it('allows deleting groups until there is one left', async function () {
        renderTestComponent({
          query: {displayType: 'line'},
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
        });

        await screen.findByText('Group your results');
        userEvent.click(screen.getByText('Add Group'));
        expect(screen.getAllByLabelText('Remove group')).toHaveLength(2);

        userEvent.click(screen.getAllByLabelText('Remove group')[1]);
        await waitFor(() =>
          expect(screen.queryByLabelText('Remove group')).not.toBeInTheDocument()
        );
      });

      it("display 'remove' and 'drag to reorder' buttons", async function () {
        renderTestComponent({
          query: {displayType: 'line'},
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
        });

        await screen.findByText('Select group');

        expect(screen.queryByLabelText('Remove group')).not.toBeInTheDocument();

        await selectEvent.select(screen.getByText('Select group'), 'project');

        expect(screen.getByLabelText('Remove group')).toBeInTheDocument();
        expect(screen.queryByLabelText('Drag to reorder')).not.toBeInTheDocument();

        userEvent.click(screen.getByText('Add Group'));

        expect(screen.getAllByLabelText('Remove group')).toHaveLength(2);
        expect(screen.getAllByLabelText('Drag to reorder')).toHaveLength(2);
      });

      it.todo(
        'Since simulate drag and drop with RTL is not recommended because of browser layout, remember to create acceptance test for this'
      );
    });

    describe('limit field', function () {
      it('renders if groupBy value is present', async function () {
        const handleSave = jest.fn();

        renderTestComponent({
          query: {displayType: 'line'},
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
          onSave: handleSave,
        });

        await selectEvent.select(await screen.findByText('Select group'), 'project');

        expect(screen.getByText('Limit to 5 results')).toBeInTheDocument();

        userEvent.click(screen.getByText('Add Widget'));

        await waitFor(() =>
          expect(handleSave).toHaveBeenCalledWith([
            expect.objectContaining({
              limit: 5,
            }),
          ])
        );
      });

      it('update value', async function () {
        renderTestComponent({
          query: {displayType: 'line'},
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
        });

        await selectEvent.select(await screen.findByText('Select group'), 'project');

        userEvent.click(screen.getByText('Limit to 5 results'));
        userEvent.click(screen.getByText('Limit to 2 results'));

        await waitFor(() =>
          expect(eventsStatsMock).toHaveBeenCalledWith(
            '/organizations/org-slug/events-stats/',
            expect.objectContaining({
              query: expect.objectContaining({
                query: '',
                yAxis: ['count()'],
                field: ['project', 'count()'],
                topEvents: 2,
                orderby: '-count()',
              }),
            })
          )
        );
      });

      it('gets removed if no groupBy value', async function () {
        renderTestComponent({
          query: {displayType: 'line'},
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
        });

        await selectEvent.select(await screen.findByText('Select group'), 'project');

        expect(screen.getByText('Limit to 5 results')).toBeInTheDocument();

        userEvent.click(screen.getByLabelText('Remove group'));

        await waitFor(() =>
          expect(screen.queryByText('Limit to 5 results')).not.toBeInTheDocument()
        );
      });

      it('applies a limit when switching from a table to timeseries chart with grouping', async function () {
        const widget: Widget = {
          displayType: DisplayType.TABLE,
          interval: '1d',
          queries: [
            {
              name: 'Test Widget',
              fields: ['count()', 'count_unique(user)', 'epm()', 'project'],
              columns: ['project'],
              aggregates: ['count()', 'count_unique(user)', 'epm()'],
              conditions: '',
              orderby: '',
            },
          ],
          title: 'Transactions',
          id: '1',
        };

        const dashboard = mockDashboard({widgets: [widget]});

        renderTestComponent({
          dashboard,
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
          params: {
            widgetIndex: '0',
          },
        });

        userEvent.click(await screen.findByText('Table'));
        userEvent.click(screen.getByText('Line Chart'));

        expect(screen.getByText('Limit to 3 results')).toBeInTheDocument();
        expect(eventsStatsMock).toHaveBeenCalledWith(
          '/organizations/org-slug/events-stats/',
          expect.objectContaining({
            query: expect.objectContaining({
              topEvents: 3,
            }),
          })
        );
      });

      it('persists the limit when switching between timeseries charts', async function () {
        const widget: Widget = {
          displayType: DisplayType.AREA,
          interval: '1d',
          queries: [
            {
              name: 'Test Widget',
              fields: ['count()', 'count_unique(user)', 'epm()', 'project'],
              columns: ['project'],
              aggregates: ['count()', 'count_unique(user)', 'epm()'],
              conditions: '',
              orderby: '',
            },
          ],
          title: 'Transactions',
          id: '1',
          limit: 1,
        };

        const dashboard = mockDashboard({widgets: [widget]});

        renderTestComponent({
          dashboard,
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
          params: {
            widgetIndex: '0',
          },
        });

        userEvent.click(await screen.findByText('Area Chart'));
        userEvent.click(screen.getByText('Line Chart'));

        expect(screen.getByText('Limit to 1 result')).toBeInTheDocument();
        expect(eventsStatsMock).toHaveBeenCalledWith(
          '/organizations/org-slug/events-stats/',
          expect.objectContaining({
            query: expect.objectContaining({
              topEvents: 1,
            }),
          })
        );
      });

      it('unsets the limit when going from timeseries to table', async function () {
        const widget: Widget = {
          displayType: DisplayType.AREA,
          interval: '1d',
          queries: [
            {
              name: 'Test Widget',
              fields: ['count()', 'count_unique(user)', 'epm()', 'project'],
              columns: ['project'],
              aggregates: ['count()', 'count_unique(user)', 'epm()'],
              conditions: '',
              orderby: '',
            },
          ],
          title: 'Transactions',
          id: '1',
          limit: 1,
        };

        const dashboard = mockDashboard({widgets: [widget]});

        renderTestComponent({
          dashboard,
          orgFeatures: [...defaultOrgFeatures, 'new-widget-builder-experience-design'],
          params: {
            widgetIndex: '0',
          },
        });

        userEvent.click(await screen.findByText('Area Chart'));
        userEvent.click(screen.getByText('Table'));

        expect(screen.queryByText('Limit to 1 result')).not.toBeInTheDocument();
        expect(eventsv2Mock).toHaveBeenCalledWith(
          '/organizations/org-slug/eventsv2/',
          expect.objectContaining({
            query: expect.objectContaining({
              topEvents: undefined,
            }),
          })
        );
      });
    });
  });

  describe('with events', function () {
    it('should properly query for table fields', async function () {
      const defaultWidgetQuery = {
        name: '',
        fields: ['title', 'count()'],
        columns: ['title'],
        aggregates: ['count()'],
        conditions: '',
        orderby: '',
      };

      const defaultTableColumns = ['title', 'count()', 'count_unique(user)', 'epm()'];

      renderTestComponent({
        query: {
          source: DashboardWidgetSource.DISCOVERV2,
          defaultWidgetQuery: urlEncode(defaultWidgetQuery),
          displayType: DisplayType.LINE,
          defaultTableColumns,
        },
        orgFeatures: [...defaultOrgFeatures, 'discover-frontend-use-events-endpoint'],
      });

      expect(await screen.findByText('Line Chart')).toBeInTheDocument();
      userEvent.click(screen.getByText('Line Chart'));
      userEvent.click(screen.getByText('Table'));

      await waitFor(() => {
        expect(eventsMock).toHaveBeenLastCalledWith(
          '/organizations/org-slug/events/',
          expect.objectContaining({
            query: expect.objectContaining({
              field: defaultTableColumns,
            }),
          })
        );
      });
    });

    describe('Sort by selectors', function () {
      it('ordering by column uses field form when selecting orderby', async function () {
        const widget: Widget = {
          id: '1',
          title: 'Test Widget',
          interval: '5m',
          displayType: DisplayType.TABLE,
          queries: [
            {
              name: 'errors',
              conditions: 'event.type:error',
              fields: ['count()'],
              aggregates: ['count()'],
              columns: ['project'],
              orderby: '-project',
            },
          ],
        };

        const dashboard = mockDashboard({widgets: [widget]});

        renderTestComponent({
          orgFeatures: [
            ...defaultOrgFeatures,
            'new-widget-builder-experience-design',
            'discover-frontend-use-events-endpoint',
          ],
          dashboard,
          params: {
            widgetIndex: '0',
          },
        });

        await waitFor(async () => {
          expect(await screen.findAllByText('project')).toHaveLength(3);
        });

        await selectEvent.select(screen.getAllByText('project')[2], 'count()');

        await waitFor(() => {
          expect(eventsMock).toHaveBeenCalledWith(
            '/organizations/org-slug/events/',
            expect.objectContaining({
              query: expect.objectContaining({
                sort: ['-count()'],
              }),
            })
          );
        });
      });

      it('hides Custom Equation input and resets orderby when switching to table', async function () {
        renderTestComponent({
          orgFeatures: [
            ...defaultOrgFeatures,
            'new-widget-builder-experience-design',
            'discover-frontend-use-events-endpoint',
          ],
          query: {
            source: DashboardWidgetSource.DASHBOARDS,
            displayType: DisplayType.LINE,
          },
        });

        await selectEvent.select(await screen.findByText('Select group'), 'project');
        expect(screen.getAllByText('count()')).toHaveLength(2);
        await selectEvent.select(screen.getAllByText('count()')[1], 'Custom Equation');
        userEvent.paste(
          screen.getByPlaceholderText('Enter Equation'),
          'count_unique(user) * 2'
        );
        userEvent.keyboard('{enter}');

        // Switch the display type to Table
        userEvent.click(screen.getByText('Line Chart'));
        userEvent.click(screen.getByText('Table'));

        expect(screen.getAllByText('count()')).toHaveLength(2);
        expect(screen.queryByPlaceholderText('Enter Equation')).not.toBeInTheDocument();

        await waitFor(() => {
          expect(eventsMock).toHaveBeenCalledWith(
            '/organizations/org-slug/events/',
            expect.objectContaining({
              query: expect.objectContaining({
                sort: ['-count()'],
              }),
            })
          );
        });
      });
    });

    describe('Widget creation coming from other verticals', function () {
      it('shows the correct orderby when switching from a line chart to table', async function () {
        const defaultWidgetQuery = {
          name: '',
          fields: ['count_unique(user)'],
          columns: [],
          aggregates: ['count_unique(user)'],
          conditions: '',
          orderby: 'count_unique(user)',
        };

        const defaultTableColumns = ['title', 'count_unique(user)'];

        renderTestComponent({
          orgFeatures: [
            ...defaultOrgFeatures,
            'new-widget-builder-experience-design',
            'discover-frontend-use-events-endpoint',
          ],
          query: {
            source: DashboardWidgetSource.DISCOVERV2,
            defaultWidgetQuery: urlEncode(defaultWidgetQuery),
            displayType: DisplayType.LINE,
            defaultTableColumns,
          },
        });

        userEvent.click(await screen.findByText('Line Chart'));
        userEvent.click(screen.getByText('Table'));

        expect(screen.getByText('count_unique(user)')).toBeInTheDocument();
        await waitFor(() => {
          expect(eventsMock).toHaveBeenLastCalledWith(
            '/organizations/org-slug/events/',
            expect.objectContaining({
              query: expect.objectContaining({
                field: defaultTableColumns,
                sort: ['count_unique(user)'],
              }),
            })
          );
        });
      });
    });

    describe('Custom Performance Metrics', function () {
      it('can choose a custom measurement', async function () {
        measurementsMetaMock = MockApiClient.addMockResponse({
          url: '/organizations/org-slug/measurements-meta/',
          method: 'GET',
          body: {'measurements.custom.measurement': {functions: ['p99']}},
        });

        eventsMock = MockApiClient.addMockResponse({
          url: '/organizations/org-slug/events/',
          method: 'GET',
          statusCode: 200,
          body: {
            meta: {
              fields: {'p99(measurements.total.db.calls)': 'duration'},
              isMetricsData: true,
            },
            data: [{'p99(measurements.total.db.calls)': 10}],
          },
        });

        const {router} = renderTestComponent({
          query: {source: DashboardWidgetSource.DISCOVERV2},
          dashboard: testDashboard,
          orgFeatures: [...defaultOrgFeatures, 'discover-frontend-use-events-endpoint'],
        });

        expect(await screen.findAllByText('Custom Widget')).toHaveLength(2);

        // 1 in the table header, 1 in the column selector, 1 in the sort field
        const countFields = screen.getAllByText('count()');
        expect(countFields).toHaveLength(3);

        await selectEvent.select(countFields[1], ['p99(…)']);
        await selectEvent.select(screen.getByText('transaction.duration'), [
          'measurements.custom.measurement',
        ]);

        userEvent.click(screen.getByText('Add Widget'));

        await waitFor(() => {
          expect(router.push).toHaveBeenCalledWith(
            expect.objectContaining({
              pathname: '/organizations/org-slug/dashboard/2/',
              query: {
                displayType: 'table',
                interval: '5m',
                title: 'Custom Widget',
                queryNames: [''],
                queryConditions: [''],
                queryFields: ['p99(measurements.custom.measurement)'],
                queryOrderby: '-p99(measurements.custom.measurement)',
                start: null,
                end: null,
                statsPeriod: '24h',
                utc: null,
                project: [],
                environment: [],
              },
            })
          );
        });
      });

      it('raises an alert banner and disables saving widget if widget result is not metrics data and widget is using custom measurements', async function () {
        eventsMock = MockApiClient.addMockResponse({
          url: '/organizations/org-slug/events/',
          method: 'GET',
          statusCode: 200,
          body: {
            meta: {
              fields: {'p99(measurements.custom.measurement)': 'duration'},
              isMetricsData: false,
            },
            data: [{'p99(measurements.custom.measurement)': 10}],
          },
        });

        const defaultWidgetQuery = {
          name: '',
          fields: ['p99(measurements.custom.measurement)'],
          columns: [],
          aggregates: ['p99(measurements.custom.measurement)'],
          conditions: 'user:test.user@sentry.io',
          orderby: '',
        };

        const defaultTableColumns = ['p99(measurements.custom.measurement)'];

        renderTestComponent({
          query: {
            source: DashboardWidgetSource.DISCOVERV2,
            defaultWidgetQuery: urlEncode(defaultWidgetQuery),
            displayType: DisplayType.TABLE,
            defaultTableColumns,
          },
          orgFeatures: [
            ...defaultOrgFeatures,
            'discover-frontend-use-events-endpoint',
            'dashboards-mep',
          ],
        });

        await waitFor(() => {
          expect(measurementsMetaMock).toHaveBeenCalled();
        });

        await waitFor(() => {
          expect(eventsMock).toHaveBeenCalled();
        });

        screen.getByText('You have inputs that are incompatible with');
        expect(screen.getByText('Add Widget').closest('button')).toBeDisabled();
      });

      it('raises an alert banner if widget result is not metrics data', async function () {
        eventsMock = MockApiClient.addMockResponse({
          url: '/organizations/org-slug/events/',
          method: 'GET',
          statusCode: 200,
          body: {
            meta: {
              fields: {'p99(measurements.lcp)': 'duration'},
              isMetricsData: false,
            },
            data: [{'p99(measurements.lcp)': 10}],
          },
        });

        const defaultWidgetQuery = {
          name: '',
          fields: ['p99(measurements.lcp)'],
          columns: [],
          aggregates: ['p99(measurements.lcp)'],
          conditions: 'user:test.user@sentry.io',
          orderby: '',
        };

        const defaultTableColumns = ['p99(measurements.lcp)'];

        renderTestComponent({
          query: {
            source: DashboardWidgetSource.DISCOVERV2,
            defaultWidgetQuery: urlEncode(defaultWidgetQuery),
            displayType: DisplayType.TABLE,
            defaultTableColumns,
          },
          orgFeatures: [
            ...defaultOrgFeatures,
            'discover-frontend-use-events-endpoint',
            'dashboards-mep',
          ],
        });

        await waitFor(() => {
          expect(measurementsMetaMock).toHaveBeenCalled();
        });

        await waitFor(() => {
          expect(eventsMock).toHaveBeenCalled();
        });

        screen.getByText('Your selection is only applicable to');
      });

      it('does not raise an alert banner if widget result is not metrics data but widget contains error fields', async function () {
        eventsMock = MockApiClient.addMockResponse({
          url: '/organizations/org-slug/events/',
          method: 'GET',
          statusCode: 200,
          body: {
            meta: {
              fields: {'p99(measurements.lcp)': 'duration'},
              isMetricsData: false,
            },
            data: [{'p99(measurements.lcp)': 10}],
          },
        });

        const defaultWidgetQuery = {
          name: '',
          fields: ['p99(measurements.lcp)'],
          columns: ['error.handled'],
          aggregates: ['p99(measurements.lcp)'],
          conditions: 'user:test.user@sentry.io',
          orderby: '',
        };

        const defaultTableColumns = ['p99(measurements.lcp)'];

        renderTestComponent({
          query: {
            source: DashboardWidgetSource.DISCOVERV2,
            defaultWidgetQuery: urlEncode(defaultWidgetQuery),
            displayType: DisplayType.TABLE,
            defaultTableColumns,
          },
          orgFeatures: [
            ...defaultOrgFeatures,
            'discover-frontend-use-events-endpoint',
            'dashboards-mep',
          ],
        });

        await waitFor(() => {
          expect(measurementsMetaMock).toHaveBeenCalled();
        });

        await waitFor(() => {
          expect(eventsMock).toHaveBeenCalled();
        });

        expect(
          screen.queryByText('Your selection is only applicable to')
        ).not.toBeInTheDocument();
      });

      it('only displays custom measurements in supported functions', async function () {
        measurementsMetaMock = MockApiClient.addMockResponse({
          url: '/organizations/org-slug/measurements-meta/',
          method: 'GET',
          body: {
            'measurements.custom.measurement': {functions: ['p99']},
            'measurements.another.custom.measurement': {functions: ['p95']},
          },
        });

        renderTestComponent({
          query: {source: DashboardWidgetSource.DISCOVERV2},
          dashboard: testDashboard,
          orgFeatures: [...defaultOrgFeatures, 'discover-frontend-use-events-endpoint'],
        });

        expect(await screen.findAllByText('Custom Widget')).toHaveLength(2);

        await selectEvent.select(screen.getAllByText('count()')[1], ['p99(…)']);
        userEvent.click(screen.getByText('transaction.duration'));
        screen.getByText('measurements.custom.measurement');
        expect(
          screen.queryByText('measurements.another.custom.measurement')
        ).not.toBeInTheDocument();
        await selectEvent.select(screen.getAllByText('p99(…)')[0], ['p95(…)']);
        userEvent.click(screen.getByText('transaction.duration'));
        screen.getByText('measurements.another.custom.measurement');
        expect(
          screen.queryByText('measurements.custom.measurement')
        ).not.toBeInTheDocument();
      });

      it('renders custom performance metric using duration units from events meta', async function () {
        eventsMock = MockApiClient.addMockResponse({
          url: '/organizations/org-slug/events/',
          method: 'GET',
          statusCode: 200,
          body: {
            meta: {
              fields: {'p99(measurements.custom.measurement)': 'duration'},
              isMetricsData: true,
              units: {'p99(measurements.custom.measurement)': 'hour'},
            },
            data: [{'p99(measurements.custom.measurement)': 12}],
          },
        });

        renderTestComponent({
          query: {source: DashboardWidgetSource.DISCOVERV2},
          dashboard: {
            ...testDashboard,
            widgets: [
              {
                title: 'Custom Measurement Widget',
                interval: '1d',
                id: '1',
                widgetType: WidgetType.DISCOVER,
                displayType: DisplayType.TABLE,
                queries: [
                  {
                    conditions: '',
                    name: '',
                    fields: ['p99(measurements.custom.measurement)'],
                    columns: [],
                    aggregates: ['p99(measurements.custom.measurement)'],
                    orderby: '-p99(measurements.custom.measurement)',
                  },
                ],
              },
            ],
          },
          params: {
            widgetIndex: '0',
          },
          orgFeatures: [...defaultOrgFeatures, 'discover-frontend-use-events-endpoint'],
        });

        await screen.findByText('12.00hr');
      });

      it('renders custom performance metric using size units from events meta', async function () {
        eventsMock = MockApiClient.addMockResponse({
          url: '/organizations/org-slug/events/',
          method: 'GET',
          statusCode: 200,
          body: {
            meta: {
              fields: {'p99(measurements.custom.measurement)': 'size'},
              isMetricsData: true,
              units: {'p99(measurements.custom.measurement)': 'kibibyte'},
            },
            data: [{'p99(measurements.custom.measurement)': 12}],
          },
        });

        renderTestComponent({
          query: {source: DashboardWidgetSource.DISCOVERV2},
          dashboard: {
            ...testDashboard,
            widgets: [
              {
                title: 'Custom Measurement Widget',
                interval: '1d',
                id: '1',
                widgetType: WidgetType.DISCOVER,
                displayType: DisplayType.TABLE,
                queries: [
                  {
                    conditions: '',
                    name: '',
                    fields: ['p99(measurements.custom.measurement)'],
                    columns: [],
                    aggregates: ['p99(measurements.custom.measurement)'],
                    orderby: '-p99(measurements.custom.measurement)',
                  },
                ],
              },
            ],
          },
          params: {
            widgetIndex: '0',
          },
          orgFeatures: [...defaultOrgFeatures, 'discover-frontend-use-events-endpoint'],
        });

        await screen.findByText('12.0 KiB');
      });

      it('displays custom performance metric in column select', async function () {
        renderTestComponent({
          query: {source: DashboardWidgetSource.DISCOVERV2},
          dashboard: {
            ...testDashboard,
            widgets: [
              {
                title: 'Custom Measurement Widget',
                interval: '1d',
                id: '1',
                widgetType: WidgetType.DISCOVER,
                displayType: DisplayType.TABLE,
                queries: [
                  {
                    conditions: '',
                    name: '',
                    fields: ['p99(measurements.custom.measurement)'],
                    columns: [],
                    aggregates: ['p99(measurements.custom.measurement)'],
                    orderby: '-p99(measurements.custom.measurement)',
                  },
                ],
              },
            ],
          },
          params: {
            widgetIndex: '0',
          },
          orgFeatures: [...defaultOrgFeatures, 'discover-frontend-use-events-endpoint'],
        });
        await screen.findByText('measurements.custom.measurement');
      });
    });
  });
});
