123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874 |
- import {browserHistory} from 'react-router';
- 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 = TestStubs.location({
- 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, {}, TestStubs.Event());
- expect(result.fields).toEqual([{field: 'id', width: -1}]);
- });
- it('preserves aggregated fields', () => {
- let view = new EventView(state);
- let result = getExpandedResults(view, {}, TestStubs.Event());
- // 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, {}, TestStubs.Event());
- // 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, {}, TestStubs.Event());
- 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, {}, TestStubs.Event());
- 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'}, TestStubs.Event());
- expect(result.query).toEqual('event.type:error extra:condition title:ApiException');
- // handles user tag values.
- result = getExpandedResults(view, {user: 'id:12735'}, TestStubs.Event());
- expect(result.query).toEqual('event.type:error user:id:12735 title:ApiException');
- result = getExpandedResults(view, {user: 'name:uhoh'}, TestStubs.Event());
- expect(result.query).toEqual('event.type:error user:name:uhoh title:ApiException');
- // quotes value
- result = getExpandedResults(view, {extra: 'has space'}, TestStubs.Event());
- expect(result.query).toEqual('event.type:error extra:"has space" title:ApiException');
- // appends to existing conditions
- result = getExpandedResults(view, {'event.type': 'csp'}, TestStubs.Event());
- expect(result.query).toEqual('event.type:csp title:ApiException');
- // Includes empty strings
- result = getExpandedResults(view, {}, TestStubs.Event({id: '0', custom_tag: ''}));
- expect(result.query).toEqual('event.type:error title:ApiException custom_tag:""');
- // Includes 0
- result = getExpandedResults(view, {}, TestStubs.Event({id: '0', custom_tag: 0}));
- expect(result.query).toEqual('event.type:error title:ApiException custom_tag:0');
- // Includes null
- result = getExpandedResults(view, {}, TestStubs.Event({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'}, TestStubs.Event());
- 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'},
- TestStubs.Event({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 = TestStubs.Event({
- 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(TestStubs.location());
- const result = getExpandedResults(view, {some_tag: 'value'}, TestStubs.Event());
- 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 = TestStubs.Event({
- 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 = TestStubs.Event({
- 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 = TestStubs.Event({
- 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 = TestStubs.Event({
- 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 = TestStubs.Event({
- 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 = TestStubs.Event({
- 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 = TestStubs.Event({
- 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 = TestStubs.Event({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 = TestStubs.Event({'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 = TestStubs.Event({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 = TestStubs.Event({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 = TestStubs.Event({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:test@example.com'},
- {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:test@example.com\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',
- },
- },
- });
- });
- });
|