profilesProvider.tsx 4.7 KB

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