+
+
+
+
);
}
diff --git a/static/app/views/performance/landing/widgets/components/queryHandler.tsx b/static/app/views/performance/landing/widgets/components/queryHandler.tsx
index da20b7672cec39..75c84401d0358d 100644
--- a/static/app/views/performance/landing/widgets/components/queryHandler.tsx
+++ b/static/app/views/performance/landing/widgets/components/queryHandler.tsx
@@ -41,8 +41,6 @@ export function QueryHandler
(
organization={props.queryProps.organization}
orgSlug={props.queryProps.organization.slug}
query={props.queryProps.eventView.getQueryWithAdditionalConditions()}
- eventView={props.queryProps.eventView}
- location={props.queryProps.location}
widgetData={props.widgetData}
>
{results => {
diff --git a/static/app/views/performance/landing/widgets/components/widgetHeader.tsx b/static/app/views/performance/landing/widgets/components/widgetHeader.tsx
index 8d805b0d6c19c3..24b026bbe80b2a 100644
--- a/static/app/views/performance/landing/widgets/components/widgetHeader.tsx
+++ b/static/app/views/performance/landing/widgets/components/widgetHeader.tsx
@@ -17,10 +17,12 @@ export function WidgetHeader(
return (
-
- {title}
-
-
+
{Subtitle ? : null}
diff --git a/static/app/views/performance/landing/widgets/types.tsx b/static/app/views/performance/landing/widgets/types.tsx
index 8c8b8990a3a799..2f1b8490c9a196 100644
--- a/static/app/views/performance/landing/widgets/types.tsx
+++ b/static/app/views/performance/landing/widgets/types.tsx
@@ -48,9 +48,7 @@ export type QueryFC = FunctionComponent<
team?: Readonly;
query?: string;
orgSlug: string;
- location: Location;
organization: OrganizationSummary;
- eventView: EventView;
widgetData: T;
}
>;
diff --git a/static/app/views/performance/landing/widgets/widgetDefinitions.tsx b/static/app/views/performance/landing/widgets/widgetDefinitions.tsx
index 19a5ea8e0c071e..e3b552563d23de 100644
--- a/static/app/views/performance/landing/widgets/widgetDefinitions.tsx
+++ b/static/app/views/performance/landing/widgets/widgetDefinitions.tsx
@@ -23,6 +23,7 @@ export enum PerformanceWidgetSetting {
FID_HISTOGRAM = 'fid_histogram',
APDEX_AREA = 'apdex_area',
P50_DURATION_AREA = 'p50_duration_area',
+ P75_DURATION_AREA = 'p75_duration_area',
P95_DURATION_AREA = 'p95_duration_area',
P99_DURATION_AREA = 'p99_duration_area',
P75_LCP_AREA = 'p75_lcp_area',
@@ -34,6 +35,16 @@ export enum PerformanceWidgetSetting {
MOST_REGRESSED = 'most_regressed',
MOST_RELATED_ERRORS = 'most_related_errors',
MOST_RELATED_ISSUES = 'most_related_issues',
+ SLOW_HTTP_OPS = 'slow_http_ops',
+ SLOW_DB_OPS = 'slow_db_ops',
+ SLOW_RESOURCE_OPS = 'slow_resource_ops',
+ SLOW_BROWSER_OPS = 'slow_browser_ops',
+ COLD_STARTUP_AREA = 'cold_startup_area',
+ WARM_STARTUP_AREA = 'warm_startup_area',
+ SLOW_FRAMES_AREA = 'slow_frames_area',
+ FROZEN_FRAMES_AREA = 'frozen_frames_area',
+ MOST_SLOW_FRAMES = 'most_slow_frames',
+ MOST_FROZEN_FRAMES = 'most_frozen_frames',
}
const WIDGET_PALETTE = CHART_PALETTE[5];
@@ -105,6 +116,13 @@ export const WIDGET_DEFINITIONS: ({
dataType: GenericPerformanceWidgetDataType.area,
chartColor: WIDGET_PALETTE[3],
},
+ [PerformanceWidgetSetting.P75_DURATION_AREA]: {
+ title: t('p75 Duration'),
+ titleTooltip: getTermHelp(organization, PERFORMANCE_TERM.P75),
+ fields: ['p75(transaction.duration)'], // TODO(k-fish): Check
+ dataType: GenericPerformanceWidgetDataType.area,
+ chartColor: WIDGET_PALETTE[3],
+ },
[PerformanceWidgetSetting.P95_DURATION_AREA]: {
title: t('p95 Duration'),
titleTooltip: getTermHelp(organization, PERFORMANCE_TERM.P95),
@@ -140,20 +158,90 @@ export const WIDGET_DEFINITIONS: ({
dataType: GenericPerformanceWidgetDataType.area,
chartColor: WIDGET_PALETTE[0],
},
+ [PerformanceWidgetSetting.COLD_STARTUP_AREA]: {
+ title: t('Cold Startup Time'),
+ titleTooltip: getTermHelp(organization, PERFORMANCE_TERM.APP_START_COLD),
+ fields: ['p75(measurements.app_start_cold)'],
+ dataType: GenericPerformanceWidgetDataType.area,
+ chartColor: WIDGET_PALETTE[4],
+ },
+ [PerformanceWidgetSetting.WARM_STARTUP_AREA]: {
+ title: t('Warm Startup Time'),
+ titleTooltip: getTermHelp(organization, PERFORMANCE_TERM.APP_START_WARM),
+ fields: ['p75(measurements.app_start_warm)'],
+ dataType: GenericPerformanceWidgetDataType.area,
+ chartColor: WIDGET_PALETTE[3],
+ },
+ [PerformanceWidgetSetting.SLOW_FRAMES_AREA]: {
+ title: t('Slow Frames'),
+ titleTooltip: getTermHelp(organization, PERFORMANCE_TERM.SLOW_FRAMES),
+ fields: ['p75(measurements.frames_slow_rate)'],
+ dataType: GenericPerformanceWidgetDataType.area,
+ chartColor: WIDGET_PALETTE[0],
+ },
+ [PerformanceWidgetSetting.FROZEN_FRAMES_AREA]: {
+ title: t('Frozen Frames'),
+ titleTooltip: getTermHelp(organization, PERFORMANCE_TERM.FROZEN_FRAMES),
+ fields: ['p75(measurements.frames_frozen_rate)'],
+ dataType: GenericPerformanceWidgetDataType.area,
+ chartColor: WIDGET_PALETTE[5],
+ },
[PerformanceWidgetSetting.MOST_RELATED_ERRORS]: {
title: t('Most Related Errors'),
- titleTooltip: getTermHelp(organization, PERFORMANCE_TERM.USER_MISERY),
+ titleTooltip: getTermHelp(organization, PERFORMANCE_TERM.MOST_ERRORS),
fields: [`failure_count()`],
dataType: GenericPerformanceWidgetDataType.line_list,
chartColor: WIDGET_PALETTE[0],
},
[PerformanceWidgetSetting.MOST_RELATED_ISSUES]: {
title: t('Most Related Issues'),
- titleTooltip: getTermHelp(organization, PERFORMANCE_TERM.USER_MISERY),
+ titleTooltip: getTermHelp(organization, PERFORMANCE_TERM.MOST_ISSUES),
fields: [`count()`],
dataType: GenericPerformanceWidgetDataType.line_list,
chartColor: WIDGET_PALETTE[0],
},
+ [PerformanceWidgetSetting.SLOW_HTTP_OPS]: {
+ title: t('Slow HTTP Ops'),
+ titleTooltip: getTermHelp(organization, PERFORMANCE_TERM.SLOW_HTTP_SPANS),
+ fields: [`p75(spans.http)`],
+ dataType: GenericPerformanceWidgetDataType.line_list,
+ chartColor: WIDGET_PALETTE[0],
+ },
+ [PerformanceWidgetSetting.SLOW_BROWSER_OPS]: {
+ title: t('Slow Browser Ops'),
+ titleTooltip: getTermHelp(organization, PERFORMANCE_TERM.SLOW_HTTP_SPANS),
+ fields: [`p75(spans.browser)`],
+ dataType: GenericPerformanceWidgetDataType.line_list,
+ chartColor: WIDGET_PALETTE[0],
+ },
+ [PerformanceWidgetSetting.SLOW_RESOURCE_OPS]: {
+ title: t('Slow Resource Ops'),
+ titleTooltip: getTermHelp(organization, PERFORMANCE_TERM.SLOW_HTTP_SPANS),
+ fields: [`p75(spans.resource)`],
+ dataType: GenericPerformanceWidgetDataType.line_list,
+ chartColor: WIDGET_PALETTE[0],
+ },
+ [PerformanceWidgetSetting.SLOW_DB_OPS]: {
+ title: t('Slow DB Ops'),
+ titleTooltip: getTermHelp(organization, PERFORMANCE_TERM.SLOW_HTTP_SPANS),
+ fields: [`p75(spans.db)`],
+ dataType: GenericPerformanceWidgetDataType.line_list,
+ chartColor: WIDGET_PALETTE[0],
+ },
+ [PerformanceWidgetSetting.MOST_SLOW_FRAMES]: {
+ title: t('Most Slow Frames'),
+ titleTooltip: getTermHelp(organization, PERFORMANCE_TERM.SLOW_FRAMES),
+ fields: ['p75(measurements.frames_slow_rate)'],
+ dataType: GenericPerformanceWidgetDataType.line_list,
+ chartColor: WIDGET_PALETTE[0],
+ },
+ [PerformanceWidgetSetting.MOST_FROZEN_FRAMES]: {
+ title: t('Most Frozen Frames'),
+ titleTooltip: getTermHelp(organization, PERFORMANCE_TERM.FROZEN_FRAMES),
+ fields: ['p75(measurements.frames_frozen_rate)'],
+ dataType: GenericPerformanceWidgetDataType.line_list,
+ chartColor: WIDGET_PALETTE[0],
+ },
[PerformanceWidgetSetting.MOST_IMPROVED]: {
title: t('Most Improved'),
titleTooltip: t(
diff --git a/static/app/views/performance/landing/widgets/widgets/histogramWidget.tsx b/static/app/views/performance/landing/widgets/widgets/histogramWidget.tsx
index 3d39713bb103da..a06a124d7d03ca 100644
--- a/static/app/views/performance/landing/widgets/widgets/histogramWidget.tsx
+++ b/static/app/views/performance/landing/widgets/widgets/histogramWidget.tsx
@@ -39,12 +39,18 @@ export function HistogramWidget(props: Props) {
chart: {
fields: props.fields,
component: provided => (
-
+
),
transform: transformHistogramQuery,
},
};
- }, [props.eventView, props.fields, props.organization.slug]);
+ }, [props.eventView.query, props.fields[0], props.organization.slug]);
const onFilterChange = () => {};
diff --git a/static/app/views/performance/landing/widgets/widgets/lineChartListWidget.tsx b/static/app/views/performance/landing/widgets/widgets/lineChartListWidget.tsx
index d20645d424899f..b1b6b0ff7b0566 100644
--- a/static/app/views/performance/landing/widgets/widgets/lineChartListWidget.tsx
+++ b/static/app/views/performance/landing/widgets/widgets/lineChartListWidget.tsx
@@ -14,10 +14,12 @@ import space from 'app/styles/space';
import {Organization} from 'app/types';
import DiscoverQuery from 'app/utils/discover/discoverQuery';
import EventView from 'app/utils/discover/eventView';
+import {getAggregateAlias} from 'app/utils/discover/fields';
import {MutableSearch} from 'app/utils/tokenizeSearch';
import withApi from 'app/utils/withApi';
import _DurationChart from 'app/views/performance/charts/chart';
import {transactionSummaryRouteWithQuery} from 'app/views/performance/transactionSummary/utils';
+import {getPerformanceDuration} from 'app/views/performance/utils';
import {excludeTransaction} from '../../utils';
import {GenericPerformanceWidget} from '../components/performanceWidget';
@@ -56,98 +58,126 @@ export function LineChartListWidget(props: Props) {
);
}
- const Queries = {
- list: useMemo>(
- () => ({
- fields: props.fields[0],
- component: provided => {
- const eventView = provided.eventView.clone();
- eventView.sorts = [{kind: 'desc', field: props.fields[0]}];
- if (props.chartSetting === PerformanceWidgetSetting.MOST_RELATED_ISSUES) {
- eventView.fields = [
- {field: 'issue'},
- {field: 'transaction'},
- {field: 'title'},
- {field: 'project.id'},
- {field: props.fields[0]},
- ];
- eventView.additionalConditions.setFilterValues('event.type', ['error']);
- eventView.additionalConditions.setFilterValues('!tags[transaction]', ['']);
- const mutableSearch = new MutableSearch(eventView.query);
- mutableSearch.removeFilter('transaction.duration');
- eventView.query = mutableSearch.formatString();
- } else {
- eventView.fields = [
- {field: 'transaction'},
- {field: 'project.id'},
- {field: props.fields[0]},
- ];
- }
- return ;
- },
- transform: transformDiscoverToList,
- }),
- [props.eventView, props.fields, props.chartSetting, props.organization.slug]
- ),
- chart: useMemo>(
- () => ({
- enabled: widgetData => {
- return !!widgetData?.list?.data?.length;
- },
- fields: props.fields[0],
- component: provided => {
- const eventView = provided.eventView.clone();
- eventView.additionalConditions.setFilterValues('transaction', [
- provided.widgetData.list.data[selectedListIndex].transaction as string,
+ const slowList = [
+ PerformanceWidgetSetting.SLOW_HTTP_OPS,
+ PerformanceWidgetSetting.SLOW_DB_OPS,
+ PerformanceWidgetSetting.SLOW_BROWSER_OPS,
+ PerformanceWidgetSetting.SLOW_RESOURCE_OPS,
+ PerformanceWidgetSetting.MOST_SLOW_FRAMES,
+ PerformanceWidgetSetting.MOST_FROZEN_FRAMES,
+ ];
+ const isSlowestType = slowList.includes(props.chartSetting);
+
+ const eventView = props.eventView.clone();
+
+ const listQuery = useMemo>(
+ () => ({
+ fields: props.fields[0],
+ component: provided => {
+ eventView.sorts = [{kind: 'desc', field: props.fields[0]}];
+ if (props.chartSetting === PerformanceWidgetSetting.MOST_RELATED_ISSUES) {
+ eventView.fields = [
+ {field: 'issue'},
+ {field: 'transaction'},
+ {field: 'title'},
+ {field: 'project.id'},
+ {field: props.fields[0]},
+ ];
+ eventView.additionalConditions.setFilterValues('event.type', ['error']);
+ eventView.additionalConditions.setFilterValues('!tags[transaction]', ['']);
+ const mutableSearch = new MutableSearch(eventView.query);
+ mutableSearch.removeFilter('transaction.duration');
+ eventView.query = mutableSearch.formatString();
+ } else if (isSlowestType) {
+ eventView.additionalConditions.setFilterValues('epm()', ['>0.01']);
+ eventView.fields = [
+ {field: 'transaction'},
+ {field: 'project.id'},
+ {field: 'epm()'},
+ {field: props.fields[0]},
+ ];
+ } else {
+ // Most related errors
+ eventView.fields = [
+ {field: 'transaction'},
+ {field: 'project.id'},
+ {field: props.fields[0]},
+ ];
+ }
+ return (
+
+ );
+ },
+ transform: transformDiscoverToList,
+ }),
+ [props.eventView.query, props.fields[0], props.organization.slug]
+ );
+
+ const chartQuery = useMemo>(() => {
+ return {
+ enabled: widgetData => {
+ return !!widgetData?.list?.data?.length;
+ },
+ fields: props.fields[0],
+ component: provided => {
+ eventView.additionalConditions.setFilterValues('transaction', [
+ provided.widgetData.list.data[selectedListIndex].transaction as string,
+ ]);
+ if (props.chartSetting === PerformanceWidgetSetting.MOST_RELATED_ISSUES) {
+ eventView.fields = [
+ {field: 'issue'},
+ {field: 'issue.id'},
+ {field: 'transaction'},
+ {field: props.fields[0]},
+ ];
+ eventView.additionalConditions.setFilterValues('issue', [
+ provided.widgetData.list.data[selectedListIndex].issue as string,
]);
- if (props.chartSetting === PerformanceWidgetSetting.MOST_RELATED_ISSUES) {
- eventView.fields = [
- {field: 'issue'},
- {field: 'issue.id'},
- {field: 'transaction'},
- {field: props.fields[0]},
- ];
- eventView.additionalConditions.setFilterValues('issue', [
- provided.widgetData.list.data[selectedListIndex].issue as string,
- ]);
- eventView.additionalConditions.setFilterValues('event.type', ['error']);
- eventView.additionalConditions.setFilterValues('!tags[transaction]', ['']);
- const mutableSearch = new MutableSearch(eventView.query);
- mutableSearch.removeFilter('transaction.duration');
- eventView.query = mutableSearch.formatString();
- } else {
- eventView.fields = [{field: 'transaction'}, {field: props.fields[0]}];
- }
- return (
-
- );
- },
- transform: transformEventsRequestToArea,
- }),
- [
- props.eventView,
- props.fields,
- props.organization.slug,
- props.chartSetting,
- selectedListIndex,
- ]
- ),
+ eventView.additionalConditions.setFilterValues('event.type', ['error']);
+ eventView.additionalConditions.setFilterValues('!tags[transaction]', ['']);
+ const mutableSearch = new MutableSearch(eventView.query);
+ mutableSearch.removeFilter('transaction.duration');
+ eventView.query = mutableSearch.formatString();
+ } else {
+ eventView.fields = [{field: 'transaction'}, {field: props.fields[0]}];
+ }
+ return (
+
+ );
+ },
+ transform: transformEventsRequestToArea,
+ };
+ }, [
+ props.eventView.query,
+ props.fields[0],
+ props.organization.slug,
+ selectedListIndex,
+ ]);
+
+ const Queries = {
+ list: listQuery,
+ chart: chartQuery,
};
return (
@@ -167,6 +197,7 @@ export function LineChartListWidget(props: Props) {
disableMultiAxis
disableXAxis
chartColors={props.chartColor ? [props.chartColor] : undefined}
+ isLineChart
/>
),
height: 160,
@@ -178,17 +209,50 @@ export function LineChartListWidget(props: Props) {
setSelectedIndex={setSelectListIndex}
items={provided.widgetData.list.data.map(listItem => () => {
const transaction = listItem.transaction as string;
+
+ const additionalQuery: Record = {};
+
+ if (props.chartSetting === PerformanceWidgetSetting.SLOW_HTTP_OPS) {
+ additionalQuery.breakdown = 'http';
+ additionalQuery.display = 'latency';
+ } else if (props.chartSetting === PerformanceWidgetSetting.SLOW_DB_OPS) {
+ additionalQuery.breakdown = 'db';
+ additionalQuery.display = 'latency';
+ } else if (
+ props.chartSetting === PerformanceWidgetSetting.SLOW_BROWSER_OPS
+ ) {
+ additionalQuery.breakdown = 'browser';
+ additionalQuery.display = 'latency';
+ } else if (
+ props.chartSetting === PerformanceWidgetSetting.SLOW_RESOURCE_OPS
+ ) {
+ additionalQuery.breakdown = 'resource';
+ additionalQuery.display = 'latency';
+ }
+
const transactionTarget = transactionSummaryRouteWithQuery({
orgSlug: props.organization.slug,
projectID: listItem['project.id'] as string,
transaction,
query: props.eventView.getGlobalSelectionQuery(),
+ additionalQuery,
});
+
+ const fieldString = getAggregateAlias(props.fields[0]);
+
+ const valueMap = {
+ [PerformanceWidgetSetting.MOST_RELATED_ERRORS]: listItem.failure_count,
+ [PerformanceWidgetSetting.MOST_RELATED_ISSUES]: listItem.issue,
+ slowest: getPerformanceDuration(listItem[fieldString] as number),
+ };
+ const rightValue =
+ valueMap[isSlowestType ? 'slowest' : props.chartSetting];
+
switch (props.chartSetting) {
case PerformanceWidgetSetting.MOST_RELATED_ISSUES:
return (
-
+
@@ -196,7 +260,7 @@ export function LineChartListWidget(props: Props) {
- {listItem.issue}
+ {rightValue}
@@ -212,10 +276,10 @@ export function LineChartListWidget(props: Props) {
default:
return (
-
+
- {listItem.failure_count}
+ {rightValue}
diff --git a/static/app/views/performance/landing/widgets/widgets/singleFieldAreaWidget.tsx b/static/app/views/performance/landing/widgets/widgets/singleFieldAreaWidget.tsx
index 86335f4fd470f9..d10655b6c24965 100644
--- a/static/app/views/performance/landing/widgets/widgets/singleFieldAreaWidget.tsx
+++ b/static/app/views/performance/landing/widgets/widgets/singleFieldAreaWidget.tsx
@@ -51,13 +51,14 @@ export function SingleFieldAreaWidget(props: Props) {
includeTransformedData
partial
currentSeriesName={props.fields[0]}
+ eventView={props.eventView}
query={props.eventView.getQueryWithAdditionalConditions()}
/>
),
transform: transformEventsRequestToArea,
},
};
- }, [props.eventView, props.fields, props.organization.slug]);
+ }, [props.eventView.query, props.fields[0], props.organization.slug]);
return (
diff --git a/static/app/views/performance/landing/widgets/widgets/trendsWidget.tsx b/static/app/views/performance/landing/widgets/widgets/trendsWidget.tsx
index a77c8cc40c8dea..3eb9552030e6f3 100644
--- a/static/app/views/performance/landing/widgets/widgets/trendsWidget.tsx
+++ b/static/app/views/performance/landing/widgets/widgets/trendsWidget.tsx
@@ -86,7 +86,7 @@ export function TrendsWidget(props: Props) {
transform: transformTrendsDiscover,
},
};
- }, [_eventView, trendChangeType]);
+ }, [eventView.query, eventView.fields, trendChangeType]);
return (
diff --git a/static/app/views/performance/landing/widgets/widgets/vitalWidget.tsx b/static/app/views/performance/landing/widgets/widgets/vitalWidget.tsx
index 3ef075d9dadd16..28bfd0f7d7b691 100644
--- a/static/app/views/performance/landing/widgets/widgets/vitalWidget.tsx
+++ b/static/app/views/performance/landing/widgets/widgets/vitalWidget.tsx
@@ -57,7 +57,7 @@ export function VitalWidget(props: Props) {
() => ({
fields: props.fields[0],
component: provided => {
- const _eventView = provided.eventView.clone();
+ const _eventView = props.eventView.clone();
const fieldFromProps = props.fields.map(field => ({
field,
@@ -73,7 +73,14 @@ export function VitalWidget(props: Props) {
];
const mutableSearch = new MutableSearch(_eventView.query);
_eventView.query = mutableSearch.formatString();
- return ;
+ return (
+
+ );
},
transform: transformDiscoverToList,
}),
diff --git a/static/app/views/performance/transactionSummary/utils.tsx b/static/app/views/performance/transactionSummary/utils.tsx
index 59c9bfc8a8dfca..a3bbf574426725 100644
--- a/static/app/views/performance/transactionSummary/utils.tsx
+++ b/static/app/views/performance/transactionSummary/utils.tsx
@@ -32,6 +32,7 @@ export function transactionSummaryRouteWithQuery({
trendFunction,
trendColumn,
showTransactions,
+ additionalQuery,
}: {
orgSlug: string;
transaction: string;
@@ -42,6 +43,7 @@ export function transactionSummaryRouteWithQuery({
unselectedSeries?: string | string[];
projectID?: string | string[];
showTransactions?: TransactionFilterOptions;
+ additionalQuery?: Record;
}) {
const pathname = generateTransactionSummaryRoute({
orgSlug,
@@ -62,6 +64,7 @@ export function transactionSummaryRouteWithQuery({
display,
trendFunction,
trendColumn,
+ ...additionalQuery,
},
};
}