domainViewHeader.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  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 {Badge} from 'sentry/components/core/badge';
  6. import FeedbackWidgetButton from 'sentry/components/feedback/widget/feedbackWidgetButton';
  7. import * as Layout from 'sentry/components/layouts/thirds';
  8. import {extractSelectionParameters} from 'sentry/components/organizations/pageFilters/utils';
  9. import {TabList} from 'sentry/components/tabs';
  10. import type {TabListItemProps} from 'sentry/components/tabs/item';
  11. import {IconBusiness} from 'sentry/icons';
  12. import {t} from 'sentry/locale';
  13. import {space} from 'sentry/styles/space';
  14. import {useLocation} from 'sentry/utils/useLocation';
  15. import useOrganization from 'sentry/utils/useOrganization';
  16. import {useModuleTitles} from 'sentry/views/insights/common/utils/useModuleTitle';
  17. import {
  18. type RoutableModuleNames,
  19. useModuleURLBuilder,
  20. } from 'sentry/views/insights/common/utils/useModuleURL';
  21. import {OVERVIEW_PAGE_TITLE} from 'sentry/views/insights/pages/settings';
  22. import {
  23. isModuleConsideredNew,
  24. isModuleEnabled,
  25. isModuleVisible,
  26. } from 'sentry/views/insights/pages/utils';
  27. import type {ModuleName} from 'sentry/views/insights/types';
  28. export type Props = {
  29. domainBaseUrl: string;
  30. domainTitle: string;
  31. modules: ModuleName[];
  32. selectedModule: ModuleName | undefined;
  33. additionalBreadCrumbs?: Crumb[];
  34. additonalHeaderActions?: React.ReactNode;
  35. // TODO - hasOverviewPage could be improved, the overview page could just be a "module", but that has a lot of other implications that have to be considered
  36. hasOverviewPage?: boolean;
  37. headerTitle?: React.ReactNode;
  38. hideDefaultTabs?: boolean;
  39. tabs?: {onTabChange: (key: string) => void; tabList: React.ReactNode; value: string};
  40. };
  41. export function DomainViewHeader({
  42. modules,
  43. hasOverviewPage = true,
  44. headerTitle,
  45. domainTitle,
  46. selectedModule,
  47. hideDefaultTabs,
  48. additonalHeaderActions,
  49. additionalBreadCrumbs = [],
  50. domainBaseUrl,
  51. tabs,
  52. }: Props) {
  53. const organization = useOrganization();
  54. const location = useLocation();
  55. const moduleURLBuilder = useModuleURLBuilder();
  56. const crumbs: Crumb[] = [
  57. {
  58. label: domainTitle,
  59. to: domainBaseUrl,
  60. preservePageFilters: true,
  61. },
  62. ...additionalBreadCrumbs,
  63. ];
  64. const tabValue =
  65. hideDefaultTabs && tabs?.value ? tabs.value : (selectedModule ?? OVERVIEW_PAGE_TITLE);
  66. const globalQuery = extractSelectionParameters(location?.query);
  67. const tabList: TabListItemProps[] = [
  68. ...(hasOverviewPage
  69. ? [
  70. {
  71. key: OVERVIEW_PAGE_TITLE,
  72. children: OVERVIEW_PAGE_TITLE,
  73. to: {pathname: domainBaseUrl, query: globalQuery},
  74. },
  75. ]
  76. : []),
  77. ...modules
  78. .filter(moduleName => isModuleVisible(moduleName, organization))
  79. .map(moduleName => ({
  80. key: moduleName,
  81. children: <TabLabel moduleName={moduleName} />,
  82. textValue: moduleName,
  83. to: {
  84. pathname: `${moduleURLBuilder(moduleName as RoutableModuleNames)}/`,
  85. query: globalQuery,
  86. },
  87. })),
  88. ];
  89. return (
  90. <Fragment>
  91. <Layout.Header>
  92. <Layout.HeaderContent>
  93. {crumbs.length > 1 && <Breadcrumbs crumbs={crumbs} />}
  94. <Layout.Title>{headerTitle || domainTitle}</Layout.Title>
  95. </Layout.HeaderContent>
  96. <Layout.HeaderActions>
  97. <ButtonBar gap={1}>
  98. <FeedbackWidgetButton />
  99. {additonalHeaderActions}
  100. </ButtonBar>
  101. </Layout.HeaderActions>
  102. <Layout.HeaderTabs value={tabValue} onChange={tabs?.onTabChange}>
  103. {!hideDefaultTabs && (
  104. <TabList hideBorder>
  105. {tabList.map(tab => (
  106. <TabList.Item {...tab} key={tab.key} />
  107. ))}
  108. </TabList>
  109. )}
  110. {hideDefaultTabs && tabs && tabs.tabList}
  111. </Layout.HeaderTabs>
  112. </Layout.Header>
  113. </Fragment>
  114. );
  115. }
  116. interface TabLabelProps {
  117. moduleName: ModuleName;
  118. }
  119. function TabLabel({moduleName}: TabLabelProps) {
  120. const moduleTitles = useModuleTitles();
  121. const organization = useOrganization();
  122. const showBusinessIcon = !isModuleEnabled(moduleName, organization);
  123. if (showBusinessIcon || isModuleConsideredNew(moduleName)) {
  124. return (
  125. <TabContainer>
  126. {moduleTitles[moduleName]}
  127. {isModuleConsideredNew(moduleName) && <Badge type="new">{t('New')}</Badge>}
  128. {showBusinessIcon && <IconBusiness />}
  129. </TabContainer>
  130. );
  131. }
  132. return moduleTitles[moduleName];
  133. }
  134. const TabContainer = styled('div')`
  135. display: inline-flex;
  136. align-items: center;
  137. text-align: left;
  138. gap: ${space(0.5)};
  139. `;