domainViewHeader.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import {Fragment} from 'react';
  2. import styled from '@emotion/styled';
  3. import {Breadcrumbs, type Crumb} from 'sentry/components/breadcrumbs';
  4. import ButtonBar from 'sentry/components/buttonBar';
  5. import FeedbackWidgetButton from 'sentry/components/feedback/widget/feedbackWidgetButton';
  6. import * as Layout from 'sentry/components/layouts/thirds';
  7. import {TabList, Tabs} from 'sentry/components/tabs';
  8. import {IconBusiness} from 'sentry/icons';
  9. import {space} from 'sentry/styles/space';
  10. import {useNavigate} from 'sentry/utils/useNavigate';
  11. import useOrganization from 'sentry/utils/useOrganization';
  12. import {useModuleTitles} from 'sentry/views/insights/common/utils/useModuleTitle';
  13. import {
  14. type RoutableModuleNames,
  15. useModuleURLBuilder,
  16. } from 'sentry/views/insights/common/utils/useModuleURL';
  17. import {
  18. DOMAIN_VIEW_BASE_TITLE,
  19. OVERVIEW_PAGE_TITLE,
  20. } from 'sentry/views/insights/pages/settings';
  21. import {isModuleEnabled} from 'sentry/views/insights/pages/utils';
  22. import type {ModuleName} from 'sentry/views/insights/types';
  23. export type Props = {
  24. domainBaseUrl: string;
  25. domainTitle: string;
  26. headerTitle: React.ReactNode;
  27. modules: ModuleName[];
  28. selectedModule: ModuleName | undefined;
  29. additionalBreadCrumbs?: Crumb[];
  30. additonalHeaderActions?: React.ReactNode;
  31. hideDefaultTabs?: boolean;
  32. tabs?: {onTabChange: (key: string) => void; tabList: React.ReactNode; value: string};
  33. };
  34. type Tab = {
  35. key: string;
  36. label: React.ReactNode;
  37. };
  38. export function DomainViewHeader({
  39. modules,
  40. headerTitle,
  41. domainTitle,
  42. selectedModule,
  43. hideDefaultTabs,
  44. additonalHeaderActions,
  45. additionalBreadCrumbs = [],
  46. domainBaseUrl,
  47. tabs,
  48. }: Props) {
  49. const navigate = useNavigate();
  50. const organization = useOrganization();
  51. const moduleURLBuilder = useModuleURLBuilder();
  52. const moduleTitles = useModuleTitles();
  53. const baseCrumbs: Crumb[] = [
  54. {
  55. label: DOMAIN_VIEW_BASE_TITLE,
  56. to: undefined, // There is no base /performance/ page
  57. preservePageFilters: true,
  58. },
  59. {
  60. label: domainTitle,
  61. to: domainBaseUrl,
  62. preservePageFilters: true,
  63. },
  64. {
  65. label: selectedModule ? moduleTitles[selectedModule] : OVERVIEW_PAGE_TITLE,
  66. to: selectedModule
  67. ? `${moduleURLBuilder(selectedModule as RoutableModuleNames)}/`
  68. : domainBaseUrl,
  69. preservePageFilters: true,
  70. },
  71. ...additionalBreadCrumbs,
  72. ];
  73. const showModuleTabs = organization.features.includes('insights-entry-points');
  74. const defaultHandleTabChange = (key: ModuleName | typeof OVERVIEW_PAGE_TITLE) => {
  75. if (key === selectedModule || (key === OVERVIEW_PAGE_TITLE && !module)) {
  76. return;
  77. }
  78. if (!key) {
  79. return;
  80. }
  81. if (key === OVERVIEW_PAGE_TITLE) {
  82. navigate(domainBaseUrl);
  83. return;
  84. }
  85. navigate(`${moduleURLBuilder(key as RoutableModuleNames)}/`);
  86. };
  87. const tabValue =
  88. hideDefaultTabs && tabs?.value ? tabs.value : selectedModule ?? OVERVIEW_PAGE_TITLE;
  89. const handleTabChange =
  90. hideDefaultTabs && tabs ? tabs.onTabChange : defaultHandleTabChange;
  91. const tabList: Tab[] = [
  92. {
  93. key: OVERVIEW_PAGE_TITLE,
  94. label: OVERVIEW_PAGE_TITLE,
  95. },
  96. ];
  97. if (showModuleTabs) {
  98. tabList.push(
  99. ...modules.map(moduleName => ({
  100. key: moduleName,
  101. label: <TabLabel moduleName={moduleName} />,
  102. }))
  103. );
  104. }
  105. return (
  106. <Fragment>
  107. <Layout.Header>
  108. <Layout.HeaderContent>
  109. <Breadcrumbs crumbs={baseCrumbs} />
  110. <Layout.Title>{headerTitle}</Layout.Title>
  111. </Layout.HeaderContent>
  112. <Layout.HeaderActions>
  113. <ButtonBar gap={1}>
  114. {additonalHeaderActions}
  115. <FeedbackWidgetButton />
  116. </ButtonBar>
  117. </Layout.HeaderActions>
  118. <Tabs value={tabValue} onChange={handleTabChange}>
  119. {!hideDefaultTabs && (
  120. <TabList hideBorder>
  121. {tabList.map(tab => (
  122. <TabList.Item key={tab.key}>{tab.label}</TabList.Item>
  123. ))}
  124. </TabList>
  125. )}
  126. {hideDefaultTabs && tabs && tabs.tabList}
  127. </Tabs>
  128. </Layout.Header>
  129. </Fragment>
  130. );
  131. }
  132. function TabLabel({moduleName}: {moduleName: ModuleName}) {
  133. const moduleTitles = useModuleTitles();
  134. const organization = useOrganization();
  135. const showBusinessIcon = !isModuleEnabled(moduleName, organization);
  136. if (showBusinessIcon) {
  137. return (
  138. <TabWithIconContainer>
  139. {moduleTitles[moduleName]}
  140. <IconBusiness />
  141. </TabWithIconContainer>
  142. );
  143. }
  144. return <Fragment>{moduleTitles[moduleName]}</Fragment>;
  145. }
  146. const TabWithIconContainer = styled('div')`
  147. display: inline-flex;
  148. align-items: center;
  149. text-align: left;
  150. gap: ${space(0.5)};
  151. `;