editAccessSelector.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. import {useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import isEqual from 'lodash/isEqual';
  4. import AvatarList from 'sentry/components/avatar/avatarList';
  5. import Badge from 'sentry/components/badge/badge';
  6. import Checkbox from 'sentry/components/checkbox';
  7. import {CompactSelect} from 'sentry/components/compactSelect';
  8. import {CheckWrap} from 'sentry/components/compactSelect/styles';
  9. import UserBadge from 'sentry/components/idBadge/userBadge';
  10. import {InnerWrap, LeadingItems} from 'sentry/components/menuListItem';
  11. import {Tooltip} from 'sentry/components/tooltip';
  12. import {t, tct} from 'sentry/locale';
  13. import type {User} from 'sentry/types/user';
  14. import {defined} from 'sentry/utils';
  15. import {useTeamsById} from 'sentry/utils/useTeamsById';
  16. import {useUser} from 'sentry/utils/useUser';
  17. import type {DashboardDetails, DashboardPermissions} from 'sentry/views/dashboards/types';
  18. interface EditAccessSelectorProps {
  19. dashboard: DashboardDetails;
  20. onChangeEditAccess?: (newDashboardPermissions: DashboardPermissions) => void;
  21. }
  22. /**
  23. * Dropdown multiselect button to enable selective Dashboard editing access to
  24. * specific users and teams
  25. */
  26. function EditAccessSelector({dashboard, onChangeEditAccess}: EditAccessSelectorProps) {
  27. const currentUser: User = useUser();
  28. const dashboardCreator: User | undefined = dashboard.createdBy;
  29. const {teams} = useTeamsById();
  30. const teamIds: string[] = Object.values(teams).map(team => team.id);
  31. const [selectedOptions, setselectedOptions] = useState<string[]>(
  32. dashboard.permissions?.isCreatorOnlyEditable
  33. ? ['_creator']
  34. : ['_everyone', '_creator', ...teamIds]
  35. );
  36. // Dashboard creator option in the dropdown
  37. const makeCreatorOption = () => ({
  38. value: '_creator',
  39. label: (
  40. <UserBadge
  41. avatarSize={18}
  42. user={dashboardCreator}
  43. displayName={
  44. <StyledDisplayName>
  45. {dashboardCreator?.id === currentUser.id
  46. ? tct('You ([email])', {email: currentUser.email})
  47. : dashboardCreator?.email ||
  48. tct('You ([email])', {email: currentUser.email})}
  49. </StyledDisplayName>
  50. }
  51. displayEmail={t('Creator')}
  52. />
  53. ),
  54. textValue: `creator_${currentUser.email}`,
  55. disabled: dashboardCreator?.id !== currentUser.id,
  56. // Creator option is always disabled
  57. leadingItems: <Checkbox size="sm" checked disabled />,
  58. hideCheck: true,
  59. });
  60. // Single team option in the dropdown [WIP]
  61. // const makeTeamOption = (team: Team) => ({
  62. // value: team.id,
  63. // label: `#${team.slug}`,
  64. // leadingItems: <TeamAvatar team={team} size={18} />,
  65. // });
  66. // Avatars/Badges in the Edit Selector Button
  67. const triggerAvatars =
  68. selectedOptions.includes('_everyone') || !dashboardCreator ? (
  69. <StyledBadge key="_all" text={'All'} />
  70. ) : (
  71. <StyledAvatarList key="avatar-list" users={[dashboardCreator]} avatarSize={25} />
  72. );
  73. const dropdownOptions = [
  74. makeCreatorOption(),
  75. {
  76. value: '_everyone_section',
  77. options: [
  78. {
  79. value: '_everyone',
  80. label: t('Everyone'),
  81. disabled: dashboardCreator?.id !== currentUser.id,
  82. },
  83. ],
  84. },
  85. // [WIP: Selective edit access to teams]
  86. // {
  87. // value: '_teams',
  88. // label: t('Teams'),
  89. // options: teams.map(makeTeamOption),
  90. // showToggleAllButton: true,
  91. // disabled: true,
  92. // },
  93. ];
  94. // Handles state change when dropdown options are selected
  95. const onSelectOptions = newSelectedOptions => {
  96. const newSelectedValues = newSelectedOptions.map(
  97. (option: {value: string}) => option.value
  98. );
  99. if (newSelectedValues.includes('_everyone')) {
  100. setselectedOptions(['_everyone', '_creator', ...teamIds]);
  101. } else if (!newSelectedValues.includes('_everyone')) {
  102. setselectedOptions(['_creator']);
  103. }
  104. };
  105. // Creates or modifies permissions object based on the options selected
  106. function getDashboardPermissions() {
  107. return {
  108. isCreatorOnlyEditable: !selectedOptions.includes('_everyone'),
  109. };
  110. }
  111. const dropdownMenu = (
  112. <StyledCompactSelect
  113. size="sm"
  114. onChange={newSelectedOptions => {
  115. onSelectOptions(newSelectedOptions);
  116. }}
  117. onClose={() => {
  118. const isDefaultState =
  119. !defined(dashboard.permissions) && selectedOptions.includes('_everyone');
  120. const newDashboardPermissions = getDashboardPermissions();
  121. if (!isDefaultState && !isEqual(newDashboardPermissions, dashboard.permissions)) {
  122. onChangeEditAccess?.(newDashboardPermissions);
  123. }
  124. }}
  125. multiple
  126. searchable
  127. options={dropdownOptions}
  128. value={selectedOptions}
  129. triggerLabel={[t('Edit Access:'), triggerAvatars]}
  130. searchPlaceholder={t('Search Teams')}
  131. />
  132. );
  133. return dashboardCreator?.id !== currentUser.id ? (
  134. <Tooltip title={t('Only Dashboard Creator may change Edit Access')}>
  135. {dropdownMenu}
  136. </Tooltip>
  137. ) : (
  138. dropdownMenu
  139. );
  140. }
  141. export default EditAccessSelector;
  142. const StyledCompactSelect = styled(CompactSelect)`
  143. ${InnerWrap} {
  144. align-items: center;
  145. }
  146. ${LeadingItems} {
  147. margin-top: 0;
  148. }
  149. ${CheckWrap} {
  150. padding-bottom: 0;
  151. }
  152. `;
  153. const StyledDisplayName = styled('div')`
  154. font-weight: normal;
  155. `;
  156. const StyledAvatarList = styled(AvatarList)`
  157. margin-left: 10px;
  158. `;
  159. const StyledBadge = styled(Badge)`
  160. color: ${p => p.theme.white};
  161. background: ${p => p.theme.purple300};
  162. margin-right: 3px;
  163. `;