Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 6 additions & 9 deletions src/sentry/static/sentry/app/components/charts/baseChart.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ class BaseChart extends React.Component {
// x-axis and tooltips.
isGroupedByDate: PropTypes.bool,

// How is data grouped (affects formatting of axis labels and tooltips)
interval: PropTypes.oneOf(['hour', 'day']),
// Should we render hours on xaxis instead of day?
shouldXAxisRenderTimeOnly: PropTypes.bool,

// Formats dates as UTC?
utc: PropTypes.bool,
Expand All @@ -140,7 +140,7 @@ class BaseChart extends React.Component {
xAxis: {},
yAxis: {},
isGroupedByDate: false,
interval: 'day',
shouldXAxisRenderTimeOnly: false,
};

getEventsMap = () => {
Expand Down Expand Up @@ -197,7 +197,7 @@ class BaseChart extends React.Component {
toolBox,

isGroupedByDate,
interval,
shouldXAxisRenderTimeOnly,
previousPeriod,
utc,
yAxes,
Expand Down Expand Up @@ -243,17 +243,14 @@ class BaseChart extends React.Component {
useUTC: utc,
color: colors || this.getColorPalette(),
grid: Grid(grid),
tooltip:
tooltip !== null
? Tooltip({interval, isGroupedByDate, utc, ...tooltip})
: null,
tooltip: tooltip !== null ? Tooltip({isGroupedByDate, utc, ...tooltip}) : null,
legend: legend ? Legend({...legend}) : null,
yAxis: yAxisOrCustom,
xAxis:
xAxis !== null
? XAxis({
...xAxis,
interval,
shouldRenderTimeOnly: shouldXAxisRenderTimeOnly,
isGroupedByDate,
utc,
})
Expand Down
7 changes: 2 additions & 5 deletions src/sentry/static/sentry/app/components/charts/chartZoom.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import {withRouter} from 'react-router';
import PropTypes from 'prop-types';
import React from 'react';
import moment from 'moment';

import {callIfFunction} from 'app/utils/callIfFunction';
import {getFormattedDate} from 'app/utils/dates';
import {getInterval, useShortInterval} from 'app/components/charts/utils';
import {useShortInterval} from 'app/components/charts/utils';
import {updateParams} from 'app/actionCreators/globalSelection';
import DataZoom from 'app/components/charts/components/dataZoom';
import SentryTypes from 'app/sentryTypes';
Expand Down Expand Up @@ -213,7 +212,6 @@ class ChartZoom extends React.Component {
}

const hasShortInterval = useShortInterval(this.props);
const interval = getInterval(this.props);
const xAxisOptions = {
axisLabel: {
formatter: (value, index) => {
Expand All @@ -239,7 +237,6 @@ class ChartZoom extends React.Component {
isGroupedByDate: true,
onChartReady: this.handleChartReady,
utc,
interval,
dataZoom: DataZoom(),
tooltip,
toolBox: ToolBox(
Expand Down Expand Up @@ -269,4 +266,4 @@ class ChartZoom extends React.Component {
}
}

export default withRouter(ChartZoom);
export default ChartZoom;
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import {getFormattedDate} from 'app/utils/dates';
import theme from 'app/utils/theme';
import {truncationFormatter} from '../utils';

export default function XAxis({isGroupedByDate, interval, utc, ...props} = {}) {
export default function XAxis(
{isGroupedByDate, shouldRenderTimeOnly, utc, ...props} = {}
) {
const axisLabelFormatter = value => {
if (isGroupedByDate) {
const format = interval === 'hour' ? 'LT' : 'MMM Do';
const format = shouldRenderTimeOnly === 'hour' ? 'LT' : 'MMM Do';
return getFormattedDate(value, format, {local: !utc});
} else if (props.truncate) {
return truncationFormatter(value, props.truncate);
Expand Down
46 changes: 39 additions & 7 deletions src/sentry/static/sentry/app/components/charts/utils.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import moment from 'moment';

import {parsePeriodToHours} from 'app/utils';

const DEFAULT_TRUNCATE_LENGTH = 80;

// In minutes
const TWENTY_FOUR_HOURS = 1440;
const THIRTY_MINUTES = 30;

export function truncationFormatter(value, truncate) {
if (!truncate) {
return value;
Expand All @@ -17,15 +23,41 @@ export function truncationFormatter(value, truncate) {
* Use a shorter interval if the time difference is <= 24 hours.
*/
export function useShortInterval(datetimeObj) {
const {period, start, end} = datetimeObj;
const diffInMinutes = getDiffInMinutes(datetimeObj);

if (typeof period === 'string') {
return period.endsWith('h') || period === '1d';
}
return diffInMinutes <= TWENTY_FOUR_HOURS;
}

export function getInterval(datetimeObj, highFidelity = false) {
const diffInMinutes = getDiffInMinutes(datetimeObj);

return moment(end).diff(start, 'hours') <= 24;
if (diffInMinutes > TWENTY_FOUR_HOURS) {
// Greater than 24 hours
if (highFidelity) {
return '30m';
} else {
return '24h';
}
} else if (diffInMinutes < THIRTY_MINUTES) {
// Less than 30 minutes
if (highFidelity) {
return '1m';
} else {
return '5m';
}
} else {
// Between 30 minutes and 24 hours
if (highFidelity) {
return '5m';
} else {
return '15m';
}
}
}

export function getInterval(datetimeObj) {
return useShortInterval(datetimeObj) ? '5m' : '30m';
export function getDiffInMinutes(datetimeObj) {
const {period, start, end} = datetimeObj;
return typeof period === 'string'
? parsePeriodToHours(period) * 60
: moment(end).diff(start, 'minutes');
}
18 changes: 18 additions & 0 deletions src/sentry/static/sentry/app/sentryTypes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,23 @@ export const DiscoverSavedQuery = PropTypes.shape({
...DiscoverQueryShape,
});

const DiscoverResultsShape = {
data: PropTypes.arrayOf(PropTypes.object),
meta: PropTypes.arrayOf(
PropTypes.shape({
type: PropTypes.string,
name: PropTypes.string,
})
),
timing: PropTypes.shape({
duration_ms: PropTypes.number,
marks_ms: PropTypes.object,
timestamp: PropTypes.number,
}),
};

export const DiscoverResults = PropTypes.arrayOf(PropTypes.shape(DiscoverResultsShape));

/**
* A Member is someone that was invited to Sentry but may
* not have registered for an account yet
Expand Down Expand Up @@ -879,6 +896,7 @@ let SentryTypes = {
Deploy,
DiscoverQuery,
DiscoverSavedQuery,
DiscoverResults,
Environment,
Event,
Organization: PropTypes.shape({
Expand Down
6 changes: 4 additions & 2 deletions src/sentry/static/sentry/app/utils.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -249,17 +249,19 @@ export function isWebpackChunkLoadingError(error) {
* and converts it into hours
*/
export function parsePeriodToHours(str) {
const [, periodNumber, periodLength] = str.match(/([0-9]+)([mhdw])/);
const [, periodNumber, periodLength] = str.match(/([0-9]+)([smhdw])/);

switch (periodLength) {
case 's':
return periodNumber / (60 * 60);
case 'm':
return periodNumber / 60;
case 'h':
return periodNumber;
case 'd':
return periodNumber * 24;
case 'w':
return periodLength * 24 * 7;
return periodNumber * 24 * 7;
default:
return -1;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import {isEqual, omit} from 'lodash';
import PropTypes from 'prop-types';
import React from 'react';

import {getInterval} from 'app/components/charts/utils';
import {getPeriod} from 'app/utils/getPeriod';
import {parsePeriodToHours} from 'app/utils';
import SentryTypes from 'app/sentryTypes';
import createQueryBuilder from 'app/views/organizationDiscover/queryBuilder';
import withGlobalSelection from 'app/utils/withGlobalSelection';
import withOrganization from 'app/utils/withOrganization';

class DiscoverQuery extends React.Component {
static propTypes = {
Expand All @@ -24,6 +25,7 @@ class DiscoverQuery extends React.Component {

this.state = {
results: null,
reloading: null,
};

// Query builders based on `queries`
Expand All @@ -36,8 +38,24 @@ class DiscoverQuery extends React.Component {
this.fetchData();
}

shouldComponentUpdate(nextProps, nextState) {
if (this.state !== nextState) {
return true;
}

if (
this.props.organization === nextProps.organization &&
this.props.selection === nextProps.selection
) {
return false;
}

return true;
}

componentDidUpdate(prevProps) {
if (prevProps === this.props) {
const keysToIgnore = ['children'];
if (isEqual(omit(prevProps, keysToIgnore), omit(this.props, keysToIgnore))) {
return;
}

Expand Down Expand Up @@ -67,6 +85,12 @@ class DiscoverQuery extends React.Component {
period = {start, end, range: statsPeriod};
}

if (query.rollup) {
// getInterval returns a period string depending on current datetime range selected
// we then use a helper function to parse into hours and then convert back to seconds
query.rollup = parsePeriodToHours(getInterval(datetime)) * 60 * 60;
}

return {
...query,
...selection,
Expand All @@ -88,15 +112,13 @@ class DiscoverQuery extends React.Component {
this.resetQueries();

// Fetch
this.setState({reloading: true});
const promises = this.queryBuilders.map(builder => builder.fetchWithoutLimit());
let results = await Promise.all(promises);
let previousData = null;
let data = null;

this.setState({
reloading: false,
results,
data,
previousData,
});
}

Expand All @@ -105,11 +127,10 @@ class DiscoverQuery extends React.Component {

return children({
queries: this.queryBuilders.map(builder => builder.getInternal()),
reloading: this.state.reloading,
results: this.state.results,
data: this.state.data,
previousData: this.state.previousData,
});
}
}

export default withGlobalSelection(withOrganization(DiscoverQuery));
export default DiscoverQuery;
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import PropTypes from 'prop-types';
import React from 'react';

import {getQueryStringFromQuery} from 'app/views/organizationDiscover/utils';
import Button from 'app/components/button';
import InlineSvg from 'app/components/inlineSvg';
import SentryTypes from 'app/sentryTypes';
import withOrganization from 'app/utils/withOrganization';

class ExploreWidget extends React.Component {
static propTypes = {
widget: SentryTypes.Widget,
organization: SentryTypes.Organization,
selection: SentryTypes.GlobalSelection,
router: PropTypes.object,
};

handleExportToDiscover = event => {
const {organization, widget, router} = this.props;
const [firstQuery] = widget.queries.discover;
const {
datetime,
environments, // eslint-disable-line no-unused-vars
...selection
} = this.props.selection;

event.stopPropagation();

// Discover does not support importing these
const {
groupby, // eslint-disable-line no-unused-vars
rollup, // eslint-disable-line no-unused-vars
orderby,
...query
} = firstQuery;

const orderbyTimeIndex = orderby.indexOf('time');
let visual = 'table';

if (orderbyTimeIndex !== -1) {
query.orderby = `${orderbyTimeIndex === 0 ? '' : '-'}${query.aggregations[0][2]}`;
visual = 'line-by-day';
} else {
query.orderby = orderby;
}

router.push(
`/organizations/${organization.slug}/discover/${getQueryStringFromQuery({
...query,
...selection,
start: datetime.start,
end: datetime.end,
range: datetime.period,
limit: 1000,
})}&visual=${visual}`
);
};

render() {
// TODO(billy): This is temporary
// Need design followups
return (
<Button size="xsmall" onClick={this.handleExportToDiscover}>
<InlineSvg src="icon-discover" />
</Button>
);
}
}
export default withOrganization(ExploreWidget);
Loading