index.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import styled from '@emotion/styled';
  2. import {t} from 'sentry/locale';
  3. import {space} from 'sentry/styles/space';
  4. import {Event} from 'sentry/types/event';
  5. import {objectIsEmpty} from 'sentry/utils';
  6. import {ContextSummaryDevice} from './contextSummaryDevice';
  7. import {ContextSummaryGeneric} from './contextSummaryGeneric';
  8. import {ContextSummaryGPU} from './contextSummaryGPU';
  9. import {ContextSummaryOS} from './contextSummaryOS';
  10. import {ContextSummaryUser} from './contextSummaryUser';
  11. import {Context} from './types';
  12. import {makeContextFilter} from './utils';
  13. const MIN_CONTEXTS = 3;
  14. const MAX_CONTEXTS = 4;
  15. const KNOWN_CONTEXTS: Context[] = [
  16. {
  17. keys: ['user'],
  18. Component: ContextSummaryUser,
  19. },
  20. {
  21. keys: ['browser'],
  22. Component: ContextSummaryGeneric,
  23. unknownTitle: t('Unknown Browser'),
  24. },
  25. {
  26. keys: ['runtime'],
  27. Component: ContextSummaryGeneric,
  28. unknownTitle: t('Unknown Runtime'),
  29. omitUnknownVersion: true,
  30. },
  31. {
  32. keys: ['client_os', 'os'],
  33. Component: ContextSummaryOS,
  34. },
  35. {
  36. keys: ['device'],
  37. Component: ContextSummaryDevice,
  38. },
  39. {
  40. keys: ['gpu'],
  41. Component: ContextSummaryGPU,
  42. },
  43. ];
  44. type Props = {
  45. event: Event;
  46. };
  47. function ContextSummary({event}: Props) {
  48. const filteredContexts = KNOWN_CONTEXTS.filter(makeContextFilter(event));
  49. // XXX: We want to have *at least* MIN_CONTEXTS, so we first find all the
  50. // contexts that have data, if that doesn't complete our MIN_CONTEXTS, we add
  51. // in contextrs that have no data.
  52. const itemsWithData = filteredContexts
  53. .map(context => {
  54. // Find data for any of the keys
  55. const [key, data] = context.keys
  56. .map(k => [k, event.contexts[k] ?? event[k]] as const)
  57. .find(([_k, d]) => !objectIsEmpty(d)) ?? [null, null];
  58. // No context value? Skip it
  59. if (!key) {
  60. return null;
  61. }
  62. return {context, data, key};
  63. })
  64. .filter((items): items is NonNullable<typeof items> => !!items)
  65. .slice(0, MAX_CONTEXTS);
  66. // Don't render anything if we don't have any context
  67. if (itemsWithData.length === 0) {
  68. return null;
  69. }
  70. // Don't render anything if we only have the user context
  71. if (itemsWithData.length === 1) {
  72. return null;
  73. }
  74. // How many contexts without data do we need to add?
  75. const remaining = Math.max(0, MIN_CONTEXTS - itemsWithData.length);
  76. const itemsWithoutData = filteredContexts
  77. // Ignore context keys we already have data for
  78. .filter(context => !itemsWithData.some(({key}) => context.keys.includes(key)))
  79. .map(context => ({
  80. context,
  81. data: {} as any,
  82. key: context.keys[0],
  83. }))
  84. .slice(0, remaining);
  85. const contexts = [...itemsWithData, ...itemsWithoutData].map(({context, key, data}) => {
  86. const {Component, unknownTitle, omitUnknownVersion} = context;
  87. const props = {
  88. data,
  89. unknownTitle,
  90. omitUnknownVersion,
  91. meta: event._meta?.contexts?.[key] ?? {},
  92. };
  93. return <Component key={key} {...props} />;
  94. });
  95. return <Wrapper>{contexts}</Wrapper>;
  96. }
  97. export default ContextSummary;
  98. const Wrapper = styled('div')`
  99. margin-bottom: ${space(2)};
  100. @media (min-width: ${p => p.theme.breakpoints.small}) {
  101. display: flex;
  102. gap: ${space(4)};
  103. }
  104. `;