|
@@ -64,6 +64,7 @@ import {
|
|
|
import {IssueSortOptions} from 'sentry/views/issueList/utils';
|
|
|
|
|
|
import {DEFAULT_STATS_PERIOD} from '../data';
|
|
|
+import {getNumEquations} from '../utils';
|
|
|
|
|
|
import {ColumnsStep} from './buildSteps/columnsStep';
|
|
|
import {DashboardStep} from './buildSteps/dashboardStep';
|
|
@@ -190,12 +191,6 @@ function WidgetBuilder({
|
|
|
location.query.defaultWidgetQuery
|
|
|
);
|
|
|
|
|
|
- useEffect(() => {
|
|
|
- if (objectIsEmpty(tags)) {
|
|
|
- loadOrganizationTags(api, organization.slug, selection);
|
|
|
- }
|
|
|
- }, []);
|
|
|
-
|
|
|
// Feature flag for new widget builder design. This feature is still a work in progress and not yet available internally.
|
|
|
const widgetBuilderNewDesign = organization.features.includes(
|
|
|
'new-widget-builder-experience-design'
|
|
@@ -272,6 +267,10 @@ function WidgetBuilder({
|
|
|
new_widget: !isEditing,
|
|
|
});
|
|
|
|
|
|
+ if (objectIsEmpty(tags)) {
|
|
|
+ loadOrganizationTags(api, organization.slug, selection);
|
|
|
+ }
|
|
|
+
|
|
|
if (isEditing && isValidWidgetIndex) {
|
|
|
const widgetFromDashboard = filteredDashboardWidgets[widgetIndexNum];
|
|
|
setState({
|
|
@@ -433,7 +432,10 @@ function WidgetBuilder({
|
|
|
set(newState, 'queries', normalized);
|
|
|
|
|
|
if (widgetBuilderNewDesign) {
|
|
|
- if (getIsTimeseriesChart(newDisplayType) && normalized[0].columns.length) {
|
|
|
+ if (
|
|
|
+ getIsTimeseriesChart(newDisplayType) &&
|
|
|
+ normalized[0].columns.filter(column => !!column).length
|
|
|
+ ) {
|
|
|
// If a limit already exists (i.e. going between timeseries) then keep it,
|
|
|
// otherwise calculate a limit
|
|
|
newState.limit =
|
|
@@ -566,7 +568,7 @@ function WidgetBuilder({
|
|
|
|
|
|
const newQueries = state.queries.map(query => {
|
|
|
const isDescending = query.orderby.startsWith('-');
|
|
|
- const orderbyAggregateAliasField = trimStart(query.orderby, '-');
|
|
|
+ const rawOrderby = trimStart(query.orderby, '-');
|
|
|
const prevAggregateAliasFieldStrings = query.aggregates.map(aggregate =>
|
|
|
state.dataSet === DataSet.RELEASE
|
|
|
? stripDerivedMetricsPrefix(aggregate)
|
|
@@ -601,32 +603,36 @@ function WidgetBuilder({
|
|
|
newQuery.columns = columnsAndAggregates?.columns ?? [];
|
|
|
}
|
|
|
|
|
|
- if (
|
|
|
- !aggregateAliasFieldStrings.includes(orderbyAggregateAliasField) &&
|
|
|
- query.orderby !== ''
|
|
|
- ) {
|
|
|
- if (prevAggregateAliasFieldStrings.length === newFields.length) {
|
|
|
- // The Field that was used in orderby has changed. Get the new field.
|
|
|
+ if (!aggregateAliasFieldStrings.includes(rawOrderby) && query.orderby !== '') {
|
|
|
+ if (
|
|
|
+ prevAggregateAliasFieldStrings.length === newFields.length &&
|
|
|
+ prevAggregateAliasFieldStrings.includes(rawOrderby)
|
|
|
+ ) {
|
|
|
+ // The aggregate that was used in orderby has changed. Get the new field.
|
|
|
let newOrderByValue =
|
|
|
aggregateAliasFieldStrings[
|
|
|
- prevAggregateAliasFieldStrings.indexOf(orderbyAggregateAliasField)
|
|
|
+ prevAggregateAliasFieldStrings.indexOf(rawOrderby)
|
|
|
];
|
|
|
|
|
|
if (!stripEquationPrefix(newOrderByValue ?? '')) {
|
|
|
newOrderByValue = '';
|
|
|
}
|
|
|
|
|
|
- if (isDescending) {
|
|
|
- newQuery.orderby = `-${newOrderByValue}`;
|
|
|
- } else {
|
|
|
- newQuery.orderby = newOrderByValue;
|
|
|
- }
|
|
|
+ newQuery.orderby = `${isDescending ? '-' : ''}${newOrderByValue}`;
|
|
|
} else {
|
|
|
+ const isFromAggregates = aggregateAliasFieldStrings.includes(rawOrderby);
|
|
|
+ const isCustomEquation = isEquation(rawOrderby);
|
|
|
+ const isUsedInGrouping = newQuery.columns.includes(rawOrderby);
|
|
|
+
|
|
|
+ const keepCurrentOrderby =
|
|
|
+ isFromAggregates || isCustomEquation || isUsedInGrouping;
|
|
|
+ const firstAggregateAlias = isEquation(aggregateAliasFieldStrings[0])
|
|
|
+ ? `equation[${getNumEquations(aggregateAliasFieldStrings) - 1}]`
|
|
|
+ : aggregateAliasFieldStrings[0];
|
|
|
+
|
|
|
newQuery.orderby = widgetBuilderNewDesign
|
|
|
- ? ((aggregateAliasFieldStrings.includes(orderbyAggregateAliasField) ||
|
|
|
- isEquation(orderbyAggregateAliasField)) &&
|
|
|
- newQuery.orderby) ||
|
|
|
- `${isDescending ? '-' : ''}${aggregateAliasFieldStrings[0]}`
|
|
|
+ ? (keepCurrentOrderby && newQuery.orderby) ||
|
|
|
+ `${isDescending ? '-' : ''}${firstAggregateAlias}`
|
|
|
: '';
|
|
|
}
|
|
|
}
|
|
@@ -670,28 +676,47 @@ function WidgetBuilder({
|
|
|
const newQueries = state.queries.map(query => {
|
|
|
const newQuery = cloneDeep(query);
|
|
|
newQuery.columns = fieldStrings;
|
|
|
+ const orderby = trimStart(newQuery.orderby, '-');
|
|
|
+ const aggregateAliasFieldStrings = newQuery.aggregates.map(getAggregateAlias);
|
|
|
+
|
|
|
+ if (!fieldStrings.length) {
|
|
|
+ // The grouping was cleared, so clear the orderby
|
|
|
+ newQuery.orderby = '';
|
|
|
+ } else if (
|
|
|
+ aggregateAliasFieldStrings.length &&
|
|
|
+ !aggregateAliasFieldStrings.includes(orderby) &&
|
|
|
+ !newQuery.columns.includes(orderby) &&
|
|
|
+ !isEquation(orderby)
|
|
|
+ ) {
|
|
|
+ // If the orderby isn't contained in either aggregates or columns, choose the first aggregate
|
|
|
+ const isDescending = newQuery.orderby.startsWith('-');
|
|
|
+ const prefix = orderby && !isDescending ? '' : '-';
|
|
|
+ const firstAggregateAlias = isEquation(aggregateAliasFieldStrings[0])
|
|
|
+ ? `equation[${getNumEquations(aggregateAliasFieldStrings) - 1}]`
|
|
|
+ : aggregateAliasFieldStrings[0];
|
|
|
+ newQuery.orderby = `${prefix}${firstAggregateAlias}`;
|
|
|
+ }
|
|
|
return newQuery;
|
|
|
});
|
|
|
|
|
|
set(newState, 'userHasModified', true);
|
|
|
set(newState, 'queries', newQueries);
|
|
|
|
|
|
- if (widgetBuilderNewDesign && isTimeseriesChart) {
|
|
|
- const groupByFields = newState.queries[0].columns.filter(
|
|
|
- field => !(field === 'equation|')
|
|
|
+ const groupByFields = newState.queries[0].columns.filter(
|
|
|
+ field => !(field === 'equation|')
|
|
|
+ );
|
|
|
+
|
|
|
+ if (groupByFields.length === 0) {
|
|
|
+ set(newState, 'limit', undefined);
|
|
|
+ } else {
|
|
|
+ set(
|
|
|
+ newState,
|
|
|
+ 'limit',
|
|
|
+ Math.min(
|
|
|
+ newState.limit ?? DEFAULT_RESULTS_LIMIT,
|
|
|
+ getResultsLimit(newQueries.length, newQueries[0].aggregates.length)
|
|
|
+ )
|
|
|
);
|
|
|
- if (groupByFields.length === 0) {
|
|
|
- set(newState, 'limit', undefined);
|
|
|
- } else {
|
|
|
- set(
|
|
|
- newState,
|
|
|
- 'limit',
|
|
|
- Math.min(
|
|
|
- newState.limit ?? DEFAULT_RESULTS_LIMIT,
|
|
|
- getResultsLimit(newQueries.length, newQueries[0].aggregates.length)
|
|
|
- )
|
|
|
- );
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
setState(newState);
|
|
@@ -920,6 +945,16 @@ function WidgetBuilder({
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
+ if (isEditing && !isValidWidgetIndex) {
|
|
|
+ return (
|
|
|
+ <SentryDocumentTitle title={dashboard.title} orgSlug={orgSlug}>
|
|
|
+ <PageContent>
|
|
|
+ <LoadingError message={t('The widget you want to edit was not found.')} />
|
|
|
+ </PageContent>
|
|
|
+ </SentryDocumentTitle>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
const canAddSearchConditions =
|
|
|
[DisplayType.LINE, DisplayType.AREA, DisplayType.BAR].includes(state.displayType) &&
|
|
|
state.queries.length < 3;
|
|
@@ -950,15 +985,16 @@ function WidgetBuilder({
|
|
|
? fields.map((field, index) => explodeField({field, alias: fieldAliases[index]}))
|
|
|
: [...explodedColumns, ...explodedAggregates];
|
|
|
|
|
|
- if (isEditing && !isValidWidgetIndex) {
|
|
|
- return (
|
|
|
- <SentryDocumentTitle title={dashboard.title} orgSlug={orgSlug}>
|
|
|
- <PageContent>
|
|
|
- <LoadingError message={t('The widget you want to edit was not found.')} />
|
|
|
- </PageContent>
|
|
|
- </SentryDocumentTitle>
|
|
|
- );
|
|
|
- }
|
|
|
+ const groupByValueSelected = currentWidget.queries.some(query => {
|
|
|
+ const noEmptyColumns = query.columns.filter(column => !!column);
|
|
|
+ return noEmptyColumns.length > 0;
|
|
|
+ });
|
|
|
+
|
|
|
+ // The SortBy field shall only be displayed in tabular visualizations or
|
|
|
+ // on time-series visualizations when at least one groupBy value is selected
|
|
|
+ const displaySortByStep =
|
|
|
+ (widgetBuilderNewDesign && isTimeseriesChart && groupByValueSelected) ||
|
|
|
+ isTabularChart;
|
|
|
|
|
|
return (
|
|
|
<SentryDocumentTitle title={dashboard.title} orgSlug={orgSlug}>
|
|
@@ -1053,7 +1089,7 @@ function WidgetBuilder({
|
|
|
dataSet={state.dataSet}
|
|
|
/>
|
|
|
)}
|
|
|
- {((widgetBuilderNewDesign && isTimeseriesChart) || isTabularChart) && (
|
|
|
+ {displaySortByStep && (
|
|
|
<SortByStep
|
|
|
limit={state.limit}
|
|
|
displayType={state.displayType}
|