index.tsx 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848
  1. import {Fragment, useCallback, useContext, useEffect} from 'react';
  2. import {css} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import {hideSidebar, showSidebar} from 'sentry/actionCreators/preferences';
  5. import Feature from 'sentry/components/acl/feature';
  6. import GuideAnchor from 'sentry/components/assistant/guideAnchor';
  7. import {Chevron} from 'sentry/components/chevron';
  8. import FeedbackOnboardingSidebar from 'sentry/components/feedback/feedbackOnboarding/sidebar';
  9. import Hook from 'sentry/components/hook';
  10. import {OnboardingContext} from 'sentry/components/onboarding/onboardingContext';
  11. import {getMergedTasks} from 'sentry/components/onboardingWizard/taskConfig';
  12. import PerformanceOnboardingSidebar from 'sentry/components/performanceOnboarding/sidebar';
  13. import ReplaysOnboardingSidebar from 'sentry/components/replaysOnboarding/sidebar';
  14. import {
  15. ExpandedContext,
  16. ExpandedContextProvider,
  17. } from 'sentry/components/sidebar/expandedContextProvider';
  18. import {isDone} from 'sentry/components/sidebar/utils';
  19. import {
  20. IconDashboard,
  21. IconGraph,
  22. IconIssues,
  23. IconLightning,
  24. IconMegaphone,
  25. IconPlay,
  26. IconProfiling,
  27. IconProject,
  28. IconReleases,
  29. IconSettings,
  30. IconSiren,
  31. IconStats,
  32. IconSupport,
  33. IconTelescope,
  34. IconTimer,
  35. } from 'sentry/icons';
  36. import {IconRobot} from 'sentry/icons/iconRobot';
  37. import {t} from 'sentry/locale';
  38. import ConfigStore from 'sentry/stores/configStore';
  39. import DemoWalkthroughStore from 'sentry/stores/demoWalkthroughStore';
  40. import HookStore from 'sentry/stores/hookStore';
  41. import PreferencesStore from 'sentry/stores/preferencesStore';
  42. import SidebarPanelStore from 'sentry/stores/sidebarPanelStore';
  43. import {useLegacyStore} from 'sentry/stores/useLegacyStore';
  44. import {space} from 'sentry/styles/space';
  45. import type {Organization} from 'sentry/types/organization';
  46. import {isDemoWalkthrough} from 'sentry/utils/demoMode';
  47. import {getDiscoverLandingUrl} from 'sentry/utils/discover/urls';
  48. import {isActiveSuperuser} from 'sentry/utils/isActiveSuperuser';
  49. import {canSeeMetricsPage, hasRolledOutMetrics} from 'sentry/utils/metrics/features';
  50. import theme from 'sentry/utils/theme';
  51. import {useLocation} from 'sentry/utils/useLocation';
  52. import useMedia from 'sentry/utils/useMedia';
  53. import useOrganization from 'sentry/utils/useOrganization';
  54. import useProjects from 'sentry/utils/useProjects';
  55. import {normalizeUrl} from 'sentry/utils/withDomainRequired';
  56. import MetricsOnboardingSidebar from 'sentry/views/metrics/ddmOnboarding/sidebar';
  57. import {
  58. MODULE_TITLE as CACHE_MODULE_TITLE,
  59. releaseLevelAsBadgeProps as CacheModuleBadgeProps,
  60. } from 'sentry/views/performance/cache/settings';
  61. import {
  62. MODULE_TITLE as HTTP_MODULE_TITLE,
  63. releaseLevelAsBadgeProps as HTTPModuleBadgeProps,
  64. } from 'sentry/views/performance/http/settings';
  65. import {
  66. MODULE_TITLE as QUEUES_MODULE_TITLE,
  67. releaseLevelAsBadgeProps as QueuesModuleBadgeProps,
  68. } from 'sentry/views/performance/queues/settings';
  69. import {useModuleURLBuilder} from 'sentry/views/performance/utils/useModuleURL';
  70. import {ProfilingOnboardingSidebar} from '../profiling/ProfilingOnboarding/profilingOnboardingSidebar';
  71. import Broadcasts from './broadcasts';
  72. import SidebarHelp from './help';
  73. import OnboardingStatus from './onboardingStatus';
  74. import ServiceIncidents from './serviceIncidents';
  75. import {SidebarAccordion} from './sidebarAccordion';
  76. import SidebarDropdown from './sidebarDropdown';
  77. import SidebarItem from './sidebarItem';
  78. import type {SidebarOrientation} from './types';
  79. import {SidebarPanelKey} from './types';
  80. function activatePanel(panel: SidebarPanelKey) {
  81. SidebarPanelStore.activatePanel(panel);
  82. }
  83. function togglePanel(panel: SidebarPanelKey) {
  84. SidebarPanelStore.togglePanel(panel);
  85. }
  86. function hidePanel(hash?: string) {
  87. SidebarPanelStore.hidePanel(hash);
  88. }
  89. function useOpenOnboardingSidebar(organization?: Organization) {
  90. const onboardingContext = useContext(OnboardingContext);
  91. const {projects: project} = useProjects();
  92. const location = useLocation();
  93. const openOnboardingSidebar = (() => {
  94. if (location?.hash === '#welcome') {
  95. if (organization && !ConfigStore.get('demoMode')) {
  96. const tasks = getMergedTasks({
  97. organization,
  98. projects: project,
  99. onboardingContext,
  100. });
  101. const allDisplayedTasks = tasks
  102. .filter(task => task.display)
  103. .filter(task => !task.renderCard);
  104. const doneTasks = allDisplayedTasks.filter(isDone);
  105. return !(doneTasks.length >= allDisplayedTasks.length);
  106. }
  107. return true;
  108. }
  109. return false;
  110. })();
  111. useEffect(() => {
  112. if (openOnboardingSidebar) {
  113. activatePanel(SidebarPanelKey.ONBOARDING_WIZARD);
  114. }
  115. }, [openOnboardingSidebar]);
  116. }
  117. function Sidebar() {
  118. const location = useLocation();
  119. const preferences = useLegacyStore(PreferencesStore);
  120. const activePanel = useLegacyStore(SidebarPanelStore);
  121. const organization = useOrganization({allowNull: true});
  122. const {shouldAccordionFloat} = useContext(ExpandedContext);
  123. const collapsed = !!preferences.collapsed;
  124. const horizontal = useMedia(`(max-width: ${theme.breakpoints.medium})`);
  125. // Avoid showing superuser UI on self-hosted instances
  126. const showSuperuserWarning = () => {
  127. return isActiveSuperuser() && !ConfigStore.get('isSelfHosted');
  128. };
  129. // Avoid showing superuser UI on certain organizations
  130. const isExcludedOrg = () => {
  131. return HookStore.get('component:superuser-warning-excluded')[0]?.(organization);
  132. };
  133. useOpenOnboardingSidebar();
  134. const toggleCollapse = useCallback(() => {
  135. if (collapsed) {
  136. showSidebar();
  137. } else {
  138. hideSidebar();
  139. }
  140. }, [collapsed]);
  141. // Close panel on any navigation
  142. useEffect(() => void hidePanel(), [location?.pathname]);
  143. // Add classname to body
  144. useEffect(() => {
  145. const bcl = document.body.classList;
  146. bcl.add('body-sidebar');
  147. return () => bcl.remove('body-sidebar');
  148. }, []);
  149. useEffect(() => {
  150. Object.values(SidebarPanelKey).forEach(key => {
  151. if (location?.hash === `#sidebar-${key}`) {
  152. togglePanel(key);
  153. }
  154. });
  155. }, [location?.hash]);
  156. // Add sidebar collapse classname to body
  157. useEffect(() => {
  158. const bcl = document.body.classList;
  159. if (collapsed) {
  160. bcl.add('collapsed');
  161. } else {
  162. bcl.remove('collapsed');
  163. }
  164. return () => bcl.remove('collapsed');
  165. }, [collapsed]);
  166. const hasPanel = !!activePanel;
  167. const hasOrganization = !!organization;
  168. const orientation: SidebarOrientation = horizontal ? 'top' : 'left';
  169. const sidebarItemProps = {
  170. orientation,
  171. collapsed,
  172. hasPanel,
  173. organization,
  174. };
  175. const sidebarAnchor = isDemoWalkthrough() ? (
  176. <GuideAnchor target="projects" disabled={!DemoWalkthroughStore.get('sidebar')}>
  177. {t('Projects')}
  178. </GuideAnchor>
  179. ) : (
  180. <GuideAnchor target="projects">{t('Projects')}</GuideAnchor>
  181. );
  182. const projects = hasOrganization && (
  183. <SidebarItem
  184. {...sidebarItemProps}
  185. index
  186. icon={<IconProject />}
  187. label={sidebarAnchor}
  188. to={`/organizations/${organization.slug}/projects/`}
  189. id="projects"
  190. />
  191. );
  192. const issues = hasOrganization && (
  193. <SidebarItem
  194. {...sidebarItemProps}
  195. icon={<IconIssues />}
  196. label={<GuideAnchor target="issues">{t('Issues')}</GuideAnchor>}
  197. to={`/organizations/${organization.slug}/issues/`}
  198. search="?referrer=sidebar"
  199. id="issues"
  200. />
  201. );
  202. const discover2 = hasOrganization && (
  203. <Feature
  204. hookName="feature-disabled:discover2-sidebar-item"
  205. features="discover-basic"
  206. organization={organization}
  207. >
  208. <SidebarItem
  209. {...sidebarItemProps}
  210. icon={<IconTelescope />}
  211. label={<GuideAnchor target="discover">{t('Discover')}</GuideAnchor>}
  212. to={getDiscoverLandingUrl(organization)}
  213. id="discover-v2"
  214. />
  215. </Feature>
  216. );
  217. const moduleURLBuilder = useModuleURLBuilder(true);
  218. const performance = hasOrganization && (
  219. <Feature
  220. hookName="feature-disabled:performance-sidebar-item"
  221. features="performance-view"
  222. organization={organization}
  223. >
  224. {(() => {
  225. // If Database View or Web Vitals View is enabled, show a Performance accordion with a Database and/or Web Vitals sub-item
  226. if (
  227. organization.features.includes('spans-first-ui') ||
  228. organization.features.includes('performance-cache-view') ||
  229. organization.features.includes('performance-queues-view')
  230. ) {
  231. return (
  232. <SidebarAccordion
  233. {...sidebarItemProps}
  234. icon={<IconLightning />}
  235. label={<GuideAnchor target="performance">{t('Performance')}</GuideAnchor>}
  236. to={`/organizations/${organization.slug}/performance/`}
  237. id="performance"
  238. exact={!shouldAccordionFloat}
  239. >
  240. <Feature features="spans-first-ui" organization={organization}>
  241. <SidebarItem
  242. {...sidebarItemProps}
  243. label={
  244. <GuideAnchor target="performance-database">
  245. {t('Queries')}
  246. </GuideAnchor>
  247. }
  248. to={`/organizations/${organization.slug}/${moduleURLBuilder('db')}/`}
  249. id="performance-database"
  250. // collapsed controls whether the dot is visible or not.
  251. // We always want it visible for these sidebar items so force it to true.
  252. icon={<SubitemDot collapsed />}
  253. />
  254. </Feature>
  255. <Feature features="spans-first-ui" organization={organization}>
  256. <SidebarItem
  257. {...sidebarItemProps}
  258. label={
  259. <GuideAnchor target="performance-http">
  260. {HTTP_MODULE_TITLE}
  261. </GuideAnchor>
  262. }
  263. to={`/organizations/${organization.slug}/${moduleURLBuilder('http')}/`}
  264. id="performance-http"
  265. icon={<SubitemDot collapsed />}
  266. {...HTTPModuleBadgeProps}
  267. />
  268. </Feature>
  269. <Feature features="performance-cache-view" organization={organization}>
  270. <SidebarItem
  271. {...sidebarItemProps}
  272. label={
  273. <GuideAnchor target="performance-cache">
  274. {CACHE_MODULE_TITLE}
  275. </GuideAnchor>
  276. }
  277. to={`/organizations/${organization.slug}/${moduleURLBuilder('cache')}/`}
  278. id="performance-cache"
  279. icon={<SubitemDot collapsed />}
  280. {...CacheModuleBadgeProps}
  281. />
  282. </Feature>
  283. <Feature features="spans-first-ui" organization={organization}>
  284. <SidebarItem
  285. {...sidebarItemProps}
  286. label={
  287. <GuideAnchor target="performance-webvitals">
  288. {t('Web Vitals')}
  289. </GuideAnchor>
  290. }
  291. to={`/organizations/${organization.slug}/${moduleURLBuilder('vital')}/`}
  292. id="performance-webvitals"
  293. icon={<SubitemDot collapsed />}
  294. />
  295. </Feature>
  296. <Feature features="performance-queues-view" organization={organization}>
  297. <SidebarItem
  298. {...sidebarItemProps}
  299. label={
  300. <GuideAnchor target="performance-queues">
  301. {QUEUES_MODULE_TITLE}
  302. </GuideAnchor>
  303. }
  304. {...QueuesModuleBadgeProps}
  305. to={`/organizations/${organization.slug}/${moduleURLBuilder('queue')}/`}
  306. id="performance-queues"
  307. icon={<SubitemDot collapsed />}
  308. />
  309. </Feature>
  310. <Feature features="spans-first-ui" organization={organization}>
  311. <SidebarItem
  312. {...sidebarItemProps}
  313. label={t('Screen Loads')}
  314. to={`/organizations/${organization.slug}/${moduleURLBuilder('screen_load')}/`}
  315. id="performance-mobile-screens"
  316. icon={<SubitemDot collapsed />}
  317. />
  318. </Feature>
  319. <Feature features="spans-first-ui" organization={organization}>
  320. <SidebarItem
  321. {...sidebarItemProps}
  322. label={t('App Starts')}
  323. to={`/organizations/${organization.slug}/${moduleURLBuilder('app_start')}/`}
  324. id="performance-mobile-app-startup"
  325. icon={<SubitemDot collapsed />}
  326. />
  327. </Feature>
  328. <Feature
  329. features={['spans-first-ui', 'starfish-mobile-ui-module']}
  330. organization={organization}
  331. >
  332. <SidebarItem
  333. {...sidebarItemProps}
  334. label={t('Mobile UI')}
  335. to={`/organizations/${organization.slug}/${moduleURLBuilder('mobile-ui')}/`}
  336. id="performance-mobile-ui"
  337. icon={<SubitemDot collapsed />}
  338. isAlpha
  339. />
  340. </Feature>
  341. <Feature features="spans-first-ui">
  342. <SidebarItem
  343. {...sidebarItemProps}
  344. label={<GuideAnchor target="starfish">{t('Resources')}</GuideAnchor>}
  345. to={`/organizations/${organization.slug}/${moduleURLBuilder('resource')}/`}
  346. id="performance-browser-resources"
  347. icon={<SubitemDot collapsed />}
  348. />
  349. </Feature>
  350. <Feature features="performance-trace-explorer">
  351. <SidebarItem
  352. {...sidebarItemProps}
  353. label={<GuideAnchor target="traces">{t('Traces')}</GuideAnchor>}
  354. to={`/organizations/${organization.slug}/performance/traces/`}
  355. id="performance-trace-explorer"
  356. icon={<SubitemDot collapsed />}
  357. isAlpha
  358. />
  359. </Feature>
  360. </SidebarAccordion>
  361. );
  362. }
  363. // Otherwise, show a regular sidebar link to the Performance landing page
  364. return (
  365. <SidebarItem
  366. {...sidebarItemProps}
  367. icon={<IconLightning />}
  368. label={<GuideAnchor target="performance">{t('Performance')}</GuideAnchor>}
  369. to={`/organizations/${organization.slug}/performance/`}
  370. id="performance"
  371. />
  372. );
  373. })()}
  374. </Feature>
  375. );
  376. const releases = hasOrganization && (
  377. <SidebarItem
  378. {...sidebarItemProps}
  379. icon={<IconReleases />}
  380. label={<GuideAnchor target="releases">{t('Releases')}</GuideAnchor>}
  381. to={`/organizations/${organization.slug}/releases/`}
  382. id="releases"
  383. />
  384. );
  385. const llmMonitoring = hasOrganization && (
  386. <Feature features="ai-analytics" organization={organization}>
  387. <SidebarItem
  388. {...sidebarItemProps}
  389. icon={<IconRobot />}
  390. label={t('LLM Monitoring')}
  391. isAlpha
  392. variant="short"
  393. to={`/organizations/${organization.slug}/${moduleURLBuilder('ai')}/`}
  394. id="llm-monitoring"
  395. />
  396. </Feature>
  397. );
  398. const userFeedback = hasOrganization && (
  399. <Feature features="old-user-feedback" organization={organization}>
  400. <SidebarItem
  401. {...sidebarItemProps}
  402. icon={<IconSupport />}
  403. label={t('User Feedback')}
  404. to={`/organizations/${organization.slug}/user-feedback/`}
  405. id="user-feedback"
  406. />
  407. </Feature>
  408. );
  409. const feedback = hasOrganization && (
  410. <Feature features="user-feedback-ui" organization={organization}>
  411. <SidebarItem
  412. {...sidebarItemProps}
  413. icon={<IconMegaphone />}
  414. label={t('User Feedback')}
  415. variant="short"
  416. to={`/organizations/${organization.slug}/feedback/`}
  417. id="feedback"
  418. />
  419. </Feature>
  420. );
  421. const alerts = hasOrganization && (
  422. <SidebarItem
  423. {...sidebarItemProps}
  424. icon={<IconSiren />}
  425. label={t('Alerts')}
  426. to={`/organizations/${organization.slug}/alerts/rules/`}
  427. id="alerts"
  428. />
  429. );
  430. const monitors = hasOrganization && (
  431. <SidebarItem
  432. {...sidebarItemProps}
  433. icon={<IconTimer />}
  434. label={t('Crons')}
  435. to={`/organizations/${organization.slug}/crons/`}
  436. id="crons"
  437. />
  438. );
  439. const replays = hasOrganization && (
  440. <Feature
  441. hookName="feature-disabled:replay-sidebar-item"
  442. features="session-replay-ui"
  443. organization={organization}
  444. requireAll={false}
  445. >
  446. <SidebarItem
  447. {...sidebarItemProps}
  448. icon={<IconPlay />}
  449. label={t('Replays')}
  450. to={`/organizations/${organization.slug}/replays/`}
  451. id="replays"
  452. />
  453. </Feature>
  454. );
  455. const metricsPath = `/organizations/${organization?.slug}/metrics/`;
  456. const isNewFeatureBadge = organization && hasRolledOutMetrics(organization);
  457. const metrics = hasOrganization && canSeeMetricsPage(organization) && (
  458. <SidebarItem
  459. {...sidebarItemProps}
  460. icon={<IconGraph />}
  461. label={t('Metrics')}
  462. to={metricsPath}
  463. search={location.pathname === normalizeUrl(metricsPath) ? location.search : ''}
  464. id="metrics"
  465. isBeta={!isNewFeatureBadge}
  466. isNew={!!isNewFeatureBadge}
  467. />
  468. );
  469. const dashboards = hasOrganization && (
  470. <Feature
  471. hookName="feature-disabled:dashboards-sidebar-item"
  472. features={['discover', 'discover-query', 'dashboards-basic', 'dashboards-edit']}
  473. organization={organization}
  474. requireAll={false}
  475. >
  476. <SidebarItem
  477. {...sidebarItemProps}
  478. index
  479. icon={<IconDashboard />}
  480. label={t('Dashboards')}
  481. to={`/organizations/${organization.slug}/dashboards/`}
  482. id="customizable-dashboards"
  483. />
  484. </Feature>
  485. );
  486. const profiling = hasOrganization && (
  487. <Feature
  488. hookName="feature-disabled:profiling-sidebar-item"
  489. features="profiling"
  490. organization={organization}
  491. requireAll={false}
  492. >
  493. <SidebarItem
  494. {...sidebarItemProps}
  495. index
  496. icon={<IconProfiling />}
  497. label={t('Profiling')}
  498. to={`/organizations/${organization.slug}/profiling/`}
  499. id="profiling"
  500. />
  501. </Feature>
  502. );
  503. const stats = hasOrganization && (
  504. <SidebarItem
  505. {...sidebarItemProps}
  506. icon={<IconStats />}
  507. label={t('Stats')}
  508. to={`/organizations/${organization.slug}/stats/`}
  509. id="stats"
  510. />
  511. );
  512. const settings = hasOrganization && (
  513. <SidebarItem
  514. {...sidebarItemProps}
  515. icon={<IconSettings />}
  516. label={t('Settings')}
  517. to={`/settings/${organization.slug}/`}
  518. id="settings"
  519. />
  520. );
  521. return (
  522. <SidebarWrapper aria-label={t('Primary Navigation')} collapsed={collapsed}>
  523. <ExpandedContextProvider>
  524. <SidebarSectionGroupPrimary>
  525. <DropdownSidebarSection
  526. isSuperuser={showSuperuserWarning() && !isExcludedOrg()}
  527. >
  528. <SidebarDropdown orientation={orientation} collapsed={collapsed} />
  529. {showSuperuserWarning() && !isExcludedOrg() && (
  530. <Hook name="component:superuser-warning" organization={organization} />
  531. )}
  532. </DropdownSidebarSection>
  533. <PrimaryItems>
  534. {hasOrganization && (
  535. <Fragment>
  536. <SidebarSection>
  537. {issues}
  538. {projects}
  539. </SidebarSection>
  540. <SidebarSection>
  541. {performance}
  542. {profiling}
  543. {metrics}
  544. {replays}
  545. {llmMonitoring}
  546. {feedback}
  547. {monitors}
  548. {alerts}
  549. </SidebarSection>
  550. <SidebarSection>
  551. {discover2}
  552. {dashboards}
  553. {releases}
  554. {userFeedback}
  555. </SidebarSection>
  556. <SidebarSection>
  557. {stats}
  558. {settings}
  559. </SidebarSection>
  560. </Fragment>
  561. )}
  562. </PrimaryItems>
  563. </SidebarSectionGroupPrimary>
  564. {hasOrganization && (
  565. <SidebarSectionGroup>
  566. <PerformanceOnboardingSidebar
  567. currentPanel={activePanel}
  568. onShowPanel={() => togglePanel(SidebarPanelKey.PERFORMANCE_ONBOARDING)}
  569. hidePanel={() => hidePanel('performance-sidequest')}
  570. {...sidebarItemProps}
  571. />
  572. <FeedbackOnboardingSidebar
  573. currentPanel={activePanel}
  574. onShowPanel={() => togglePanel(SidebarPanelKey.FEEDBACK_ONBOARDING)}
  575. hidePanel={hidePanel}
  576. {...sidebarItemProps}
  577. />
  578. <ReplaysOnboardingSidebar
  579. currentPanel={activePanel}
  580. onShowPanel={() => togglePanel(SidebarPanelKey.REPLAYS_ONBOARDING)}
  581. hidePanel={hidePanel}
  582. {...sidebarItemProps}
  583. />
  584. <ProfilingOnboardingSidebar
  585. currentPanel={activePanel}
  586. onShowPanel={() => togglePanel(SidebarPanelKey.PROFILING_ONBOARDING)}
  587. hidePanel={hidePanel}
  588. {...sidebarItemProps}
  589. />
  590. <MetricsOnboardingSidebar
  591. currentPanel={activePanel}
  592. onShowPanel={() => togglePanel(SidebarPanelKey.METRICS_ONBOARDING)}
  593. hidePanel={hidePanel}
  594. {...sidebarItemProps}
  595. />
  596. <SidebarSection noMargin noPadding>
  597. <OnboardingStatus
  598. org={organization}
  599. currentPanel={activePanel}
  600. onShowPanel={() => togglePanel(SidebarPanelKey.ONBOARDING_WIZARD)}
  601. hidePanel={hidePanel}
  602. {...sidebarItemProps}
  603. />
  604. </SidebarSection>
  605. <SidebarSection>
  606. {HookStore.get('sidebar:bottom-items').length > 0 &&
  607. HookStore.get('sidebar:bottom-items')[0]({
  608. orientation,
  609. collapsed,
  610. hasPanel,
  611. organization,
  612. })}
  613. <SidebarHelp
  614. orientation={orientation}
  615. collapsed={collapsed}
  616. hidePanel={hidePanel}
  617. organization={organization}
  618. />
  619. <Broadcasts
  620. orientation={orientation}
  621. collapsed={collapsed}
  622. currentPanel={activePanel}
  623. onShowPanel={() => togglePanel(SidebarPanelKey.BROADCASTS)}
  624. hidePanel={hidePanel}
  625. organization={organization}
  626. />
  627. <ServiceIncidents
  628. orientation={orientation}
  629. collapsed={collapsed}
  630. currentPanel={activePanel}
  631. onShowPanel={() => togglePanel(SidebarPanelKey.SERVICE_INCIDENTS)}
  632. hidePanel={hidePanel}
  633. />
  634. </SidebarSection>
  635. {!horizontal && (
  636. <SidebarSection>
  637. <SidebarCollapseItem
  638. id="collapse"
  639. data-test-id="sidebar-collapse"
  640. {...sidebarItemProps}
  641. icon={<Chevron direction={collapsed ? 'right' : 'left'} />}
  642. label={collapsed ? t('Expand') : t('Collapse')}
  643. onClick={toggleCollapse}
  644. />
  645. </SidebarSection>
  646. )}
  647. </SidebarSectionGroup>
  648. )}
  649. </ExpandedContextProvider>
  650. </SidebarWrapper>
  651. );
  652. }
  653. export default Sidebar;
  654. const responsiveFlex = css`
  655. display: flex;
  656. flex-direction: column;
  657. @media (max-width: ${theme.breakpoints.medium}) {
  658. flex-direction: row;
  659. }
  660. `;
  661. export const SidebarWrapper = styled('nav')<{collapsed: boolean}>`
  662. background: ${p => p.theme.sidebarGradient};
  663. color: ${p => p.theme.sidebar.color};
  664. line-height: 1;
  665. padding: 12px 0 2px; /* Allows for 32px avatars */
  666. width: ${p => p.theme.sidebar[p.collapsed ? 'collapsedWidth' : 'expandedWidth']};
  667. position: fixed;
  668. top: ${p => (ConfigStore.get('demoMode') ? p.theme.demo.headerSize : 0)};
  669. left: 0;
  670. bottom: 0;
  671. justify-content: space-between;
  672. z-index: ${p => p.theme.zIndex.sidebar};
  673. border-right: solid 1px ${p => p.theme.sidebarBorder};
  674. ${responsiveFlex};
  675. @media (max-width: ${p => p.theme.breakpoints.medium}) {
  676. top: 0;
  677. left: 0;
  678. right: 0;
  679. height: ${p => p.theme.sidebar.mobileHeight};
  680. bottom: auto;
  681. width: auto;
  682. padding: 0 ${space(1)};
  683. align-items: center;
  684. border-right: none;
  685. border-bottom: solid 1px ${p => p.theme.sidebarBorder};
  686. }
  687. `;
  688. const SidebarSectionGroup = styled('div')`
  689. ${responsiveFlex};
  690. flex-shrink: 0; /* prevents shrinking on Safari */
  691. gap: 1px;
  692. `;
  693. const SidebarSectionGroupPrimary = styled('div')`
  694. ${responsiveFlex};
  695. /* necessary for child flexing on msedge and ff */
  696. min-height: 0;
  697. min-width: 0;
  698. flex: 1;
  699. /* expand to fill the entire height on mobile */
  700. @media (max-width: ${p => p.theme.breakpoints.medium}) {
  701. height: 100%;
  702. align-items: center;
  703. }
  704. `;
  705. const PrimaryItems = styled('div')`
  706. overflow: auto;
  707. flex: 1;
  708. display: flex;
  709. flex-direction: column;
  710. gap: 1px;
  711. -ms-overflow-style: -ms-autohiding-scrollbar;
  712. @media (max-height: 675px) and (min-width: ${p => p.theme.breakpoints.medium}) {
  713. border-bottom: 1px solid ${p => p.theme.sidebarBorder};
  714. padding-bottom: ${space(1)};
  715. box-shadow: rgba(0, 0, 0, 0.15) 0px -10px 10px inset;
  716. }
  717. @media (max-width: ${p => p.theme.breakpoints.medium}) {
  718. overflow-y: visible;
  719. flex-direction: row;
  720. height: 100%;
  721. align-items: center;
  722. border-right: 1px solid ${p => p.theme.sidebarBorder};
  723. padding-right: ${space(1)};
  724. margin-right: ${space(0.5)};
  725. box-shadow: rgba(0, 0, 0, 0.15) -10px 0px 10px inset;
  726. ::-webkit-scrollbar {
  727. display: none;
  728. }
  729. }
  730. `;
  731. const SubitemDot = styled('div')<{collapsed: boolean}>`
  732. width: 3px;
  733. height: 3px;
  734. background: currentcolor;
  735. border-radius: 50%;
  736. opacity: ${p => (p.collapsed ? 1 : 0)};
  737. @media (max-width: ${p => p.theme.breakpoints.medium}) {
  738. opacity: 1;
  739. }
  740. `;
  741. const SidebarSection = styled(SidebarSectionGroup)<{
  742. noMargin?: boolean;
  743. noPadding?: boolean;
  744. }>`
  745. ${p => !p.noMargin && `margin: ${space(1)} 0`};
  746. ${p => !p.noPadding && `padding: 0 ${space(2)}`};
  747. @media (max-width: ${p => p.theme.breakpoints.small}) {
  748. margin: 0;
  749. padding: 0;
  750. }
  751. &:empty {
  752. display: none;
  753. }
  754. `;
  755. const DropdownSidebarSection = styled(SidebarSection)<{
  756. isSuperuser?: boolean;
  757. }>`
  758. position: relative;
  759. margin: 0;
  760. padding: ${space(1)} ${space(2)};
  761. ${p =>
  762. p.isSuperuser &&
  763. css`
  764. &:before {
  765. content: '';
  766. position: absolute;
  767. inset: 0 ${space(1)};
  768. border-radius: ${p.theme.borderRadius};
  769. background: ${p.theme.superuserSidebar};
  770. }
  771. `}
  772. `;
  773. const SidebarCollapseItem = styled(SidebarItem)`
  774. @media (max-width: ${p => p.theme.breakpoints.medium}) {
  775. display: none;
  776. }
  777. `;