health.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import {Fragment} from 'react';
  2. import type {RouteComponentProps} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import * as Layout from 'sentry/components/layouts/thirds';
  5. import LoadingError from 'sentry/components/loadingError';
  6. import LoadingIndicator from 'sentry/components/loadingIndicator';
  7. import NoProjectMessage from 'sentry/components/noProjectMessage';
  8. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  9. import {t} from 'sentry/locale';
  10. import type {TeamWithProjects} from 'sentry/types/project';
  11. import localStorage from 'sentry/utils/localStorage';
  12. import useRouteAnalyticsEventNames from 'sentry/utils/routeAnalytics/useRouteAnalyticsEventNames';
  13. import useOrganization from 'sentry/utils/useOrganization';
  14. import {useUserTeams} from 'sentry/utils/useUserTeams';
  15. import Header from '../header';
  16. import TeamStatsControls from './controls';
  17. import DescriptionCard from './descriptionCard';
  18. import TeamAlertsTriggered from './teamAlertsTriggered';
  19. import TeamMisery from './teamMisery';
  20. import TeamReleases from './teamReleases';
  21. import TeamStability from './teamStability';
  22. import {dataDatetime} from './utils';
  23. type Props = RouteComponentProps<{}, {}>;
  24. function TeamStatsHealth({location, router}: Props) {
  25. const organization = useOrganization();
  26. const {teams, isLoading, isError} = useUserTeams();
  27. useRouteAnalyticsEventNames('team_insights.viewed', 'Team Insights: Viewed');
  28. const query = location?.query ?? {};
  29. const localStorageKey = `teamInsightsSelectedTeamId:${organization.slug}`;
  30. let localTeamId: string | null | undefined =
  31. query.team ?? localStorage.getItem(localStorageKey);
  32. if (localTeamId && !teams.find(team => team.id === localTeamId)) {
  33. localTeamId = null;
  34. }
  35. const currentTeamId = localTeamId ?? teams[0]?.id;
  36. const currentTeam = teams.find(team => team.id === currentTeamId) as
  37. | TeamWithProjects
  38. | undefined;
  39. const projects = currentTeam?.projects ?? [];
  40. const {period, start, end, utc} = dataDatetime(query);
  41. if (teams.length === 0) {
  42. return (
  43. <NoProjectMessage organization={organization} superuserNeedsToBeProjectMember />
  44. );
  45. }
  46. if (isError) {
  47. return <LoadingError />;
  48. }
  49. return (
  50. <Fragment>
  51. <SentryDocumentTitle title={t('Project Health')} orgSlug={organization.slug} />
  52. <Header organization={organization} activeTab="health" />
  53. <Body>
  54. <TeamStatsControls
  55. location={location}
  56. router={router}
  57. currentTeam={currentTeam}
  58. />
  59. {isLoading && <LoadingIndicator />}
  60. {!isLoading && (
  61. <Layout.Main fullWidth>
  62. <DescriptionCard
  63. title={t('Crash Free Sessions')}
  64. description={t(
  65. 'The percentage of healthy, errored, and abnormal sessions that didn’t cause a crash.'
  66. )}
  67. >
  68. <TeamStability
  69. projects={projects}
  70. organization={organization}
  71. period={period}
  72. start={start}
  73. end={end}
  74. utc={utc}
  75. />
  76. </DescriptionCard>
  77. <DescriptionCard
  78. title={t('User Misery')}
  79. description={t(
  80. 'The number of unique users that experienced load times 4x the project’s configured threshold.'
  81. )}
  82. >
  83. <TeamMisery
  84. organization={organization}
  85. projects={projects}
  86. teamId={currentTeam!.id}
  87. period={period}
  88. start={start?.toString()}
  89. end={end?.toString()}
  90. location={location}
  91. />
  92. </DescriptionCard>
  93. <DescriptionCard
  94. title={t('Metric Alerts Triggered')}
  95. description={t('Alerts triggered from the Alert Rules your team created.')}
  96. >
  97. <TeamAlertsTriggered
  98. organization={organization}
  99. projects={projects}
  100. teamSlug={currentTeam!.slug}
  101. period={period}
  102. start={start?.toString()}
  103. end={end?.toString()}
  104. />
  105. </DescriptionCard>
  106. <DescriptionCard
  107. title={t('Number of Releases')}
  108. description={t('The releases that were created in your team’s projects.')}
  109. >
  110. <TeamReleases
  111. projects={projects}
  112. organization={organization}
  113. teamSlug={currentTeam!.slug}
  114. period={period}
  115. start={start}
  116. end={end}
  117. utc={utc}
  118. />
  119. </DescriptionCard>
  120. </Layout.Main>
  121. )}
  122. </Body>
  123. </Fragment>
  124. );
  125. }
  126. export default TeamStatsHealth;
  127. const Body = styled(Layout.Body)`
  128. @media (min-width: ${p => p.theme.breakpoints.medium}) {
  129. display: block;
  130. }
  131. `;