123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- import {createContext, useContext, useLayoutEffect, useMemo, useState} from 'react';
- import * as Sentry from '@sentry/react';
- import * as qs from 'query-string';
- import type {Client} from 'sentry/api';
- import {ContinuousProfileHeader} from 'sentry/components/profiling/continuousProfileHeader';
- import type {RequestState} from 'sentry/types/core';
- import type {EventTransaction} from 'sentry/types/event';
- import type {Organization} from 'sentry/types/organization';
- import type {Project} from 'sentry/types/project';
- import {useSentryEvent} from 'sentry/utils/profiling/hooks/useSentryEvent';
- import useApi from 'sentry/utils/useApi';
- import useOrganization from 'sentry/utils/useOrganization';
- import {useParams} from 'sentry/utils/useParams';
- import useProjects from 'sentry/utils/useProjects';
- interface ContinuousProfileQueryParams {
- end: string;
- profiler_id: string;
- start: string;
- }
- function fetchContinuousProfileFlamegraph(
- api: Client,
- query: ContinuousProfileQueryParams,
- projectSlug: Project['slug'],
- orgSlug: Organization['slug']
- ): Promise<Profiling.ProfileInput> {
- return api
- .requestPromise(`/organizations/${orgSlug}/profiling/chunks/`, {
- method: 'GET',
- query: {
- ...query,
- project: projectSlug,
- },
- includeAllArgs: true,
- })
- .then(([data]) => data.chunk);
- }
- type ContinuousProfileProviderValue = RequestState<Profiling.ProfileInput>;
- export const ContinuousProfileContext =
- createContext<ContinuousProfileProviderValue | null>(null);
- export function useContinuousProfile() {
- const context = useContext(ContinuousProfileContext);
- if (!context) {
- throw new Error('useContinuousProfile was called outside of ProfileProvider');
- }
- return context;
- }
- type ContinuousProfileSegmentProviderValue = RequestState<EventTransaction>;
- export const ContinuousProfileSegmentContext =
- createContext<ContinuousProfileSegmentProviderValue | null>(null);
- export function useContinuousProfileSegment() {
- const context = useContext(ContinuousProfileSegmentContext);
- if (!context) {
- throw new Error(
- 'useContinuousProfileSegment was called outside of ContinuousProfileSegmentProvider'
- );
- }
- return context;
- }
- function isValidDate(date: string): boolean {
- return !isNaN(Date.parse(date));
- }
- function getContinuousChunkQueryParams(
- query: string
- ): ContinuousProfileQueryParams | null {
- const queryString = new URLSearchParams(query);
- const start = queryString.get('start');
- const end = queryString.get('end');
- const profiler_id = queryString.get('profilerId');
- if (!start || !end || !profiler_id) {
- return null;
- }
- if (!isValidDate(start) || !isValidDate(end)) {
- return null;
- }
- return {
- start,
- end,
- profiler_id,
- };
- }
- interface ContinuousFlamegraphViewProps {
- children: React.ReactNode;
- }
- function ContinuousProfileProvider(
- props: ContinuousFlamegraphViewProps
- ): React.ReactElement {
- const api = useApi();
- const params = useParams();
- const organization = useOrganization();
- const projects = useProjects();
- const [profiles, setProfiles] = useState<RequestState<Profiling.ProfileInput>>({
- type: 'initial',
- });
- useLayoutEffect(() => {
- if (!params.projectId) {
- return undefined;
- }
- const chunkParams = getContinuousChunkQueryParams(window.location.search);
- const project = projects.projects.find(p => p.slug === params.projectId);
- if (!chunkParams) {
- Sentry.captureMessage(
- 'Failed to fetch continuous profile - invalid query parameters.'
- );
- return undefined;
- }
- if (!project) {
- Sentry.captureMessage('Failed to fetch continuous profile - project not found.');
- return undefined;
- }
- setProfiles({type: 'loading'});
- fetchContinuousProfileFlamegraph(api, chunkParams, project.id, organization.slug)
- .then(p => {
- setProfiles({type: 'resolved', data: p});
- })
- .catch(err => {
- setProfiles({type: 'errored', error: 'Failed to fetch profiles'});
- Sentry.captureException(err);
- });
- return () => api.clear();
- }, [api, organization.slug, projects.projects, params.projectId]);
- const eventPayload = useMemo(() => {
- const query = qs.parse(window.location.search);
- return {
- project: projects.projects.find(p => p.slug === params.projectId),
- eventId: query.eventId as string,
- };
- }, [projects, params.projectId]);
- const profileTransaction = useSentryEvent<EventTransaction>(
- organization.slug,
- eventPayload.project?.id ?? '',
- eventPayload.eventId
- );
- return (
- <ContinuousProfileContext.Provider value={profiles}>
- <ContinuousProfileSegmentContext.Provider value={profileTransaction}>
- <ContinuousProfileHeader />
- {props.children}
- </ContinuousProfileSegmentContext.Provider>
- </ContinuousProfileContext.Provider>
- );
- }
- export default ContinuousProfileProvider;
|