broadcastSdkUpdates.tsx 5.7 KB

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