header.tsx 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. import * as React from 'react';
  2. import styled from '@emotion/styled';
  3. import {Location} from 'history';
  4. import Feature from 'app/components/acl/feature';
  5. import Button from 'app/components/button';
  6. import ButtonBar from 'app/components/buttonBar';
  7. import {CreateAlertFromViewButton} from 'app/components/createAlertButton';
  8. import * as Layout from 'app/components/layouts/thirds';
  9. import ListLink from 'app/components/links/listLink';
  10. import NavTabs from 'app/components/navTabs';
  11. import {IconSettings} from 'app/icons';
  12. import {t} from 'app/locale';
  13. import {Organization, Project} from 'app/types';
  14. import {trackAnalyticsEvent} from 'app/utils/analytics';
  15. import EventView from 'app/utils/discover/eventView';
  16. import {decodeScalar} from 'app/utils/queryString';
  17. import Breadcrumb from 'app/views/performance/breadcrumb';
  18. import {tagsRouteWithQuery} from './transactionTags/utils';
  19. import {vitalsRouteWithQuery} from './transactionVitals/utils';
  20. import KeyTransactionButton from './keyTransactionButton';
  21. import TeamKeyTransactionButton from './teamKeyTransactionButton';
  22. import {transactionSummaryRouteWithQuery} from './utils';
  23. export enum Tab {
  24. TransactionSummary,
  25. RealUserMonitoring,
  26. Tags,
  27. }
  28. type Props = {
  29. eventView: EventView;
  30. location: Location;
  31. organization: Organization;
  32. projects: Project[];
  33. transactionName: string;
  34. currentTab: Tab;
  35. hasWebVitals: boolean;
  36. handleIncompatibleQuery: React.ComponentProps<
  37. typeof CreateAlertFromViewButton
  38. >['onIncompatibleQuery'];
  39. };
  40. class TransactionHeader extends React.Component<Props> {
  41. trackAlertClick(errors?: Record<string, boolean>) {
  42. const {organization} = this.props;
  43. trackAnalyticsEvent({
  44. eventKey: 'performance_views.summary.create_alert_clicked',
  45. eventName: 'Performance Views: Create alert clicked',
  46. organization_id: organization.id,
  47. status: errors ? 'error' : 'success',
  48. errors,
  49. url: window.location.href,
  50. });
  51. }
  52. trackVitalsTabClick = () => {
  53. const {organization} = this.props;
  54. trackAnalyticsEvent({
  55. eventKey: 'performance_views.vitals.vitals_tab_clicked',
  56. eventName: 'Performance Views: Vitals tab clicked',
  57. organization_id: organization.id,
  58. });
  59. };
  60. trackTagsTabClick = () => {
  61. // TODO(k-fish): Add analytics for tags
  62. };
  63. handleIncompatibleQuery: React.ComponentProps<
  64. typeof CreateAlertFromViewButton
  65. >['onIncompatibleQuery'] = (incompatibleAlertNoticeFn, errors) => {
  66. this.trackAlertClick(errors);
  67. this.props.handleIncompatibleQuery?.(incompatibleAlertNoticeFn, errors);
  68. };
  69. handleCreateAlertSuccess = () => {
  70. this.trackAlertClick();
  71. };
  72. renderCreateAlertButton() {
  73. const {eventView, organization, projects} = this.props;
  74. return (
  75. <CreateAlertFromViewButton
  76. eventView={eventView}
  77. organization={organization}
  78. projects={projects}
  79. onIncompatibleQuery={this.handleIncompatibleQuery}
  80. onSuccess={this.handleCreateAlertSuccess}
  81. referrer="performance"
  82. />
  83. );
  84. }
  85. renderKeyTransactionButton() {
  86. const {eventView, organization, transactionName} = this.props;
  87. return (
  88. <Feature organization={organization} features={['team-key-transactions']}>
  89. {({hasFeature}) =>
  90. hasFeature ? (
  91. <TeamKeyTransactionButton
  92. transactionName={transactionName}
  93. eventView={eventView}
  94. organization={organization}
  95. />
  96. ) : (
  97. <KeyTransactionButton
  98. transactionName={transactionName}
  99. eventView={eventView}
  100. organization={organization}
  101. />
  102. )
  103. }
  104. </Feature>
  105. );
  106. }
  107. render() {
  108. const {
  109. organization,
  110. location,
  111. transactionName,
  112. currentTab,
  113. hasWebVitals,
  114. } = this.props;
  115. const summaryTarget = transactionSummaryRouteWithQuery({
  116. orgSlug: organization.slug,
  117. transaction: transactionName,
  118. projectID: decodeScalar(location.query.project),
  119. query: location.query,
  120. });
  121. const vitalsTarget = vitalsRouteWithQuery({
  122. orgSlug: organization.slug,
  123. transaction: transactionName,
  124. projectID: decodeScalar(location.query.project),
  125. query: location.query,
  126. });
  127. const tagsTarget = tagsRouteWithQuery({
  128. orgSlug: organization.slug,
  129. transaction: transactionName,
  130. projectID: decodeScalar(location.query.project),
  131. query: location.query,
  132. });
  133. return (
  134. <Layout.Header>
  135. <Layout.HeaderContent>
  136. <Breadcrumb
  137. organization={organization}
  138. location={location}
  139. transactionName={transactionName}
  140. realUserMonitoring={currentTab === Tab.RealUserMonitoring}
  141. />
  142. <Layout.Title>{transactionName}</Layout.Title>
  143. </Layout.HeaderContent>
  144. <Layout.HeaderActions>
  145. <ButtonBar gap={1}>
  146. <Feature organization={organization} features={['incidents']}>
  147. {({hasFeature}) => hasFeature && this.renderCreateAlertButton()}
  148. </Feature>
  149. {this.renderKeyTransactionButton()}
  150. <Button
  151. href={`/settings/${organization.slug}/performance/`}
  152. icon={<IconSettings />}
  153. aria-label="Settings"
  154. />
  155. </ButtonBar>
  156. </Layout.HeaderActions>
  157. <React.Fragment>
  158. <StyledNavTabs>
  159. <ListLink
  160. to={summaryTarget}
  161. isActive={() => currentTab === Tab.TransactionSummary}
  162. >
  163. {t('Overview')}
  164. </ListLink>
  165. {hasWebVitals && (
  166. <ListLink
  167. to={vitalsTarget}
  168. isActive={() => currentTab === Tab.RealUserMonitoring}
  169. onClick={this.trackVitalsTabClick}
  170. >
  171. {t('Web Vitals')}
  172. </ListLink>
  173. )}
  174. <Feature features={['organizations:performance-tag-page']}>
  175. <ListLink
  176. to={tagsTarget}
  177. isActive={() => currentTab === Tab.Tags}
  178. onClick={this.trackTagsTabClick}
  179. >
  180. {t('Tags')}
  181. </ListLink>
  182. </Feature>
  183. </StyledNavTabs>
  184. </React.Fragment>
  185. </Layout.Header>
  186. );
  187. }
  188. }
  189. const StyledNavTabs = styled(NavTabs)`
  190. margin-bottom: 0;
  191. /* Makes sure the tabs are pushed into another row */
  192. width: 100%;
  193. `;
  194. export default TransactionHeader;