domainViewHeader.tsx 4.4 KB

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