123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- import {Fragment} from 'react';
- import styled from '@emotion/styled';
- import {logout} from 'sentry/actionCreators/account';
- import {Client} from 'sentry/api';
- import DemoModeGate from 'sentry/components/acl/demoModeGate';
- import Avatar from 'sentry/components/avatar';
- import DropdownMenu from 'sentry/components/dropdownMenu';
- import Hook from 'sentry/components/hook';
- import IdBadge from 'sentry/components/idBadge';
- import Link from 'sentry/components/links/link';
- import SidebarDropdownMenu from 'sentry/components/sidebar/sidebarDropdownMenu.styled';
- import SidebarMenuItem, {menuItemStyles} from 'sentry/components/sidebar/sidebarMenuItem';
- import SidebarOrgSummary from 'sentry/components/sidebar/sidebarOrgSummary';
- import TextOverflow from 'sentry/components/textOverflow';
- import {IconChevron, IconSentry} from 'sentry/icons';
- import {t} from 'sentry/locale';
- import ConfigStore from 'sentry/stores/configStore';
- import space from 'sentry/styles/space';
- import {Config, Organization, Project, User} from 'sentry/types';
- import withApi from 'sentry/utils/withApi';
- import withProjects from 'sentry/utils/withProjects';
- import SidebarMenuItemLink from '../sidebarMenuItemLink';
- import {CommonSidebarProps} from '../types';
- import Divider from './divider.styled';
- import SwitchOrganization from './switchOrganization';
- // TODO: make org and user optional props
- type Props = Pick<CommonSidebarProps, 'orientation' | 'collapsed'> & {
- api: Client;
- config: Config;
- projects: Project[];
- user: User;
- /**
- * Set to true to hide links within the organization
- */
- hideOrgLinks?: boolean;
- org?: Organization;
- };
- const SidebarDropdown = ({
- api,
- org,
- projects,
- orientation,
- collapsed,
- config,
- user,
- hideOrgLinks,
- }: Props) => {
- const handleLogout = async () => {
- await logout(api);
- window.location.assign('/auth/login/');
- };
- const hasOrganization = !!org;
- const hasUser = !!user;
- // It's possible we do not have an org in context (e.g. RouteNotFound)
- // Otherwise, we should have the full org
- const hasOrgRead = org?.access?.includes('org:read');
- const hasMemberRead = org?.access?.includes('member:read');
- const hasTeamRead = org?.access?.includes('team:read');
- const canCreateOrg = ConfigStore.get('features').has('organizations:create');
- // Avatar to use: Organization --> user --> Sentry
- const avatar =
- hasOrganization || hasUser ? (
- <StyledAvatar
- collapsed={collapsed}
- organization={org}
- user={!org ? user : undefined}
- size={32}
- round={false}
- />
- ) : (
- <SentryLink to="/">
- <IconSentry size="32px" />
- </SentryLink>
- );
- return (
- <DropdownMenu>
- {({isOpen, getRootProps, getActorProps, getMenuProps}) => (
- <SidebarDropdownRoot {...getRootProps()}>
- <SidebarDropdownActor
- type="button"
- data-test-id="sidebar-dropdown"
- {...getActorProps({})}
- >
- {avatar}
- {!collapsed && orientation !== 'top' && (
- <OrgAndUserWrapper>
- <OrgOrUserName>
- {hasOrganization ? org.name : user.name}{' '}
- <StyledIconChevron color="white" size="xs" direction="down" />
- </OrgOrUserName>
- <UserNameOrEmail>
- {hasOrganization ? user.name : user.email}
- </UserNameOrEmail>
- </OrgAndUserWrapper>
- )}
- </SidebarDropdownActor>
- {isOpen && (
- <OrgAndUserMenu {...getMenuProps({})}>
- {hasOrganization && (
- <Fragment>
- <SidebarOrgSummary organization={org} projectCount={projects.length} />
- {!hideOrgLinks && (
- <Fragment>
- {hasOrgRead && (
- <SidebarMenuItem to={`/settings/${org.slug}/`}>
- {t('Organization settings')}
- </SidebarMenuItem>
- )}
- {hasMemberRead && (
- <SidebarMenuItem to={`/settings/${org.slug}/members/`}>
- {t('Members')}
- </SidebarMenuItem>
- )}
- {hasTeamRead && (
- <SidebarMenuItem to={`/settings/${org.slug}/teams/`}>
- {t('Teams')}
- </SidebarMenuItem>
- )}
- <Hook
- name="sidebar:organization-dropdown-menu"
- organization={org}
- />
- </Fragment>
- )}
- {!config.singleOrganization && (
- <DemoModeGate>
- <SidebarMenuItem>
- <SwitchOrganization canCreateOrganization={canCreateOrg} />
- </SidebarMenuItem>
- </DemoModeGate>
- )}
- </Fragment>
- )}
- <DemoModeGate>
- {hasOrganization && user && <Divider />}
- {!!user && (
- <Fragment>
- <UserSummary to="/settings/account/details/">
- <UserBadgeNoOverflow user={user} avatarSize={32} />
- </UserSummary>
- <div>
- <SidebarMenuItem to="/settings/account/">
- {t('User settings')}
- </SidebarMenuItem>
- <SidebarMenuItem to="/settings/account/api/">
- {t('API keys')}
- </SidebarMenuItem>
- {hasOrganization && (
- <Hook
- name="sidebar:organization-dropdown-menu-bottom"
- organization={org}
- />
- )}
- {user.isSuperuser && (
- <SidebarMenuItem to="/manage/">{t('Admin')}</SidebarMenuItem>
- )}
- <SidebarMenuItem
- data-test-id="sidebar-signout"
- onClick={handleLogout}
- >
- {t('Sign out')}
- </SidebarMenuItem>
- </div>
- </Fragment>
- )}
- </DemoModeGate>
- </OrgAndUserMenu>
- )}
- </SidebarDropdownRoot>
- )}
- </DropdownMenu>
- );
- };
- export default withApi(withProjects(SidebarDropdown));
- const SentryLink = styled(Link)`
- color: ${p => p.theme.white};
- &:hover {
- color: ${p => p.theme.white};
- }
- `;
- const UserSummary = styled(Link)<
- Omit<React.ComponentProps<typeof SidebarMenuItemLink>, 'children'>
- >`
- ${p => menuItemStyles(p)}
- padding: 10px 15px;
- `;
- const UserBadgeNoOverflow = styled(IdBadge)`
- overflow: hidden;
- `;
- const SidebarDropdownRoot = styled('div')`
- position: relative;
- `;
- // So that long org names and user names do not overflow
- const OrgAndUserWrapper = styled('div')`
- overflow-x: hidden;
- text-align: left;
- `;
- const OrgOrUserName = styled(TextOverflow)`
- font-size: ${p => p.theme.fontSizeLarge};
- line-height: 1.2;
- font-weight: bold;
- color: ${p => p.theme.white};
- text-shadow: 0 0 6px rgba(255, 255, 255, 0);
- transition: 0.15s text-shadow linear;
- `;
- const UserNameOrEmail = styled(TextOverflow)`
- font-size: ${p => p.theme.fontSizeMedium};
- line-height: 16px;
- transition: 0.15s color linear;
- `;
- const SidebarDropdownActor = styled('button')`
- display: flex;
- align-items: flex-start;
- cursor: pointer;
- border: none;
- padding: 0;
- background: none;
- width: 100%;
- &:hover {
- ${OrgOrUserName} {
- text-shadow: 0 0 6px rgba(255, 255, 255, 0.1);
- }
- ${UserNameOrEmail} {
- color: ${p => p.theme.white};
- }
- }
- `;
- const StyledAvatar = styled(Avatar)<{collapsed: boolean}>`
- margin: ${space(0.25)} 0;
- margin-right: ${p => (p.collapsed ? '0' : space(1.5))};
- box-shadow: 0 2px 0 rgba(0, 0, 0, 0.08);
- border-radius: 6px; /* Fixes background bleeding on corners */
- `;
- const OrgAndUserMenu = styled('div')`
- ${SidebarDropdownMenu};
- top: 42px;
- min-width: 180px;
- z-index: ${p => p.theme.zIndex.orgAndUserMenu};
- `;
- const StyledIconChevron = styled(IconChevron)`
- margin-left: ${space(0.25)};
- `;
|