platformCompatibilityChecker.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import {useEffect, useMemo} from 'react';
  2. import ExternalLink from 'sentry/components/links/externalLink';
  3. import LoadingIndicator from 'sentry/components/loadingIndicator';
  4. import {t, tct} from 'sentry/locale';
  5. import type {PageFilters} from 'sentry/types';
  6. import {useDiscoverQuery} from 'sentry/utils/discover/discoverQuery';
  7. import EventView from 'sentry/utils/discover/eventView';
  8. import {DiscoverDatasets} from 'sentry/utils/discover/types';
  9. import {usePageAlert} from 'sentry/utils/performance/contexts/pageAlert';
  10. import {useLocation} from 'sentry/utils/useLocation';
  11. import useOrganization from 'sentry/utils/useOrganization';
  12. import usePageFilters from 'sentry/utils/usePageFilters';
  13. import usePrevious from 'sentry/utils/usePrevious';
  14. import useProjects from 'sentry/utils/useProjects';
  15. const MAX_PROJECTS_TO_LIST = 3;
  16. function getEventView(selection: PageFilters, compatibleSDKNames: string[]) {
  17. const {projects: selectedProjectIds} = selection;
  18. return EventView.fromNewQueryWithPageFilters(
  19. {
  20. dataset: DiscoverDatasets.SPANS_INDEXED,
  21. fields: ['project_id', 'sdk.name', 'count()'],
  22. projects: selectedProjectIds,
  23. query: `has:sdk.name !sdk.name:[${compatibleSDKNames.join(',')}]`,
  24. name: '',
  25. version: 2,
  26. },
  27. selection
  28. );
  29. }
  30. type PlatformCompatibilityCheckerProps = {
  31. children: React.ReactNode;
  32. compatibleSDKNames: string[];
  33. docsUrl: string;
  34. };
  35. // Checks if the currently selected platforms are compatible with the current view
  36. // and if there are incompatible projects, list them in a warning message.
  37. // If there are no compatible platforms, only return the alert, otherwise return the children
  38. // after setting the alert.
  39. export function PlatformCompatibilityChecker({
  40. compatibleSDKNames,
  41. children,
  42. docsUrl,
  43. }: PlatformCompatibilityCheckerProps) {
  44. const location = useLocation();
  45. const organization = useOrganization();
  46. const {selection, isReady} = usePageFilters();
  47. const {projects} = useProjects();
  48. const {pageAlert, setPageWarning} = usePageAlert();
  49. const {projects: selectedProjectIds} = selection;
  50. // Get the projects that are incompatible with the current view
  51. const {data, isLoading} = useDiscoverQuery({
  52. eventView: getEventView(selection, compatibleSDKNames),
  53. location,
  54. orgSlug: organization.slug,
  55. referrer: 'api.starfish-mobile-platform-compatibility',
  56. options: {
  57. enabled: isReady,
  58. },
  59. });
  60. const incompatibleProjects = useMemo(() => {
  61. const incompatibleProjectIds = new Set(data?.data.map(row => row.project_id));
  62. // My Projects and All Projects are represented by an empty selection
  63. // or -1. Don't filter in these cases
  64. const selectedProjects =
  65. selectedProjectIds.length === 0 || selectedProjectIds[0] === -1
  66. ? projects
  67. : projects.filter(project =>
  68. selectedProjectIds.includes(parseInt(project.id, 10))
  69. );
  70. return selectedProjects.filter(project =>
  71. incompatibleProjectIds.has(parseInt(project.id, 10))
  72. );
  73. }, [data?.data, projects, selectedProjectIds]);
  74. const prevIncompatibleProjects = usePrevious(incompatibleProjects);
  75. useEffect(() => {
  76. if (incompatibleProjects.length > 0) {
  77. if (incompatibleProjects !== prevIncompatibleProjects) {
  78. const listedIncompatibleProjects = incompatibleProjects
  79. .slice(0, MAX_PROJECTS_TO_LIST)
  80. .map(project => project.slug)
  81. .join(', ');
  82. setPageWarning(
  83. tct(
  84. 'The following selected projects contain data from SDKs that are not supported by this view: [projects]. The currently supported SDK platforms are: [platforms]. We recommend to filter your current project selection to the supported platforms, to learn more, [link:read the docs].',
  85. {
  86. projects:
  87. incompatibleProjects.length <= MAX_PROJECTS_TO_LIST
  88. ? listedIncompatibleProjects
  89. : tct('[projects], and [count] more', {
  90. projects: listedIncompatibleProjects,
  91. count: incompatibleProjects.length - MAX_PROJECTS_TO_LIST,
  92. }),
  93. platforms: compatibleSDKNames.join(', '),
  94. link: <ExternalLink href={docsUrl}>{t('read the docs')}</ExternalLink>,
  95. }
  96. )
  97. );
  98. }
  99. } else if (incompatibleProjects.length === 0 && pageAlert?.message) {
  100. setPageWarning(undefined);
  101. }
  102. }, [
  103. compatibleSDKNames,
  104. docsUrl,
  105. incompatibleProjects,
  106. pageAlert,
  107. prevIncompatibleProjects,
  108. setPageWarning,
  109. ]);
  110. if (isLoading) {
  111. return <LoadingIndicator />;
  112. }
  113. if (
  114. incompatibleProjects.length > 0 &&
  115. incompatibleProjects.length === selectedProjectIds.length
  116. ) {
  117. // Don't return anything if all projects are incompatible
  118. return null;
  119. }
  120. return children;
  121. }