mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
877 lines
27 KiB
TypeScript
877 lines
27 KiB
TypeScript
import {browserHistory} from 'react-router';
|
|
import {EventFixture} from 'sentry-fixture/event';
|
|
import {LocationFixture} from 'sentry-fixture/locationFixture';
|
|
|
|
import {initializeOrg} from 'sentry-test/initializeOrg';
|
|
|
|
import {COL_WIDTH_UNDEFINED} from 'sentry/components/gridEditable';
|
|
import type {EventViewOptions} from 'sentry/utils/discover/eventView';
|
|
import EventView from 'sentry/utils/discover/eventView';
|
|
import {DisplayType} from 'sentry/views/dashboards/types';
|
|
import {
|
|
decodeColumnOrder,
|
|
downloadAsCsv,
|
|
eventViewToWidgetQuery,
|
|
generateFieldOptions,
|
|
getExpandedResults,
|
|
pushEventViewToLocation,
|
|
} from 'sentry/views/discover/utils';
|
|
|
|
const baseView: EventViewOptions = {
|
|
display: undefined,
|
|
start: undefined,
|
|
end: undefined,
|
|
id: '0',
|
|
name: undefined,
|
|
fields: [],
|
|
createdBy: undefined,
|
|
environment: [],
|
|
project: [],
|
|
query: '',
|
|
sorts: [],
|
|
statsPeriod: undefined,
|
|
team: [],
|
|
topEvents: undefined,
|
|
};
|
|
|
|
describe('decodeColumnOrder', function () {
|
|
it('can decode 0 elements', function () {
|
|
const results = decodeColumnOrder([]);
|
|
|
|
expect(Array.isArray(results)).toBeTruthy();
|
|
expect(results).toHaveLength(0);
|
|
});
|
|
|
|
it('can decode fields', function () {
|
|
const results = decodeColumnOrder([{field: 'title', width: 123}]);
|
|
|
|
expect(Array.isArray(results)).toBeTruthy();
|
|
|
|
expect(results[0]).toEqual({
|
|
key: 'title',
|
|
name: 'title',
|
|
column: {
|
|
kind: 'field',
|
|
field: 'title',
|
|
},
|
|
width: 123,
|
|
isSortable: false,
|
|
type: 'string',
|
|
});
|
|
});
|
|
|
|
it('can decode measurement fields', function () {
|
|
const results = decodeColumnOrder([{field: 'measurements.foo', width: 123}]);
|
|
|
|
expect(Array.isArray(results)).toBeTruthy();
|
|
|
|
expect(results[0]).toEqual({
|
|
key: 'measurements.foo',
|
|
name: 'measurements.foo',
|
|
column: {
|
|
kind: 'field',
|
|
field: 'measurements.foo',
|
|
},
|
|
width: 123,
|
|
isSortable: false,
|
|
type: 'number',
|
|
});
|
|
});
|
|
|
|
it('can decode span op breakdown fields', function () {
|
|
const results = decodeColumnOrder([{field: 'spans.foo', width: 123}]);
|
|
|
|
expect(Array.isArray(results)).toBeTruthy();
|
|
|
|
expect(results[0]).toEqual({
|
|
key: 'spans.foo',
|
|
name: 'spans.foo',
|
|
column: {
|
|
kind: 'field',
|
|
field: 'spans.foo',
|
|
},
|
|
width: 123,
|
|
isSortable: false,
|
|
type: 'duration',
|
|
});
|
|
});
|
|
|
|
it('can decode aggregate functions with no arguments', function () {
|
|
let results = decodeColumnOrder([{field: 'count()', width: 123}]);
|
|
|
|
expect(Array.isArray(results)).toBeTruthy();
|
|
expect(results[0]).toEqual({
|
|
key: 'count()',
|
|
name: 'count()',
|
|
column: {
|
|
kind: 'function',
|
|
function: ['count', '', undefined, undefined],
|
|
},
|
|
width: 123,
|
|
isSortable: true,
|
|
type: 'number',
|
|
});
|
|
|
|
results = decodeColumnOrder([{field: 'p75()', width: 123}]);
|
|
expect(results[0].type).toEqual('duration');
|
|
|
|
results = decodeColumnOrder([{field: 'p99()', width: 123}]);
|
|
expect(results[0].type).toEqual('duration');
|
|
});
|
|
|
|
it('can decode elements with aggregate functions with arguments', function () {
|
|
const results = decodeColumnOrder([{field: 'avg(transaction.duration)'}]);
|
|
|
|
expect(Array.isArray(results)).toBeTruthy();
|
|
|
|
expect(results[0]).toEqual({
|
|
key: 'avg(transaction.duration)',
|
|
name: 'avg(transaction.duration)',
|
|
column: {
|
|
kind: 'function',
|
|
function: ['avg', 'transaction.duration', undefined, undefined],
|
|
},
|
|
width: COL_WIDTH_UNDEFINED,
|
|
isSortable: true,
|
|
type: 'duration',
|
|
});
|
|
});
|
|
|
|
it('can decode elements with aggregate functions with multiple arguments', function () {
|
|
const results = decodeColumnOrder([
|
|
{field: 'percentile(transaction.duration, 0.65)'},
|
|
]);
|
|
|
|
expect(Array.isArray(results)).toBeTruthy();
|
|
|
|
expect(results[0]).toEqual({
|
|
key: 'percentile(transaction.duration, 0.65)',
|
|
name: 'percentile(transaction.duration, 0.65)',
|
|
column: {
|
|
kind: 'function',
|
|
function: ['percentile', 'transaction.duration', '0.65', undefined],
|
|
},
|
|
width: COL_WIDTH_UNDEFINED,
|
|
isSortable: true,
|
|
type: 'duration',
|
|
});
|
|
});
|
|
|
|
it('can decode elements with aggregate functions using measurements', function () {
|
|
const results = decodeColumnOrder([{field: 'avg(measurements.foo)'}]);
|
|
|
|
expect(Array.isArray(results)).toBeTruthy();
|
|
|
|
expect(results[0]).toEqual({
|
|
key: 'avg(measurements.foo)',
|
|
name: 'avg(measurements.foo)',
|
|
column: {
|
|
kind: 'function',
|
|
function: ['avg', 'measurements.foo', undefined, undefined],
|
|
},
|
|
width: COL_WIDTH_UNDEFINED,
|
|
isSortable: true,
|
|
type: 'number',
|
|
});
|
|
});
|
|
|
|
it('can decode elements with aggregate functions with multiple arguments using measurements', function () {
|
|
const results = decodeColumnOrder([{field: 'percentile(measurements.lcp, 0.65)'}]);
|
|
|
|
expect(Array.isArray(results)).toBeTruthy();
|
|
|
|
expect(results[0]).toEqual({
|
|
key: 'percentile(measurements.lcp, 0.65)',
|
|
name: 'percentile(measurements.lcp, 0.65)',
|
|
column: {
|
|
kind: 'function',
|
|
function: ['percentile', 'measurements.lcp', '0.65', undefined],
|
|
},
|
|
width: COL_WIDTH_UNDEFINED,
|
|
isSortable: true,
|
|
type: 'duration',
|
|
});
|
|
});
|
|
|
|
it('can decode elements with aggregate functions using span op breakdowns', function () {
|
|
const results = decodeColumnOrder([{field: 'avg(spans.foo)'}]);
|
|
|
|
expect(Array.isArray(results)).toBeTruthy();
|
|
|
|
expect(results[0]).toEqual({
|
|
key: 'avg(spans.foo)',
|
|
name: 'avg(spans.foo)',
|
|
column: {
|
|
kind: 'function',
|
|
function: ['avg', 'spans.foo', undefined, undefined],
|
|
},
|
|
width: COL_WIDTH_UNDEFINED,
|
|
isSortable: true,
|
|
type: 'duration',
|
|
});
|
|
});
|
|
|
|
it('can decode elements with aggregate functions with multiple arguments using span op breakdowns', function () {
|
|
const results = decodeColumnOrder([{field: 'percentile(spans.lcp, 0.65)'}]);
|
|
|
|
expect(Array.isArray(results)).toBeTruthy();
|
|
|
|
expect(results[0]).toEqual({
|
|
key: 'percentile(spans.lcp, 0.65)',
|
|
name: 'percentile(spans.lcp, 0.65)',
|
|
column: {
|
|
kind: 'function',
|
|
function: ['percentile', 'spans.lcp', '0.65', undefined],
|
|
},
|
|
width: COL_WIDTH_UNDEFINED,
|
|
isSortable: true,
|
|
type: 'duration',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('pushEventViewToLocation', function () {
|
|
const state: EventViewOptions = {
|
|
...baseView,
|
|
id: '1234',
|
|
name: 'best query',
|
|
fields: [{field: 'count()', width: 420}, {field: 'project.id'}],
|
|
sorts: [{field: 'count', kind: 'desc'}],
|
|
query: 'event.type:error',
|
|
project: [42],
|
|
start: '2019-10-01T00:00:00',
|
|
end: '2019-10-02T00:00:00',
|
|
statsPeriod: '14d',
|
|
environment: ['staging'],
|
|
};
|
|
|
|
const location = LocationFixture({
|
|
query: {
|
|
bestCountry: 'canada',
|
|
},
|
|
});
|
|
|
|
it('correct query string object pushed to history', function () {
|
|
const eventView = new EventView({...baseView, ...state});
|
|
|
|
pushEventViewToLocation({
|
|
location,
|
|
nextEventView: eventView,
|
|
});
|
|
|
|
expect(browserHistory.push).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
query: expect.objectContaining({
|
|
id: '1234',
|
|
name: 'best query',
|
|
field: ['count()', 'project.id'],
|
|
widths: [420],
|
|
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()',
|
|
}),
|
|
})
|
|
);
|
|
});
|
|
|
|
it('extra query params', function () {
|
|
const eventView = new EventView({...baseView, ...state});
|
|
|
|
pushEventViewToLocation({
|
|
location,
|
|
nextEventView: eventView,
|
|
extraQuery: {
|
|
cursor: 'some cursor',
|
|
},
|
|
});
|
|
|
|
expect(browserHistory.push).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
query: expect.objectContaining({
|
|
id: '1234',
|
|
name: 'best query',
|
|
field: ['count()', 'project.id'],
|
|
widths: [420],
|
|
sort: ['-count'],
|
|
query: 'event.type:error',
|
|
project: [42],
|
|
start: '2019-10-01T00:00:00',
|
|
end: '2019-10-02T00:00:00',
|
|
statsPeriod: '14d',
|
|
environment: ['staging'],
|
|
cursor: 'some cursor',
|
|
yAxis: 'count()',
|
|
}),
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('getExpandedResults()', function () {
|
|
const state: EventViewOptions = {
|
|
...baseView,
|
|
id: '1234',
|
|
name: 'best query',
|
|
fields: [
|
|
{field: 'count()'},
|
|
{field: 'last_seen()'},
|
|
{field: 'title'},
|
|
{field: 'custom_tag'},
|
|
],
|
|
sorts: [{field: 'count', kind: 'desc'}],
|
|
query: 'event.type:error',
|
|
project: [42],
|
|
start: '2019-10-01T00:00:00',
|
|
end: '2019-10-02T00:00:00',
|
|
statsPeriod: '14d',
|
|
environment: ['staging'],
|
|
};
|
|
|
|
it('id should be default column when drilldown results in no columns', () => {
|
|
const view = new EventView({
|
|
...baseView,
|
|
...state,
|
|
fields: [{field: 'count()'}, {field: 'epm()'}, {field: 'eps()'}],
|
|
});
|
|
|
|
const result = getExpandedResults(view, {}, EventFixture());
|
|
|
|
expect(result.fields).toEqual([{field: 'id', width: -1}]);
|
|
});
|
|
|
|
it('preserves aggregated fields', () => {
|
|
let view = new EventView(state);
|
|
|
|
let result = getExpandedResults(view, {}, EventFixture());
|
|
// id should be omitted as it is an implicit property on unaggregated results.
|
|
expect(result.fields).toEqual([
|
|
{field: 'timestamp', width: -1},
|
|
{field: 'title'},
|
|
{field: 'custom_tag'},
|
|
]);
|
|
expect(result.query).toEqual('event.type:error title:ApiException');
|
|
|
|
// de-duplicate transformed columns
|
|
view = new EventView({
|
|
...baseView,
|
|
...state,
|
|
fields: [
|
|
{field: 'count()'},
|
|
{field: 'last_seen()'},
|
|
{field: 'title'},
|
|
{field: 'custom_tag'},
|
|
{field: 'count(id)'},
|
|
],
|
|
});
|
|
|
|
result = getExpandedResults(view, {}, EventFixture());
|
|
// id should be omitted as it is an implicit property on unaggregated results.
|
|
expect(result.fields).toEqual([
|
|
{field: 'timestamp', width: -1},
|
|
{field: 'title'},
|
|
{field: 'custom_tag'},
|
|
]);
|
|
|
|
// transform aliased fields, & de-duplicate any transforms
|
|
view = new EventView({
|
|
...baseView,
|
|
...state,
|
|
fields: [
|
|
{field: 'last_seen()'}, // expect this to be transformed to timestamp
|
|
{field: 'title'},
|
|
{field: 'avg(transaction.duration)'}, // expect this to be dropped
|
|
{field: 'p50()'},
|
|
{field: 'p75()'},
|
|
{field: 'p95()'},
|
|
{field: 'p99()'},
|
|
{field: 'p100()'},
|
|
{field: 'p9001()'}, // it's over 9000
|
|
{field: 'foobar()'}, // unknown function with no parameter
|
|
{field: 'custom_tag'},
|
|
{field: 'transaction.duration'}, // should be dropped
|
|
{field: 'title'}, // should be dropped
|
|
{field: 'unique_count(id)'},
|
|
{field: 'apdex(300)'}, // should be dropped
|
|
{field: 'user_misery(300)'}, // should be dropped
|
|
{field: 'failure_count()'}, // expect this to be transformed to transaction.status
|
|
],
|
|
});
|
|
|
|
result = getExpandedResults(view, {}, EventFixture());
|
|
expect(result.fields).toEqual([
|
|
{field: 'timestamp', width: -1},
|
|
{field: 'title'},
|
|
{field: 'transaction.duration', width: -1},
|
|
{field: 'custom_tag'},
|
|
{field: 'transaction.status', width: -1},
|
|
]);
|
|
|
|
// transforms pXX functions with optional arguments properly
|
|
view = new EventView({
|
|
...baseView,
|
|
...state,
|
|
fields: [
|
|
{field: 'p50(transaction.duration)'},
|
|
{field: 'p75(measurements.foo)'},
|
|
{field: 'p95(measurements.bar)'},
|
|
{field: 'p99(measurements.fcp)'},
|
|
{field: 'p100(measurements.lcp)'},
|
|
],
|
|
});
|
|
|
|
result = getExpandedResults(view, {}, EventFixture());
|
|
expect(result.fields).toEqual([
|
|
{field: 'transaction.duration', width: -1},
|
|
{field: 'measurements.foo', width: -1},
|
|
{field: 'measurements.bar', width: -1},
|
|
{field: 'measurements.fcp', width: -1},
|
|
{field: 'measurements.lcp', width: -1},
|
|
]);
|
|
});
|
|
|
|
it('applies provided additional conditions', () => {
|
|
const view = new EventView({
|
|
...baseView,
|
|
...state,
|
|
fields: [...state.fields, {field: 'measurements.lcp'}, {field: 'measurements.fcp'}],
|
|
});
|
|
let result = getExpandedResults(view, {extra: 'condition'}, EventFixture());
|
|
expect(result.query).toEqual('event.type:error extra:condition title:ApiException');
|
|
|
|
// handles user tag values.
|
|
result = getExpandedResults(view, {user: 'id:12735'}, EventFixture());
|
|
expect(result.query).toEqual('event.type:error user:id:12735 title:ApiException');
|
|
result = getExpandedResults(view, {user: 'name:uhoh'}, EventFixture());
|
|
expect(result.query).toEqual('event.type:error user:name:uhoh title:ApiException');
|
|
|
|
// quotes value
|
|
result = getExpandedResults(view, {extra: 'has space'}, EventFixture());
|
|
expect(result.query).toEqual('event.type:error extra:"has space" title:ApiException');
|
|
|
|
// appends to existing conditions
|
|
result = getExpandedResults(view, {'event.type': 'csp'}, EventFixture());
|
|
expect(result.query).toEqual('event.type:csp title:ApiException');
|
|
|
|
// Includes empty strings
|
|
result = getExpandedResults(view, {}, EventFixture({id: '0', custom_tag: ''}));
|
|
expect(result.query).toEqual('event.type:error title:ApiException custom_tag:""');
|
|
|
|
// Includes 0
|
|
result = getExpandedResults(view, {}, EventFixture({id: '0', custom_tag: 0}));
|
|
expect(result.query).toEqual('event.type:error title:ApiException custom_tag:0');
|
|
|
|
// Includes null
|
|
result = getExpandedResults(view, {}, EventFixture({id: '0', custom_tag: null}));
|
|
expect(result.query).toEqual('event.type:error title:ApiException custom_tag:""');
|
|
|
|
// Handles measurements while ignoring null values
|
|
result = getExpandedResults(
|
|
view,
|
|
{},
|
|
// The type on this is wrong, the actual type is ReactText which is just string|number
|
|
// however we seem to have tests that test for null values as well, hence the expect error
|
|
// @ts-expect-error
|
|
{'measurements.lcp': 2, 'measurements.fcp': null}
|
|
);
|
|
expect(result.query).toEqual('event.type:error measurements.lcp:2');
|
|
});
|
|
|
|
it('removes any aggregates in either search conditions or extra conditions', () => {
|
|
const view = new EventView({...state, query: 'event.type:error count(id):<10'});
|
|
const result = getExpandedResults(view, {'count(id)': '>2'}, EventFixture());
|
|
expect(result.query).toEqual('event.type:error title:ApiException');
|
|
});
|
|
|
|
it('applies conditions from dataRow map structure based on fields', () => {
|
|
const view = new EventView(state);
|
|
const result = getExpandedResults(
|
|
view,
|
|
{extra: 'condition'},
|
|
EventFixture({title: 'Event title'})
|
|
);
|
|
expect(result.query).toEqual('event.type:error extra:condition title:"Event title"');
|
|
});
|
|
|
|
it('applies tag key conditions from event data', () => {
|
|
const view = new EventView(state);
|
|
const event = EventFixture({
|
|
type: 'error',
|
|
tags: [
|
|
{key: 'nope', value: 'nope'},
|
|
{key: 'custom_tag', value: 'tag_value'},
|
|
],
|
|
});
|
|
const result = getExpandedResults(view, {}, event);
|
|
expect(result.query).toEqual(
|
|
'event.type:error title:ApiException custom_tag:tag_value'
|
|
);
|
|
});
|
|
|
|
it('generate eventview from an empty eventview', () => {
|
|
const view = EventView.fromLocation(LocationFixture());
|
|
const result = getExpandedResults(view, {some_tag: 'value'}, EventFixture());
|
|
expect(result.fields).toEqual([]);
|
|
expect(result.query).toEqual('some_tag:value');
|
|
});
|
|
|
|
it('removes equations on aggregates', () => {
|
|
const view = new EventView({
|
|
...baseView,
|
|
...state,
|
|
fields: [
|
|
{field: 'count()'},
|
|
{field: 'equation|count() / 2'},
|
|
{field: 'equation|(count() - count()) + 5'},
|
|
],
|
|
});
|
|
const result = getExpandedResults(view, {});
|
|
expect(result.fields).toEqual([
|
|
{
|
|
field: 'id',
|
|
width: -1,
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('keeps equations without aggregates', () => {
|
|
const view = new EventView({
|
|
...baseView,
|
|
...state,
|
|
fields: [{field: 'count()'}, {field: 'equation|transaction.duration / 2'}],
|
|
});
|
|
const result = getExpandedResults(view, {});
|
|
expect(result.fields).toEqual([
|
|
{
|
|
field: 'equation|transaction.duration / 2',
|
|
width: -1,
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('applies array value conditions from event data', () => {
|
|
const view = new EventView({
|
|
...baseView,
|
|
...state,
|
|
fields: [...state.fields, {field: 'error.type'}],
|
|
});
|
|
const event = EventFixture({
|
|
type: 'error',
|
|
tags: [
|
|
{key: 'nope', value: 'nope'},
|
|
{key: 'custom_tag', value: 'tag_value'},
|
|
],
|
|
'error.type': ['DeadSystem Exception', 'RuntimeException', 'RuntimeException'],
|
|
});
|
|
const result = getExpandedResults(view, {}, event);
|
|
expect(result.query).toEqual(
|
|
'event.type:error title:ApiException custom_tag:tag_value error.type:"DeadSystem Exception" error.type:RuntimeException error.type:RuntimeException'
|
|
);
|
|
});
|
|
|
|
it('applies project condition to project property', () => {
|
|
const view = new EventView(state);
|
|
|
|
const result = getExpandedResults(view, {'project.id': '1'});
|
|
expect(result.query.includes('event.type:error')).toBeTruthy();
|
|
expect(result.project).toEqual([42, 1]);
|
|
});
|
|
|
|
it('applies environment condition to environment property', () => {
|
|
const view = new EventView(state);
|
|
const result = getExpandedResults(view, {environment: 'dev'});
|
|
expect(result.query).toEqual('event.type:error');
|
|
expect(result.environment).toEqual(['staging', 'dev']);
|
|
});
|
|
|
|
it('applies tags that overlap PageFilters state', () => {
|
|
const view = new EventView({
|
|
...baseView,
|
|
...state,
|
|
fields: [{field: 'project'}, {field: 'environment'}, {field: 'title'}],
|
|
});
|
|
const event = EventFixture({
|
|
title: 'something bad',
|
|
timestamp: '2020-02-13T17:05:46+00:00',
|
|
tags: [
|
|
{key: 'project', value: '12345'},
|
|
{key: 'environment', value: 'earth'},
|
|
],
|
|
});
|
|
const result = getExpandedResults(view, {}, event);
|
|
expect(result.query).toEqual(
|
|
'event.type:error tags[project]:12345 tags[environment]:earth title:"something bad"'
|
|
);
|
|
expect(result.project).toEqual([42]);
|
|
expect(result.environment).toEqual(['staging']);
|
|
});
|
|
|
|
it('applies the normalized user tag', function () {
|
|
const view = new EventView({
|
|
...baseView,
|
|
...state,
|
|
fields: [{field: 'user'}, {field: 'title'}],
|
|
});
|
|
let event = EventFixture({
|
|
title: 'something bad',
|
|
// user context should be ignored.
|
|
user: {
|
|
id: 1234,
|
|
username: 'uhoh',
|
|
},
|
|
tags: [{key: 'user', value: 'id:1234'}],
|
|
});
|
|
let result = getExpandedResults(view, {}, event);
|
|
expect(result.query).toEqual('event.type:error user:id:1234 title:"something bad"');
|
|
|
|
event = EventFixture({
|
|
title: 'something bad',
|
|
tags: [{key: 'user', value: '1234'}],
|
|
});
|
|
result = getExpandedResults(view, {}, event);
|
|
expect(result.query).toEqual('event.type:error user:1234 title:"something bad"');
|
|
});
|
|
|
|
it('applies the user field in a table row', function () {
|
|
const view = new EventView({
|
|
...state,
|
|
fields: [{field: 'user'}, {field: 'title'}],
|
|
});
|
|
const event = EventFixture({
|
|
title: 'something bad',
|
|
user: 'id:1234',
|
|
});
|
|
const result = getExpandedResults(view, {}, event);
|
|
expect(result.query).toEqual('event.type:error user:id:1234 title:"something bad"');
|
|
});
|
|
|
|
it('normalizes the timestamp field', () => {
|
|
const view = new EventView({
|
|
...state,
|
|
fields: [{field: 'timestamp'}],
|
|
sorts: [{field: 'timestamp', kind: 'desc'}],
|
|
});
|
|
const event = EventFixture({
|
|
type: 'error',
|
|
timestamp: '2020-02-13T17:05:46+00:00',
|
|
});
|
|
const result = getExpandedResults(view, {}, event);
|
|
expect(result.query).toEqual('event.type:error timestamp:2020-02-13T17:05:46');
|
|
});
|
|
|
|
it('does not duplicate conditions', () => {
|
|
const view = new EventView({
|
|
...baseView,
|
|
...state,
|
|
query: 'event.type:error title:bogus',
|
|
});
|
|
const event = EventFixture({
|
|
title: 'bogus',
|
|
});
|
|
const result = getExpandedResults(view, {trace: 'abc123'}, event);
|
|
expect(result.query).toEqual('event.type:error trace:abc123 title:bogus');
|
|
});
|
|
|
|
it('applies project as condition if present', () => {
|
|
const view = new EventView({
|
|
...baseView,
|
|
...state,
|
|
query: '',
|
|
fields: [{field: 'project'}],
|
|
});
|
|
const event = EventFixture({project: 'whoosh'});
|
|
const result = getExpandedResults(view, {}, event);
|
|
expect(result.query).toEqual('project:whoosh');
|
|
});
|
|
|
|
it('applies project name as condition if present', () => {
|
|
const view = new EventView({
|
|
...baseView,
|
|
...state,
|
|
query: '',
|
|
fields: [{field: 'project.name'}],
|
|
});
|
|
const event = EventFixture({'project.name': 'whoosh'});
|
|
const result = getExpandedResults(view, {}, event);
|
|
expect(result.query).toEqual('project.name:whoosh');
|
|
});
|
|
|
|
it('should not trim values that need to be quoted', () => {
|
|
const view = new EventView({
|
|
...baseView,
|
|
...state,
|
|
query: '',
|
|
fields: [{field: 'title'}],
|
|
});
|
|
// needs to be quoted because of whitespace in middle
|
|
const event = EventFixture({title: 'hello there '});
|
|
const result = getExpandedResults(view, {}, event);
|
|
expect(result.query).toEqual('title:"hello there "');
|
|
});
|
|
|
|
it('should add environment from the data row', () => {
|
|
const view = new EventView({
|
|
...baseView,
|
|
...state,
|
|
environment: [],
|
|
query: '',
|
|
fields: [{field: 'environment'}],
|
|
});
|
|
expect(view.environment).toEqual([]);
|
|
const event = EventFixture({environment: 'staging'});
|
|
const result = getExpandedResults(view, {}, event);
|
|
expect(result.environment).toEqual(['staging']);
|
|
});
|
|
|
|
it('should not add duplicate environment', () => {
|
|
const view = new EventView({
|
|
...baseView,
|
|
...state,
|
|
query: '',
|
|
fields: [{field: 'environment'}],
|
|
});
|
|
expect(view.environment).toEqual(['staging']);
|
|
const event = EventFixture({environment: 'staging'});
|
|
const result = getExpandedResults(view, {}, event);
|
|
expect(result.environment).toEqual(['staging']);
|
|
});
|
|
});
|
|
|
|
describe('downloadAsCsv', function () {
|
|
const messageColumn = {key: 'message', name: 'message'};
|
|
const environmentColumn = {key: 'environment', name: 'environment'};
|
|
const countColumn = {key: 'count', name: 'count'};
|
|
const userColumn = {key: 'user', name: 'user'};
|
|
const equationColumn = {key: 'equation| count() + count()', name: 'count() + count()'};
|
|
it('handles raw data', function () {
|
|
const result = {
|
|
data: [
|
|
{message: 'test 1', environment: 'prod'},
|
|
{message: 'test 2', environment: 'test'},
|
|
],
|
|
};
|
|
expect(
|
|
downloadAsCsv(result, [messageColumn, environmentColumn], 'filename.csv')
|
|
).toContain(encodeURIComponent('message,environment\r\ntest 1,prod\r\ntest 2,test'));
|
|
});
|
|
it('handles aggregations', function () {
|
|
const result = {
|
|
data: [{count: 3}],
|
|
};
|
|
expect(downloadAsCsv(result, [countColumn], 'filename.csv')).toContain(
|
|
encodeURI('count\r\n3')
|
|
);
|
|
});
|
|
it('quotes unsafe strings', function () {
|
|
const result = {
|
|
data: [{message: '=HYPERLINK(http://some-bad-website#)'}],
|
|
};
|
|
expect(downloadAsCsv(result, [messageColumn], 'filename.csv')).toContain(
|
|
encodeURIComponent("message\r\n'=HYPERLINK(http://some-bad-website#)")
|
|
);
|
|
});
|
|
it('handles the user column', function () {
|
|
const result = {
|
|
data: [
|
|
{message: 'test 0', user: 'name:baz'},
|
|
{message: 'test 1', user: 'id:123'},
|
|
{message: 'test 2', user: 'email:[email protected]'},
|
|
{message: 'test 3', user: 'ip:127.0.0.1'},
|
|
],
|
|
};
|
|
expect(downloadAsCsv(result, [messageColumn, userColumn], 'filename.csv')).toContain(
|
|
encodeURIComponent(
|
|
'message,user\r\ntest 0,name:baz\r\ntest 1,id:123\r\ntest 2,email:[email protected]\r\ntest 3,ip:127.0.0.1'
|
|
)
|
|
);
|
|
});
|
|
it('handles equations', function () {
|
|
const result = {
|
|
data: [{'equation| count() + count()': 3}],
|
|
};
|
|
expect(downloadAsCsv(result, [equationColumn], 'filename.csv')).toContain(
|
|
encodeURIComponent('count() + count()\r\n3')
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('eventViewToWidgetQuery', function () {
|
|
const state: EventViewOptions = {
|
|
...baseView,
|
|
id: '1234',
|
|
name: 'best query',
|
|
fields: [{field: 'count()', width: 420}, {field: 'project.id'}],
|
|
sorts: [{field: 'count', kind: 'desc'}],
|
|
query: 'event.type:error',
|
|
project: [42],
|
|
start: '2019-10-01T00:00:00',
|
|
end: '2019-10-02T00:00:00',
|
|
statsPeriod: '14d',
|
|
environment: ['staging'],
|
|
};
|
|
|
|
it('updates orderby to function format for top N query', function () {
|
|
const view = new EventView({...baseView, ...state});
|
|
const widgetQuery = eventViewToWidgetQuery({
|
|
eventView: view,
|
|
displayType: DisplayType.TOP_N,
|
|
yAxis: ['count()'],
|
|
});
|
|
expect(widgetQuery.orderby).toEqual('-count()');
|
|
});
|
|
|
|
it('updates orderby to function format for complex function', function () {
|
|
const view = new EventView({
|
|
...baseView,
|
|
...state,
|
|
fields: [{field: 'count_unique(device.locale)', width: 420}, {field: 'project.id'}],
|
|
sorts: [{field: 'count_unique_device_locale', kind: 'desc'}],
|
|
});
|
|
const widgetQuery = eventViewToWidgetQuery({
|
|
eventView: view,
|
|
displayType: DisplayType.TABLE,
|
|
});
|
|
expect(widgetQuery.orderby).toEqual('-count_unique(device.locale)');
|
|
});
|
|
|
|
it('updates orderby to field', function () {
|
|
const view = new EventView({
|
|
...baseView,
|
|
...state,
|
|
sorts: [{field: 'project.id', kind: 'desc'}],
|
|
});
|
|
const widgetQuery = eventViewToWidgetQuery({
|
|
eventView: view,
|
|
displayType: DisplayType.TABLE,
|
|
});
|
|
expect(widgetQuery.orderby).toEqual('-project.id');
|
|
});
|
|
});
|
|
|
|
describe('generateFieldOptions', function () {
|
|
it('generates custom measurement field options', function () {
|
|
expect(
|
|
generateFieldOptions({
|
|
organization: initializeOrg().organization,
|
|
customMeasurements: [
|
|
{functions: ['p99'], key: 'measurements.custom.measurement'},
|
|
],
|
|
})['measurement:measurements.custom.measurement']
|
|
).toEqual({
|
|
label: 'measurements.custom.measurement',
|
|
value: {
|
|
kind: 'custom_measurement',
|
|
meta: {
|
|
dataType: 'number',
|
|
functions: ['p99'],
|
|
name: 'measurements.custom.measurement',
|
|
},
|
|
},
|
|
});
|
|
});
|
|
});
|