From ec3a6f378473fc8f0a2c6d118cb8883c60f9b0d9 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Mon, 3 Mar 2025 15:24:48 -0500 Subject: [PATCH] ref(insights): Replace usage of `` in `` (#86129) ...with a hook (`useReleaseStats`) This will be needed for the new release bubbles feature as we will show releases on the mini widgets instead of only on fullscreen. Using `` renderer will cause duplicate requests to the API. ref https://github.com/getsentry/sentry/issues/85779 --- static/app/utils/queryClient.tsx | 3 + .../timeSeriesWidget/useReleaseStats.tsx | 56 +++++++++++++++++++ .../views/resourcesLandingPage.spec.tsx | 10 ++++ .../cache/views/cacheLandingPage.spec.tsx | 11 ++++ .../components/insightsTimeSeriesWidget.tsx | 38 +++---------- .../views/databaseLandingPage.spec.tsx | 11 ++++ .../views/databaseSpanSummaryPage.spec.tsx | 11 ++++ .../http/views/httpDomainSummaryPage.spec.tsx | 11 ++++ .../http/views/httpLandingPage.spec.tsx | 11 ++++ .../queues/charts/latencyChart.spec.tsx | 11 ++++ .../queues/charts/throughputChart.spec.tsx | 11 ++++ .../views/destinationSummaryPage.spec.tsx | 10 ++++ .../queues/views/queuesLandingPage.spec.tsx | 10 ++++ 13 files changed, 174 insertions(+), 30 deletions(-) create mode 100644 static/app/views/dashboards/widgets/timeSeriesWidget/useReleaseStats.tsx diff --git a/static/app/utils/queryClient.tsx b/static/app/utils/queryClient.tsx index faa41d2c91dfd8..a6b483b0be3053 100644 --- a/static/app/utils/queryClient.tsx +++ b/static/app/utils/queryClient.tsx @@ -287,9 +287,11 @@ function parsePageParam(dir: 'previous' | 'next') { export function useInfiniteApiQuery({ queryKey, enabled, + staleTime, }: { queryKey: ApiQueryKey; enabled?: boolean; + staleTime?: number; }) { const api = useApi({persistInFlight: PERSIST_IN_FLIGHT}); const query = useInfiniteQuery({ @@ -299,6 +301,7 @@ export function useInfiniteApiQuery({ getNextPageParam: parsePageParam('next'), initialPageParam: undefined, enabled: enabled ?? true, + staleTime, }); return query; diff --git a/static/app/views/dashboards/widgets/timeSeriesWidget/useReleaseStats.tsx b/static/app/views/dashboards/widgets/timeSeriesWidget/useReleaseStats.tsx new file mode 100644 index 00000000000000..26648630dc26fb --- /dev/null +++ b/static/app/views/dashboards/widgets/timeSeriesWidget/useReleaseStats.tsx @@ -0,0 +1,56 @@ +import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse'; +import type {PageFilters} from 'sentry/types/core'; +import {useInfiniteApiQuery} from 'sentry/utils/queryClient'; +import useOrganization from 'sentry/utils/useOrganization'; + +interface ReleaseMetaBasic { + date: string; + version: string; +} + +/** + * Fetches *ALL* releases (e.g. all pages worth) + */ +export function useReleaseStats({datetime, environments, projects}: PageFilters) { + const organization = useOrganization(); + + const { + isLoading, + isFetching, + fetchNextPage, + hasNextPage, + isPending, + isError, + error, + data, + } = useInfiniteApiQuery({ + queryKey: [ + `/organizations/${organization.slug}/releases/stats/`, + { + query: { + environment: environments, + project: projects, + ...normalizeDateTimeParams(datetime), + }, + }, + ], + staleTime: Infinity, + }); + + if (!isFetching && hasNextPage) { + fetchNextPage(); + } + + const releases = + data?.pages.flatMap(([pageData]) => + pageData.map(({date, version}) => ({timestamp: date, version})) + ) ?? []; + + return { + isLoading, + isPending, + isError, + error, + releases, + }; +} diff --git a/static/app/views/insights/browser/resources/views/resourcesLandingPage.spec.tsx b/static/app/views/insights/browser/resources/views/resourcesLandingPage.spec.tsx index e88238e58dcff4..17fc02f0776ead 100644 --- a/static/app/views/insights/browser/resources/views/resourcesLandingPage.spec.tsx +++ b/static/app/views/insights/browser/resources/views/resourcesLandingPage.spec.tsx @@ -27,6 +27,9 @@ jest.mock('sentry/utils/useLocation'); jest.mock('sentry/utils/usePageFilters'); jest.mock('sentry/utils/useProjects'); jest.mock('sentry/views/insights/common/queries/useOnboardingProject'); +import {useReleaseStats} from 'sentry/views/dashboards/widgets/timeSeriesWidget/useReleaseStats'; + +jest.mock('sentry/views/dashboards/widgets/timeSeriesWidget/useReleaseStats'); const requestMocks: Record = {}; @@ -171,6 +174,13 @@ const setupMocks = () => { reloadProjects: jest.fn(), placeholders: [], }); + jest.mocked(useReleaseStats).mockReturnValue({ + isLoading: false, + isPending: false, + isError: false, + error: null, + releases: [], + }); }; const setupMockRequests = (organization: Organization) => { diff --git a/static/app/views/insights/cache/views/cacheLandingPage.spec.tsx b/static/app/views/insights/cache/views/cacheLandingPage.spec.tsx index a636e46b842cae..fd6bcfe5519475 100644 --- a/static/app/views/insights/cache/views/cacheLandingPage.spec.tsx +++ b/static/app/views/insights/cache/views/cacheLandingPage.spec.tsx @@ -19,6 +19,9 @@ jest.mock('sentry/utils/useLocation'); jest.mock('sentry/utils/usePageFilters'); jest.mock('sentry/utils/useProjects'); jest.mock('sentry/views/insights/common/queries/useOnboardingProject'); +import {useReleaseStats} from 'sentry/views/dashboards/widgets/timeSeriesWidget/useReleaseStats'; + +jest.mock('sentry/views/dashboards/widgets/timeSeriesWidget/useReleaseStats'); const requestMocks = { missRateChart: jest.fn(), @@ -79,6 +82,14 @@ describe('CacheLandingPage', function () { initiallyLoaded: false, }); + jest.mocked(useReleaseStats).mockReturnValue({ + isLoading: false, + isPending: false, + isError: false, + error: null, + releases: [], + }); + beforeEach(function () { jest.clearAllMocks(); setRequestMocks(organization); diff --git a/static/app/views/insights/common/components/insightsTimeSeriesWidget.tsx b/static/app/views/insights/common/components/insightsTimeSeriesWidget.tsx index 05a2f261b47c8f..610b8c66d8accf 100644 --- a/static/app/views/insights/common/components/insightsTimeSeriesWidget.tsx +++ b/static/app/views/insights/common/components/insightsTimeSeriesWidget.tsx @@ -2,7 +2,6 @@ import styled from '@emotion/styled'; import {openInsightChartModal} from 'sentry/actionCreators/modal'; import {Button} from 'sentry/components/button'; -import ReleaseSeries from 'sentry/components/charts/releaseSeries'; import {CHART_PALETTE} from 'sentry/constants/chartPalette'; import {IconExpand} from 'sentry/icons'; import {t} from 'sentry/locale'; @@ -13,6 +12,7 @@ import { TimeSeriesWidgetVisualization, type TimeSeriesWidgetVisualizationProps, } from 'sentry/views/dashboards/widgets/timeSeriesWidget/timeSeriesWidgetVisualization'; +import {useReleaseStats} from 'sentry/views/dashboards/widgets/timeSeriesWidget/useReleaseStats'; import {Widget} from 'sentry/views/dashboards/widgets/widget/widget'; import { @@ -38,8 +38,7 @@ export interface InsightsTimeSeriesWidgetProps { export function InsightsTimeSeriesWidget(props: InsightsTimeSeriesWidgetProps) { const pageFilters = usePageFilters(); - const {start, end, period, utc} = pageFilters.selection.datetime; - const {projects, environments} = pageFilters.selection; + const {releases} = useReleaseStats(pageFilters.selection); const visualizationProps: TimeSeriesWidgetVisualizationProps = { visualizationType: props.visualizationType, @@ -108,33 +107,12 @@ export function InsightsTimeSeriesWidget(props: InsightsTimeSeriesWidgetProps) { openInsightChartModal({ title: props.title, children: ( - - {({releases}) => { - return ( - - ({ - timestamp: release.date, - version: release.version, - })) - : [] - } - /> - - ); - }} - + + + ), }); }} diff --git a/static/app/views/insights/database/views/databaseLandingPage.spec.tsx b/static/app/views/insights/database/views/databaseLandingPage.spec.tsx index ab830349c0b2f1..cfd72637fd8efc 100644 --- a/static/app/views/insights/database/views/databaseLandingPage.spec.tsx +++ b/static/app/views/insights/database/views/databaseLandingPage.spec.tsx @@ -13,6 +13,9 @@ jest.mock('sentry/utils/useLocation'); jest.mock('sentry/utils/usePageFilters'); jest.mock('sentry/utils/useProjects'); jest.mock('sentry/views/insights/common/queries/useOnboardingProject'); +import {useReleaseStats} from 'sentry/views/dashboards/widgets/timeSeriesWidget/useReleaseStats'; + +jest.mock('sentry/views/dashboards/widgets/timeSeriesWidget/useReleaseStats'); describe('DatabaseLandingPage', function () { const organization = OrganizationFixture({features: ['insights-initial-modules']}); @@ -60,6 +63,14 @@ describe('DatabaseLandingPage', function () { key: '', }); + jest.mocked(useReleaseStats).mockReturnValue({ + isLoading: false, + isPending: false, + isError: false, + error: null, + releases: [], + }); + beforeEach(function () { MockApiClient.addMockResponse({ url: '/organizations/org-slug/sdk-updates/', diff --git a/static/app/views/insights/database/views/databaseSpanSummaryPage.spec.tsx b/static/app/views/insights/database/views/databaseSpanSummaryPage.spec.tsx index 3062081e9c43e6..7f1a4169ed81c2 100644 --- a/static/app/views/insights/database/views/databaseSpanSummaryPage.spec.tsx +++ b/static/app/views/insights/database/views/databaseSpanSummaryPage.spec.tsx @@ -10,6 +10,9 @@ import {DatabaseSpanSummaryPage} from 'sentry/views/insights/database/views/data jest.mock('sentry/utils/useLocation'); jest.mock('sentry/utils/usePageFilters'); +import {useReleaseStats} from 'sentry/views/dashboards/widgets/timeSeriesWidget/useReleaseStats'; + +jest.mock('sentry/views/dashboards/widgets/timeSeriesWidget/useReleaseStats'); describe('DatabaseSpanSummaryPage', function () { const organization = OrganizationFixture({ @@ -44,6 +47,14 @@ describe('DatabaseSpanSummaryPage', function () { key: '', }); + jest.mocked(useReleaseStats).mockReturnValue({ + isLoading: false, + isPending: false, + isError: false, + error: null, + releases: [], + }); + beforeEach(function () { jest.clearAllMocks(); }); diff --git a/static/app/views/insights/http/views/httpDomainSummaryPage.spec.tsx b/static/app/views/insights/http/views/httpDomainSummaryPage.spec.tsx index a0d959aee02f5a..fc981c7d641149 100644 --- a/static/app/views/insights/http/views/httpDomainSummaryPage.spec.tsx +++ b/static/app/views/insights/http/views/httpDomainSummaryPage.spec.tsx @@ -13,6 +13,9 @@ import {HTTPDomainSummaryPage} from 'sentry/views/insights/http/views/httpDomain jest.mock('sentry/utils/useLocation'); jest.mock('sentry/utils/usePageFilters'); +import {useReleaseStats} from 'sentry/views/dashboards/widgets/timeSeriesWidget/useReleaseStats'; + +jest.mock('sentry/views/dashboards/widgets/timeSeriesWidget/useReleaseStats'); describe('HTTPSummaryPage', function () { const organization = OrganizationFixture({features: ['insights-initial-modules']}); @@ -52,6 +55,14 @@ describe('HTTPSummaryPage', function () { key: '', }); + jest.mocked(useReleaseStats).mockReturnValue({ + isLoading: false, + isPending: false, + isError: false, + error: null, + releases: [], + }); + beforeEach(function () { jest.clearAllMocks(); diff --git a/static/app/views/insights/http/views/httpLandingPage.spec.tsx b/static/app/views/insights/http/views/httpLandingPage.spec.tsx index 028fc788def81d..1c07d6ce01d42f 100644 --- a/static/app/views/insights/http/views/httpLandingPage.spec.tsx +++ b/static/app/views/insights/http/views/httpLandingPage.spec.tsx @@ -13,6 +13,9 @@ jest.mock('sentry/utils/useLocation'); jest.mock('sentry/utils/usePageFilters'); jest.mock('sentry/utils/useProjects'); jest.mock('sentry/views/insights/common/queries/useOnboardingProject'); +import {useReleaseStats} from 'sentry/views/dashboards/widgets/timeSeriesWidget/useReleaseStats'; + +jest.mock('sentry/views/dashboards/widgets/timeSeriesWidget/useReleaseStats'); describe('HTTPLandingPage', function () { const organization = OrganizationFixture({ @@ -75,6 +78,14 @@ describe('HTTPLandingPage', function () { initiallyLoaded: false, }); + jest.mocked(useReleaseStats).mockReturnValue({ + isLoading: false, + isPending: false, + isError: false, + error: null, + releases: [], + }); + beforeEach(function () { jest.clearAllMocks(); diff --git a/static/app/views/insights/queues/charts/latencyChart.spec.tsx b/static/app/views/insights/queues/charts/latencyChart.spec.tsx index 7a44014f937e91..8b74430190b332 100644 --- a/static/app/views/insights/queues/charts/latencyChart.spec.tsx +++ b/static/app/views/insights/queues/charts/latencyChart.spec.tsx @@ -2,12 +2,23 @@ import {OrganizationFixture} from 'sentry-fixture/organization'; import {render, screen, waitForElementToBeRemoved} from 'sentry-test/reactTestingLibrary'; +import {useReleaseStats} from 'sentry/views/dashboards/widgets/timeSeriesWidget/useReleaseStats'; import {LatencyChart} from 'sentry/views/insights/queues/charts/latencyChart'; import {Referrer} from 'sentry/views/insights/queues/referrers'; +jest.mock('sentry/views/dashboards/widgets/timeSeriesWidget/useReleaseStats'); + describe('latencyChart', () => { const organization = OrganizationFixture(); + jest.mocked(useReleaseStats).mockReturnValue({ + isLoading: false, + isPending: false, + isError: false, + error: null, + releases: [], + }); + let eventsStatsMock: jest.Mock; beforeEach(() => { diff --git a/static/app/views/insights/queues/charts/throughputChart.spec.tsx b/static/app/views/insights/queues/charts/throughputChart.spec.tsx index 916cd6f7ba24b3..6e4eb1df909976 100644 --- a/static/app/views/insights/queues/charts/throughputChart.spec.tsx +++ b/static/app/views/insights/queues/charts/throughputChart.spec.tsx @@ -2,14 +2,25 @@ import {OrganizationFixture} from 'sentry-fixture/organization'; import {render, screen, waitForElementToBeRemoved} from 'sentry-test/reactTestingLibrary'; +import {useReleaseStats} from 'sentry/views/dashboards/widgets/timeSeriesWidget/useReleaseStats'; import {ThroughputChart} from 'sentry/views/insights/queues/charts/throughputChart'; import {Referrer} from 'sentry/views/insights/queues/referrers'; +jest.mock('sentry/views/dashboards/widgets/timeSeriesWidget/useReleaseStats'); + describe('throughputChart', () => { const organization = OrganizationFixture(); let eventsStatsMock!: jest.Mock; + jest.mocked(useReleaseStats).mockReturnValue({ + isLoading: false, + isPending: false, + isError: false, + error: null, + releases: [], + }); + beforeEach(() => { eventsStatsMock = MockApiClient.addMockResponse({ url: `/organizations/${organization.slug}/events-stats/`, diff --git a/static/app/views/insights/queues/views/destinationSummaryPage.spec.tsx b/static/app/views/insights/queues/views/destinationSummaryPage.spec.tsx index 7275cd0f2d6e3b..2ee4fa7f44df80 100644 --- a/static/app/views/insights/queues/views/destinationSummaryPage.spec.tsx +++ b/static/app/views/insights/queues/views/destinationSummaryPage.spec.tsx @@ -5,11 +5,13 @@ import {render, screen, waitForElementToBeRemoved} from 'sentry-test/reactTestin import {useLocation} from 'sentry/utils/useLocation'; import usePageFilters from 'sentry/utils/usePageFilters'; import useProjects from 'sentry/utils/useProjects'; +import {useReleaseStats} from 'sentry/views/dashboards/widgets/timeSeriesWidget/useReleaseStats'; import PageWithProviders from 'sentry/views/insights/queues/views/destinationSummaryPage'; jest.mock('sentry/utils/useLocation'); jest.mock('sentry/utils/usePageFilters'); jest.mock('sentry/utils/useProjects'); +jest.mock('sentry/views/dashboards/widgets/timeSeriesWidget/useReleaseStats'); describe('destinationSummaryPage', () => { const organization = OrganizationFixture({ @@ -54,6 +56,14 @@ describe('destinationSummaryPage', () => { initiallyLoaded: false, }); + jest.mocked(useReleaseStats).mockReturnValue({ + isLoading: false, + isPending: false, + isError: false, + error: null, + releases: [], + }); + let eventsMock: jest.Mock; let eventsStatsMock: jest.Mock; diff --git a/static/app/views/insights/queues/views/queuesLandingPage.spec.tsx b/static/app/views/insights/queues/views/queuesLandingPage.spec.tsx index c9f3a876c0887b..0a82da3f512438 100644 --- a/static/app/views/insights/queues/views/queuesLandingPage.spec.tsx +++ b/static/app/views/insights/queues/views/queuesLandingPage.spec.tsx @@ -6,11 +6,13 @@ import {render, screen} from 'sentry-test/reactTestingLibrary'; import {useLocation} from 'sentry/utils/useLocation'; import usePageFilters from 'sentry/utils/usePageFilters'; import useProjects from 'sentry/utils/useProjects'; +import {useReleaseStats} from 'sentry/views/dashboards/widgets/timeSeriesWidget/useReleaseStats'; import QueuesLandingPage from 'sentry/views/insights/queues/views/queuesLandingPage'; jest.mock('sentry/utils/useLocation'); jest.mock('sentry/utils/usePageFilters'); jest.mock('sentry/utils/useProjects'); +jest.mock('sentry/views/dashboards/widgets/timeSeriesWidget/useReleaseStats'); describe('queuesLandingPage', () => { const organization = OrganizationFixture({ @@ -58,6 +60,14 @@ describe('queuesLandingPage', () => { initiallyLoaded: false, }); + jest.mocked(useReleaseStats).mockReturnValue({ + isLoading: false, + isPending: false, + isError: false, + error: null, + releases: [], + }); + let eventsMock: jest.Mock; let eventsStatsMock: jest.Mock;