Skip to content

Commit 6c69583

Browse files
authored
fix(discover2): Various fixes (#15315)
1 parent b1655a3 commit 6c69583

File tree

15 files changed

+548
-113
lines changed

15 files changed

+548
-113
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import {DEFAULT_STATS_PERIOD} from 'app/constants';
2+
import {defined} from 'app/utils';
3+
import moment from 'moment';
4+
5+
const STATS_PERIOD_PATTERN = '^\\d+[hdmsw]?$';
6+
7+
function validStatsPeriod(input: string) {
8+
return !!input.match(STATS_PERIOD_PATTERN);
9+
}
10+
11+
const getStatsPeriodValue = (
12+
maybe: string | string[] | undefined | null
13+
): string | undefined => {
14+
if (Array.isArray(maybe)) {
15+
if (maybe.length <= 0) {
16+
return undefined;
17+
}
18+
19+
return maybe.find(validStatsPeriod);
20+
}
21+
22+
if (typeof maybe === 'string' && validStatsPeriod(maybe)) {
23+
return maybe;
24+
}
25+
26+
return undefined;
27+
};
28+
29+
// We normalize potential datetime strings into the form that would be valid
30+
// if it were to be parsed by datetime.strptime using the format %Y-%m-%dT%H:%M:%S.%f
31+
// This format was transformed to the form that moment.js understands using
32+
// https://gist.github.com/asafge/0b13c5066d06ae9a4446
33+
const normalizeDateTimeString = (
34+
input: string | undefined | null
35+
): string | undefined => {
36+
if (!input) {
37+
return undefined;
38+
}
39+
40+
const parsed = moment.utc(input);
41+
42+
if (!parsed.isValid()) {
43+
return undefined;
44+
}
45+
46+
return parsed.format('YYYY-MM-DDTHH:mm:ss.SSS');
47+
};
48+
49+
const getDateTimeString = (
50+
maybe: string | string[] | undefined | null
51+
): string | undefined => {
52+
if (Array.isArray(maybe)) {
53+
if (maybe.length <= 0) {
54+
return undefined;
55+
}
56+
57+
const result = maybe.find(needle => {
58+
return moment.utc(needle).isValid();
59+
});
60+
61+
return normalizeDateTimeString(result);
62+
}
63+
64+
return normalizeDateTimeString(maybe);
65+
};
66+
67+
const parseUtcValue = (utc: any) => {
68+
if (typeof utc !== 'undefined') {
69+
return utc === true || utc === 'true' ? 'true' : 'false';
70+
}
71+
return undefined;
72+
};
73+
74+
const getUtcValue = (maybe: string | string[] | undefined | null): string | undefined => {
75+
if (Array.isArray(maybe)) {
76+
if (maybe.length <= 0) {
77+
return undefined;
78+
}
79+
80+
return maybe.find(needle => {
81+
return !!parseUtcValue(needle);
82+
});
83+
}
84+
85+
maybe = parseUtcValue(maybe);
86+
87+
if (typeof maybe === 'string') {
88+
return maybe;
89+
}
90+
91+
return undefined;
92+
};
93+
94+
interface Params {
95+
start?: string | string[] | undefined | null;
96+
end?: string | string[] | undefined | null;
97+
period?: string | string[] | undefined | null;
98+
statsPeriod?: string | string[] | undefined | null;
99+
utc?: string | string[] | undefined | null;
100+
[others: string]: string | string[] | undefined | null;
101+
}
102+
103+
// Filters out params with null values and returns a default
104+
// `statsPeriod` when necessary.
105+
//
106+
// Accepts `period` and `statsPeriod` but will only return `statsPeriod`
107+
//
108+
// TODO(billy): Make period parameter name consistent
109+
export function getParams(params: Params): {[key: string]: string | string[]} {
110+
const {start, end, period, statsPeriod, utc, ...otherParams} = params;
111+
112+
// `statsPeriod` takes precendence for now
113+
let coercedPeriod = getStatsPeriodValue(statsPeriod) || getStatsPeriodValue(period);
114+
115+
const dateTimeStart = getDateTimeString(start);
116+
const dateTimeEnd = getDateTimeString(end);
117+
118+
if (!(dateTimeStart && dateTimeEnd)) {
119+
if (!coercedPeriod) {
120+
coercedPeriod = DEFAULT_STATS_PERIOD;
121+
}
122+
}
123+
124+
// Filter null values
125+
return Object.entries({
126+
statsPeriod: coercedPeriod,
127+
start: coercedPeriod ? null : dateTimeStart,
128+
end: coercedPeriod ? null : dateTimeEnd,
129+
// coerce utc into a string (it can be both: a string representation from router,
130+
// or a boolean from time range picker)
131+
utc: getUtcValue(utc),
132+
...otherParams,
133+
})
134+
.filter(([_key, value]) => defined(value))
135+
.reduce(
136+
(acc, [key, value]) => ({
137+
...acc,
138+
[key]: value,
139+
}),
140+
{}
141+
);
142+
}

src/sentry/static/sentry/app/components/organizations/globalSelectionHeader/utils.jsx

+7-4
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,20 @@ import {pick, pickBy, identity} from 'lodash';
22

33
import {defined} from 'app/utils';
44
import {getUtcToLocalDateObject} from 'app/utils/dates';
5+
import {getParams} from 'app/components/organizations/globalSelectionHeader/getParams';
56

67
import {URL_PARAM} from 'app/constants/globalSelectionHeader';
78

89
// Parses URL query parameters for values relevant to global selection header
910
export function getStateFromQuery(query) {
10-
let start = query[URL_PARAM.START] !== 'null' && query[URL_PARAM.START];
11-
let end = query[URL_PARAM.END] !== 'null' && query[URL_PARAM.END];
11+
const parsedParams = getParams(query);
12+
13+
let start = parsedParams.start;
14+
let end = parsedParams.end;
1215
let project = query[URL_PARAM.PROJECT];
1316
let environment = query[URL_PARAM.ENVIRONMENT];
14-
const period = query[URL_PARAM.PERIOD];
15-
const utc = query[URL_PARAM.UTC];
17+
const period = parsedParams.statsPeriod;
18+
const utc = parsedParams.utc;
1619

1720
const hasAbsolute = !!start && !!end;
1821

src/sentry/static/sentry/app/views/events/events.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import SentryTypes from 'app/sentryTypes';
1616
import parseLinkHeader from 'app/utils/parseLinkHeader';
1717
import withOrganization from 'app/utils/withOrganization';
1818

19-
import {getParams} from './utils/getParams';
19+
import {getParams} from 'app/components/organizations/globalSelectionHeader/getParams';
2020
import EventsChart from './eventsChart';
2121
import EventsTable from './eventsTable';
2222

src/sentry/static/sentry/app/views/events/index.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
22
import React from 'react';
33
import styled from 'react-emotion';
44

5-
import {getParams} from 'app/views/events/utils/getParams';
5+
import {getParams} from 'app/components/organizations/globalSelectionHeader/getParams';
66
import {t} from 'app/locale';
77
import BetaTag from 'app/components/betaTag';
88
import Feature from 'app/components/acl/feature';

src/sentry/static/sentry/app/views/events/utils/getParams.tsx

-55
This file was deleted.

src/sentry/static/sentry/app/views/eventsV2/eventView.tsx

+37-12
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {DEFAULT_PER_PAGE} from 'app/constants';
66
import {EventViewv1} from 'app/types';
77
import {SavedQuery as LegacySavedQuery} from 'app/views/discover/types';
88
import {SavedQuery, NewQuery} from 'app/stores/discoverSavedQueriesStore';
9+
import {getParams} from 'app/components/organizations/globalSelectionHeader/getParams';
910

1011
import {AUTOLINK_FIELDS, SPECIAL_FIELDS, FIELD_FORMATTERS} from './data';
1112
import {
@@ -350,6 +351,8 @@ class EventView {
350351
}
351352

352353
static fromLocation(location: Location): EventView {
354+
const {start, end, statsPeriod} = getParams(location.query);
355+
353356
return new EventView({
354357
id: decodeScalar(location.query.id),
355358
name: decodeScalar(location.query.name),
@@ -358,9 +361,9 @@ class EventView {
358361
tags: collectQueryStringByKey(location.query, 'tag'),
359362
query: decodeQuery(location) || '',
360363
project: decodeProjects(location),
361-
start: decodeScalar(location.query.start),
362-
end: decodeScalar(location.query.end),
363-
statsPeriod: decodeScalar(location.query.statsPeriod),
364+
start: decodeScalar(start),
365+
end: decodeScalar(end),
366+
statsPeriod: decodeScalar(statsPeriod),
364367
environment: collectQueryStringByKey(location.query, 'environment'),
365368
});
366369
}
@@ -402,22 +405,30 @@ class EventView {
402405
});
403406
}
404407

408+
// normalize datetime selection
409+
410+
const {start, end, statsPeriod} = getParams({
411+
start: saved.start,
412+
end: saved.end,
413+
statsPeriod: saved.range,
414+
});
415+
405416
return new EventView({
406417
fields,
407418
id: saved.id,
408419
name: saved.name,
409420
query: queryStringFromSavedQuery(saved),
410421
project: saved.projects,
411-
start: saved.start,
412-
end: saved.end,
422+
start: decodeScalar(start),
423+
end: decodeScalar(end),
424+
statsPeriod: decodeScalar(statsPeriod),
413425
sorts: fromSorts(saved.orderby),
414426
tags: collectQueryStringByKey(
415427
{
416428
tags: (saved as SavedQuery).tags as string[],
417429
},
418430
'tags'
419431
),
420-
statsPeriod: saved.range,
421432
environment: collectQueryStringByKey(
422433
{
423434
environment: (saved as SavedQuery).environment as string[],
@@ -845,17 +856,31 @@ class EventView {
845856

846857
const picked = pickRelevantLocationQueryStrings(location);
847858

859+
// normalize datetime selection
860+
861+
const normalizedTimeWindowParams = getParams({
862+
start: this.start,
863+
end: this.end,
864+
period: decodeScalar(query.period),
865+
statsPeriod: this.statsPeriod,
866+
utc: decodeScalar(query.utc),
867+
});
868+
848869
const sort = this.sorts.length > 0 ? encodeSort(this.sorts[0]) : undefined;
849870
const fields = this.getFields();
850871

851872
// generate event query
852873

853-
const eventQuery: EventQuery & LocationQuery = Object.assign(picked, {
854-
field: [...new Set(fields)],
855-
sort,
856-
per_page: DEFAULT_PER_PAGE,
857-
query: this.getQuery(query.query),
858-
});
874+
const eventQuery: EventQuery & LocationQuery = Object.assign(
875+
picked,
876+
normalizedTimeWindowParams,
877+
{
878+
field: [...new Set(fields)],
879+
sort,
880+
per_page: DEFAULT_PER_PAGE,
881+
query: this.getQuery(query.query),
882+
}
883+
);
859884

860885
if (!eventQuery.sort) {
861886
delete eventQuery.sort;

src/sentry/static/sentry/app/views/eventsV2/events.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {Panel} from 'app/components/panels';
1111
import EventsChart from 'app/views/events/eventsChart';
1212
import getDynamicText from 'app/utils/getDynamicText';
1313

14-
import {getParams} from 'app/views/events/utils/getParams';
14+
import {getParams} from 'app/components/organizations/globalSelectionHeader/getParams';
1515

1616
import Table from './table';
1717
import Tags from './tags';

src/sentry/static/sentry/app/views/eventsV2/table/tableView.tsx

+6-4
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class TableView extends React.Component<TableViewProps> {
8585
_updateColumn = (columnIndex: number, nextColumn: TableColumn<keyof TableDataRow>) => {
8686
const {location, eventView, tableData} = this.props;
8787

88-
if (!tableData) {
88+
if (!tableData || !tableData.meta) {
8989
return;
9090
}
9191

@@ -112,7 +112,7 @@ class TableView extends React.Component<TableViewProps> {
112112
_deleteColumn = (columnIndex: number) => {
113113
const {location, eventView, tableData} = this.props;
114114

115-
if (!tableData) {
115+
if (!tableData || !tableData.meta) {
116116
return;
117117
}
118118

@@ -143,7 +143,7 @@ class TableView extends React.Component<TableViewProps> {
143143
_renderGridHeaderCell = (column: TableColumn<keyof TableDataRow>): React.ReactNode => {
144144
const {eventView, location, tableData} = this.props;
145145

146-
if (!tableData) {
146+
if (!tableData || !tableData.meta) {
147147
return column.name;
148148
}
149149

@@ -178,9 +178,11 @@ class TableView extends React.Component<TableViewProps> {
178178
dataRow: TableDataRow
179179
): React.ReactNode => {
180180
const {location, organization, tableData, eventView} = this.props;
181-
if (!tableData) {
181+
182+
if (!tableData || !tableData.meta) {
182183
return dataRow[column.key];
183184
}
185+
184186
const hasLinkField = eventView.hasAutolinkField();
185187
const forceLink =
186188
!hasLinkField && eventView.getFields().indexOf(String(column.field)) === 0;

0 commit comments

Comments
 (0)