overview.tsx 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. import {Fragment} from 'react';
  2. import {RouteComponentProps} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import {LocationDescriptorObject} from 'history';
  5. import omit from 'lodash/omit';
  6. import pick from 'lodash/pick';
  7. import moment from 'moment';
  8. import {Client} from 'app/api';
  9. import {DateTimeObject} from 'app/components/charts/utils';
  10. import * as Layout from 'app/components/layouts/thirds';
  11. import LoadingIndicator from 'app/components/loadingIndicator';
  12. import {getParams} from 'app/components/organizations/globalSelectionHeader/getParams';
  13. import {ChangeData} from 'app/components/organizations/timeRangeSelector';
  14. import PageTimeRangeSelector from 'app/components/pageTimeRangeSelector';
  15. import {DEFAULT_RELATIVE_PERIODS, DEFAULT_STATS_PERIOD} from 'app/constants';
  16. import {t} from 'app/locale';
  17. import space from 'app/styles/space';
  18. import {DateString, Organization, RelativePeriod, TeamWithProjects} from 'app/types';
  19. import withApi from 'app/utils/withApi';
  20. import withOrganization from 'app/utils/withOrganization';
  21. import withTeamsForUser from 'app/utils/withTeamsForUser';
  22. import HeaderTabs from './headerTabs';
  23. import TeamKeyTransactions from './keyTransactions';
  24. import TeamDropdown from './teamDropdown';
  25. type Props = {
  26. api: Client;
  27. organization: Organization;
  28. teams: TeamWithProjects[];
  29. loadingTeams: boolean;
  30. error: Error | null;
  31. } & RouteComponentProps<{orgId: string}, {}>;
  32. const PAGE_QUERY_PARAMS = [
  33. 'pageStatsPeriod',
  34. 'pageStart',
  35. 'pageEnd',
  36. 'pageUtc',
  37. 'dataCategory',
  38. 'transform',
  39. 'sort',
  40. 'query',
  41. 'cursor',
  42. 'team',
  43. ];
  44. function TeamInsightsOverview({
  45. organization,
  46. teams,
  47. loadingTeams,
  48. location,
  49. router,
  50. }: Props) {
  51. const query = location?.query ?? {};
  52. const currentTeamId = query.team ?? teams[0]?.id;
  53. const currentTeam = teams.find(team => team.id === currentTeamId);
  54. const projects = currentTeam?.projects ?? [];
  55. function handleChangeTeam(teamId: string) {
  56. setStateOnUrl({team: teamId});
  57. }
  58. function handleUpdateDatetime(datetime: ChangeData): LocationDescriptorObject {
  59. const {start, end, relative, utc} = datetime;
  60. if (start && end) {
  61. const parser = utc ? moment.utc : moment;
  62. return setStateOnUrl({
  63. pageStatsPeriod: undefined,
  64. pageStart: parser(start).format(),
  65. pageEnd: parser(end).format(),
  66. pageUtc: utc ?? undefined,
  67. });
  68. }
  69. return setStateOnUrl({
  70. pageStatsPeriod: (relative as RelativePeriod) || undefined,
  71. pageStart: undefined,
  72. pageEnd: undefined,
  73. pageUtc: undefined,
  74. });
  75. }
  76. function setStateOnUrl(nextState: {
  77. pageStatsPeriod?: RelativePeriod;
  78. pageStart?: DateString;
  79. pageEnd?: DateString;
  80. pageUtc?: boolean | null;
  81. sort?: string;
  82. query?: string;
  83. cursor?: string;
  84. team?: string;
  85. }): LocationDescriptorObject {
  86. const nextQueryParams = pick(nextState, PAGE_QUERY_PARAMS);
  87. const nextLocation = {
  88. ...location,
  89. query: {
  90. ...query,
  91. ...nextQueryParams,
  92. },
  93. };
  94. router.push(nextLocation);
  95. return nextLocation;
  96. }
  97. function dataDatetime(): DateTimeObject {
  98. const {
  99. start,
  100. end,
  101. statsPeriod,
  102. utc: utcString,
  103. } = getParams(query, {
  104. allowEmptyPeriod: true,
  105. allowAbsoluteDatetime: true,
  106. allowAbsolutePageDatetime: true,
  107. });
  108. if (!statsPeriod && !start && !end) {
  109. return {period: DEFAULT_STATS_PERIOD};
  110. }
  111. // Following getParams, statsPeriod will take priority over start/end
  112. if (statsPeriod) {
  113. return {period: statsPeriod};
  114. }
  115. const utc = utcString === 'true';
  116. if (start && end) {
  117. return utc
  118. ? {
  119. start: moment.utc(start).format(),
  120. end: moment.utc(end).format(),
  121. utc,
  122. }
  123. : {
  124. start: moment(start).utc().format(),
  125. end: moment(end).utc().format(),
  126. utc,
  127. };
  128. }
  129. return {period: DEFAULT_STATS_PERIOD};
  130. }
  131. const {period, start, end, utc} = dataDatetime();
  132. return (
  133. <Fragment>
  134. <BorderlessHeader>
  135. <StyledHeaderContent>
  136. <StyledLayoutTitle>{t('Team Insights')}</StyledLayoutTitle>
  137. </StyledHeaderContent>
  138. </BorderlessHeader>
  139. <Layout.Header>
  140. <HeaderTabs organization={organization} activeTab="teamInsights" />
  141. </Layout.Header>
  142. <Layout.Body>
  143. {loadingTeams && <LoadingIndicator />}
  144. {!loadingTeams && (
  145. <Layout.Main fullWidth>
  146. <ControlsWrapper>
  147. <TeamDropdown
  148. teams={teams}
  149. selectedTeam={currentTeamId}
  150. handleChangeTeam={handleChangeTeam}
  151. />
  152. <PageTimeRangeSelector
  153. organization={organization}
  154. relative={period ?? ''}
  155. start={start ?? null}
  156. end={end ?? null}
  157. utc={utc ?? null}
  158. onUpdate={handleUpdateDatetime}
  159. relativeOptions={omit(DEFAULT_RELATIVE_PERIODS, ['1h'])}
  160. />
  161. </ControlsWrapper>
  162. <SectionTitle>{t('Performance')}</SectionTitle>
  163. <TeamKeyTransactions
  164. organization={organization}
  165. projects={projects}
  166. period={period}
  167. start={start?.toString()}
  168. end={end?.toString()}
  169. location={location}
  170. />
  171. </Layout.Main>
  172. )}
  173. </Layout.Body>
  174. </Fragment>
  175. );
  176. }
  177. export {TeamInsightsOverview};
  178. export default withApi(withOrganization(withTeamsForUser(TeamInsightsOverview)));
  179. const BorderlessHeader = styled(Layout.Header)`
  180. border-bottom: 0;
  181. `;
  182. const StyledHeaderContent = styled(Layout.HeaderContent)`
  183. margin-bottom: 0;
  184. `;
  185. const StyledLayoutTitle = styled(Layout.Title)`
  186. margin-top: ${space(0.5)};
  187. `;
  188. const ControlsWrapper = styled('div')`
  189. display: flex;
  190. align-items: center;
  191. gap: ${space(1)};
  192. margin-bottom: ${space(2)};
  193. `;
  194. const SectionTitle = styled(Layout.Title)`
  195. margin-bottom: ${space(1)} !important;
  196. `;