profilesProvider.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. import {createContext, useContext, useLayoutEffect, useState} from 'react';
  2. import * as Sentry from '@sentry/react';
  3. import type {Client} from 'sentry/api';
  4. import {ProfileHeader} from 'sentry/components/profiling/profileHeader';
  5. import {t} from 'sentry/locale';
  6. import type {RequestState} from 'sentry/types/core';
  7. import type {EventTransaction} from 'sentry/types/event';
  8. import type {Organization} from 'sentry/types/organization';
  9. import type {Project} from 'sentry/types/project';
  10. import {isSchema, isSentrySampledProfile} from 'sentry/utils/profiling/guards/profile';
  11. import {useSentryEvent} from 'sentry/utils/profiling/hooks/useSentryEvent';
  12. import useApi from 'sentry/utils/useApi';
  13. import useOrganization from 'sentry/utils/useOrganization';
  14. import {useParams} from 'sentry/utils/useParams';
  15. function fetchFlamegraphs(
  16. api: Client,
  17. eventId: string,
  18. projectSlug: Project['slug'],
  19. orgSlug: Organization['slug']
  20. ): Promise<Profiling.ProfileInput> {
  21. return api
  22. .requestPromise(
  23. `/projects/${orgSlug}/${projectSlug}/profiling/profiles/${eventId}/`,
  24. {
  25. method: 'GET',
  26. includeAllArgs: true,
  27. }
  28. )
  29. .then(([data]) => data);
  30. }
  31. function getTransactionId(input: Profiling.ProfileInput): string | null {
  32. if (isSchema(input)) {
  33. return input.metadata.transactionID;
  34. }
  35. if (isSentrySampledProfile(input)) {
  36. return input.transaction.id;
  37. }
  38. return null;
  39. }
  40. interface FlamegraphViewProps {
  41. children: React.ReactNode;
  42. }
  43. type ProfileProviderValue = RequestState<Profiling.ProfileInput>;
  44. type SetProfileProviderValue = React.Dispatch<
  45. React.SetStateAction<RequestState<Profiling.ProfileInput>>
  46. >;
  47. export const ProfileContext = createContext<ProfileProviderValue | null>(null);
  48. const SetProfileProvider = createContext<SetProfileProviderValue | null>(null);
  49. export function useProfiles() {
  50. const context = useContext(ProfileContext);
  51. if (!context) {
  52. throw new Error('useProfiles was called outside of ProfileProvider');
  53. }
  54. return context;
  55. }
  56. export function useSetProfiles() {
  57. const context = useContext(SetProfileProvider);
  58. if (!context) {
  59. throw new Error('useSetProfiles was called outside of SetProfileProvider');
  60. }
  61. return context;
  62. }
  63. export const ProfileTransactionContext =
  64. createContext<RequestState<EventTransaction | null> | null>(null);
  65. export function useProfileTransaction() {
  66. const context = useContext(ProfileTransactionContext);
  67. if (!context) {
  68. throw new Error(
  69. 'useProfileTransaction was called outside of ProfileTransactionContext'
  70. );
  71. }
  72. return context;
  73. }
  74. function ProfilesAndTransactionProvider(props: FlamegraphViewProps): React.ReactElement {
  75. const organization = useOrganization();
  76. const params = useParams();
  77. const [profiles, setProfiles] = useState<RequestState<Profiling.ProfileInput>>({
  78. type: 'initial',
  79. });
  80. const profileTransaction = useSentryEvent<EventTransaction>(
  81. organization.slug,
  82. params.projectId,
  83. profiles.type === 'resolved' ? getTransactionId(profiles.data) : null
  84. );
  85. return (
  86. <ProfilesProvider
  87. onUpdateProfiles={setProfiles}
  88. orgSlug={organization.slug}
  89. profileId={params.eventId}
  90. projectSlug={params.projectId}
  91. >
  92. <SetProfileProvider.Provider value={setProfiles}>
  93. <ProfileTransactionContext.Provider value={profileTransaction}>
  94. <ProfileHeader
  95. eventId={params.eventId}
  96. projectId={params.projectId}
  97. transaction={
  98. profileTransaction.type === 'resolved' ? profileTransaction.data : null
  99. }
  100. />
  101. {props.children}
  102. </ProfileTransactionContext.Provider>
  103. </SetProfileProvider.Provider>
  104. </ProfilesProvider>
  105. );
  106. }
  107. interface ProfilesProviderProps {
  108. children: React.ReactNode;
  109. orgSlug: Organization['slug'];
  110. profileId: string;
  111. projectSlug: Project['slug'];
  112. onUpdateProfiles?: (any) => void;
  113. }
  114. export function ProfilesProvider({
  115. children,
  116. onUpdateProfiles,
  117. orgSlug,
  118. projectSlug,
  119. profileId,
  120. }: ProfilesProviderProps) {
  121. const api = useApi();
  122. const [profiles, setProfiles] = useState<RequestState<Profiling.ProfileInput>>({
  123. type: 'initial',
  124. });
  125. useLayoutEffect(() => {
  126. if (!profileId || !projectSlug || !orgSlug) {
  127. return undefined;
  128. }
  129. setProfiles({type: 'loading'});
  130. fetchFlamegraphs(api, profileId, projectSlug, orgSlug)
  131. .then(p => {
  132. setProfiles({type: 'resolved', data: p});
  133. onUpdateProfiles?.({type: 'resolved', data: p});
  134. })
  135. .catch(err => {
  136. // XXX: our API client mock implementation does not mimick the real
  137. // implementation, so we need to check for an empty object here. #sad
  138. const isEmptyObject = err.toString() === '[object Object]';
  139. const message = isEmptyObject
  140. ? t('Error: Unable to load profiles')
  141. : err.toString();
  142. setProfiles({type: 'errored', error: message});
  143. onUpdateProfiles?.({type: 'errored', error: message});
  144. Sentry.captureException(err);
  145. });
  146. return () => {
  147. api.clear();
  148. };
  149. }, [api, onUpdateProfiles, orgSlug, projectSlug, profileId]);
  150. return <ProfileContext.Provider value={profiles}>{children}</ProfileContext.Provider>;
  151. }
  152. export default ProfilesAndTransactionProvider;