organizationCrumb.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. import styled from '@emotion/styled';
  2. import sortBy from 'lodash/sortBy';
  3. import IdBadge from 'sentry/components/idBadge';
  4. import OrganizationsStore from 'sentry/stores/organizationsStore';
  5. import {useLegacyStore} from 'sentry/stores/useLegacyStore';
  6. import type {RouteComponentProps} from 'sentry/types/legacyReactRouter';
  7. import type {Organization} from 'sentry/types/organization';
  8. import recreateRoute from 'sentry/utils/recreateRoute';
  9. import {resolveRoute} from 'sentry/utils/resolveRoute';
  10. import {useNavigate} from 'sentry/utils/useNavigate';
  11. import useOrganization from 'sentry/utils/useOrganization';
  12. import BreadcrumbDropdown from './breadcrumbDropdown';
  13. import findFirstRouteWithoutRouteParam from './findFirstRouteWithoutRouteParam';
  14. import MenuItem from './menuItem';
  15. import {CrumbLink} from '.';
  16. type Props = RouteComponentProps<{projectId?: string}, {}>;
  17. function OrganizationCrumb({params, routes, route, ...props}: Props) {
  18. const navigate = useNavigate();
  19. const {organizations} = useLegacyStore(OrganizationsStore);
  20. const organization = useOrganization();
  21. const handleSelect = (item: {value: Organization}) => {
  22. // If we are currently in a project context, and we're attempting to switch organizations,
  23. // then we need to default to index route (e.g. `route`)
  24. //
  25. // Otherwise, find the last route without a router param
  26. // e.g. if you are on API details, we want the API listing
  27. // This fails if our route tree is not nested
  28. const hasProjectParam = !!params.projectId;
  29. let destinationRoute = hasProjectParam
  30. ? route
  31. : findFirstRouteWithoutRouteParam(routes.slice(routes.indexOf(route)));
  32. // It's possible there is no route without route params (e.g. organization settings index),
  33. // in which case, we can use the org settings index route (e.g. `route`)
  34. if (!hasProjectParam && typeof destinationRoute === 'undefined') {
  35. destinationRoute = route;
  36. }
  37. if (destinationRoute === undefined) {
  38. return;
  39. }
  40. const itemOrg = item.value;
  41. const path = recreateRoute(destinationRoute, {
  42. routes,
  43. params: {...params, orgId: itemOrg.slug},
  44. });
  45. const resolvedUrl = resolveRoute(path, organization, itemOrg);
  46. // If we have a shift in domains, we can't use history
  47. if (resolvedUrl.startsWith('http')) {
  48. window.location.assign(resolvedUrl);
  49. } else {
  50. navigate(resolvedUrl);
  51. }
  52. };
  53. if (!organization) {
  54. return null;
  55. }
  56. const hasMenu = organizations.length > 1;
  57. const orgSettings = `/settings/${organization.slug}/`;
  58. return (
  59. <BreadcrumbDropdown
  60. name={
  61. <CrumbLink to={orgSettings}>
  62. <BadgeWrapper>
  63. <IdBadge avatarSize={18} organization={organization} />
  64. </BadgeWrapper>
  65. </CrumbLink>
  66. }
  67. onSelect={handleSelect}
  68. hasMenu={hasMenu}
  69. route={route}
  70. items={sortBy(organizations, ['name']).map((org, index) => ({
  71. index,
  72. value: org,
  73. label: (
  74. <MenuItem>
  75. <IdBadge organization={org} />
  76. </MenuItem>
  77. ),
  78. }))}
  79. {...props}
  80. />
  81. );
  82. }
  83. const BadgeWrapper = styled('div')`
  84. display: flex;
  85. align-items: center;
  86. `;
  87. export {OrganizationCrumb};