mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
911 lines
27 KiB
TypeScript
911 lines
27 KiB
TypeScript
import styled from '@emotion/styled';
|
|
|
|
import {initializeOrg} from 'sentry-test/initializeOrg';
|
|
import {
|
|
fireEvent,
|
|
render,
|
|
screen,
|
|
userEvent,
|
|
waitFor,
|
|
within,
|
|
} from 'sentry-test/reactTestingLibrary';
|
|
|
|
import {makeCloseButton} from 'sentry/components/globalModal/components';
|
|
import TagStore from 'sentry/stores/tagStore';
|
|
import type {QueryFieldValue} from 'sentry/utils/discover/fields';
|
|
import ColumnEditModal from 'sentry/views/discover/table/columnEditModal';
|
|
|
|
const stubEl = styled(p => p.children);
|
|
|
|
function mountModal({columns, onApply, customMeasurements}, initialData) {
|
|
return render(
|
|
<ColumnEditModal
|
|
CloseButton={makeCloseButton(() => {})}
|
|
Header={c => <div>{c.children}</div>}
|
|
Footer={stubEl()}
|
|
Body={stubEl()}
|
|
organization={initialData.organization}
|
|
columns={columns}
|
|
onApply={onApply}
|
|
closeModal={jest.fn()}
|
|
measurementKeys={null}
|
|
customMeasurements={customMeasurements}
|
|
/>,
|
|
{context: initialData.routerContext}
|
|
);
|
|
}
|
|
|
|
// Get all queryField components which represent a row in the column editor.
|
|
const findAllQueryFields = () => screen.findAllByTestId('queryField');
|
|
|
|
// Get the nth label (value) within the row of the column editor.
|
|
const findAllQueryFieldNthCell = async nth =>
|
|
(await findAllQueryFields())
|
|
.map(f => within(f).getAllByTestId('label')[nth])
|
|
.filter(Boolean);
|
|
|
|
const getAllQueryFields = () => screen.getAllByTestId('queryField');
|
|
const getAllQueryFieldsNthCell = nth =>
|
|
getAllQueryFields()
|
|
.map(f => within(f).getAllByTestId('label')[nth])
|
|
.filter(Boolean);
|
|
|
|
const openMenu = async (row, column = 0) => {
|
|
const queryFields = await screen.findAllByTestId('queryField');
|
|
const queryField = queryFields[row];
|
|
expect(queryField).toBeInTheDocument();
|
|
|
|
const labels = within(queryField).queryAllByTestId('label');
|
|
if (labels.length > 0) {
|
|
await userEvent.click(labels[column]);
|
|
} else {
|
|
// For test adding a new column, no existing label.
|
|
await userEvent.click(screen.getByText('(Required)'));
|
|
}
|
|
};
|
|
|
|
const selectByLabel = async (label, options) => {
|
|
await openMenu(options.at);
|
|
const menuOptions = screen.getAllByTestId('menu-list-item-label'); // TODO: Can likely switch to menuitem role and match against label
|
|
const opt = menuOptions.find(e => e.textContent?.includes(label));
|
|
await userEvent.click(opt!);
|
|
};
|
|
|
|
describe('Discover -> ColumnEditModal', function () {
|
|
beforeEach(() => {
|
|
TagStore.reset();
|
|
TagStore.loadTagsSuccess([
|
|
{name: 'browser.name', key: 'browser.name'},
|
|
{name: 'custom-field', key: 'custom-field'},
|
|
{name: 'user', key: 'user'},
|
|
]);
|
|
});
|
|
const initialData = initializeOrg({
|
|
organization: {
|
|
features: ['performance-view', 'dashboards-mep'],
|
|
},
|
|
});
|
|
const columns: QueryFieldValue[] = [
|
|
{
|
|
kind: 'field',
|
|
field: 'event.type',
|
|
},
|
|
{
|
|
kind: 'field',
|
|
field: 'browser.name',
|
|
},
|
|
{
|
|
kind: 'function',
|
|
function: ['count', 'id', '', ''],
|
|
},
|
|
{
|
|
kind: 'function',
|
|
function: ['count_unique', 'title', '', ''],
|
|
},
|
|
{
|
|
kind: 'function',
|
|
function: ['p95', '', '', ''],
|
|
},
|
|
{
|
|
kind: 'field',
|
|
field: 'issue.id',
|
|
},
|
|
{
|
|
kind: 'function',
|
|
function: ['count_unique', 'issue.id', '', ''],
|
|
},
|
|
];
|
|
|
|
describe('basic rendering', function () {
|
|
it('renders fields and basic controls, async delete and grab buttons', async function () {
|
|
mountModal(
|
|
{
|
|
columns,
|
|
onApply: jest.fn(),
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
// Should have fields equal to the columns.
|
|
expect((await findAllQueryFieldNthCell(0)).map(el => el.textContent)).toEqual([
|
|
'event.type',
|
|
'browser.name',
|
|
'count()',
|
|
'count_unique(…)',
|
|
'p95(…)',
|
|
'issue.id',
|
|
'count_unique(…)', // extra because of the function
|
|
]);
|
|
expect(screen.getByRole('button', {name: 'Apply'})).toBeInTheDocument();
|
|
expect(screen.getByRole('button', {name: 'Add a Column'})).toBeInTheDocument();
|
|
|
|
expect(screen.getAllByRole('button', {name: 'Remove column'})).toHaveLength(
|
|
columns.length
|
|
);
|
|
expect(screen.getAllByRole('button', {name: 'Drag to reorder'})).toHaveLength(
|
|
columns.length
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('rendering unknown fields', function () {
|
|
it('renders unknown fields in field and field parameter controls', async function () {
|
|
mountModal(
|
|
{
|
|
columns: [
|
|
{kind: 'function', function: ['count_unique', 'user-defined']},
|
|
{kind: 'field', field: 'user-def'},
|
|
],
|
|
onApply: jest.fn(),
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
|
|
expect((await findAllQueryFieldNthCell(0)).map(el => el.textContent)).toEqual([
|
|
'count_unique(…)',
|
|
'user-def',
|
|
]);
|
|
|
|
expect(getAllQueryFieldsNthCell(1).map(el => el.textContent)).toEqual([
|
|
'user-defined',
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('rendering tags that overlap fields & functions', function () {
|
|
beforeEach(() => {
|
|
TagStore.reset();
|
|
TagStore.loadTagsSuccess([
|
|
{name: 'project', key: 'project'},
|
|
{name: 'count', key: 'count'},
|
|
]);
|
|
});
|
|
|
|
it('selects tag expressions that overlap fields', async function () {
|
|
mountModal(
|
|
{
|
|
columns: [
|
|
{kind: 'field', field: 'tags[project]'},
|
|
{kind: 'field', field: 'tags[count]'},
|
|
],
|
|
onApply: jest.fn(),
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
|
|
expect((await findAllQueryFieldNthCell(0)).map(el => el.textContent)).toEqual([
|
|
'project',
|
|
'count',
|
|
]);
|
|
});
|
|
|
|
it('selects tag expressions that overlap functions', async function () {
|
|
mountModal(
|
|
{
|
|
columns: [
|
|
{kind: 'field', field: 'tags[project]'},
|
|
{kind: 'field', field: 'tags[count]'},
|
|
],
|
|
onApply: jest.fn(),
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
|
|
expect((await findAllQueryFieldNthCell(0)).map(el => el.textContent)).toEqual([
|
|
'project',
|
|
'count',
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('rendering functions', function () {
|
|
it('renders three columns when needed', async function () {
|
|
mountModal(
|
|
{
|
|
columns: [
|
|
{kind: 'function', function: ['count', 'id']},
|
|
{kind: 'function', function: ['count_unique', 'title']},
|
|
{kind: 'function', function: ['percentile', 'transaction.duration', '0.66']},
|
|
],
|
|
onApply: jest.fn(),
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
|
|
const queryFields = await findAllQueryFields();
|
|
|
|
const countRow = queryFields[0];
|
|
|
|
expect(
|
|
within(countRow)
|
|
.getAllByTestId('label')
|
|
.map(el => el.textContent)
|
|
).toEqual(['count()']);
|
|
|
|
const percentileRow = queryFields[2];
|
|
|
|
expect(
|
|
within(percentileRow)
|
|
.getAllByTestId('label')
|
|
.map(el => el.textContent)
|
|
).toEqual(['percentile(…)', 'transaction.duration']);
|
|
expect(within(percentileRow).getByDisplayValue('0.66')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('function & column selection', function () {
|
|
let onApply;
|
|
beforeEach(function () {
|
|
onApply = jest.fn();
|
|
});
|
|
|
|
it('restricts column choices', async function () {
|
|
mountModal(
|
|
{
|
|
columns: [columns[0]],
|
|
onApply,
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
await selectByLabel('avg(…)', {
|
|
at: 0,
|
|
});
|
|
|
|
await openMenu(0, 1);
|
|
|
|
const menuOptions = await screen.findAllByTestId('menu-list-item-label');
|
|
const menuOptionsText = menuOptions.map(el => el.textContent);
|
|
expect(menuOptionsText).toContain('transaction.duration');
|
|
expect(menuOptionsText).not.toContain('title');
|
|
});
|
|
|
|
it('shows no options for parameterless functions', async function () {
|
|
mountModal(
|
|
{
|
|
columns: [columns[0]],
|
|
onApply,
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
await selectByLabel('last_seen()', {name: 'field', at: 0, control: true});
|
|
|
|
expect(screen.getByTestId('blankSpace')).toBeInTheDocument();
|
|
});
|
|
|
|
it('shows additional inputs for multi-parameter functions', async function () {
|
|
mountModal(
|
|
{
|
|
columns: [columns[0]],
|
|
onApply,
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
await selectByLabel('percentile(\u2026)', {
|
|
name: 'field',
|
|
at: 0,
|
|
});
|
|
|
|
expect(screen.getAllByTestId('label')[0]).toHaveTextContent('percentile(…)');
|
|
expect(
|
|
within(screen.getByTestId('queryField')).getByDisplayValue(0.5)
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it('handles scalar field parameters', async function () {
|
|
mountModal(
|
|
{
|
|
columns: [columns[0]],
|
|
onApply,
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
await selectByLabel('apdex(\u2026)', {
|
|
name: 'field',
|
|
at: 0,
|
|
});
|
|
|
|
expect(screen.getAllByRole('textbox')[1]).toHaveValue('300');
|
|
await userEvent.click(screen.getByRole('button', {name: 'Apply'}));
|
|
|
|
await waitFor(() => {
|
|
expect(onApply).toHaveBeenCalledWith([
|
|
{kind: 'function', function: ['apdex', '300', undefined, undefined]},
|
|
]);
|
|
});
|
|
});
|
|
|
|
it('handles parameter overrides', async function () {
|
|
mountModal(
|
|
{
|
|
columns: [columns[0]],
|
|
onApply,
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
await selectByLabel('apdex(…)', {
|
|
name: 'field',
|
|
at: 0,
|
|
});
|
|
|
|
expect(screen.getAllByRole('textbox')[1]).toHaveValue('300');
|
|
});
|
|
|
|
it('clears unused parameters', async function () {
|
|
mountModal(
|
|
{
|
|
columns: [columns[0]],
|
|
onApply,
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
// Choose percentile, then apdex which has fewer parameters and different types.
|
|
await selectByLabel('percentile(\u2026)', {
|
|
name: 'field',
|
|
at: 0,
|
|
});
|
|
await selectByLabel('apdex(\u2026)', {
|
|
name: 'field',
|
|
at: 0,
|
|
});
|
|
|
|
// Apply the changes so we can see the new columns.
|
|
await userEvent.click(screen.getByRole('button', {name: 'Apply'}));
|
|
|
|
expect(onApply).toHaveBeenCalledWith([
|
|
{kind: 'function', function: ['apdex', '300', undefined, undefined]},
|
|
]);
|
|
});
|
|
|
|
it('clears all unused parameters', async function () {
|
|
mountModal(
|
|
{
|
|
columns: [columns[0]],
|
|
onApply,
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
// Choose percentile, then failure_rate which has no parameters.
|
|
await selectByLabel('percentile(\u2026)', {
|
|
name: 'field',
|
|
at: 0,
|
|
});
|
|
await selectByLabel('failure_rate()', {
|
|
name: 'field',
|
|
at: 0,
|
|
});
|
|
|
|
// Apply the changes so we can see the new columns.
|
|
await userEvent.click(screen.getByRole('button', {name: 'Apply'}));
|
|
|
|
expect(onApply).toHaveBeenCalledWith([
|
|
{kind: 'function', function: ['failure_rate', '', undefined, undefined]},
|
|
]);
|
|
});
|
|
|
|
it('clears all unused parameters with count_if to two parameter function', async function () {
|
|
mountModal(
|
|
{
|
|
columns: [columns[0]],
|
|
onApply,
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
// Choose percentile, then failure_rate which has no parameters.
|
|
await selectByLabel('count_if(\u2026)', {
|
|
name: 'field',
|
|
at: 0,
|
|
});
|
|
await selectByLabel('user', {name: 'parameter', at: 0});
|
|
await selectByLabel('count_miserable(\u2026)', {
|
|
name: 'field',
|
|
at: 0,
|
|
});
|
|
|
|
// Apply the changes so we can see the new columns.
|
|
await userEvent.click(screen.getByRole('button', {name: 'Apply'}));
|
|
expect(onApply).toHaveBeenCalledWith([
|
|
{kind: 'function', function: ['count_miserable', 'user', '300', undefined]},
|
|
]);
|
|
});
|
|
|
|
it('clears all unused parameters with count_if to one parameter function', async function () {
|
|
mountModal(
|
|
{
|
|
columns: [columns[0]],
|
|
onApply,
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
// Choose percentile, then failure_rate which has no parameters.
|
|
await selectByLabel('count_if(\u2026)', {
|
|
name: 'field',
|
|
at: 0,
|
|
});
|
|
await selectByLabel('user', {name: 'parameter', at: 0});
|
|
await selectByLabel('count_unique(\u2026)', {
|
|
name: 'field',
|
|
at: 0,
|
|
});
|
|
|
|
// Apply the changes so we can see the new columns.
|
|
await userEvent.click(screen.getByRole('button', {name: 'Apply'}));
|
|
expect(onApply).toHaveBeenCalledWith([
|
|
{kind: 'function', function: ['count_unique', '300', undefined, undefined]},
|
|
]);
|
|
});
|
|
|
|
it('clears all unused parameters with count_if to parameterless function', async function () {
|
|
mountModal(
|
|
{
|
|
columns: [columns[0]],
|
|
onApply,
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
// Choose percentile, then failure_rate which has no parameters.
|
|
await selectByLabel('count_if(\u2026)', {
|
|
name: 'field',
|
|
at: 0,
|
|
});
|
|
await selectByLabel('count()', {
|
|
name: 'field',
|
|
at: 0,
|
|
});
|
|
|
|
// Apply the changes so we can see the new columns.
|
|
await userEvent.click(screen.getByRole('button', {name: 'Apply'}));
|
|
expect(onApply).toHaveBeenCalledWith([
|
|
{kind: 'function', function: ['count', '', undefined, undefined]},
|
|
]);
|
|
});
|
|
|
|
it('updates equation errors when they change', async function () {
|
|
mountModal(
|
|
{
|
|
columns: [
|
|
{
|
|
kind: 'equation',
|
|
field: '1 / 0',
|
|
},
|
|
],
|
|
onApply,
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
|
|
await userEvent.hover(await screen.findByTestId('arithmeticErrorWarning'));
|
|
expect(await screen.findByText('Division by 0 is not allowed')).toBeInTheDocument();
|
|
|
|
const input = screen.getAllByRole('textbox')[0];
|
|
expect(input).toHaveValue('1 / 0');
|
|
|
|
await userEvent.clear(input);
|
|
await userEvent.type(input, '1+1+1+1+1+1+1+1+1+1+1+1');
|
|
await userEvent.click(document.body);
|
|
|
|
await waitFor(() => expect(input).toHaveValue('1+1+1+1+1+1+1+1+1+1+1+1'));
|
|
|
|
await userEvent.hover(screen.getByTestId('arithmeticErrorWarning'));
|
|
expect(await screen.findByText('Maximum operators exceeded')).toBeInTheDocument();
|
|
});
|
|
|
|
it('resets required field to previous value if cleared', async function () {
|
|
const initialColumnVal = '0.6';
|
|
mountModal(
|
|
{
|
|
columns: [
|
|
{
|
|
kind: 'function',
|
|
function: [
|
|
'percentile',
|
|
'transaction.duration',
|
|
initialColumnVal,
|
|
undefined,
|
|
],
|
|
},
|
|
],
|
|
onApply,
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
|
|
const input = screen.getAllByRole('textbox')[2]; // The numeric input
|
|
expect(input).toHaveValue(initialColumnVal);
|
|
await userEvent.clear(input);
|
|
await userEvent.click(document.body); // Unfocusing the input should revert it to the previous value
|
|
|
|
expect(input).toHaveValue(initialColumnVal);
|
|
|
|
await userEvent.click(screen.getByRole('button', {name: 'Apply'}));
|
|
expect(onApply).toHaveBeenCalledWith([
|
|
{
|
|
kind: 'function',
|
|
function: ['percentile', 'transaction.duration', initialColumnVal, undefined],
|
|
},
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('equation automatic update', function () {
|
|
let onApply;
|
|
beforeEach(function () {
|
|
onApply = jest.fn();
|
|
});
|
|
it('update simple equation columns when they change', async function () {
|
|
mountModal(
|
|
{
|
|
columns: [
|
|
{
|
|
kind: 'function',
|
|
function: ['count_unique', 'user'],
|
|
},
|
|
{
|
|
kind: 'function',
|
|
function: ['p95', ''],
|
|
},
|
|
{
|
|
kind: 'equation',
|
|
field: '(p95() / count_unique(user) ) * 100',
|
|
},
|
|
],
|
|
onApply,
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
await selectByLabel('count_if(\u2026)', {
|
|
name: 'field',
|
|
at: 0,
|
|
});
|
|
|
|
// Apply the changes so we can see the new columns.
|
|
await userEvent.click(screen.getByRole('button', {name: 'Apply'}));
|
|
expect(onApply).toHaveBeenCalledWith([
|
|
{kind: 'function', function: ['count_if', 'user', 'equals', '300']},
|
|
{kind: 'function', function: ['p95', '']},
|
|
{kind: 'equation', field: '(p95() / count_if(user,equals,300) ) * 100'},
|
|
]);
|
|
});
|
|
it('update equation with repeated columns when they change', async function () {
|
|
mountModal(
|
|
{
|
|
columns: [
|
|
{
|
|
kind: 'function',
|
|
function: ['count_unique', 'user'],
|
|
},
|
|
{
|
|
kind: 'equation',
|
|
field:
|
|
'count_unique(user) + (count_unique(user) - count_unique(user)) * 5',
|
|
},
|
|
],
|
|
onApply,
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
await selectByLabel('count()', {
|
|
name: 'field',
|
|
at: 0,
|
|
});
|
|
|
|
// Apply the changes so we can see the new columns.
|
|
await userEvent.click(screen.getByRole('button', {name: 'Apply'}));
|
|
expect(onApply).toHaveBeenCalledWith([
|
|
{kind: 'function', function: ['count', '', undefined, undefined]},
|
|
{kind: 'equation', field: 'count() + (count() - count()) * 5'},
|
|
]);
|
|
});
|
|
it('handles equations with duplicate fields', async function () {
|
|
mountModal(
|
|
{
|
|
columns: [
|
|
{
|
|
kind: 'field',
|
|
field: 'spans.db',
|
|
},
|
|
{
|
|
kind: 'field',
|
|
field: 'spans.db',
|
|
},
|
|
{
|
|
kind: 'equation',
|
|
field: 'spans.db - spans.db',
|
|
},
|
|
],
|
|
onApply,
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
await selectByLabel('count()', {
|
|
name: 'field',
|
|
at: 0,
|
|
});
|
|
|
|
// Apply the changes so we can see the new columns.
|
|
await userEvent.click(screen.getByRole('button', {name: 'Apply'}));
|
|
// Because spans.db is still a selected column it isn't swapped
|
|
expect(onApply).toHaveBeenCalledWith([
|
|
{kind: 'function', function: ['count', '', undefined, undefined]},
|
|
{kind: 'field', field: 'spans.db'},
|
|
{kind: 'equation', field: 'spans.db - spans.db'},
|
|
]);
|
|
});
|
|
it('handles equations with duplicate functions', async function () {
|
|
mountModal(
|
|
{
|
|
columns: [
|
|
{
|
|
kind: 'function',
|
|
function: ['count', '', undefined, undefined],
|
|
},
|
|
{
|
|
kind: 'function',
|
|
function: ['count', '', undefined, undefined],
|
|
},
|
|
{
|
|
kind: 'equation',
|
|
field: 'count() - count()',
|
|
},
|
|
],
|
|
onApply,
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
await selectByLabel('count_unique(\u2026)', {
|
|
name: 'field',
|
|
at: 0,
|
|
});
|
|
|
|
// Apply the changes so we can see the new columns.
|
|
await userEvent.click(screen.getByRole('button', {name: 'Apply'}));
|
|
expect(onApply).toHaveBeenCalledWith([
|
|
{kind: 'function', function: ['count_unique', 'user', undefined, undefined]},
|
|
{kind: 'function', function: ['count', '', undefined, undefined]},
|
|
{kind: 'equation', field: 'count() - count()'},
|
|
]);
|
|
});
|
|
it('handles incomplete equations', async function () {
|
|
mountModal(
|
|
{
|
|
columns: [
|
|
{
|
|
kind: 'function',
|
|
function: ['count', '', undefined, undefined],
|
|
},
|
|
{
|
|
kind: 'equation',
|
|
field: 'count() - count() arst count() ',
|
|
},
|
|
],
|
|
onApply,
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
expect(await screen.findByTestId('arithmeticErrorWarning')).toBeInTheDocument();
|
|
await selectByLabel('count_unique(\u2026)', {
|
|
name: 'field',
|
|
at: 0,
|
|
});
|
|
|
|
// Apply the changes so we can see the new columns.
|
|
await userEvent.click(screen.getByRole('button', {name: 'Apply'}));
|
|
// With the way the parser works only tokens up to the error will be updated
|
|
expect(onApply).toHaveBeenCalledWith([
|
|
{kind: 'function', function: ['count_unique', 'user', undefined, undefined]},
|
|
{
|
|
kind: 'equation',
|
|
field: 'count_unique(user) - count_unique(user) arst count() ',
|
|
},
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe('adding rows', function () {
|
|
it('allows rows to be added, async but only up to 20', async function () {
|
|
mountModal(
|
|
{
|
|
columns: [columns[0]],
|
|
onApply: jest.fn(),
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
expect(await screen.findByTestId('queryField')).toBeInTheDocument();
|
|
const addColumnButton = screen.getByRole('button', {name: 'Add a Column'});
|
|
for (let i = 2; i <= 20; i++) {
|
|
fireEvent.click(addColumnButton);
|
|
expect(await screen.findAllByTestId('queryField')).toHaveLength(i);
|
|
}
|
|
|
|
expect(screen.getByRole('button', {name: 'Add a Column'})).toBeDisabled();
|
|
});
|
|
});
|
|
|
|
describe('removing rows', function () {
|
|
it('allows rows to be removed, async but not the last one', async function () {
|
|
mountModal(
|
|
{
|
|
columns: [columns[0], columns[1]],
|
|
onApply: jest.fn(),
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
|
|
expect(await screen.findAllByTestId('queryField')).toHaveLength(2);
|
|
await userEvent.click(screen.getByTestId('remove-column-0'));
|
|
|
|
expect(await screen.findByTestId('queryField')).toBeInTheDocument();
|
|
|
|
expect(
|
|
screen.queryByRole('button', {name: 'Remove column'})
|
|
).not.toBeInTheDocument();
|
|
expect(
|
|
screen.queryByRole('button', {name: 'Drag to reorder'})
|
|
).not.toBeInTheDocument();
|
|
});
|
|
it('does not count equations towards the count of rows', async function () {
|
|
mountModal(
|
|
{
|
|
columns: [
|
|
columns[0],
|
|
columns[1],
|
|
{
|
|
kind: 'equation',
|
|
field: '5 + 5',
|
|
},
|
|
],
|
|
onApply: jest.fn(),
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
expect(await screen.findAllByTestId('queryField')).toHaveLength(3);
|
|
await userEvent.click(screen.getByTestId('remove-column-0'));
|
|
|
|
expect(await screen.findAllByTestId('queryField')).toHaveLength(2);
|
|
|
|
expect(screen.queryByRole('button', {name: 'Remove column'})).toBeInTheDocument();
|
|
expect(screen.queryAllByRole('button', {name: 'Drag to reorder'})).toHaveLength(2);
|
|
});
|
|
it('handles equations being deleted', async function () {
|
|
mountModal(
|
|
{
|
|
columns: [
|
|
{
|
|
kind: 'equation',
|
|
field: '1 / 0',
|
|
},
|
|
columns[0],
|
|
columns[1],
|
|
],
|
|
onApply: jest.fn(),
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
expect(await screen.findAllByTestId('queryField')).toHaveLength(3);
|
|
expect(screen.getByTestId('arithmeticErrorWarning')).toBeInTheDocument();
|
|
|
|
await userEvent.click(screen.getByTestId('remove-column-0'));
|
|
|
|
expect(await screen.findAllByTestId('queryField')).toHaveLength(2);
|
|
|
|
expect(screen.queryByTestId('arithmeticErrorWarning')).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('apply action', function () {
|
|
const onApply = jest.fn();
|
|
it('reflects added and removed columns', async function () {
|
|
mountModal(
|
|
{
|
|
columns: [columns[0], columns[1]],
|
|
onApply,
|
|
customMeasurements: {},
|
|
},
|
|
initialData
|
|
);
|
|
expect(await screen.findAllByTestId('queryField')).toHaveLength(2);
|
|
// Remove a column, then add a blank one an select a value in it.
|
|
await userEvent.click(screen.getByTestId('remove-column-0'));
|
|
|
|
await userEvent.click(screen.getByRole('button', {name: 'Add a Column'}));
|
|
|
|
expect(await screen.findAllByTestId('queryField')).toHaveLength(2);
|
|
|
|
await selectByLabel('title', {name: 'field', at: 1});
|
|
|
|
await userEvent.click(screen.getByRole('button', {name: 'Apply'}));
|
|
|
|
expect(onApply).toHaveBeenCalledWith([columns[1], {kind: 'field', field: 'title'}]);
|
|
});
|
|
});
|
|
|
|
describe('custom performance metrics', function () {
|
|
it('allows selecting custom performance metrics in dropdown', async function () {
|
|
render(
|
|
<ColumnEditModal
|
|
CloseButton={makeCloseButton(() => {})}
|
|
Header={c => <div>{c.children}</div>}
|
|
Footer={stubEl()}
|
|
Body={stubEl()}
|
|
organization={initialData.organization}
|
|
columns={[columns[0]]}
|
|
onApply={() => undefined}
|
|
closeModal={() => undefined}
|
|
measurementKeys={[]}
|
|
customMeasurements={{
|
|
'measurements.custom.kibibyte': {
|
|
fieldType: 'number',
|
|
unit: 'KiB',
|
|
key: 'measurements.custom.kibibyte',
|
|
name: 'measurements.custom.kibibyte',
|
|
functions: ['p99'],
|
|
},
|
|
'measurements.custom.minute': {
|
|
fieldType: 'number',
|
|
key: 'measurements.custom.minute',
|
|
name: 'measurements.custom.minute',
|
|
unit: 'minute',
|
|
functions: ['p99'],
|
|
},
|
|
'measurements.custom.ratio': {
|
|
fieldType: 'number',
|
|
key: 'measurements.custom.ratio',
|
|
name: 'measurements.custom.ratio',
|
|
unit: 'ratio',
|
|
functions: ['p99'],
|
|
},
|
|
}}
|
|
/>
|
|
);
|
|
expect(screen.getByText('event.type')).toBeInTheDocument();
|
|
await userEvent.click(screen.getByText('event.type'));
|
|
await userEvent.type(screen.getAllByText('event.type')[0], 'custom');
|
|
expect(screen.getByText('measurements.custom.kibibyte')).toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|