broadcastSdkUpdates.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import styled from '@emotion/styled';
  2. import groupBy from 'lodash/groupBy';
  3. import partition from 'lodash/partition';
  4. import ProjectBadge from 'sentry/components/idBadge/projectBadge';
  5. import Tag from 'sentry/components/tag';
  6. import {IconWarning} from 'sentry/icons/iconWarning';
  7. import {t, tct, tn} from 'sentry/locale';
  8. import space from 'sentry/styles/space';
  9. import {
  10. Organization,
  11. Project,
  12. ProjectSdkUpdates,
  13. SDKUpdatesSuggestion,
  14. } from 'sentry/types';
  15. import getSdkUpdateSuggestion from 'sentry/utils/getSdkUpdateSuggestion';
  16. import withOrganization from 'sentry/utils/withOrganization';
  17. import withProjects from 'sentry/utils/withProjects';
  18. import withSdkUpdates from 'sentry/utils/withSdkUpdates';
  19. import Alert from '../alert';
  20. import Collapsible from '../collapsible';
  21. import List from '../list';
  22. import ListItem from '../list/listItem';
  23. import SidebarPanelItem from './sidebarPanelItem';
  24. type Props = {
  25. organization: Organization;
  26. projects: Project[];
  27. sdkUpdates?: ProjectSdkUpdates[] | null;
  28. };
  29. const flattenSuggestions = (list: ProjectSdkUpdates[]) =>
  30. list.reduce<SDKUpdatesSuggestion[]>(
  31. (suggestions, sdk) => [...suggestions, ...sdk.suggestions],
  32. []
  33. );
  34. function BroadcastSdkUpdates({projects, sdkUpdates, organization}: Props) {
  35. if (!sdkUpdates) {
  36. return null;
  37. }
  38. // Are there any updates?
  39. if (!flattenSuggestions(sdkUpdates).length) {
  40. return null;
  41. }
  42. function renderUpdates(projectSdkUpdates: ProjectSdkUpdates[]) {
  43. // Group SDK updates by project
  44. const items = Object.entries(groupBy(projectSdkUpdates, 'projectId'));
  45. return items
  46. .map(([projectId, updates]) => {
  47. const project = projects.find(p => p.id === projectId);
  48. if (!project) {
  49. return null;
  50. }
  51. // Updates should only be shown to users who are project members or users who have open membership or org write permission
  52. const hasPermissionToSeeUpdates =
  53. project.isMember ||
  54. organization.features.includes('open-membership') ||
  55. organization.access.includes('org:write');
  56. if (!hasPermissionToSeeUpdates) {
  57. return null;
  58. }
  59. return updates.map(({sdkName, sdkVersion, suggestions}) => {
  60. const isDeprecated = suggestions.some(
  61. suggestion => suggestion.type === 'changeSdk'
  62. );
  63. return (
  64. <div key={sdkName}>
  65. <Header>
  66. <SdkProjectBadge project={project} />
  67. {isDeprecated && <Tag type="warning">{t('Deprecated')}</Tag>}
  68. </Header>
  69. <SdkOutdatedVersion>
  70. {tct('This project is on [current-version]', {
  71. ['current-version']: (
  72. <OutdatedVersion>{`${sdkName}@v${sdkVersion}`}</OutdatedVersion>
  73. ),
  74. })}
  75. </SdkOutdatedVersion>
  76. <StyledList>
  77. {suggestions.map((suggestion, i) => (
  78. <ListItem key={i}>
  79. {getSdkUpdateSuggestion({
  80. sdk: {
  81. name: sdkName,
  82. version: sdkVersion,
  83. },
  84. suggestion,
  85. shortStyle: true,
  86. capitalized: true,
  87. })}
  88. </ListItem>
  89. ))}
  90. </StyledList>
  91. </div>
  92. );
  93. });
  94. })
  95. .filter(item => !!item);
  96. }
  97. const [deprecatedRavenSdkUpdates, otherSdkUpdates] = partition(
  98. sdkUpdates,
  99. sdkUpdate =>
  100. sdkUpdate.sdkName.includes('raven') &&
  101. sdkUpdate.suggestions.some(suggestion => suggestion.type === 'changeSdk')
  102. );
  103. return (
  104. <SidebarPanelItem
  105. hasSeen
  106. title={t('Update your SDKs')}
  107. message={t(
  108. 'We recommend updating the following SDKs to make sure you’re getting all the data you need.'
  109. )}
  110. >
  111. {!!deprecatedRavenSdkUpdates.length && (
  112. <StyledAlert type="warning" icon={<IconWarning />}>
  113. {tct(
  114. `[first-sentence]. Any SDK that has the package name ‘raven’ may be missing data. Migrate to the latest SDK version.`,
  115. {
  116. ['first-sentence']: tn(
  117. 'You have %s project using a deprecated version of the Sentry client',
  118. 'You have %s projects using a deprecated version of the Sentry client',
  119. deprecatedRavenSdkUpdates.length
  120. ),
  121. }
  122. )}
  123. </StyledAlert>
  124. )}
  125. <UpdatesList>
  126. <Collapsible>
  127. {renderUpdates(deprecatedRavenSdkUpdates)}
  128. {renderUpdates(otherSdkUpdates)}
  129. </Collapsible>
  130. </UpdatesList>
  131. </SidebarPanelItem>
  132. );
  133. }
  134. export default withSdkUpdates(withProjects(withOrganization(BroadcastSdkUpdates)));
  135. const UpdatesList = styled('div')`
  136. margin-top: ${space(3)};
  137. display: grid;
  138. grid-auto-flow: row;
  139. gap: ${space(3)};
  140. `;
  141. const Header = styled('div')`
  142. display: grid;
  143. grid-template-columns: 1fr auto;
  144. gap: ${space(0.5)};
  145. margin-bottom: ${space(0.25)};
  146. align-items: center;
  147. `;
  148. const SdkOutdatedVersion = styled('div')`
  149. /* 24px + 8px to be aligned with the SdkProjectBadge data */
  150. padding-left: calc(24px + ${space(1)});
  151. `;
  152. const OutdatedVersion = styled('span')`
  153. color: ${p => p.theme.gray400};
  154. `;
  155. const SdkProjectBadge = styled(ProjectBadge)`
  156. font-size: ${p => p.theme.fontSizeExtraLarge};
  157. line-height: 1;
  158. `;
  159. const StyledAlert = styled(Alert)`
  160. margin-top: ${space(2)};
  161. `;
  162. const StyledList = styled(List)`
  163. /* 24px + 8px to be aligned with the project name
  164. * displayed by the SdkProjectBadge component */
  165. padding-left: calc(24px + ${space(1)});
  166. `;