github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/webapp/javascript/components/Sidebar.tsx (about)

     1  import React, { useMemo } from 'react';
     2  import { faWindowMaximize } from '@fortawesome/free-regular-svg-icons';
     3  import { faChartBar } from '@fortawesome/free-solid-svg-icons/faChartBar';
     4  import { faColumns } from '@fortawesome/free-solid-svg-icons/faColumns';
     5  import { faFileAlt } from '@fortawesome/free-solid-svg-icons/faFileAlt';
     6  import { faCog } from '@fortawesome/free-solid-svg-icons/faCog';
     7  import { faInfoCircle } from '@fortawesome/free-solid-svg-icons/faInfoCircle';
     8  import { faSlack } from '@fortawesome/free-brands-svg-icons/faSlack';
     9  import { faGithub } from '@fortawesome/free-brands-svg-icons/faGithub';
    10  import { faChevronLeft } from '@fortawesome/free-solid-svg-icons/faChevronLeft';
    11  import { faSignOutAlt } from '@fortawesome/free-solid-svg-icons/faSignOutAlt';
    12  import { faSync } from '@fortawesome/free-solid-svg-icons/faSync';
    13  import { faSearch } from '@fortawesome/free-solid-svg-icons/faSearch';
    14  
    15  import Sidebar, {
    16    MenuItem,
    17    SidebarHeader,
    18    SidebarFooter,
    19    SidebarContent,
    20    SubMenu,
    21    Menu,
    22  } from '@webapp/ui/Sidebar';
    23  import { useAppSelector, useAppDispatch } from '@webapp/redux/hooks';
    24  import {
    25    selectSidebarCollapsed,
    26    collapseSidebar,
    27    uncollapseSidebar,
    28    recalculateSidebar,
    29  } from '@webapp/redux/reducers/ui';
    30  // import useColorMode from '@webapp/hooks/colorMode.hook';
    31  import { useLocation, NavLink } from 'react-router-dom';
    32  import {
    33    isAdhocUIEnabled,
    34    isAuthRequired,
    35    isExemplarsPageEnabled,
    36  } from '@webapp/util/features';
    37  import Icon from '@webapp/ui/Icon';
    38  import clsx from 'clsx';
    39  import { useWindowWidth } from '@react-hook/window-size';
    40  import {
    41    AdhocIcon,
    42    ExemplarsIcon,
    43    MergeExemplarsIcon,
    44  } from './SidebarCustomIcons';
    45  import styles from './Sidebar.module.css';
    46  import { PAGES } from '../pages/constants';
    47  import { mountURL } from '../services/base';
    48  
    49  function signOut() {
    50    // By visiting /logout we're clearing jwtCookie
    51    window.location.href = mountURL('/logout');
    52  }
    53  
    54  export function SidebarComponent() {
    55    const collapsed = useAppSelector(selectSidebarCollapsed);
    56    // const { changeColorMode, colorMode } = useColorMode();
    57    const dispatch = useAppDispatch();
    58  
    59    const { search, pathname } = useLocation();
    60    const windowWidth = useWindowWidth();
    61    const authEnabled = isAuthRequired;
    62  
    63    // the component doesn't seem to support setting up an active item
    64    // so we must set it up manually
    65    // https://github.com/azouaoui-med/react-pro-sidebar/issues/84
    66    const isRouteActive = (route: string) => {
    67      if (
    68        route === PAGES.CONTINOUS_SINGLE_VIEW ||
    69        route === PAGES.COMPARISON_VIEW ||
    70        route === PAGES.ADHOC_COMPARISON ||
    71        route === PAGES.TRACING_EXEMPLARS_SINGLE ||
    72        route === PAGES.TRACING_EXEMPLARS_MERGE
    73      ) {
    74        return pathname === route;
    75      }
    76  
    77      return pathname.startsWith(route);
    78    };
    79  
    80    const isSidebarVisible = useMemo(
    81      () =>
    82        (
    83          [
    84            PAGES.CONTINOUS_SINGLE_VIEW,
    85            PAGES.COMPARISON_VIEW,
    86            PAGES.ADHOC_COMPARISON,
    87            PAGES.COMPARISON_DIFF_VIEW,
    88            PAGES.SETTINGS,
    89            PAGES.SERVICE_DISCOVERY,
    90            PAGES.ADHOC_SINGLE,
    91            PAGES.ADHOC_COMPARISON,
    92            PAGES.ADHOC_COMPARISON_DIFF,
    93            PAGES.TAG_EXPLORER,
    94            PAGES.TRACING_EXEMPLARS_MERGE,
    95            PAGES.TRACING_EXEMPLARS_SINGLE,
    96          ] as string[]
    97        ).includes(pathname) || pathname.startsWith(PAGES.SETTINGS),
    98      [pathname]
    99    );
   100  
   101    React.useLayoutEffect(() => {
   102      dispatch(recalculateSidebar());
   103    }, [windowWidth]);
   104  
   105    // TODO
   106    // simplify this
   107    const isContinuousActive =
   108      isRouteActive(PAGES.CONTINOUS_SINGLE_VIEW) ||
   109      isRouteActive(PAGES.COMPARISON_VIEW) ||
   110      isRouteActive(PAGES.COMPARISON_DIFF_VIEW) ||
   111      isRouteActive(PAGES.TAG_EXPLORER);
   112    const isAdhocActive =
   113      isRouteActive(PAGES.ADHOC_SINGLE) ||
   114      isRouteActive(PAGES.ADHOC_COMPARISON) ||
   115      isRouteActive(PAGES.ADHOC_COMPARISON_DIFF);
   116    const isTracingActive =
   117      isRouteActive(PAGES.TRACING_EXEMPLARS_MERGE) ||
   118      isRouteActive(PAGES.TRACING_EXEMPLARS_SINGLE);
   119    const isSettingsActive = isRouteActive(PAGES.SETTINGS);
   120  
   121    const adhoc = (
   122      <SubMenu
   123        title="Adhoc Profiling"
   124        icon={<AdhocIcon />}
   125        active={isAdhocActive}
   126        defaultOpen={isAdhocActive}
   127        data-testid="sidebar-adhoc"
   128      >
   129        {collapsed && (
   130          <SidebarHeader className={styles.collapsedHeader}>
   131            Adhoc Profiling
   132          </SidebarHeader>
   133        )}
   134        <MenuItem
   135          data-testid="sidebar-adhoc-single"
   136          active={isRouteActive(PAGES.ADHOC_SINGLE)}
   137          icon={<Icon icon={faWindowMaximize} />}
   138        >
   139          Single View
   140          <NavLink to={{ pathname: PAGES.ADHOC_SINGLE, search }} exact />
   141        </MenuItem>
   142        <MenuItem
   143          data-testid="sidebar-adhoc-comparison"
   144          active={isRouteActive(PAGES.ADHOC_COMPARISON)}
   145          icon={<Icon icon={faColumns} />}
   146        >
   147          Comparison View
   148          <NavLink to={{ pathname: PAGES.ADHOC_COMPARISON, search }} exact />
   149        </MenuItem>
   150        <MenuItem
   151          data-testid="sidebar-adhoc-comparison-diff"
   152          active={isRouteActive(PAGES.ADHOC_COMPARISON_DIFF)}
   153          icon={<Icon icon={faChartBar} />}
   154        >
   155          Diff View
   156          <NavLink to={{ pathname: PAGES.ADHOC_COMPARISON_DIFF, search }} exact />
   157        </MenuItem>
   158      </SubMenu>
   159    );
   160  
   161    const toggleCollapse = () => {
   162      const action = collapsed ? uncollapseSidebar : collapseSidebar;
   163      dispatch(action());
   164    };
   165  
   166    return isSidebarVisible ? (
   167      <Sidebar collapsed={collapsed}>
   168        <SidebarHeader>
   169          <div className={styles.logo}>
   170            <div className="logo-main" />
   171            <span
   172              className={clsx(styles.logoText, {
   173                [styles.logoTextCollapsed]: collapsed,
   174              })}
   175            >
   176              Pyroscope
   177            </span>
   178          </div>
   179        </SidebarHeader>
   180        <SidebarContent>
   181          <Menu iconShape="square" popperArrow>
   182            <SubMenu
   183              title="Continuous Profiling"
   184              icon={<Icon icon={faSync} />}
   185              active={isContinuousActive}
   186              defaultOpen={isContinuousActive}
   187              data-testid="sidebar-continuous"
   188            >
   189              {collapsed && (
   190                <SidebarHeader className={styles.collapsedHeader}>
   191                  Continuous Profiling
   192                </SidebarHeader>
   193              )}
   194              <MenuItem
   195                data-testid="sidebar-explore-page"
   196                active={isRouteActive(PAGES.TAG_EXPLORER)}
   197                icon={<Icon icon={faSearch} />}
   198              >
   199                Tag explorer
   200                <NavLink to={{ pathname: PAGES.TAG_EXPLORER, search }} exact />
   201              </MenuItem>
   202              <MenuItem
   203                data-testid="sidebar-continuous-single"
   204                active={isRouteActive(PAGES.CONTINOUS_SINGLE_VIEW)}
   205                icon={<Icon icon={faWindowMaximize} />}
   206              >
   207                Single View
   208                <NavLink
   209                  activeClassName="active-route"
   210                  data-testid="sidebar-root"
   211                  to={{ pathname: PAGES.CONTINOUS_SINGLE_VIEW, search }}
   212                  exact
   213                />
   214              </MenuItem>
   215              <MenuItem
   216                data-testid="sidebar-continuous-comparison"
   217                active={isRouteActive(PAGES.COMPARISON_VIEW)}
   218                icon={<Icon icon={faColumns} />}
   219              >
   220                Comparison View
   221                <NavLink to={{ pathname: PAGES.COMPARISON_VIEW, search }} exact />
   222              </MenuItem>
   223              <MenuItem
   224                data-testid="sidebar-continuous-diff"
   225                active={isRouteActive(PAGES.COMPARISON_DIFF_VIEW)}
   226                icon={<Icon icon={faChartBar} />}
   227              >
   228                Diff View
   229                <NavLink
   230                  to={{ pathname: PAGES.COMPARISON_DIFF_VIEW, search }}
   231                  exact
   232                />
   233              </MenuItem>
   234            </SubMenu>
   235            {isAdhocUIEnabled && adhoc}
   236            {isExemplarsPageEnabled && (
   237              <SubMenu
   238                title="Tracing Exemplars"
   239                icon={<ExemplarsIcon />}
   240                active={isTracingActive}
   241                defaultOpen={isTracingActive}
   242              >
   243                {collapsed && (
   244                  <SidebarHeader className={styles.collapsedHeader}>
   245                    Tracing Exemplars
   246                  </SidebarHeader>
   247                )}
   248                <MenuItem
   249                  active={isRouteActive(PAGES.TRACING_EXEMPLARS_SINGLE)}
   250                  icon={<ExemplarsIcon />}
   251                >
   252                  Exemplars
   253                  <NavLink
   254                    activeClassName="active-route"
   255                    to={{ pathname: PAGES.TRACING_EXEMPLARS_SINGLE, search }}
   256                    exact
   257                  />
   258                </MenuItem>
   259                <MenuItem
   260                  active={isRouteActive(PAGES.TRACING_EXEMPLARS_MERGE)}
   261                  icon={<MergeExemplarsIcon />}
   262                >
   263                  Merge Exemplars
   264                  <NavLink
   265                    activeClassName="active-route"
   266                    to={{ pathname: PAGES.TRACING_EXEMPLARS_MERGE, search }}
   267                    exact
   268                  />
   269                </MenuItem>
   270              </SubMenu>
   271            )}
   272          </Menu>
   273        </SidebarContent>
   274        <SidebarFooter>
   275          <Menu iconShape="square">
   276            {authEnabled && (
   277              <MenuItem
   278                data-testid="sidebar-settings"
   279                active={isSettingsActive}
   280                icon={<Icon icon={faCog} />}
   281              >
   282                Settings
   283                <NavLink to={{ pathname: PAGES.SETTINGS, search }} exact />
   284              </MenuItem>
   285            )}
   286            <MenuItem icon={<Icon icon={faInfoCircle} />}>
   287              Scrape Targets
   288              <NavLink to={{ pathname: PAGES.SERVICE_DISCOVERY, search }} exact />
   289            </MenuItem>
   290            <MenuItem icon={<Icon icon={faFileAlt} />}>
   291              <a
   292                rel="noreferrer"
   293                target="_blank"
   294                href="https://pyroscope.io/docs"
   295              >
   296                Documentation
   297              </a>
   298            </MenuItem>
   299            <MenuItem icon={<Icon icon={faSlack} />}>
   300              <a
   301                rel="noreferrer"
   302                target="_blank"
   303                href="https://pyroscope.io/slack"
   304              >
   305                Slack
   306              </a>
   307            </MenuItem>
   308            <MenuItem icon={<Icon icon={faGithub} />}>
   309              <a
   310                rel="noreferrer"
   311                target="_blank"
   312                href="https://github.com/pyroscope-io/pyroscope"
   313              >
   314                Github
   315              </a>
   316            </MenuItem>
   317            {isAuthRequired && (
   318              <MenuItem
   319                onClick={() => signOut()}
   320                icon={<Icon icon={faSignOutAlt} />}
   321              >
   322                Sign out
   323              </MenuItem>
   324            )}
   325            <MenuItem
   326              data-testid="collapse-sidebar"
   327              className={`${styles.collapseIcon} ${
   328                collapsed ? styles.collapsedIconCollapsed : ''
   329              }`}
   330              onClick={toggleCollapse}
   331              icon={<Icon icon={faChevronLeft} />}
   332            >
   333              Collapse Sidebar
   334            </MenuItem>
   335          </Menu>
   336        </SidebarFooter>
   337        {/* <select
   338          value={colorMode}
   339          onChange={(e: ChangeEvent<HTMLSelectElement>) =>
   340            changeColorMode(e.target?.value as 'light' | 'dark')
   341          }
   342        >
   343          <option value="dark">dark</option>
   344          <option value="light">light</option>
   345        </select> */}
   346      </Sidebar>
   347    ) : null;
   348  }
   349  
   350  export default SidebarComponent;