Files
2024-02-15 17:01:50 +00:00

3891 lines
110 KiB
TypeScript

import shuffle from 'lodash/shuffle';
import {LocationFixture} from 'sentry-fixture/locationFixture';
import {OrganizationFixture} from 'sentry-fixture/organization';
import {PageFiltersFixture} from 'sentry-fixture/pageFilters';
import {COL_WIDTH_UNDEFINED} from 'sentry/components/gridEditable';
import type {NewQuery, SavedQuery} from 'sentry/types';
import type {MetaType} from 'sentry/utils/discover/eventView';
import EventView, {
isAPIPayloadSimilar,
pickRelevantLocationQueryStrings,
} from 'sentry/utils/discover/eventView';
import type {Column} from 'sentry/utils/discover/fields';
import {
CHART_AXIS_OPTIONS,
DiscoverDatasets,
DISPLAY_MODE_OPTIONS,
DisplayModes,
} from 'sentry/utils/discover/types';
import {AggregationKey, WebVital} from 'sentry/utils/fields';
import {SpanOperationBreakdownFilter} from 'sentry/views/performance/transactionSummary/filter';
import {EventsDisplayFilterName} from 'sentry/views/performance/transactionSummary/transactionEvents/utils';
const generateFields = fields =>
fields.map(field => ({
field,
}));
const generateSorts = sorts =>
sorts.map(sortName => ({
field: sortName,
kind: 'desc',
}));
const REQUIRED_CONSTRUCTOR_PROPS = {
createdBy: undefined,
end: undefined,
environment: [],
fields: [],
name: undefined,
project: [],
query: '',
start: undefined,
team: [],
sorts: [],
statsPeriod: undefined,
topEvents: undefined,
id: undefined,
display: undefined,
};
describe('EventView constructor', function () {
it('instantiates default values', function () {
const eventView = new EventView(REQUIRED_CONSTRUCTOR_PROPS);
expect(eventView).toMatchObject({
id: undefined,
name: undefined,
fields: [],
sorts: [],
query: '',
project: [],
start: undefined,
end: undefined,
statsPeriod: undefined,
environment: [],
yAxis: undefined,
display: undefined,
});
});
});
describe('EventView.fromLocation()', function () {
it('maps query strings', function () {
const location = LocationFixture({
query: {
id: '42',
name: 'best query',
field: ['count()', 'id'],
widths: ['123', '456'],
sort: ['title', '-count'],
query: 'event.type:transaction',
project: ['123'],
team: ['myteams', '1', '2'],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
statsPeriod: '14d',
environment: ['staging'],
yAxis: 'p95',
display: 'previous',
dataset: DiscoverDatasets.DISCOVER,
},
});
const eventView = EventView.fromLocation(location);
expect(eventView).toMatchObject({
id: '42',
name: 'best query',
fields: [
{field: 'count()', width: 123},
{field: 'id', width: 456},
],
sorts: generateSorts([AggregationKey.COUNT]),
query: 'event.type:transaction',
project: [123],
team: ['myteams', 1, 2],
start: undefined,
end: undefined,
statsPeriod: '14d',
environment: ['staging'],
yAxis: 'p95',
display: 'previous',
dataset: DiscoverDatasets.DISCOVER,
});
});
it('includes first valid statsPeriod', function () {
const location = LocationFixture({
query: {
id: '42',
name: 'best query',
field: ['count()', 'id'],
widths: ['123', '456'],
sort: ['title', '-count'],
query: 'event.type:transaction',
project: ['123'],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
statsPeriod: ['invalid', '28d'],
environment: ['staging'],
},
});
const eventView = EventView.fromLocation(location);
expect(eventView).toMatchObject({
id: '42',
name: 'best query',
fields: [
{field: 'count()', width: 123},
{field: 'id', width: 456},
],
sorts: generateSorts([AggregationKey.COUNT]),
query: 'event.type:transaction',
project: [123],
start: undefined,
end: undefined,
statsPeriod: '28d',
environment: ['staging'],
});
});
it('includes start and end', function () {
const location = LocationFixture({
query: {
id: '42',
name: 'best query',
field: ['count()', 'id'],
widths: ['123', '456'],
sort: ['title', '-count'],
query: 'event.type:transaction',
project: ['123'],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
environment: ['staging'],
},
});
const eventView = EventView.fromLocation(location);
expect(eventView).toMatchObject({
id: '42',
name: 'best query',
fields: [
{field: 'count()', width: 123},
{field: 'id', width: 456},
],
sorts: generateSorts([AggregationKey.COUNT]),
query: 'event.type:transaction',
project: [123],
start: '2019-10-01T00:00:00.000',
end: '2019-10-02T00:00:00.000',
environment: ['staging'],
});
});
it('generates event view when there are no query strings', function () {
const location = LocationFixture({
query: {},
});
const eventView = EventView.fromLocation(location);
expect(eventView).toMatchObject({
id: void 0,
name: void 0,
fields: [],
sorts: [],
query: '',
project: [],
start: void 0,
end: void 0,
statsPeriod: '14d',
environment: [],
yAxis: void 0,
});
});
});
describe('EventView.fromSavedQuery()', function () {
it('maps basic properties of saved query', function () {
const saved: SavedQuery = {
id: '42',
name: 'best query',
fields: ['count()', 'id'],
query: 'event.type:transaction',
projects: [123],
teams: ['myteams', 1],
range: '14d',
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
orderby: '-id',
environment: ['staging'],
display: 'previous',
dataset: DiscoverDatasets.DISCOVER,
dateCreated: '2019-10-30T06:13:17.632078Z',
dateUpdated: '2019-10-30T06:13:17.632096Z',
version: 2,
};
const eventView = EventView.fromSavedQuery(saved);
expect(eventView).toMatchObject({
id: saved.id,
name: saved.name,
fields: [
{field: 'count()', width: COL_WIDTH_UNDEFINED},
{field: 'id', width: COL_WIDTH_UNDEFINED},
],
sorts: [{field: 'id', kind: 'desc'}],
query: 'event.type:transaction',
project: [123],
team: ['myteams', 1],
start: undefined,
end: undefined,
// statsPeriod has precedence
statsPeriod: '14d',
environment: ['staging'],
yAxis: undefined,
display: 'previous',
dataset: DiscoverDatasets.DISCOVER,
});
const eventView2 = EventView.fromSavedQuery({
...saved,
range: undefined,
});
expect(eventView2).toMatchObject({
id: saved.id,
name: saved.name,
fields: [
{field: 'count()', width: COL_WIDTH_UNDEFINED},
{field: 'id', width: COL_WIDTH_UNDEFINED},
],
sorts: [{field: 'id', kind: 'desc'}],
query: 'event.type:transaction',
project: [123],
team: ['myteams', 1],
start: '2019-10-01T00:00:00.000',
end: '2019-10-02T00:00:00.000',
statsPeriod: undefined,
environment: ['staging'],
});
});
it('maps saved query with no conditions', function () {
const saved: SavedQuery = {
orderby: '-count',
name: 'foo bar',
fields: ['release', 'count()'],
widths: ['111', '222'],
dateCreated: '2019-10-30T06:13:17.632078Z',
environment: ['dev', 'production'],
version: 2,
dateUpdated: '2019-10-30T06:13:17.632096Z',
id: '5',
projects: [1],
yAxis: ['count()'],
};
const eventView = EventView.fromSavedQuery(saved);
const expected = {
id: '5',
name: 'foo bar',
fields: [
{field: 'release', width: 111},
{field: 'count()', width: 222},
],
sorts: generateSorts([AggregationKey.COUNT]),
query: '',
project: [1],
environment: ['dev', 'production'],
yAxis: 'count()',
};
expect(eventView).toMatchObject(expected);
});
it('maps properties from v2 saved query', function () {
const saved: SavedQuery = {
id: '42',
projects: [123],
name: 'best query',
fields: ['count()', 'title'],
range: '14d',
start: '',
end: '',
dateCreated: '2019-10-30T06:13:17.632078Z',
dateUpdated: '2019-10-30T06:13:17.632096Z',
version: 2,
};
const eventView = EventView.fromSavedQuery(saved);
expect(eventView.fields).toEqual([
{field: 'count()', width: COL_WIDTH_UNDEFINED},
{field: 'title', width: COL_WIDTH_UNDEFINED},
]);
expect(eventView.name).toEqual(saved.name);
expect(eventView.statsPeriod).toEqual('14d');
expect(eventView.start).toEqual(undefined);
expect(eventView.end).toEqual(undefined);
});
it('saved queries are equal when start and end datetime differ in format', function () {
const saved: SavedQuery = {
orderby: '-count_timestamp',
end: '2019-10-23T19:27:04+0000',
name: 'release query',
fields: ['release', 'count(timestamp)'],
dateCreated: '2019-10-30T05:10:23.718937Z',
environment: ['dev', 'production'],
start: '2019-10-20T21:02:51+0000',
version: 2,
dateUpdated: '2019-10-30T07:25:58.291917Z',
id: '3',
projects: [1],
};
const eventView = EventView.fromSavedQuery(saved);
const eventView2 = EventView.fromSavedQuery({
...saved,
start: '2019-10-20T21:02:51Z',
end: '2019-10-23T19:27:04Z',
});
expect(eventView.isEqualTo(eventView2)).toBe(true);
const eventView3 = EventView.fromSavedQuery({
...saved,
start: '2019-10-20T21:02:51Z',
});
expect(eventView.isEqualTo(eventView3)).toBe(true);
const eventView4 = EventView.fromSavedQuery({
...saved,
end: '2019-10-23T19:27:04Z',
});
expect(eventView.isEqualTo(eventView4)).toBe(true);
});
it('saved queries are not equal when datetime selection are invalid', function () {
const saved: SavedQuery = {
orderby: '-count_timestamp',
end: '2019-10-23T19:27:04+0000',
name: 'release query',
fields: ['release', 'count(timestamp)'],
dateCreated: '2019-10-30T05:10:23.718937Z',
environment: ['dev', 'production'],
start: '2019-10-20T21:02:51+0000',
version: 2,
dateUpdated: '2019-10-30T07:25:58.291917Z',
id: '3',
projects: [1],
};
const eventView = EventView.fromSavedQuery(saved);
const eventView2 = EventView.fromSavedQuery({
...saved,
start: '',
});
expect(eventView.isEqualTo(eventView2)).toBe(false);
const eventView3 = EventView.fromSavedQuery({
...saved,
end: '',
});
expect(eventView.isEqualTo(eventView3)).toBe(false);
// this is expected since datetime (start and end) are normalized
expect(eventView2.isEqualTo(eventView3)).toBe(true);
});
it('saved queries with undefined yAxis are defaulted to count() when comparing with isEqualTo', function () {
const saved: SavedQuery = {
orderby: '-count_timestamp',
end: '2019-10-23T19:27:04+0000',
name: 'release query',
fields: ['release', 'count(timestamp)'],
dateCreated: '2019-10-30T05:10:23.718937Z',
environment: ['dev', 'production'],
start: '2019-10-20T21:02:51+0000',
version: 2,
dateUpdated: '2019-10-30T07:25:58.291917Z',
id: '3',
projects: [1],
};
const eventView = EventView.fromSavedQuery(saved);
const eventView2 = EventView.fromSavedQuery({
...saved,
yAxis: ['count()'],
});
expect(eventView.isEqualTo(eventView2)).toBe(true);
});
it('uses the first yAxis from the SavedQuery', function () {
const saved: SavedQuery = {
id: '42',
name: 'best query',
fields: ['count()', 'id'],
query: 'event.type:transaction',
projects: [123],
teams: ['myteams', 1],
range: '14d',
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
orderby: '-id',
environment: ['staging'],
display: 'previous',
dataset: DiscoverDatasets.DISCOVER,
yAxis: ['count()'],
dateCreated: '2019-10-30T06:13:17.632078Z',
dateUpdated: '2019-10-30T06:13:17.632096Z',
version: 2,
};
const eventView = EventView.fromSavedQuery(saved);
expect(eventView).toMatchObject({
id: saved.id,
name: saved.name,
fields: [
{field: 'count()', width: COL_WIDTH_UNDEFINED},
{field: 'id', width: COL_WIDTH_UNDEFINED},
],
sorts: [{field: 'id', kind: 'desc'}],
query: 'event.type:transaction',
project: [123],
team: ['myteams', 1],
start: undefined,
end: undefined,
statsPeriod: '14d',
environment: ['staging'],
yAxis: 'count()',
dataset: DiscoverDatasets.DISCOVER,
display: 'previous',
});
});
it('preserves utc with start/end', function () {
const saved: SavedQuery = {
id: '42',
version: 2,
projects: [123],
name: 'best query',
query: 'event.type:transaction',
fields: ['count()', 'title'],
start: '2019-10-20T21:02:51+0000',
end: '2019-10-23T19:27:04+0000',
utc: 'true',
dateCreated: '2019-10-30T06:13:17.632078Z',
dateUpdated: '2019-10-30T06:13:17.632096Z',
};
const eventView = EventView.fromSavedQuery(saved);
expect(eventView).toMatchObject({
id: saved.id,
name: saved.name,
fields: [
{field: 'count()', width: COL_WIDTH_UNDEFINED},
{field: 'title', width: COL_WIDTH_UNDEFINED},
],
query: 'event.type:transaction',
start: '2019-10-20T21:02:51.000',
end: '2019-10-23T19:27:04.000',
utc: 'true',
});
});
});
describe('EventView.fromNewQueryWithPageFilters()', function () {
const prebuiltQuery: NewQuery = {
id: undefined,
name: 'Page Filter Events',
query: '',
projects: undefined,
fields: ['title', 'project', 'timestamp'],
orderby: '-timestamp',
version: 2,
};
it('maps basic properties of a prebuilt query', function () {
const pageFilters = PageFiltersFixture();
const eventView = EventView.fromNewQueryWithPageFilters(prebuiltQuery, pageFilters);
expect(eventView).toMatchObject({
id: undefined,
name: 'Page Filter Events',
fields: [{field: 'title'}, {field: 'project'}, {field: 'timestamp'}],
sorts: [{field: 'timestamp', kind: 'desc'}],
query: '',
project: [],
start: undefined,
end: undefined,
statsPeriod: '14d',
environment: [],
yAxis: undefined,
});
});
it('merges page filter values', function () {
const pageFilters = PageFiltersFixture({
datetime: {
period: '3d',
start: null,
end: null,
utc: null,
},
projects: [42],
environments: ['prod'],
});
const eventView = EventView.fromNewQueryWithPageFilters(prebuiltQuery, pageFilters);
expect(eventView).toMatchObject({
id: undefined,
name: 'Page Filter Events',
fields: [{field: 'title'}, {field: 'project'}, {field: 'timestamp'}],
sorts: [{field: 'timestamp', kind: 'desc'}],
query: '',
project: [42],
start: undefined,
end: undefined,
statsPeriod: '3d',
environment: ['prod'],
yAxis: undefined,
});
});
});
describe('EventView.fromNewQueryWithLocation()', function () {
const prebuiltQuery: NewQuery = {
id: undefined,
name: 'Sampled Events',
query: '',
projects: [],
fields: ['title', 'event.type', 'project', 'user', 'timestamp'],
orderby: '-timestamp',
version: 2,
};
it('maps basic properties of a prebuilt query', function () {
const location = LocationFixture({
query: {
statsPeriod: '99d',
},
});
const eventView = EventView.fromNewQueryWithLocation(prebuiltQuery, location);
expect(eventView).toMatchObject({
id: undefined,
name: 'Sampled Events',
fields: [
{field: 'title'},
{field: 'event.type'},
{field: 'project'},
{field: 'user'},
{field: 'timestamp'},
],
sorts: [{field: 'timestamp', kind: 'desc'}],
query: '',
project: [],
start: undefined,
end: undefined,
// statsPeriod has precedence
statsPeriod: '99d',
environment: [],
yAxis: undefined,
});
});
it('merges global selection values', function () {
const location = LocationFixture({
query: {
statsPeriod: '99d',
project: ['456'],
environment: ['prod'],
},
});
const eventView = EventView.fromNewQueryWithLocation(prebuiltQuery, location);
expect(eventView).toMatchObject({
id: undefined,
name: 'Sampled Events',
fields: [
{field: 'title'},
{field: 'event.type'},
{field: 'project'},
{field: 'user'},
{field: 'timestamp'},
],
sorts: [{field: 'timestamp', kind: 'desc'}],
query: '',
project: [456],
start: undefined,
end: undefined,
statsPeriod: '99d',
environment: ['prod'],
yAxis: undefined,
});
});
it('new query takes precedence over global selection values', function () {
const location = LocationFixture({
query: {
statsPeriod: '99d',
project: ['456'],
environment: ['prod'],
},
});
const prebuiltQuery2: NewQuery = {
...prebuiltQuery,
range: '42d',
projects: [987],
environment: ['staging'],
};
const eventView = EventView.fromNewQueryWithLocation(prebuiltQuery2, location);
expect(eventView).toMatchObject({
id: undefined,
name: 'Sampled Events',
fields: [
{field: 'title'},
{field: 'event.type'},
{field: 'project'},
{field: 'user'},
{field: 'timestamp'},
],
sorts: [{field: 'timestamp', kind: 'desc'}],
query: '',
project: [987],
start: undefined,
end: undefined,
statsPeriod: '42d',
environment: ['staging'],
yAxis: undefined,
});
// also test start and end
const location2 = LocationFixture({
query: {
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
project: ['456'],
environment: ['prod'],
},
});
const prebuiltQuery3: NewQuery = {
...prebuiltQuery,
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
projects: [987],
environment: ['staging'],
};
const eventView2 = EventView.fromNewQueryWithLocation(prebuiltQuery3, location2);
expect(eventView2).toMatchObject({
id: undefined,
name: 'Sampled Events',
fields: [
{field: 'title'},
{field: 'event.type'},
{field: 'project'},
{field: 'user'},
{field: 'timestamp'},
],
sorts: [{field: 'timestamp', kind: 'desc'}],
query: '',
project: [987],
start: '2019-10-01T00:00:00.000',
end: '2019-10-02T00:00:00.000',
statsPeriod: undefined,
environment: ['staging'],
yAxis: undefined,
});
});
});
describe('EventView.fromSavedQueryOrLocation()', function () {
it('maps basic properties of saved query', function () {
const saved: SavedQuery = {
id: '42',
name: 'best query',
fields: ['count()', 'id'],
query: 'event.type:transaction',
projects: [123],
range: '14d',
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
orderby: '-id',
environment: ['staging'],
display: 'previous',
dataset: DiscoverDatasets.DISCOVER,
dateUpdated: '2019-10-30T06:13:17.632096Z',
dateCreated: '2019-10-30T06:13:17.632078Z',
version: 2,
};
const location = LocationFixture({
query: {
statsPeriod: '14d',
project: ['123'],
team: ['myteams', '1', '2'],
environment: ['staging'],
},
});
const eventView = EventView.fromSavedQueryOrLocation(saved, location);
expect(eventView).toMatchObject({
id: saved.id,
name: saved.name,
fields: [
{field: 'count()', width: COL_WIDTH_UNDEFINED},
{field: 'id', width: COL_WIDTH_UNDEFINED},
],
sorts: [{field: 'id', kind: 'desc'}],
query: 'event.type:transaction',
project: [123],
team: ['myteams', 1, 2],
start: undefined,
end: undefined,
// statsPeriod has precedence
statsPeriod: '14d',
environment: ['staging'],
yAxis: undefined,
display: 'previous',
dataset: DiscoverDatasets.DISCOVER,
});
const savedQuery2: SavedQuery = {...saved, range: undefined};
const location2 = LocationFixture({
query: {
project: ['123'],
environment: ['staging'],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
},
});
const eventView2 = EventView.fromSavedQueryOrLocation(savedQuery2, location2);
expect(eventView2).toMatchObject({
id: saved.id,
name: saved.name,
fields: [
{field: 'count()', width: COL_WIDTH_UNDEFINED},
{field: 'id', width: COL_WIDTH_UNDEFINED},
],
sorts: [{field: 'id', kind: 'desc'}],
query: 'event.type:transaction',
project: [123],
start: '2019-10-01T00:00:00.000',
end: '2019-10-02T00:00:00.000',
statsPeriod: undefined,
environment: ['staging'],
});
});
it('overrides saved query params with location params', function () {
const saved: SavedQuery = {
id: '42',
name: 'best query',
fields: ['count()', 'id'],
query: 'event.type:transaction',
projects: [123],
range: '14d',
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
orderby: '-id',
environment: ['staging'],
display: 'previous',
dataset: DiscoverDatasets.DISCOVER,
dateUpdated: '2019-10-30T06:13:17.632096Z',
dateCreated: '2019-10-30T06:13:17.632078Z',
version: 2,
};
const location = LocationFixture({
query: {
id: '42',
statsPeriod: '7d',
project: ['3'],
},
});
const eventView = EventView.fromSavedQueryOrLocation(saved, location);
expect(eventView).toMatchObject({
id: saved.id,
name: saved.name,
fields: [
{field: 'count()', width: COL_WIDTH_UNDEFINED},
{field: 'id', width: COL_WIDTH_UNDEFINED},
],
sorts: [{field: 'id', kind: 'desc'}],
query: 'event.type:transaction',
project: [3],
start: undefined,
end: undefined,
// statsPeriod has precedence
statsPeriod: '7d',
environment: [],
yAxis: undefined,
display: 'previous',
dataset: DiscoverDatasets.DISCOVER,
});
});
it('maps saved query with no conditions', function () {
const saved: SavedQuery = {
orderby: '-count',
name: 'foo bar',
fields: ['release', 'count()'],
widths: ['111', '222'],
dateCreated: '2019-10-30T06:13:17.632078Z',
query: '',
environment: [],
version: 2,
dateUpdated: '2019-10-30T06:13:17.632096Z',
projects: [123],
id: '5',
yAxis: ['count()'],
};
const location = LocationFixture({
query: {
id: '5',
project: ['1'],
},
});
const eventView = EventView.fromSavedQueryOrLocation(saved, location);
const expected = {
id: '5',
name: 'foo bar',
fields: [
{field: 'release', width: 111},
{field: 'count()', width: 222},
],
sorts: generateSorts([AggregationKey.COUNT]),
query: '',
project: [1],
yAxis: 'count()',
};
expect(eventView).toMatchObject(expected);
});
it('maps query with cleared conditions', function () {
const saved: SavedQuery = {
id: '42',
name: 'best query',
fields: ['count()', 'id'],
query: 'event.type:transaction',
projects: [123],
range: '14d',
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
orderby: '-id',
environment: ['staging'],
display: 'previous',
dateUpdated: '2019-10-30T06:13:17.632096Z',
dateCreated: '2019-10-30T06:13:17.632078Z',
version: 2,
};
const location = LocationFixture({
query: {
id: '42',
statsPeriod: '7d',
},
});
const eventView = EventView.fromSavedQueryOrLocation(saved, location);
expect(eventView).toMatchObject({
id: saved.id,
name: saved.name,
fields: [
{field: 'count()', width: COL_WIDTH_UNDEFINED},
{field: 'id', width: COL_WIDTH_UNDEFINED},
],
sorts: [{field: 'id', kind: 'desc'}],
query: 'event.type:transaction',
start: undefined,
end: undefined,
// statsPeriod has precedence
statsPeriod: '7d',
environment: [],
yAxis: undefined,
display: 'previous',
});
const location2 = LocationFixture({
query: {
id: '42',
statsPeriod: '7d',
query: '',
},
});
const eventView2 = EventView.fromSavedQueryOrLocation(saved, location2);
expect(eventView2).toMatchObject({
id: saved.id,
name: saved.name,
fields: [
{field: 'count()', width: COL_WIDTH_UNDEFINED},
{field: 'id', width: COL_WIDTH_UNDEFINED},
],
sorts: [{field: 'id', kind: 'desc'}],
query: '',
start: undefined,
end: undefined,
// statsPeriod has precedence
statsPeriod: '7d',
environment: [],
yAxis: undefined,
display: 'previous',
});
});
it('event views are equal when start and end datetime differ in format', function () {
const saved: SavedQuery = {
orderby: '-count_timestamp',
end: '2019-10-23T19:27:04+0000',
name: 'release query',
fields: ['release', 'count(timestamp)'],
dateCreated: '2019-10-30T05:10:23.718937Z',
environment: ['dev', 'production'],
start: '2019-10-20T21:02:51+0000',
projects: [123],
version: 2,
dateUpdated: '2019-10-30T07:25:58.291917Z',
id: '3',
};
const location = LocationFixture({
query: {
id: '3',
start: '2019-10-20T21:02:51+0000',
end: '2019-10-23T19:27:04+0000',
},
});
const eventView = EventView.fromSavedQueryOrLocation(saved, location);
const location2 = LocationFixture({
query: {
id: '3',
start: '2019-10-20T21:02:51Z',
end: '2019-10-23T19:27:04Z',
},
});
const eventView2 = EventView.fromSavedQueryOrLocation(saved, location2);
expect(eventView.isEqualTo(eventView2)).toBe(true);
const location3 = LocationFixture({
query: {
id: '3',
start: '2019-10-20T21:02:51Z',
end: '2019-10-23T19:27:04+0000',
},
});
const eventView3 = EventView.fromSavedQueryOrLocation(saved, location3);
expect(eventView.isEqualTo(eventView3)).toBe(true);
const location4 = LocationFixture({
query: {
id: '3',
start: '2019-10-20T21:02:51+0000',
end: '2019-10-23T19:27:04Z',
},
});
const eventView4 = EventView.fromSavedQueryOrLocation(saved, location4);
expect(eventView.isEqualTo(eventView4)).toBe(true);
});
it('event views are not equal when datetime selection are invalid', function () {
const saved: SavedQuery = {
orderby: '-count_timestamp',
end: '2019-10-23T19:27:04+0000',
name: 'release query',
fields: ['release', 'count(timestamp)'],
dateCreated: '2019-10-30T05:10:23.718937Z',
environment: ['dev', 'production'],
start: '2019-10-20T21:02:51+0000',
version: 2,
dateUpdated: '2019-10-30T07:25:58.291917Z',
id: '3',
projects: [1],
};
const location = LocationFixture({
query: {
id: '3',
end: '2019-10-23T19:27:04+0000',
start: '2019-10-20T21:02:51+0000',
},
});
const eventView = EventView.fromSavedQueryOrLocation(saved, location);
const location2 = LocationFixture({
query: {
id: '3',
end: '2019-10-23T19:27:04+0000',
start: '',
},
});
const eventView2 = EventView.fromSavedQueryOrLocation(saved, location2);
expect(eventView.isEqualTo(eventView2)).toBe(false);
const location3 = LocationFixture({
query: {
id: '3',
end: '',
start: '2019-10-20T21:02:51+0000',
},
});
const eventView3 = EventView.fromSavedQueryOrLocation(saved, location3);
expect(eventView.isEqualTo(eventView3)).toBe(false);
// this is expected since datetime (start and end) are normalized
expect(eventView2.isEqualTo(eventView3)).toBe(true);
});
it('uses the first yAxis from the SavedQuery', function () {
const saved: SavedQuery = {
id: '42',
name: 'best query',
fields: ['count()', 'id'],
query: 'event.type:transaction',
projects: [123],
range: '14d',
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
orderby: '-id',
environment: ['staging'],
display: 'previous',
yAxis: ['count()', 'failure_count()'],
dateCreated: '2019-10-30T05:10:23.718937Z',
dateUpdated: '2019-10-30T07:25:58.291917Z',
version: 2,
};
const location = LocationFixture({
query: {
statsPeriod: '14d',
project: ['123'],
team: ['myteams', '1', '2'],
environment: ['staging'],
},
});
const eventView = EventView.fromSavedQueryOrLocation(saved, location);
expect(eventView).toMatchObject({
id: saved.id,
name: saved.name,
fields: [
{field: 'count()', width: COL_WIDTH_UNDEFINED},
{field: 'id', width: COL_WIDTH_UNDEFINED},
],
sorts: [{field: 'id', kind: 'desc'}],
query: 'event.type:transaction',
project: [123],
team: ['myteams', 1, 2],
start: undefined,
end: undefined,
statsPeriod: '14d',
environment: ['staging'],
yAxis: 'count()',
display: 'previous',
});
});
it('filters out invalid teams', function () {
const eventView = EventView.fromSavedQueryOrLocation(
undefined,
LocationFixture({
query: {
statsPeriod: '14d',
project: ['123'],
team: ['myteams', '1', 'unassigned'],
environment: ['staging'],
},
})
);
expect(eventView.team).toEqual(['myteams', 1]);
});
});
describe('EventView.generateQueryStringObject()', function () {
it('skips empty values', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: generateFields(['id', 'title']),
sorts: [],
project: [],
environment: [],
statsPeriod: '',
start: undefined,
end: undefined,
yAxis: undefined,
display: 'previous',
});
const expected = {
id: undefined,
name: undefined,
field: ['id', 'title'],
widths: [],
sort: [],
query: '',
project: [],
environment: [],
display: 'previous',
yAxis: 'count()',
};
expect(eventView.generateQueryStringObject()).toEqual(expected);
});
it('generates query string object', function () {
const state: ConstructorParameters<typeof EventView>[0] = {
...REQUIRED_CONSTRUCTOR_PROPS,
id: '1234',
name: 'best query',
fields: [
{field: 'count()', width: 123},
{field: 'project.id', width: 456},
],
sorts: generateSorts([AggregationKey.COUNT]),
query: 'event.type:error',
project: [42],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
statsPeriod: '14d',
environment: ['staging'],
yAxis: 'count()',
display: 'releases',
interval: '1m',
};
const eventView = new EventView(state);
const expected = {
id: '1234',
name: 'best query',
field: ['count()', 'project.id'],
widths: [123, 456],
sort: ['-count'],
query: 'event.type:error',
project: [42],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
statsPeriod: '14d',
environment: ['staging'],
yAxis: 'count()',
display: 'releases',
interval: '1m',
};
expect(eventView.generateQueryStringObject()).toEqual(expected);
});
it('encodes fields', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: [{field: 'id'}, {field: 'title'}],
sorts: [],
});
const query = eventView.generateQueryStringObject();
expect(query.field).toEqual(['id', 'title']);
});
it('returns a copy of data preventing mutation', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: [{field: 'id'}, {field: 'title'}],
sorts: [],
});
const query = eventView.generateQueryStringObject();
if (Array.isArray(query.field)) {
query.field.push('newthing');
}
// Getting the query again should return the original values.
const secondQuery = eventView.generateQueryStringObject();
expect(secondQuery.field).toEqual(['id', 'title']);
expect(query).not.toEqual(secondQuery);
});
});
describe('EventView.getEventsAPIPayload()', function () {
it('generates the API payload', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
id: '34',
name: 'amazing query',
fields: generateFields(['id']),
sorts: generateSorts(['id']),
query: 'event.type:csp',
project: [567],
environment: ['prod'],
yAxis: 'users',
display: 'releases',
});
expect(eventView.getEventsAPIPayload(LocationFixture())).toEqual({
field: ['id'],
per_page: 50,
sort: '-id',
query: 'event.type:csp',
project: ['567'],
environment: ['prod'],
statsPeriod: '14d',
});
});
it('does not append query conditions in location', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: generateFields(['id']),
sorts: [],
query: 'event.type:csp',
});
const location = LocationFixture({
query: {
query: 'TypeError',
},
});
expect(eventView.getEventsAPIPayload(location).query).toEqual('event.type:csp');
});
it('only includes at most one sort key', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: generateFields(['count()', 'title']),
sorts: generateSorts(['title', AggregationKey.COUNT]),
query: 'event.type:csp',
});
const location = LocationFixture({
query: {},
});
expect(eventView.getEventsAPIPayload(location).sort).toEqual('-title');
});
it('only includes sort keys that are defined in fields', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: generateFields(['title', 'count()']),
sorts: generateSorts(['project', AggregationKey.COUNT]),
query: 'event.type:csp',
});
const location = LocationFixture({
query: {},
});
expect(eventView.getEventsAPIPayload(location).sort).toEqual('-count');
});
it('only includes relevant query strings', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: generateFields(['title', 'count()']),
sorts: generateSorts(['project', AggregationKey.COUNT]),
query: 'event.type:csp',
});
const location = LocationFixture({
query: {
start: '2020-08-12 12:13:14',
end: '2020-08-26 12:13:14',
utc: 'true',
statsPeriod: '14d',
cursor: 'some cursor',
yAxis: 'count()',
// irrelevant query strings
bestCountry: 'canada',
project: '1234',
environment: ['staging'],
},
});
expect(eventView.getEventsAPIPayload(location)).toEqual({
project: [],
environment: [],
utc: 'true',
statsPeriod: '14d',
field: ['title', 'count()'],
per_page: 50,
query: 'event.type:csp',
sort: '-count',
cursor: 'some cursor',
});
});
it('includes default coerced statsPeriod when omitted or is invalid', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: generateFields(['title', 'count()']),
sorts: generateSorts(['project', AggregationKey.COUNT]),
query: 'event.type:csp',
project: [1234],
environment: ['staging'],
});
const location = LocationFixture({
query: {
start: '',
end: '',
utc: 'true',
// invalid statsPeriod string
statsPeriod: 'invalid',
cursor: 'some cursor',
},
});
expect(eventView.getEventsAPIPayload(location)).toEqual({
project: ['1234'],
environment: ['staging'],
utc: 'true',
statsPeriod: '14d',
field: ['title', 'count()'],
per_page: 50,
query: 'event.type:csp',
sort: '-count',
cursor: 'some cursor',
});
const location2 = LocationFixture({
query: {
start: '',
end: '',
utc: 'true',
// statsPeriod is omitted here
cursor: 'some cursor',
},
});
expect(eventView.getEventsAPIPayload(location2)).toEqual({
project: ['1234'],
environment: ['staging'],
utc: 'true',
statsPeriod: '14d',
field: ['title', 'count()'],
per_page: 50,
query: 'event.type:csp',
sort: '-count',
cursor: 'some cursor',
});
});
it('includes default coerced statsPeriod when either start or end is only provided', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: generateFields(['title', 'count()']),
sorts: generateSorts(['project', AggregationKey.COUNT]),
query: 'event.type:csp',
project: [1234],
environment: ['staging'],
});
const location = LocationFixture({
query: {
start: '',
utc: 'true',
statsPeriod: 'invalid',
cursor: 'some cursor',
},
});
expect(eventView.getEventsAPIPayload(location)).toEqual({
project: ['1234'],
environment: ['staging'],
utc: 'true',
statsPeriod: '14d',
field: ['title', 'count()'],
per_page: 50,
query: 'event.type:csp',
sort: '-count',
cursor: 'some cursor',
});
const location2 = LocationFixture({
query: {
end: '',
utc: 'true',
statsPeriod: 'invalid',
cursor: 'some cursor',
},
});
expect(eventView.getEventsAPIPayload(location2)).toEqual({
project: ['1234'],
environment: ['staging'],
utc: 'true',
statsPeriod: '14d',
field: ['title', 'count()'],
per_page: 50,
query: 'event.type:csp',
sort: '-count',
cursor: 'some cursor',
});
});
it('includes start and end', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: generateFields(['title', 'count()']),
sorts: generateSorts([AggregationKey.COUNT]),
query: 'event.type:csp',
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
environment: [],
project: [],
});
const location = LocationFixture({
query: {
// these should not be part of the API payload
statsPeriod: '55d',
period: '55d',
},
});
expect(eventView.getEventsAPIPayload(location)).toEqual({
field: ['title', 'count()'],
sort: '-count',
query: 'event.type:csp',
start: '2019-10-01T00:00:00.000',
end: '2019-10-02T00:00:00.000',
per_page: 50,
project: [],
environment: [],
});
});
it("an eventview's date selection has higher precedence than the date selection in the query string", function () {
const initialState = {
fields: generateFields(['title', 'count()']),
sorts: generateSorts([AggregationKey.COUNT]),
query: 'event.type:csp',
environment: [],
project: [],
};
const output = {
field: ['title', 'count()'],
sort: '-count',
query: 'event.type:csp',
per_page: 50,
project: [],
environment: [],
};
// eventview's statsPeriod has highest precedence
let eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
...initialState,
statsPeriod: '90d',
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
});
let location = LocationFixture({
query: {
// these should not be part of the API payload
statsPeriod: '55d',
period: '30d',
start: '2020-10-01T00:00:00',
end: '2020-10-02T00:00:00',
},
});
expect(eventView.getEventsAPIPayload(location)).toEqual({
...output,
statsPeriod: '90d',
});
// eventview's start/end has higher precedence than the date selection in the query string
eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
...initialState,
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
});
location = LocationFixture({
query: {
// these should not be part of the API payload
statsPeriod: '55d',
period: '30d',
start: '2020-10-01T00:00:00',
end: '2020-10-02T00:00:00',
},
});
expect(eventView.getEventsAPIPayload(location)).toEqual({
...output,
start: '2019-10-01T00:00:00.000',
end: '2019-10-02T00:00:00.000',
});
// the date selection in the query string should be applied as expected
eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
...initialState,
});
location = LocationFixture({
query: {
statsPeriod: '55d',
period: '30d',
start: '2020-10-01T00:00:00',
end: '2020-10-02T00:00:00',
},
});
expect(eventView.getEventsAPIPayload(location)).toEqual({
...output,
statsPeriod: '55d',
});
location = LocationFixture({
query: {
period: '30d',
start: '2020-10-01T00:00:00',
end: '2020-10-02T00:00:00',
},
});
expect(eventView.getEventsAPIPayload(location)).toEqual({
...output,
statsPeriod: '30d',
});
location = LocationFixture({
query: {
start: '2020-10-01T00:00:00',
end: '2020-10-02T00:00:00',
},
});
expect(eventView.getEventsAPIPayload(location)).toEqual({
...output,
start: '2020-10-01T00:00:00.000',
end: '2020-10-02T00:00:00.000',
});
});
});
describe('EventView.getFacetsAPIPayload()', function () {
it('only includes relevant query strings', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: generateFields(['title', 'count()']),
sorts: generateSorts(['project', AggregationKey.COUNT]),
query: 'event.type:csp',
});
const location = LocationFixture({
query: {
start: '',
end: '',
utc: 'true',
statsPeriod: '14d',
// irrelevant query strings
bestCountry: 'canada',
cursor: 'some cursor',
sort: 'the world',
project: '1234',
environment: ['staging'],
display: 'releases',
},
});
expect(eventView.getFacetsAPIPayload(location)).toEqual({
project: [],
environment: [],
utc: 'true',
statsPeriod: '14d',
query: 'event.type:csp',
});
});
});
describe('EventView.toNewQuery()', function () {
const state: ConstructorParameters<typeof EventView>[0] = {
...REQUIRED_CONSTRUCTOR_PROPS,
id: '1234',
name: 'best query',
fields: [
{field: 'count()', width: 123},
{field: 'project.id', width: 456},
],
sorts: generateSorts([AggregationKey.COUNT]),
query: 'event.type:error',
project: [42],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
statsPeriod: '14d',
environment: ['staging'],
display: 'releases',
dataset: DiscoverDatasets.DISCOVER,
};
it('outputs the right fields', function () {
const eventView = new EventView(state);
const output = eventView.toNewQuery();
const expected = {
version: 2,
id: '1234',
name: 'best query',
fields: ['count()', 'project.id'],
widths: ['123', '456'],
orderby: '-count',
query: 'event.type:error',
projects: [42],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
range: '14d',
environment: ['staging'],
display: 'releases',
dataset: DiscoverDatasets.DISCOVER,
};
expect(output).toEqual(expected);
});
it('omits query when query is an empty string', function () {
const modifiedState: ConstructorParameters<typeof EventView>[0] = {
...state,
};
modifiedState.query = '';
const eventView = new EventView(modifiedState);
const output = eventView.toNewQuery();
const expected = {
version: 2,
id: '1234',
name: 'best query',
fields: ['count()', 'project.id'],
widths: ['123', '456'],
orderby: '-count',
projects: [42],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
range: '14d',
environment: ['staging'],
display: 'releases',
dataset: DiscoverDatasets.DISCOVER,
};
expect(output).toEqual(expected);
});
it('omits query when query is not defined', function () {
const modifiedState: ConstructorParameters<typeof EventView>[0] = {
...state,
};
modifiedState.query = '';
const eventView = new EventView(modifiedState);
const output = eventView.toNewQuery();
const expected = {
version: 2,
id: '1234',
name: 'best query',
fields: ['count()', 'project.id'],
widths: ['123', '456'],
orderby: '-count',
projects: [42],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
range: '14d',
environment: ['staging'],
display: 'releases',
dataset: DiscoverDatasets.DISCOVER,
};
expect(output).toEqual(expected);
});
});
describe('EventView.isValid()', function () {
it('event view is valid when there is at least one field', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: [{field: 'count()'}, {field: 'project.id'}],
sorts: [],
project: [],
});
expect(eventView.isValid()).toBe(true);
});
it('event view is not valid when there are no fields', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: [],
sorts: [],
project: [],
});
expect(eventView.isValid()).toBe(false);
});
});
describe('EventView.getWidths()', function () {
it('returns widths', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: [
{field: 'count()', width: COL_WIDTH_UNDEFINED},
{field: 'project.id', width: 2020},
{field: 'title', width: COL_WIDTH_UNDEFINED},
{field: 'time', width: 420},
{field: 'lcp', width: 69},
{field: 'lcp', width: COL_WIDTH_UNDEFINED},
{field: 'fcp', width: COL_WIDTH_UNDEFINED},
{field: 'cls', width: COL_WIDTH_UNDEFINED},
],
sorts: [],
project: [],
});
expect(eventView.getWidths()).toEqual([
COL_WIDTH_UNDEFINED,
2020,
COL_WIDTH_UNDEFINED,
420,
69,
]);
});
});
describe('EventView.getFields()', function () {
it('returns fields', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: [{field: 'count()'}, {field: 'project.id'}],
sorts: [],
project: [],
});
expect(eventView.getFields()).toEqual(['count()', 'project.id']);
});
});
describe('EventView.numOfColumns()', function () {
it('returns correct number of columns', function () {
// has columns
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: [{field: 'count()'}, {field: 'project.id'}],
sorts: [],
project: [],
});
expect(eventView.numOfColumns()).toBe(2);
// has no columns
const eventView2 = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: [],
sorts: [],
project: [],
});
expect(eventView2.numOfColumns()).toBe(0);
});
});
describe('EventView.getDays()', function () {
it('returns the right number of days for statsPeriod', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
statsPeriod: '14d',
});
expect(eventView.getDays()).toBe(14);
const eventView2 = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
statsPeriod: '12h',
});
expect(eventView2.getDays()).toBe(0.5);
});
it('returns the right number of days for start/end', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
});
expect(eventView.getDays()).toBe(1);
const eventView2 = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
start: '2019-10-01T00:00:00',
end: '2019-10-15T00:00:00',
});
expect(eventView2.getDays()).toBe(14);
});
});
describe('EventView.clone()', function () {
it('returns a unique instance', function () {
const state: ConstructorParameters<typeof EventView>[0] = {
...REQUIRED_CONSTRUCTOR_PROPS,
id: '1234',
name: 'best query',
fields: [{field: 'count()'}, {field: 'project.id'}],
sorts: generateSorts([AggregationKey.COUNT]),
query: 'event.type:error',
project: [42],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
statsPeriod: '14d',
environment: ['staging'],
interval: '5m',
display: 'releases',
dataset: DiscoverDatasets.DISCOVER,
};
const eventView = new EventView(state);
const eventView2 = eventView.clone();
expect(eventView2 !== eventView).toBeTruthy();
expect(eventView).toMatchObject(state);
expect(eventView2).toMatchObject(state);
expect(eventView.isEqualTo(eventView2)).toBe(true);
expect(
eventView.additionalConditions === eventView2.additionalConditions
).toBeFalsy();
});
});
describe('EventView.withColumns()', function () {
const state: ConstructorParameters<typeof EventView>[0] = {
...REQUIRED_CONSTRUCTOR_PROPS,
id: '1234',
name: 'best query',
fields: [
{field: 'count()', width: 30},
{field: 'project.id', width: 99},
{field: 'failure_count()', width: 30},
],
yAxis: 'failure_count()',
sorts: generateSorts([AggregationKey.COUNT]),
query: 'event.type:error',
project: [42],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
statsPeriod: '14d',
environment: ['staging'],
};
const eventView = new EventView(state);
it('adds new columns, and replaces existing ones', function () {
const newView = eventView.withColumns([
{kind: 'field', field: 'title'},
{kind: 'function', function: [AggregationKey.COUNT, '', undefined, undefined]},
{kind: 'field', field: 'project.id'},
{kind: 'field', field: 'culprit'},
]);
// Views should be different.
expect(newView.isEqualTo(eventView)).toBe(false);
expect(newView.fields).toEqual([
{field: 'title', width: COL_WIDTH_UNDEFINED},
{field: 'count()', width: COL_WIDTH_UNDEFINED},
{field: 'project.id', width: COL_WIDTH_UNDEFINED},
{field: 'culprit', width: COL_WIDTH_UNDEFINED},
]);
});
it('drops empty columns', function () {
const newView = eventView.withColumns([
{kind: 'field', field: 'issue'},
{kind: 'function', function: [AggregationKey.COUNT, '', undefined, undefined]},
{kind: 'field', field: ''},
{kind: 'function', function: ['', '', undefined, undefined]},
{kind: 'function', function: ['', '', undefined, undefined]},
]);
expect(newView.fields).toEqual([
{field: 'issue', width: COL_WIDTH_UNDEFINED},
{field: 'count()', width: COL_WIDTH_UNDEFINED},
]);
});
it('inherits widths from existing columns when names match', function () {
const newView = eventView.withColumns([
{kind: 'function', function: [AggregationKey.COUNT, '', undefined, undefined]},
{kind: 'field', field: 'project.id'},
{kind: 'field', field: 'title'},
{kind: 'field', field: 'time'},
]);
expect(newView.fields).toEqual([
{field: 'count()', width: 30},
{field: 'project.id', width: 99},
{field: 'title', width: COL_WIDTH_UNDEFINED},
{field: 'time', width: COL_WIDTH_UNDEFINED},
]);
});
it('retains sorts when sorted field is included', function () {
const newView = eventView.withColumns([
{kind: 'field', field: 'title'},
{kind: 'function', function: [AggregationKey.COUNT, '', undefined, undefined]},
]);
expect(newView.fields).toEqual([
{field: 'title', width: COL_WIDTH_UNDEFINED},
{field: 'count()', width: COL_WIDTH_UNDEFINED},
]);
expect(newView.sorts).toEqual([{field: AggregationKey.COUNT, kind: 'desc'}]);
});
it('updates sorts when sorted field is removed', function () {
const newView = eventView.withColumns([{kind: 'field', field: 'title'}]);
expect(newView.fields).toEqual([{field: 'title', width: COL_WIDTH_UNDEFINED}]);
// Should pick a sortable field.
expect(newView.sorts).toEqual([{field: 'title', kind: 'desc'}]);
});
it('has no sort if no sortable fields remain', function () {
const newView = eventView.withColumns([{kind: 'field', field: 'issue'}]);
expect(newView.fields).toEqual([{field: 'issue', width: COL_WIDTH_UNDEFINED}]);
expect(newView.sorts).toEqual([]);
});
it('updates yAxis if column is dropped', function () {
const newView = eventView.withColumns([
{kind: 'field', field: 'count()'},
{kind: 'field', field: 'project.id'},
]);
expect(newView.fields).toEqual([
{field: 'count()', width: 30},
{field: 'project.id', width: 99},
]);
expect(eventView.yAxis).toEqual('failure_count()');
expect(newView.yAxis).toEqual('count()');
});
});
describe('EventView.withNewColumn()', function () {
const state: ConstructorParameters<typeof EventView>[0] = {
...REQUIRED_CONSTRUCTOR_PROPS,
id: '1234',
name: 'best query',
fields: [
{field: 'count()', width: 30},
{field: 'project.id', width: 99},
],
sorts: generateSorts([AggregationKey.COUNT]),
query: 'event.type:error',
project: [42],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
statsPeriod: '14d',
environment: ['staging'],
};
it('adds a field', function () {
const eventView = new EventView(state);
const newColumn: Column = {
kind: 'field',
field: 'title',
};
const eventView2 = eventView.withNewColumn(newColumn);
expect(eventView2 !== eventView).toBeTruthy();
expect(eventView).toMatchObject(state);
const nextState = {
...state,
fields: [...state.fields, {field: 'title'}],
};
expect(eventView2).toMatchObject(nextState);
});
it('adds an aggregate function with no arguments', function () {
const eventView = new EventView(state);
const newColumn: Column = {
kind: 'function',
function: [AggregationKey.COUNT, '', undefined, undefined],
};
const eventView2 = eventView.withNewColumn(newColumn);
expect(eventView2 !== eventView).toBeTruthy();
expect(eventView).toMatchObject(state);
const nextState = {
...state,
fields: [...state.fields, {field: 'count()'}],
};
expect(eventView2).toMatchObject(nextState);
});
it('add an aggregate function with field', function () {
const eventView = new EventView(state);
const newColumn: Column = {
kind: 'function',
function: [AggregationKey.AVG, 'transaction.duration', undefined, undefined],
};
const eventView2 = eventView.withNewColumn(newColumn);
expect(eventView2 !== eventView).toBeTruthy();
expect(eventView).toMatchObject(state);
const nextState = {
...state,
fields: [...state.fields, {field: 'avg(transaction.duration)'}],
};
expect(eventView2).toMatchObject(nextState);
});
it('add an aggregate function with field & refinement', function () {
const eventView = new EventView(state);
const newColumn: Column = {
kind: 'function',
function: [AggregationKey.PERCENTILE, 'transaction.duration', '0.5', undefined],
};
const updated = eventView.withNewColumn(newColumn);
expect(updated.fields).toEqual([
...state.fields,
{field: 'percentile(transaction.duration,0.5)', width: COL_WIDTH_UNDEFINED},
]);
});
});
describe('EventView.withResizedColumn()', function () {
const state: ConstructorParameters<typeof EventView>[0] = {
...REQUIRED_CONSTRUCTOR_PROPS,
id: '1234',
name: 'best query',
fields: [{field: 'count()'}, {field: 'project.id'}],
sorts: generateSorts([AggregationKey.COUNT]),
query: 'event.type:error',
project: [42],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
statsPeriod: '14d',
environment: ['staging'],
};
const view = new EventView(state);
it('updates a column that exists', function () {
const newView = view.withResizedColumn(0, 99);
expect(view.fields[0].width).toBeUndefined();
expect(newView.fields[0].width).toEqual(99);
});
it('ignores columns that do not exist', function () {
const newView = view.withResizedColumn(100, 99);
expect(view.fields).toEqual(newView.fields);
});
});
describe('EventView.withUpdatedColumn()', function () {
const state: ConstructorParameters<typeof EventView>[0] = {
...REQUIRED_CONSTRUCTOR_PROPS,
id: '1234',
name: 'best query',
fields: [{field: 'count()'}, {field: 'project.id'}],
sorts: generateSorts([AggregationKey.COUNT]),
query: 'event.type:error',
project: [42],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
statsPeriod: '14d',
environment: ['staging'],
};
const meta: MetaType = {
count: 'integer',
title: 'string',
};
it('update a column with no changes', function () {
const eventView = new EventView(state);
const newColumn: Column = {
kind: 'function',
function: [AggregationKey.COUNT, '', undefined, undefined],
};
const eventView2 = eventView.withUpdatedColumn(0, newColumn, meta);
expect(eventView2 === eventView).toBeTruthy();
expect(eventView).toMatchObject(state);
});
it('update a column to a field', function () {
const eventView = new EventView(state);
const newColumn: Column = {
kind: 'field',
field: 'title',
};
const eventView2 = eventView.withUpdatedColumn(1, newColumn, meta);
expect(eventView2 !== eventView).toBeTruthy();
expect(eventView).toMatchObject(state);
const nextState = {
...state,
fields: [state.fields[0], {field: 'title'}],
};
expect(eventView2).toMatchObject(nextState);
});
it('update a column to an aggregate function with no arguments', function () {
const eventView = new EventView(state);
const newColumn: Column = {
kind: 'function',
function: [AggregationKey.COUNT, '', undefined, undefined],
};
const eventView2 = eventView.withUpdatedColumn(1, newColumn, meta);
expect(eventView2 !== eventView).toBeTruthy();
expect(eventView).toMatchObject(state);
const nextState = {
...state,
fields: [state.fields[0], {field: 'count()'}],
};
expect(eventView2).toMatchObject(nextState);
});
it('update a column to an aggregate function with field', function () {
const eventView = new EventView(state);
const newColumn: Column = {
kind: 'function',
function: [AggregationKey.AVG, 'transaction.duration', undefined, undefined],
};
const eventView2 = eventView.withUpdatedColumn(1, newColumn, meta);
expect(eventView2 !== eventView).toBeTruthy();
expect(eventView).toMatchObject(state);
const nextState = {
...state,
fields: [state.fields[0], {field: 'avg(transaction.duration)'}],
};
expect(eventView2).toMatchObject(nextState);
});
it('update a column to an aggregate function with field & refinement', function () {
const eventView = new EventView(state);
const newColumn: Column = {
kind: 'function',
function: [AggregationKey.PERCENTILE, 'transaction.duration', '0.5', undefined],
};
const newView = eventView.withUpdatedColumn(1, newColumn, meta);
expect(newView.fields).toEqual([
state.fields[0],
{field: 'percentile(transaction.duration,0.5)', width: COL_WIDTH_UNDEFINED},
]);
});
describe('update a column that is sorted', function () {
it('the sorted column is the only sorted column', function () {
const eventView = new EventView(state);
const newColumn: Column = {
kind: 'field',
field: 'title',
};
const eventView2 = eventView.withUpdatedColumn(0, newColumn, meta);
expect(eventView2 !== eventView).toBeTruthy();
expect(eventView).toMatchObject(state);
const nextState = {
...state,
sorts: [{field: 'title', kind: 'desc'}],
fields: [{field: 'title'}, state.fields[1]],
};
expect(eventView2).toMatchObject(nextState);
});
it('the sorted column occurs at least twice', function () {
const modifiedState: ConstructorParameters<typeof EventView>[0] = {
...state,
fields: [...state.fields, {field: 'count()'}],
};
const eventView = new EventView(modifiedState);
const newColumn: Column = {
kind: 'field',
field: 'title',
};
const eventView2 = eventView.withUpdatedColumn(0, newColumn, meta);
expect(eventView2 !== eventView).toBeTruthy();
expect(eventView).toMatchObject(modifiedState);
const nextState = {
...state,
fields: [{field: 'title'}, state.fields[1], {field: 'count()'}],
};
expect(eventView2).toMatchObject(nextState);
});
it('using no provided table meta', function () {
// table meta may not be provided in the invalid query state;
// we will still want to be able to update columns
const eventView = new EventView(state);
const expected = {
...state,
sorts: [{field: 'title', kind: 'desc'}],
fields: [{field: 'title'}, state.fields[1]],
};
const newColumn: Column = {
kind: 'field',
field: 'title',
};
const eventView2 = eventView.withUpdatedColumn(0, newColumn, {});
expect(eventView2).toMatchObject(expected);
const eventView3 = eventView.withUpdatedColumn(0, newColumn, undefined);
expect(eventView3).toMatchObject(expected);
});
});
describe('update a column to a non-sortable column', function () {
it('default to a sortable column', function () {
const modifiedState: ConstructorParameters<typeof EventView>[0] = {
...state,
fields: [{field: 'count()'}, {field: 'title'}],
};
const eventView = new EventView(modifiedState);
// this column is expected to be non-sortable
const newColumn: Column = {
kind: 'field',
field: 'project.id',
};
const eventView2 = eventView.withUpdatedColumn(0, newColumn, meta);
expect(eventView2 !== eventView).toBeTruthy();
expect(eventView).toMatchObject(modifiedState);
const nextState = {
...state,
sorts: [{field: 'title', kind: 'desc'}],
fields: [{field: 'project.id'}, {field: 'title'}],
};
expect(eventView2).toMatchObject(nextState);
});
it('has no sort if there are no sortable columns', function () {
const modifiedState: ConstructorParameters<typeof EventView>[0] = {
...state,
fields: [{field: 'count()'}],
};
const eventView = new EventView(modifiedState);
// this column is expected to be non-sortable
const newColumn: Column = {
kind: 'field',
field: 'project.id',
};
const eventView2 = eventView.withUpdatedColumn(0, newColumn, meta);
expect(eventView2 !== eventView).toBeTruthy();
expect(eventView).toMatchObject(modifiedState);
const nextState = {
...state,
sorts: [],
fields: [{field: 'project.id'}],
};
expect(eventView2).toMatchObject(nextState);
});
});
});
describe('EventView.withDeletedColumn()', function () {
const state: ConstructorParameters<typeof EventView>[0] = {
...REQUIRED_CONSTRUCTOR_PROPS,
id: '1234',
name: 'best query',
fields: [{field: 'count()'}, {field: 'project.id'}],
sorts: generateSorts([AggregationKey.COUNT]),
query: 'event.type:error',
project: [42],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
statsPeriod: '14d',
environment: ['staging'],
};
const meta: MetaType = {
count: 'integer',
title: 'string',
};
it('returns itself when attempting to delete the last remaining column', function () {
const modifiedState: ConstructorParameters<typeof EventView>[0] = {
...state,
fields: [{field: 'count()'}],
};
const eventView = new EventView(modifiedState);
const eventView2 = eventView.withDeletedColumn(0, meta);
expect(eventView2 === eventView).toBeTruthy();
expect(eventView).toMatchObject(modifiedState);
});
describe('deletes column, and use any remaining sortable column', function () {
it('using no provided table meta', function () {
// table meta may not be provided in the invalid query state;
// we will still want to be able to delete columns
const state2 = {
...state,
fields: [{field: 'title'}, {field: 'timestamp'}, {field: 'count()'}],
sorts: generateSorts(['timestamp']),
};
const eventView = new EventView(state2);
const expected = {
...state,
sorts: generateSorts(['title']),
fields: [{field: 'title'}, {field: 'count()'}],
};
const eventView2 = eventView.withDeletedColumn(1, {});
expect(eventView2).toMatchObject(expected);
const eventView3 = eventView.withDeletedColumn(1, undefined);
expect(eventView3).toMatchObject(expected);
});
it('has no remaining sortable column', function () {
const eventView = new EventView(state);
const eventView2 = eventView.withDeletedColumn(0, meta);
expect(eventView2 !== eventView).toBeTruthy();
expect(eventView).toMatchObject(state);
const nextState = {
...state,
// we expect sorts to be empty since project.id is non-sortable
sorts: [],
fields: [state.fields[1]],
};
expect(eventView2).toMatchObject(nextState);
});
it('has a remaining sortable column', function () {
const modifiedState: ConstructorParameters<typeof EventView>[0] = {
...state,
fields: [{field: 'count()'}, {field: 'project.id'}, {field: 'title'}],
};
const eventView = new EventView(modifiedState);
const eventView2 = eventView.withDeletedColumn(0, meta);
expect(eventView2 !== eventView).toBeTruthy();
expect(eventView).toMatchObject(modifiedState);
const nextState = {
...state,
sorts: [{field: 'title', kind: 'desc'}],
fields: [{field: 'project.id'}, {field: 'title'}],
};
expect(eventView2).toMatchObject(nextState);
});
it('sorted column occurs at least twice', function () {
const modifiedState: ConstructorParameters<typeof EventView>[0] = {
...state,
fields: [...state.fields, state.fields[0]],
};
const eventView = new EventView(modifiedState);
const eventView2 = eventView.withDeletedColumn(0, meta);
expect(eventView2 !== eventView).toBeTruthy();
expect(eventView).toMatchObject(modifiedState);
const nextState = {
...state,
fields: [state.fields[1], state.fields[0]],
};
expect(eventView2).toMatchObject(nextState);
});
it('ensures there is at one auto-width column on deletion', function () {
const modifiedState: ConstructorParameters<typeof EventView>[0] = {
...state,
fields: [
{field: 'id', width: 75},
{field: 'title', width: 100},
{field: 'project', width: 80},
{field: 'environment', width: 99},
],
};
const eventView = new EventView(modifiedState);
let updated = eventView.withDeletedColumn(0, meta);
let updatedFields = [
{field: 'title', width: -1},
{field: 'project', width: 80},
{field: 'environment', width: 99},
];
expect(updated.fields).toEqual(updatedFields);
updated = updated.withDeletedColumn(0, meta);
updatedFields = [
{field: 'project', width: -1},
{field: 'environment', width: 99},
];
expect(updated.fields).toEqual(updatedFields);
});
});
});
describe('EventView.getSorts()', function () {
it('returns fields', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: [{field: 'count()'}, {field: 'project.id'}],
sorts: generateSorts([AggregationKey.COUNT]),
project: [],
});
expect(eventView.getSorts()).toEqual([
{
key: AggregationKey.COUNT,
order: 'desc',
},
]);
});
});
describe('EventView.getQuery()', function () {
it('with query', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: [],
sorts: [],
project: [],
query: 'event.type:error',
});
expect(eventView.getQuery()).toEqual('event.type:error');
expect(eventView.getQuery(null)).toEqual('event.type:error');
expect(eventView.getQuery('hello')).toEqual('event.type:error hello');
expect(eventView.getQuery(['event.type:error', 'hello'])).toEqual(
'event.type:error hello'
);
});
it('without query', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: [],
sorts: [],
project: [],
});
expect(eventView.getQuery()).toEqual('');
expect(eventView.getQuery(null)).toEqual('');
expect(eventView.getQuery('hello')).toEqual('hello');
expect(eventView.getQuery(['event.type:error', 'hello'])).toEqual(
'event.type:error hello'
);
});
});
describe('EventView.getQueryWithAdditionalConditions', function () {
it('with overlapping conditions', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: [],
sorts: [],
project: [],
query: 'event.type:transaction foo:bar',
});
eventView.additionalConditions.setFilterValues('event.type', ['transaction']);
expect(eventView.getQueryWithAdditionalConditions()).toEqual(
'event.type:transaction foo:bar'
);
});
});
describe('EventView.sortForField()', function () {
const state: ConstructorParameters<typeof EventView>[0] = {
...REQUIRED_CONSTRUCTOR_PROPS,
id: '1234',
name: 'best query',
fields: [{field: 'count()'}, {field: 'project.id'}],
sorts: generateSorts([AggregationKey.COUNT]),
query: 'event.type:error',
project: [42],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
statsPeriod: '14d',
environment: ['staging'],
};
const eventView = new EventView(state);
const meta: MetaType = {count: 'integer'};
it('returns the sort when selected field is sorted', function () {
const field = {
field: 'count()',
};
const actual = eventView.sortForField(field, meta);
expect(actual).toEqual({
field: AggregationKey.COUNT,
kind: 'desc',
});
});
it('returns undefined when selected field is not sorted', function () {
const field = {
field: 'project.id',
};
expect(eventView.sortForField(field, meta)).toBeUndefined();
});
it('returns undefined when no meta is provided', function () {
const field = {
field: 'project.id',
};
expect(eventView.sortForField(field, undefined)).toBeUndefined();
});
});
describe('EventView.sortOnField()', function () {
const state: ConstructorParameters<typeof EventView>[0] = {
...REQUIRED_CONSTRUCTOR_PROPS,
id: '1234',
name: 'best query',
fields: [{field: 'count()'}, {field: 'project.id'}],
sorts: generateSorts([AggregationKey.COUNT]),
query: 'event.type:error',
project: [42],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
statsPeriod: '14d',
environment: ['staging'],
};
const meta: MetaType = {count: 'integer', title: 'string'};
it('returns itself when attempting to sort on un-sortable field', function () {
const eventView = new EventView(state);
expect(eventView).toMatchObject(state);
const field = state.fields[1];
const eventView2 = eventView.sortOnField(field, meta);
expect(eventView2 === eventView).toBe(true);
});
it('reverses the sorted field', function () {
const eventView = new EventView(state);
expect(eventView).toMatchObject(state);
const field = state.fields[0];
const eventView2 = eventView.sortOnField(field, meta);
expect(eventView2 !== eventView).toBe(true);
const nextState = {
...state,
sorts: [{field: AggregationKey.COUNT, kind: 'asc'}],
};
expect(eventView2).toMatchObject(nextState);
});
it('enforce sort order on sorted field', function () {
const eventView = new EventView(state);
expect(eventView).toMatchObject(state);
const field = state.fields[0];
const eventView2 = eventView.sortOnField(field, meta, 'asc');
expect(eventView2).toMatchObject({
...state,
sorts: [{field: AggregationKey.COUNT, kind: 'asc'}],
});
const eventView3 = eventView.sortOnField(field, meta, 'desc');
expect(eventView3).toMatchObject({
...state,
sorts: [{field: AggregationKey.COUNT, kind: 'desc'}],
});
});
it('supports function format on equation sorts', function () {
const modifiedState: ConstructorParameters<typeof EventView>[0] = {
...state,
fields: [{field: 'count()'}, {field: 'equation|count() + 100'}],
sorts: [{field: 'equation|count() + 100', kind: 'desc'}],
};
const eventView = new EventView(modifiedState);
expect(eventView).toMatchObject(modifiedState);
});
it('supports index format on equation sorts', function () {
const modifiedState: ConstructorParameters<typeof EventView>[0] = {
...state,
fields: [{field: 'count()'}, {field: 'equation|count() + 100'}],
sorts: [{field: 'equation[0]', kind: 'desc'}],
};
const eventView = new EventView(modifiedState);
expect(eventView).toMatchObject(modifiedState);
});
it('sort on new field', function () {
const modifiedState: ConstructorParameters<typeof EventView>[0] = {
...state,
fields: [...state.fields, {field: 'title'}],
};
const eventView = new EventView(modifiedState);
expect(eventView).toMatchObject(modifiedState);
const field = modifiedState.fields[2];
const eventView2 = eventView.sortOnField(field, meta);
expect(eventView2 !== eventView).toBe(true);
const nextState = {
...modifiedState,
sorts: [{field: 'title', kind: 'desc'}],
};
expect(eventView2).toMatchObject(nextState);
// enforce asc sort order
const eventView3 = eventView.sortOnField(field, meta, 'asc');
expect(eventView3).toMatchObject({
...modifiedState,
sorts: [{field: 'title', kind: 'asc'}],
});
// enforce desc sort order
const eventView4 = eventView.sortOnField(field, meta, 'desc');
expect(eventView4).toMatchObject({
...modifiedState,
sorts: [{field: 'title', kind: 'desc'}],
});
});
it('sorts on a field using function format', function () {
const modifiedState: ConstructorParameters<typeof EventView>[0] = {
...state,
fields: [...state.fields, {field: 'count()'}],
};
const eventView = new EventView(modifiedState);
expect(eventView).toMatchObject(modifiedState);
const field = modifiedState.fields[2];
let sortedEventView = eventView.sortOnField(field, meta, undefined, true);
expect(sortedEventView.sorts).toEqual([{field: 'count()', kind: 'asc'}]);
sortedEventView = sortedEventView.sortOnField(field, meta, undefined, true);
expect(sortedEventView.sorts).toEqual([{field: 'count()', kind: 'desc'}]);
});
});
describe('EventView.withSorts()', function () {
it('returns a clone', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: [{field: 'event.type'}],
});
const updated = eventView.withSorts([{kind: 'desc', field: 'event.type'}]);
expect(updated.sorts).not.toEqual(eventView.sorts);
});
it('only accepts sorting on fields in the view', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: [{field: 'event.type'}],
});
const updated = eventView.withSorts([
{kind: 'desc', field: 'event.type'},
{kind: 'asc', field: 'unknown'},
]);
expect(updated.sorts).toEqual([{kind: 'desc', field: 'event.type'}]);
});
it('accepts aggregate field sorts', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: [{field: 'p50()'}],
});
const updated = eventView.withSorts([
{kind: 'desc', field: 'p50'},
{kind: 'asc', field: 'unknown'},
]);
expect(updated.sorts).toEqual([{kind: 'desc', field: 'p50'}]);
});
});
describe('EventView.isEqualTo()', function () {
it('should be true when equal', function () {
const state: ConstructorParameters<typeof EventView>[0] = {
...REQUIRED_CONSTRUCTOR_PROPS,
id: '1234',
name: 'best query',
fields: [{field: 'count()'}, {field: 'project.id'}],
sorts: generateSorts([AggregationKey.COUNT]),
query: 'event.type:error',
project: [42],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
statsPeriod: '14d',
environment: ['staging'],
yAxis: 'fam',
display: 'releases',
dataset: DiscoverDatasets.DISCOVER,
};
const eventView = new EventView(state);
const eventView2 = new EventView(state);
expect(eventView2 !== eventView).toBeTruthy();
expect(eventView).toMatchObject(state);
expect(eventView2).toMatchObject(state);
expect(eventView.isEqualTo(eventView2)).toBe(true);
// commutativity property holds
expect(eventView2.isEqualTo(eventView)).toBe(true);
});
it('should be true when datetime are equal but differ in format', function () {
const state: ConstructorParameters<typeof EventView>[0] = {
...REQUIRED_CONSTRUCTOR_PROPS,
id: '1234',
name: 'best query',
fields: [{field: 'count()'}, {field: 'project.id'}],
sorts: generateSorts([AggregationKey.COUNT]),
query: 'event.type:error',
project: [42],
start: '2019-10-20T21:02:51+0000',
end: '2019-10-23T19:27:04+0000',
environment: ['staging'],
};
const eventView = new EventView(state);
const eventView2 = new EventView({
...state,
start: '2019-10-20T21:02:51Z',
end: '2019-10-23T19:27:04Z',
});
expect(eventView.isEqualTo(eventView2)).toBe(true);
});
it('should be false when not equal', function () {
const state: ConstructorParameters<typeof EventView>[0] = {
...REQUIRED_CONSTRUCTOR_PROPS,
id: '1234',
name: 'best query',
fields: [{field: 'count()'}, {field: 'project.id'}],
sorts: generateSorts([AggregationKey.COUNT]),
query: 'event.type:error',
project: [42],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
statsPeriod: '14d',
environment: ['staging'],
yAxis: 'fam',
display: 'releases',
dataset: DiscoverDatasets.DISCOVER,
};
const differences = {
id: '12',
name: 'new query',
fields: [{field: 'project.id'}, {field: 'count()'}],
sorts: [{field: AggregationKey.COUNT, kind: 'asc'}],
query: 'event.type:transaction',
project: [24],
start: '2019-09-01T00:00:00',
end: '2020-09-01T00:00:00',
statsPeriod: '24d',
environment: [],
yAxis: 'ok boomer',
display: 'previous',
dataset: DiscoverDatasets.ISSUE_PLATFORM,
};
const eventView = new EventView(state);
for (const key in differences) {
const eventView2 = new EventView({...state, [key]: differences[key]});
expect(eventView.isEqualTo(eventView2)).toBe(false);
}
});
it('undefined display type equals default display type', function () {
const state: ConstructorParameters<typeof EventView>[0] = {
...REQUIRED_CONSTRUCTOR_PROPS,
id: '1234',
name: 'best query',
fields: [{field: 'count()'}, {field: 'project.id'}],
sorts: generateSorts([AggregationKey.COUNT]),
query: 'event.type:error',
project: [42],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
statsPeriod: '14d',
environment: ['staging'],
yAxis: 'fam',
};
const eventView = new EventView(state);
const eventView2 = new EventView({...state, display: 'default'});
expect(eventView.isEqualTo(eventView2)).toBe(true);
});
});
describe('EventView.getResultsViewUrlTarget()', function () {
beforeEach(function () {
window.__initialData = {
...window.__initialData,
customerDomain: {
subdomain: 'albertos-apples',
organizationUrl: 'https://albertos-apples.sentry.io',
sentryUrl: 'https://sentry.io',
},
};
});
afterEach(function () {
window.__initialData = {
...window.__initialData,
customerDomain: null,
};
});
const state: ConstructorParameters<typeof EventView>[0] = {
...REQUIRED_CONSTRUCTOR_PROPS,
id: '1234',
name: 'best query',
fields: [{field: 'count()'}, {field: 'project.id'}],
sorts: generateSorts([AggregationKey.COUNT]),
query: 'event.type:error',
project: [42],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
statsPeriod: '14d',
environment: ['staging'],
display: 'previous',
dataset: DiscoverDatasets.DISCOVER,
};
const organization = OrganizationFixture();
it('generates a URL with non-customer domain context', function () {
window.__initialData.customerDomain = null;
const view = new EventView(state);
const result = view.getResultsViewUrlTarget(organization.slug);
expect(result.pathname).toEqual('/organizations/org-slug/discover/results/');
expect(result.query.query).toEqual(state.query);
expect(result.query.project).toEqual(state.project);
expect(result.query.display).toEqual(state.display);
});
it('generates a URL with customer domain context', function () {
const view = new EventView(state);
const result = view.getResultsViewUrlTarget(organization.slug);
expect(result.pathname).toEqual('/discover/results/');
expect(result.query.query).toEqual(state.query);
expect(result.query.project).toEqual(state.project);
expect(result.query.display).toEqual(state.display);
});
});
describe('EventView.getResultsViewShortUrlTarget()', function () {
beforeEach(function () {
window.__initialData = {
...window.__initialData,
customerDomain: {
subdomain: 'albertos-apples',
organizationUrl: 'https://albertos-apples.sentry.io',
sentryUrl: 'https://sentry.io',
},
};
});
afterEach(function () {
window.__initialData = {
...window.__initialData,
customerDomain: null,
};
});
const state: ConstructorParameters<typeof EventView>[0] = {
...REQUIRED_CONSTRUCTOR_PROPS,
id: '1234',
name: 'best query',
fields: [{field: 'count()'}, {field: 'project.id'}],
sorts: generateSorts([AggregationKey.COUNT]),
query: 'event.type:error',
project: [42],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
statsPeriod: '14d',
environment: ['staging'],
display: 'previous',
dataset: DiscoverDatasets.DISCOVER,
};
const organization = OrganizationFixture();
it('generates a URL with non-customer domain context', function () {
window.__initialData.customerDomain = null;
const view = new EventView(state);
const result = view.getResultsViewShortUrlTarget(organization.slug);
expect(result.pathname).toEqual('/organizations/org-slug/discover/results/');
expect(result.query).not.toHaveProperty('name');
expect(result.query).not.toHaveProperty('fields');
expect(result.query).not.toHaveProperty('query');
expect(result.query.id).toEqual(state.id);
expect(result.query.statsPeriod).toEqual(state.statsPeriod);
expect(result.query.project).toEqual(state.project);
expect(result.query.environment).toEqual(state.environment);
});
it('generates a URL with customer domain context', function () {
const view = new EventView(state);
const result = view.getResultsViewShortUrlTarget(organization.slug);
expect(result.pathname).toEqual('/discover/results/');
expect(result.query).not.toHaveProperty('name');
expect(result.query).not.toHaveProperty('fields');
expect(result.query).not.toHaveProperty('query');
expect(result.query.id).toEqual(state.id);
expect(result.query.statsPeriod).toEqual(state.statsPeriod);
expect(result.query.project).toEqual(state.project);
expect(result.query.environment).toEqual(state.environment);
});
});
describe('EventView.getPerformanceTransactionEventsViewUrlTarget()', function () {
beforeEach(function () {
window.__initialData = {
...window.__initialData,
customerDomain: {
subdomain: 'albertos-apples',
organizationUrl: 'https://albertos-apples.sentry.io',
sentryUrl: 'https://sentry.io',
},
};
});
afterEach(function () {
window.__initialData = {
...window.__initialData,
customerDomain: null,
};
});
const state: ConstructorParameters<typeof EventView>[0] = {
...REQUIRED_CONSTRUCTOR_PROPS,
id: '1234',
name: 'best query',
fields: [{field: 'count()'}, {field: 'project.id'}],
sorts: generateSorts([AggregationKey.COUNT]),
query: 'event.type:error',
project: [42],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
statsPeriod: '14d',
environment: ['staging'],
display: 'previous',
dataset: DiscoverDatasets.DISCOVER,
};
const organization = OrganizationFixture();
const showTransactions = EventsDisplayFilterName.P99;
const breakdown = SpanOperationBreakdownFilter.HTTP;
const webVital = WebVital.LCP;
it('generates a URL with non-customer domain context', function () {
window.__initialData.customerDomain = null;
const view = new EventView(state);
const result = view.getPerformanceTransactionEventsViewUrlTarget(organization.slug, {
showTransactions,
breakdown,
webVital,
});
expect(result.pathname).toEqual(
'/organizations/org-slug/performance/summary/events/'
);
expect(result.query.query).toEqual(state.query);
expect(result.query.project).toEqual(state.project);
expect(result.query.sort).toEqual(['-count']);
expect(result.query.transaction).toEqual(state.name);
expect(result.query.showTransactions).toEqual(showTransactions);
expect(result.query.breakdown).toEqual(breakdown);
expect(result.query.webVital).toEqual(webVital);
});
it('generates a URL with customer domain context', function () {
const view = new EventView(state);
const result = view.getPerformanceTransactionEventsViewUrlTarget(organization.slug, {
showTransactions,
breakdown,
webVital,
});
expect(result.pathname).toEqual('/performance/summary/events/');
expect(result.query.query).toEqual(state.query);
expect(result.query.project).toEqual(state.project);
expect(result.query.sort).toEqual(['-count']);
expect(result.query.transaction).toEqual(state.name);
expect(result.query.showTransactions).toEqual(showTransactions);
expect(result.query.breakdown).toEqual(breakdown);
expect(result.query.webVital).toEqual(webVital);
});
});
describe('EventView.getPageFilters()', function () {
it('return default global selection', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
});
expect(eventView.getPageFilters()).toMatchObject({
projects: [],
environments: [],
datetime: {
start: null,
end: null,
period: null,
// event views currently do not support the utc option,
// see comment in EventView.getPageFilters
utc: true,
},
});
});
it('returns global selection query', function () {
const state2 = {
...REQUIRED_CONSTRUCTOR_PROPS,
project: [42],
start: 'start',
end: 'end',
statsPeriod: '42d',
environment: ['prod'],
};
const eventView = new EventView(state2);
expect(eventView.getPageFilters()).toMatchObject({
projects: state2.project,
environments: state2.environment,
datetime: {
start: state2.start,
end: state2.end,
period: state2.statsPeriod,
// event views currently do not support the utc option,
// see comment in EventView.getPageFilters
utc: true,
},
});
});
});
describe('EventView.getPageFiltersQuery()', function () {
it('return default global selection query', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
});
expect(eventView.getPageFiltersQuery()).toMatchObject({
project: [],
start: undefined,
end: undefined,
statsPeriod: undefined,
environment: [],
// event views currently do not support the utc option,
// see comment in EventView.getPageFilters
utc: 'true',
});
});
it('returns global selection query', function () {
const state2 = {
...REQUIRED_CONSTRUCTOR_PROPS,
project: [42],
start: 'start',
end: 'end',
statsPeriod: '42d',
environment: ['prod'],
};
const eventView = new EventView(state2);
expect(eventView.getPageFiltersQuery()).toEqual({
end: 'end',
start: 'start',
statsPeriod: '42d',
environment: ['prod'],
project: ['42'],
utc: 'true',
});
});
});
describe('EventView.generateBlankQueryStringObject()', function () {
it('should return blank values', function () {
const eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
});
expect(eventView.generateBlankQueryStringObject()).toEqual({
id: undefined,
name: undefined,
fields: undefined,
sorts: undefined,
query: undefined,
project: undefined,
start: undefined,
end: undefined,
statsPeriod: undefined,
environment: undefined,
yAxis: undefined,
cursor: undefined,
});
});
});
describe('EventView.getYAxisOptions()', function () {
const state: ConstructorParameters<typeof EventView>[0] = {
...REQUIRED_CONSTRUCTOR_PROPS,
fields: [],
sorts: [],
query: '',
project: [],
statsPeriod: '42d',
environment: [],
};
function generateYaxis(value) {
return {
value,
label: value,
};
}
it('should return default options', function () {
const thisEventView = new EventView(state);
expect(thisEventView.getYAxisOptions()).toEqual(CHART_AXIS_OPTIONS);
});
it('should add aggregate fields as options', function () {
let thisEventView = new EventView({
...state,
fields: generateFields(['ignored-field', 'count_unique(issue)']),
});
expect(thisEventView.getYAxisOptions()).toEqual([
generateYaxis('count_unique(issue)'),
...CHART_AXIS_OPTIONS,
]);
// should de-duplicate entries
thisEventView = new EventView({
...state,
fields: generateFields(['ignored-field', 'count()']),
});
expect(thisEventView.getYAxisOptions()).toEqual([...CHART_AXIS_OPTIONS]);
});
it('should exclude yAxis options that are not useful', function () {
const thisEventView = new EventView({
...state,
fields: generateFields([
'ignored-field',
'count_unique(issue)',
'last_seen()',
'max(timestamp)',
]),
});
expect(thisEventView.getYAxisOptions()).toEqual([
generateYaxis('count_unique(issue)'),
...CHART_AXIS_OPTIONS,
]);
});
});
describe('EventView.getYAxis()', function () {
const state: ConstructorParameters<typeof EventView>[0] = {
...REQUIRED_CONSTRUCTOR_PROPS,
fields: [],
sorts: [],
query: '',
project: [],
statsPeriod: '42d',
environment: [],
};
it('should return first default yAxis', function () {
const thisEventView = new EventView(state);
expect(thisEventView.getYAxis()).toEqual('count()');
});
it('should return valid yAxis', function () {
const thisEventView = new EventView({
...state,
fields: generateFields(['ignored-field', 'count_unique(user)', 'last_seen']),
yAxis: 'count_unique(user)',
});
expect(thisEventView.getYAxis()).toEqual('count_unique(user)');
});
it('should ignore invalid yAxis', function () {
const invalid = [
'last_seen',
'latest_event',
'count_unique(issue)', // this is not one of the selected fields
];
for (const option of invalid) {
const thisEventView = new EventView({
...state,
fields: generateFields(['ignored-field', 'last_seen', 'latest_event']),
yAxis: option,
});
// yAxis defaults to the first entry of the default yAxis options
expect(thisEventView.getYAxis()).toEqual('count()');
}
});
});
describe('EventView.getDisplayOptions()', function () {
const state: ConstructorParameters<typeof EventView>[0] = {
...REQUIRED_CONSTRUCTOR_PROPS,
fields: [],
sorts: [],
query: '',
project: [],
statsPeriod: '42d',
environment: [],
};
it('should return default options', function () {
const eventView = new EventView({
...state,
// there needs to exist an aggregate or TOP 5 modes will be disabled
fields: [{field: 'count()'}],
});
expect(eventView.getDisplayOptions()).toEqual(DISPLAY_MODE_OPTIONS);
});
it('should disable previous when start/end are used.', function () {
const eventView = new EventView({
...state,
end: '2020-04-13T12:13:14',
start: '2020-04-01T12:13:14',
});
const options = eventView.getDisplayOptions();
expect(options[1].value).toEqual('previous');
expect(options[1].disabled).toBeTruthy();
});
it('should disable top 5 period/daily if no aggregates present', function () {
const eventView = new EventView({
...state,
});
const options = eventView.getDisplayOptions();
expect(options[2].value).toEqual('top5');
expect(options[2].disabled).toBeTruthy();
expect(options[4].value).toEqual('dailytop5');
expect(options[4].disabled).toBeTruthy();
});
});
describe('EventView.getDisplayMode()', function () {
const state: ConstructorParameters<typeof EventView>[0] = {
...REQUIRED_CONSTRUCTOR_PROPS,
fields: [],
sorts: [],
query: '',
project: [],
statsPeriod: '42d',
environment: [],
};
it('should have default', function () {
const eventView = new EventView({
...state,
});
const displayMode = eventView.getDisplayMode();
expect(displayMode).toEqual(DisplayModes.DEFAULT);
});
it('should return current mode when not disabled', function () {
const eventView = new EventView({
...state,
display: DisplayModes.DAILY,
});
const displayMode = eventView.getDisplayMode();
expect(displayMode).toEqual(DisplayModes.DAILY);
});
it('should return default mode when disabled', function () {
const eventView = new EventView({
...state,
// the existence of start and end will disable the PREVIOUS mode
end: '2020-04-13T12:13:14',
start: '2020-04-01T12:13:14',
display: DisplayModes.PREVIOUS,
});
const displayMode = eventView.getDisplayMode();
expect(displayMode).toEqual(DisplayModes.DEFAULT);
});
it('top 5 should fallback to default when disabled', function () {
const eventView = new EventView({
...state,
// the lack of an aggregate will disable the TOP5 mode
display: DisplayModes.TOP5,
});
const displayMode = eventView.getDisplayMode();
expect(displayMode).toEqual(DisplayModes.DEFAULT);
});
it('top 5 daily should fallback to daily when disabled', function () {
const eventView = new EventView({
...state,
// the lack of an aggregate will disable the DAILYTOP5 mode
display: DisplayModes.DAILYTOP5,
});
const displayMode = eventView.getDisplayMode();
expect(displayMode).toEqual(DisplayModes.DAILY);
});
it('daily mode should fall back to default when disabled', function () {
const eventView = new EventView({
...state,
// the period being less than 24h will disable the DAILY mode
statsPeriod: '1h',
display: DisplayModes.DAILY,
});
const displayMode = eventView.getDisplayMode();
expect(displayMode).toEqual(DisplayModes.DEFAULT);
});
it('top 5 daily mode should fall back to default when daily is disabled', function () {
const eventView = new EventView({
...state,
// the period being less than 24h will disable the DAILY mode
statsPeriod: undefined,
start: '2020-04-01T12:13:14',
end: '2020-04-02T12:10:14',
display: DisplayModes.DAILYTOP5,
});
const displayMode = eventView.getDisplayMode();
expect(displayMode).toEqual(DisplayModes.DEFAULT);
});
});
describe('EventView.getAggregateFields()', function () {
const state: ConstructorParameters<typeof EventView>[0] = {
...REQUIRED_CONSTRUCTOR_PROPS,
fields: [
{field: 'title'},
{field: 'count()'},
{field: 'count_unique(user)'},
{field: 'apdex(300)'},
{field: 'transaction'},
],
sorts: [],
query: '',
project: [],
statsPeriod: '42d',
environment: [],
};
it('getAggregateFields() returns only aggregates', function () {
const eventView = new EventView(state);
const expected = [
{field: 'count()'},
{field: 'count_unique(user)'},
{field: 'apdex(300)'},
];
expect(eventView.getAggregateFields()).toEqual(expected);
});
});
describe('EventView.hasAggregateField', function () {
it('ensures an eventview has an aggregate field', function () {
let eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: [{field: 'foobar'}],
sorts: [],
query: '',
project: [],
environment: [],
});
expect(eventView.hasAggregateField()).toBe(false);
eventView = new EventView({
...REQUIRED_CONSTRUCTOR_PROPS,
fields: [{field: 'count(foo.bar.is-Enterprise_42)'}],
sorts: [],
query: '',
project: [],
environment: [],
});
expect(eventView.hasAggregateField()).toBe(true);
});
});
describe('isAPIPayloadSimilar', function () {
const state: ConstructorParameters<typeof EventView>[0] = {
...REQUIRED_CONSTRUCTOR_PROPS,
id: '1234',
name: 'best query',
fields: [{field: 'count()'}, {field: 'project.id'}],
sorts: generateSorts([AggregationKey.COUNT]),
query: 'event.type:error',
project: [42],
start: '2019-10-01T00:00:00',
end: '2019-10-02T00:00:00',
statsPeriod: '14d',
environment: ['staging'],
};
const meta: MetaType = {
count: 'integer',
title: 'string',
};
describe('getEventsAPIPayload', function () {
it('is not similar when relevant query string keys are present in the Location object', function () {
const thisEventView = new EventView(state);
const location = LocationFixture({
query: {
project: 'project',
environment: 'environment',
start: 'start',
end: 'end',
utc: 'utc',
statsPeriod: 'statsPeriod',
cursor: 'cursor',
},
});
const thisAPIPayload = thisEventView.getEventsAPIPayload(location);
const otherLocation = LocationFixture({});
const otherAPIPayload = thisEventView.getEventsAPIPayload(otherLocation);
const results = isAPIPayloadSimilar(thisAPIPayload, otherAPIPayload);
expect(results).toBe(false);
});
it('is similar when irrelevant query string keys are present in the Location object', function () {
const thisEventView = new EventView(state);
const location = LocationFixture({
query: {
bestCountry: 'canada',
},
});
const thisAPIPayload = thisEventView.getEventsAPIPayload(location);
const otherLocation = LocationFixture({});
const otherAPIPayload = thisEventView.getEventsAPIPayload(otherLocation);
const results = isAPIPayloadSimilar(thisAPIPayload, otherAPIPayload);
expect(results).toBe(true);
});
it('is not similar on sort key sorted in opposite directions', function () {
const thisEventView = new EventView(state);
const location = LocationFixture({});
const thisAPIPayload = thisEventView.getEventsAPIPayload(location);
const otherEventView = thisEventView.sortOnField({field: 'count()'}, meta);
const otherLocation = LocationFixture({});
const otherAPIPayload = otherEventView.getEventsAPIPayload(otherLocation);
const results = isAPIPayloadSimilar(thisAPIPayload, otherAPIPayload);
expect(results).toBe(false);
});
it('is not similar when a new column is added', function () {
const thisEventView = new EventView(state);
const location = LocationFixture({});
const thisAPIPayload = thisEventView.getEventsAPIPayload(location);
const otherEventView = new EventView({
...state,
fields: [...state.fields, {field: 'title', width: COL_WIDTH_UNDEFINED}],
});
const otherLocation = LocationFixture({});
const otherAPIPayload = otherEventView.getEventsAPIPayload(otherLocation);
const results = isAPIPayloadSimilar(thisAPIPayload, otherAPIPayload);
expect(results).toBe(false);
});
it('is similar when a column is updated with no changes', function () {
const thisEventView = new EventView(state);
const location = LocationFixture({});
const thisAPIPayload = thisEventView.getEventsAPIPayload(location);
const newColumn: Column = {
kind: 'function',
function: [AggregationKey.COUNT, '', undefined, undefined],
};
const otherEventView = thisEventView.withUpdatedColumn(0, newColumn, meta);
const otherLocation = LocationFixture({});
const otherAPIPayload = otherEventView.getEventsAPIPayload(otherLocation);
const results = isAPIPayloadSimilar(thisAPIPayload, otherAPIPayload);
expect(results).toBe(true);
});
it('is not similar when a column is updated with a replaced field', function () {
const thisEventView = new EventView(state);
const location = LocationFixture({});
const thisAPIPayload = thisEventView.getEventsAPIPayload(location);
const newColumn: Column = {
kind: 'field',
field: 'title',
};
const otherEventView = thisEventView.withUpdatedColumn(0, newColumn, meta);
const otherLocation = LocationFixture({});
const otherAPIPayload = otherEventView.getEventsAPIPayload(otherLocation);
const results = isAPIPayloadSimilar(thisAPIPayload, otherAPIPayload);
expect(results).toBe(false);
});
it('is not similar when a column is updated with a replaced aggregation', function () {
const thisEventView = new EventView(state);
const location = LocationFixture({});
const thisAPIPayload = thisEventView.getEventsAPIPayload(location);
const newColumn: Column = {
kind: 'function',
function: [AggregationKey.AVG, '', undefined, undefined],
};
const otherEventView = thisEventView.withUpdatedColumn(0, newColumn, meta);
const otherLocation = LocationFixture({});
const otherAPIPayload = otherEventView.getEventsAPIPayload(otherLocation);
const results = isAPIPayloadSimilar(thisAPIPayload, otherAPIPayload);
expect(results).toBe(false);
});
it('is similar when a column is renamed', function () {
const thisEventView = new EventView(state);
const location = LocationFixture({});
const thisAPIPayload = thisEventView.getEventsAPIPayload(location);
const newColumn: Column = {
kind: 'function',
function: [AggregationKey.COUNT, '', undefined, undefined],
};
const otherEventView = thisEventView.withUpdatedColumn(0, newColumn, meta);
const otherLocation = LocationFixture({});
const otherAPIPayload = otherEventView.getEventsAPIPayload(otherLocation);
const results = isAPIPayloadSimilar(thisAPIPayload, otherAPIPayload);
expect(results).toBe(true);
});
it('is not similar when a column is deleted', function () {
const thisEventView = new EventView(state);
const location = LocationFixture({});
const thisAPIPayload = thisEventView.getEventsAPIPayload(location);
const otherEventView = thisEventView.withDeletedColumn(0, meta);
const otherLocation = LocationFixture({});
const otherAPIPayload = otherEventView.getEventsAPIPayload(otherLocation);
const results = isAPIPayloadSimilar(thisAPIPayload, otherAPIPayload);
expect(results).toBe(false);
});
it('is similar if column order changes', function () {
const thisEventView = new EventView(state);
const location = LocationFixture({});
const thisAPIPayload = thisEventView.getEventsAPIPayload(location);
const otherEventView = new EventView({...state, fields: shuffle(state.fields)});
const otherLocation = LocationFixture({});
const otherAPIPayload = otherEventView.getEventsAPIPayload(otherLocation);
const results = isAPIPayloadSimilar(thisAPIPayload, otherAPIPayload);
expect(results).toBe(true);
});
it('is similar if equation order relatively same', function () {
const equationField = {field: 'equation|failure_count() / count()'};
const otherEquationField = {field: 'equation|failure_count() / 2'};
state.fields = [
{field: 'project.id'},
{field: 'count()'},
equationField,
otherEquationField,
];
const thisEventView = new EventView(state);
const location = LocationFixture({});
const thisAPIPayload = thisEventView.getEventsAPIPayload(location);
state.fields = [
equationField,
{field: 'project.id'},
{field: 'count()'},
otherEquationField,
];
const otherEventView = new EventView(state);
const otherLocation = LocationFixture({});
const otherAPIPayload = otherEventView.getEventsAPIPayload(otherLocation);
const results = isAPIPayloadSimilar(thisAPIPayload, otherAPIPayload);
expect(results).toBe(true);
});
it('is not similar if equation order changes', function () {
const equationField = {field: 'equation|failure_count() / count()'};
const otherEquationField = {field: 'equation|failure_count() / 2'};
state.fields = [
{field: 'project.id'},
{field: 'count()'},
equationField,
otherEquationField,
];
const thisEventView = new EventView(state);
const location = LocationFixture({});
const thisAPIPayload = thisEventView.getEventsAPIPayload(location);
state.fields = [
{field: 'project.id'},
{field: 'count()'},
otherEquationField,
equationField,
];
const otherEventView = new EventView(state);
const otherLocation = LocationFixture({});
const otherAPIPayload = otherEventView.getEventsAPIPayload(otherLocation);
const results = isAPIPayloadSimilar(thisAPIPayload, otherAPIPayload);
expect(results).toBe(false);
});
});
describe('getFacetsAPIPayload', function () {
it('only includes relevant parameters', function () {
const thisEventView = new EventView(state);
const location = LocationFixture({});
const results = thisEventView.getFacetsAPIPayload(location);
const expected = {
query: state.query,
project: ['42'],
statsPeriod: state.statsPeriod,
environment: state.environment,
};
expect(results).toEqual(expected);
});
it('is similar on sort key sorted in opposite directions', function () {
const thisEventView = new EventView(state);
const location = LocationFixture({});
const thisAPIPayload = thisEventView.getFacetsAPIPayload(location);
const newColumn: Column = {
kind: 'function',
function: [AggregationKey.COUNT, '', undefined, undefined],
};
const otherEventView = thisEventView.withUpdatedColumn(0, newColumn, meta);
const otherLocation = LocationFixture({});
const otherAPIPayload = otherEventView.getFacetsAPIPayload(otherLocation);
const results = isAPIPayloadSimilar(thisAPIPayload, otherAPIPayload);
expect(results).toBe(true);
});
it('is similar when a columns are different', function () {
const thisEventView = new EventView(state);
const location = LocationFixture({});
const thisAPIPayload = thisEventView.getFacetsAPIPayload(location);
const otherEventView = new EventView({
...state,
fields: [...state.fields, {field: 'title', width: COL_WIDTH_UNDEFINED}],
});
const otherLocation = LocationFixture({});
const otherAPIPayload = otherEventView.getFacetsAPIPayload(otherLocation);
const results = isAPIPayloadSimilar(thisAPIPayload, otherAPIPayload);
expect(results).toBe(true);
});
});
});
describe('pickRelevantLocationQueryStrings', function () {
it('picks relevant query strings', function () {
const location = LocationFixture({
query: {
project: 'project',
environment: 'environment',
start: 'start',
end: 'end',
utc: 'utc',
statsPeriod: 'statsPeriod',
cursor: 'cursor',
// irrelevant query strings
bestCountry: 'canada',
},
});
const actual = pickRelevantLocationQueryStrings(location);
const expected = {
start: 'start',
end: 'end',
utc: 'utc',
statsPeriod: 'statsPeriod',
cursor: 'cursor',
};
expect(actual).toEqual(expected);
});
});