profilesProvider.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  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. projectId: Project['id'],
  17. organization: Organization
  18. ): Promise<Profiling.ProfileInput> {
  19. return api
  20. .requestPromise(
  21. `/projects/${organization.slug}/${projectId}/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. 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 ProfileProvider(props: FlamegraphViewProps): React.ReactElement {
  70. const api = useApi();
  71. const organization = useOrganization();
  72. const params = useParams();
  73. const [profiles, setProfiles] = useState<RequestState<Profiling.ProfileInput>>({
  74. type: 'initial',
  75. });
  76. const profileTransaction = useSentryEvent<EventTransaction>(
  77. params.orgId,
  78. params.projectId,
  79. profiles.type === 'resolved' ? getTransactionId(profiles.data) : null
  80. );
  81. useEffect(() => {
  82. if (!params.eventId || !params.projectId) {
  83. return undefined;
  84. }
  85. setProfiles({type: 'loading'});
  86. fetchFlamegraphs(api, params.eventId, params.projectId, organization)
  87. .then(p => {
  88. setProfiles({type: 'resolved', data: p});
  89. })
  90. .catch(err => {
  91. const message = err.toString() || t('Error: Unable to load profiles');
  92. setProfiles({type: 'errored', error: message});
  93. Sentry.captureException(err);
  94. });
  95. return () => {
  96. api.clear();
  97. };
  98. }, [params.eventId, params.projectId, api, organization]);
  99. return (
  100. <ProfileContext.Provider value={profiles}>
  101. <SetProfileProvider.Provider value={setProfiles}>
  102. <ProfileTransactionContext.Provider value={profileTransaction}>
  103. <ProfileHeader
  104. eventId={params.eventId}
  105. projectId={params.projectId}
  106. transaction={
  107. profileTransaction.type === 'resolved' ? profileTransaction.data : null
  108. }
  109. />
  110. {props.children}
  111. </ProfileTransactionContext.Provider>
  112. </SetProfileProvider.Provider>
  113. </ProfileContext.Provider>
  114. );
  115. }
  116. export default ProfileProvider;