index.tsx 26 KB

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