packageData.tsx 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. import {useRef} from 'react';
  2. import styled from '@emotion/styled';
  3. import ClippedBox from 'sentry/components/clippedBox';
  4. import ErrorBoundary from 'sentry/components/errorBoundary';
  5. import {useIssueDetailsColumnCount} from 'sentry/components/events/eventTags/util';
  6. import KeyValueList from 'sentry/components/events/interfaces/keyValueList';
  7. import KeyValueData from 'sentry/components/keyValueData';
  8. import {t} from 'sentry/locale';
  9. import {space} from 'sentry/styles/space';
  10. import type {Event} from 'sentry/types/event';
  11. import {isEmptyObject} from 'sentry/utils/object/isEmptyObject';
  12. import {SectionKey} from 'sentry/views/issueDetails/streamline/context';
  13. import {InterimSection} from 'sentry/views/issueDetails/streamline/interimSection';
  14. import {useHasStreamlinedUI} from 'sentry/views/issueDetails/utils';
  15. type Props = {
  16. event: Event;
  17. };
  18. export function EventPackageData({event}: Props) {
  19. const containerRef = useRef<HTMLDivElement>(null);
  20. const hasStreamlinedUI = useHasStreamlinedUI();
  21. const columnCount = useIssueDetailsColumnCount(containerRef) + 1;
  22. let longKeys: boolean, title: string;
  23. const packages = Object.entries(event.packages || {}).map(([key, value]) => ({
  24. key,
  25. value,
  26. subject: key,
  27. meta: event._meta?.packages?.[key]?.[''],
  28. }));
  29. switch (event.platform) {
  30. case 'csharp':
  31. longKeys = true;
  32. title = t('Assemblies');
  33. break;
  34. case 'java':
  35. longKeys = true;
  36. title = t('Dependencies');
  37. break;
  38. default:
  39. longKeys = false;
  40. title = t('Packages');
  41. }
  42. if (isEmptyObject(event.packages)) {
  43. return null;
  44. }
  45. const componentItems = packages.map((item, i) => (
  46. <KeyValueData.Content
  47. key={`content-card-${item.key}-${i}`}
  48. item={item}
  49. meta={item.meta}
  50. />
  51. ));
  52. const columns: React.ReactNode[] = [];
  53. const columnSize = Math.ceil(componentItems.length / columnCount);
  54. for (let i = 0; i < componentItems.length; i += columnSize) {
  55. columns.push(
  56. <Column key={`highlight-column-${i}`}>
  57. {componentItems.slice(i, i + columnSize)}
  58. </Column>
  59. );
  60. }
  61. return (
  62. <InterimSection
  63. title={title}
  64. type={SectionKey.PACKAGES}
  65. ref={containerRef}
  66. initialCollapse
  67. >
  68. {hasStreamlinedUI ? (
  69. <ColumnsContainer columnCount={columnCount}>{columns}</ColumnsContainer>
  70. ) : (
  71. <ClippedBox>
  72. <ErrorBoundary mini>
  73. <KeyValueList data={packages} longKeys={longKeys} />
  74. </ErrorBoundary>
  75. </ClippedBox>
  76. )}
  77. </InterimSection>
  78. );
  79. }
  80. export const ColumnsContainer = styled('div')<{columnCount: number}>`
  81. display: grid;
  82. align-items: start;
  83. grid-template-columns: repeat(${p => p.columnCount}, 1fr);
  84. `;
  85. export const Column = styled('div')`
  86. display: grid;
  87. grid-template-columns: fit-content(65%) 1fr;
  88. font-size: ${p => p.theme.fontSizeSmall};
  89. &:first-child {
  90. margin-left: -${space(1)};
  91. }
  92. &:not(:first-child) {
  93. border-left: 1px solid ${p => p.theme.innerBorder};
  94. padding-left: ${space(2)};
  95. margin-left: -1px;
  96. }
  97. &:not(:last-child) {
  98. border-right: 1px solid ${p => p.theme.innerBorder};
  99. padding-right: ${space(2)};
  100. }
  101. `;