diff --git a/static/app/views/performance/queues/charts/latencyChart.spec.tsx b/static/app/views/performance/queues/charts/latencyChart.spec.tsx index ec92a61924e8b..c6a45ed43463e 100644 --- a/static/app/views/performance/queues/charts/latencyChart.spec.tsx +++ b/static/app/views/performance/queues/charts/latencyChart.spec.tsx @@ -23,7 +23,7 @@ describe('latencyChart', () => { }); }); it('renders', async () => { - render(); + render(); screen.getByText('Avg Latency'); expect(eventsStatsMock).toHaveBeenCalledWith( '/organizations/org-slug/events-stats/', @@ -36,6 +36,8 @@ describe('latencyChart', () => { 'count_op(queue.publish)', 'count_op(queue.process)', ], + query: + 'span.op:[queue.process,queue.publish] messaging.destination.name:events', }), }) ); diff --git a/static/app/views/performance/queues/charts/latencyChart.tsx b/static/app/views/performance/queues/charts/latencyChart.tsx index edc9f61692251..0e10d7b39de6f 100644 --- a/static/app/views/performance/queues/charts/latencyChart.tsx +++ b/static/app/views/performance/queues/charts/latencyChart.tsx @@ -1,19 +1,16 @@ import {CHART_PALETTE} from 'sentry/constants/chartPalette'; import {t} from 'sentry/locale'; -import {decodeScalar} from 'sentry/utils/queryString'; -import {useLocation} from 'sentry/utils/useLocation'; import {CHART_HEIGHT} from 'sentry/views/performance/database/settings'; import {useQueuesTimeSeriesQuery} from 'sentry/views/performance/queues/queries/useQueuesTimeSeriesQuery'; import Chart, {ChartType} from 'sentry/views/starfish/components/chart'; import ChartPanel from 'sentry/views/starfish/components/chartPanel'; interface Props { + destination?: string; error?: Error | null; } -export function LatencyChart({error}: Props) { - const {query} = useLocation(); - const destination = decodeScalar(query.destination); +export function LatencyChart({error, destination}: Props) { const {data, isLoading} = useQueuesTimeSeriesQuery({destination}); return ( diff --git a/static/app/views/performance/queues/charts/throughputChart.spec.tsx b/static/app/views/performance/queues/charts/throughputChart.spec.tsx index 9999329330b16..bf57530c5798c 100644 --- a/static/app/views/performance/queues/charts/throughputChart.spec.tsx +++ b/static/app/views/performance/queues/charts/throughputChart.spec.tsx @@ -36,6 +36,7 @@ describe('throughputChart', () => { 'count_op(queue.publish)', 'count_op(queue.process)', ], + query: 'span.op:[queue.process,queue.publish]', }), }) ); diff --git a/static/app/views/performance/queues/charts/throughputChart.tsx b/static/app/views/performance/queues/charts/throughputChart.tsx index 06fb151da75f5..16af38434197a 100644 --- a/static/app/views/performance/queues/charts/throughputChart.tsx +++ b/static/app/views/performance/queues/charts/throughputChart.tsx @@ -1,19 +1,16 @@ import {CHART_PALETTE} from 'sentry/constants/chartPalette'; import {t} from 'sentry/locale'; -import {decodeScalar} from 'sentry/utils/queryString'; -import {useLocation} from 'sentry/utils/useLocation'; import {CHART_HEIGHT} from 'sentry/views/performance/database/settings'; import {useQueuesTimeSeriesQuery} from 'sentry/views/performance/queues/queries/useQueuesTimeSeriesQuery'; import Chart, {ChartType} from 'sentry/views/starfish/components/chart'; import ChartPanel from 'sentry/views/starfish/components/chartPanel'; interface Props { + destination?: string; error?: Error | null; } -export function ThroughputChart({error}: Props) { - const {query} = useLocation(); - const destination = decodeScalar(query.destination); +export function ThroughputChart({error, destination}: Props) { const {data, isLoading} = useQueuesTimeSeriesQuery({destination}); return ( diff --git a/static/app/views/performance/queues/destinationSummary/destinationSummaryPage.tsx b/static/app/views/performance/queues/destinationSummary/destinationSummaryPage.tsx index ff50f1c313b3c..6c26d9d5cf764 100644 --- a/static/app/views/performance/queues/destinationSummary/destinationSummaryPage.tsx +++ b/static/app/views/performance/queues/destinationSummary/destinationSummaryPage.tsx @@ -138,11 +138,11 @@ function DestinationSummaryPage() { {!onboardingProject && ( - + - + diff --git a/static/app/views/performance/queues/queries/useQueuesByDestinationQuery.tsx b/static/app/views/performance/queues/queries/useQueuesByDestinationQuery.tsx index c7933f437920f..554bdb3faf464 100644 --- a/static/app/views/performance/queues/queries/useQueuesByDestinationQuery.tsx +++ b/static/app/views/performance/queues/queries/useQueuesByDestinationQuery.tsx @@ -1,3 +1,4 @@ +import type {Sort} from 'sentry/utils/discover/fields'; import {decodeScalar} from 'sentry/utils/queryString'; import {MutableSearch} from 'sentry/utils/tokenizeSearch'; import {useLocation} from 'sentry/utils/useLocation'; @@ -6,14 +7,19 @@ import {useSpanMetrics} from 'sentry/views/starfish/queries/useDiscover'; import {QueryParameterNames} from 'sentry/views/starfish/views/queryParameters'; type Props = { + destination?: string; enabled?: boolean; + sort?: Sort; }; -export function useQueuesByDestinationQuery({enabled}: Props) { +export function useQueuesByDestinationQuery({enabled, destination, sort}: Props) { const location = useLocation(); const cursor = decodeScalar(location.query?.[QueryParameterNames.DESTINATIONS_CURSOR]); const mutableSearch = new MutableSearch(DEFAULT_QUERY_FILTER); + if (destination) { + mutableSearch.addFilterValue('messaging.destination.name', destination, false); + } const response = useSpanMetrics( { search: mutableSearch, @@ -27,9 +33,10 @@ export function useQueuesByDestinationQuery({enabled}: Props) { 'avg_if(span.duration,span.op,queue.publish)', 'avg_if(span.duration,span.op,queue.process)', 'avg(messaging.message.receive.latency)', + 'time_spent_percentage()', ], enabled, - sorts: [], + sorts: sort ? [sort] : [], limit: 10, cursor, }, diff --git a/static/app/views/performance/queues/queries/useQueuesTimeSeriesQuery.tsx b/static/app/views/performance/queues/queries/useQueuesTimeSeriesQuery.tsx index 7f014af69ae4c..33ab75ef73efc 100644 --- a/static/app/views/performance/queues/queries/useQueuesTimeSeriesQuery.tsx +++ b/static/app/views/performance/queues/queries/useQueuesTimeSeriesQuery.tsx @@ -1,4 +1,5 @@ import {MutableSearch} from 'sentry/utils/tokenizeSearch'; +import {DEFAULT_QUERY_FILTER} from 'sentry/views/performance/queues/settings'; import {useSpanMetricsSeries} from 'sentry/views/starfish/queries/useDiscoverSeries'; import type {SpanMetricsProperty} from 'sentry/views/starfish/types'; @@ -16,14 +17,15 @@ const yAxis: SpanMetricsProperty[] = [ ]; export function useQueuesTimeSeriesQuery({enabled, destination}: Props) { + const mutableSearch = new MutableSearch(DEFAULT_QUERY_FILTER); + if (destination) { + mutableSearch.addFilterValue('messaging.destination.name', destination, false); + } + return useSpanMetricsSeries( { yAxis, - search: destination - ? MutableSearch.fromQueryObject({ - 'messaging.destination.name': destination, - }) - : undefined, + search: mutableSearch, enabled, }, 'api.performance.queues.module-chart' diff --git a/static/app/views/performance/queues/queuesLandingPage.spec.tsx b/static/app/views/performance/queues/queuesLandingPage.spec.tsx index 00ebb21a0b0a5..69c4bba0ea27b 100644 --- a/static/app/views/performance/queues/queuesLandingPage.spec.tsx +++ b/static/app/views/performance/queues/queuesLandingPage.spec.tsx @@ -75,7 +75,7 @@ describe('queuesLandingPage', () => { render(); await screen.findByRole('table', {name: 'Queues'}); await waitForElementToBeRemoved(() => screen.queryAllByTestId('loading-indicator')); - screen.getByPlaceholderText('Search for events, users, tags, and more'); + screen.getByPlaceholderText('Search for more destinations'); screen.getByText('Avg Latency'); screen.getByText('Published vs Processed'); expect(eventsStatsMock).toHaveBeenCalled(); diff --git a/static/app/views/performance/queues/queuesLandingPage.tsx b/static/app/views/performance/queues/queuesLandingPage.tsx index 66060f80187d1..90cbf3028c563 100644 --- a/static/app/views/performance/queues/queuesLandingPage.tsx +++ b/static/app/views/performance/queues/queuesLandingPage.tsx @@ -10,9 +10,14 @@ import {DatePageFilter} from 'sentry/components/organizations/datePageFilter'; import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter'; import PageFilterBar from 'sentry/components/organizations/pageFilterBar'; import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter'; -import SmartSearchBar from 'sentry/components/smartSearchBar'; +import SearchBar from 'sentry/components/searchBar'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; +import {browserHistory} from 'sentry/utils/browserHistory'; +import {decodeScalar, decodeSorts} from 'sentry/utils/queryString'; +import {escapeFilterValue} from 'sentry/utils/tokenizeSearch'; +import useLocationQuery from 'sentry/utils/url/useLocationQuery'; +import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; import {normalizeUrl} from 'sentry/utils/withDomainRequired'; import {useOnboardingProject} from 'sentry/views/performance/browser/webVitals/utils/useOnboardingProject'; @@ -21,12 +26,47 @@ import {ModulePageProviders} from 'sentry/views/performance/modulePageProviders' import Onboarding from 'sentry/views/performance/onboarding'; import {LatencyChart} from 'sentry/views/performance/queues/charts/latencyChart'; import {ThroughputChart} from 'sentry/views/performance/queues/charts/throughputChart'; -import {QueuesTable} from 'sentry/views/performance/queues/queuesTable'; +import {isAValidSort, QueuesTable} from 'sentry/views/performance/queues/queuesTable'; import {MODULE_TITLE, RELEASE_LEVEL} from 'sentry/views/performance/queues/settings'; +import {QueryParameterNames} from 'sentry/views/starfish/views/queryParameters'; + +const DEFAULT_SORT = { + field: 'time_spent_percentage()' as const, + kind: 'desc' as const, +}; function QueuesLandingPage() { const organization = useOrganization(); const onboardingProject = useOnboardingProject(); + const location = useLocation(); + + const query = useLocationQuery({ + fields: { + destination: decodeScalar, + [QueryParameterNames.DOMAINS_SORT]: decodeScalar, + }, + }); + + const sort = + decodeSorts(query[QueryParameterNames.DOMAINS_SORT]).filter(isAValidSort).at(0) ?? + DEFAULT_SORT; + + const handleSearch = (newDestination: string) => { + browserHistory.push({ + ...location, + query: { + ...location.query, + destination: newDestination === '' ? undefined : newDestination, + [QueryParameterNames.DESTINATIONS_CURSOR]: undefined, + }, + }); + }; + + // The QueuesTable component queries using the destination prop. + // We wrap the user input in wildcards to allow for partial matches. + const wildCardDestinationFilter = query.destination + ? `*${escapeFilterValue(query.destination)}*` + : undefined; return ( @@ -86,9 +126,12 @@ function QueuesLandingPage() { - {/* TODO: Make search bar work */} - - + + diff --git a/static/app/views/performance/queues/queuesTable.spec.tsx b/static/app/views/performance/queues/queuesTable.spec.tsx index bc2062b5d08a6..b52815339d0c0 100644 --- a/static/app/views/performance/queues/queuesTable.spec.tsx +++ b/static/app/views/performance/queues/queuesTable.spec.tsx @@ -79,6 +79,7 @@ describe('queuesTable', () => { 'avg_if(span.duration,span.op,queue.publish)', 'avg_if(span.duration,span.op,queue.process)', 'avg(messaging.message.receive.latency)', + 'time_spent_percentage()', ], dataset: 'spansMetrics', }), @@ -91,4 +92,36 @@ describe('queuesTable', () => { expect(screen.getByRole('cell', {name: '20.00ms'})).toBeInTheDocument(); expect(screen.getByRole('button', {name: 'Next'})).toBeInTheDocument(); }); + it('searches for a destination and sorts', async () => { + render( + + ); + expect(eventsMock).toHaveBeenCalledWith( + '/organizations/org-slug/events/', + expect.objectContaining({ + query: expect.objectContaining({ + field: [ + 'messaging.destination.name', + 'count()', + 'count_op(queue.publish)', + 'count_op(queue.process)', + 'sum(span.duration)', + 'avg(span.duration)', + 'avg_if(span.duration,span.op,queue.publish)', + 'avg_if(span.duration,span.op,queue.process)', + 'avg(messaging.message.receive.latency)', + 'time_spent_percentage()', + ], + dataset: 'spansMetrics', + sort: '-messaging.destination.name', + query: + 'span.op:[queue.process,queue.publish] messaging.destination.name:*events*', + }), + }) + ); + await screen.findByText('celery.backend_cleanup'); + }); }); diff --git a/static/app/views/performance/queues/queuesTable.tsx b/static/app/views/performance/queues/queuesTable.tsx index 8cf64dd6f3694..d633da5a629d9 100644 --- a/static/app/views/performance/queues/queuesTable.tsx +++ b/static/app/views/performance/queues/queuesTable.tsx @@ -15,6 +15,7 @@ import type {Organization} from 'sentry/types'; import {browserHistory} from 'sentry/utils/browserHistory'; import type {EventsMetaType} from 'sentry/utils/discover/eventView'; import {FIELD_FORMATTERS, getFieldRenderer} from 'sentry/utils/discover/fieldRenderers'; +import type {Sort} from 'sentry/utils/discover/fields'; import {formatAbbreviatedNumber, formatPercentage} from 'sentry/utils/formatters'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; @@ -73,17 +74,34 @@ const COLUMN_ORDER: Column[] = [ }, ]; +const SORTABLE_FIELDS = [ + 'messaging.destination.name', + 'time_spent_percentage()', +] as const; + +type ValidSort = Sort & { + field: (typeof SORTABLE_FIELDS)[number]; +}; + +export function isAValidSort(sort: Sort): sort is ValidSort { + return (SORTABLE_FIELDS as ReadonlyArray).includes(sort.field); +} + interface Props { - domain?: string; + destination?: string; error?: Error | null; meta?: EventsMetaType; + sort?: ValidSort; } -export function QueuesTable({error}: Props) { +export function QueuesTable({error, destination, sort}: Props) { const location = useLocation(); const organization = useOrganization(); - const {data, isLoading, meta, pageLinks} = useQueuesByDestinationQuery({}); + const {data, isLoading, meta, pageLinks} = useQueuesByDestinationQuery({ + destination, + sort, + }); const handleCursor: CursorHandler = (newCursor, pathname, query) => { browserHistory.push({ diff --git a/static/app/views/starfish/views/queryParameters.tsx b/static/app/views/starfish/views/queryParameters.tsx index fd4784bd28a0f..e6eb27974872b 100644 --- a/static/app/views/starfish/views/queryParameters.tsx +++ b/static/app/views/starfish/views/queryParameters.tsx @@ -9,4 +9,5 @@ export enum QueryParameterNames { ENDPOINTS_SORT = 'endpointsSort', PAGES_CURSOR = 'pagesCursor', DESTINATIONS_CURSOR = 'destinationsCursor', + DESTINATIONS_SORT = 'destinationsSort', }