contextCard.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. import styled from '@emotion/styled';
  2. import startCase from 'lodash/startCase';
  3. import ErrorBoundary from 'sentry/components/errorBoundary';
  4. import type {ContextValue} from 'sentry/components/events/contexts';
  5. import {
  6. getContextIcon,
  7. getContextMeta,
  8. getContextTitle,
  9. getContextType,
  10. getFormattedContextData,
  11. } from 'sentry/components/events/contexts/utils';
  12. import KeyValueData, {
  13. type KeyValueDataContentProps,
  14. } from 'sentry/components/keyValueData';
  15. import type {Event} from 'sentry/types/event';
  16. import type {Group, KeyValueListDataItem} from 'sentry/types/group';
  17. import type {Project} from 'sentry/types/project';
  18. import {isEmptyObject} from 'sentry/utils/object/isEmptyObject';
  19. import {useLocation} from 'sentry/utils/useLocation';
  20. import useOrganization from 'sentry/utils/useOrganization';
  21. interface ContextCardProps {
  22. alias: string;
  23. event: Event;
  24. type: string;
  25. group?: Group;
  26. project?: Project;
  27. value?: ContextValue;
  28. }
  29. interface ContextCardContentConfig {
  30. // Omit error styling from being displayed, even if context is invalid
  31. disableErrors?: boolean;
  32. // Displays value as plain text, rather than a hyperlink if applicable
  33. disableLink?: boolean;
  34. // Includes the Context Type as a prefix to the key. Useful if displaying a single Context key
  35. // apart from the rest of that Context. E.g. 'Email' -> 'User: Email'
  36. includeAliasInSubject?: boolean;
  37. }
  38. export interface ContextCardContentProps {
  39. item: KeyValueListDataItem;
  40. meta: Record<string, any>;
  41. alias?: string;
  42. config?: ContextCardContentConfig;
  43. }
  44. export function ContextCardContent({
  45. item,
  46. alias,
  47. meta,
  48. config,
  49. ...props
  50. }: ContextCardContentProps) {
  51. const {key: contextKey, subject} = item;
  52. if (contextKey === 'type') {
  53. return null;
  54. }
  55. const contextMeta = meta?.[contextKey];
  56. const contextErrors = contextMeta?.['']?.err ?? [];
  57. const contextSubject =
  58. config?.includeAliasInSubject && alias ? `${startCase(alias)}: ${subject}` : subject;
  59. return (
  60. <KeyValueData.Content
  61. item={{...item, subject: contextSubject}}
  62. meta={contextMeta}
  63. errors={config?.disableErrors ? [] : contextErrors}
  64. disableLink={config?.disableLink ?? false}
  65. {...props}
  66. />
  67. );
  68. }
  69. export default function ContextCard({
  70. alias,
  71. event,
  72. type,
  73. project,
  74. value = {},
  75. }: ContextCardProps) {
  76. const location = useLocation();
  77. const organization = useOrganization();
  78. if (isEmptyObject(value)) {
  79. return null;
  80. }
  81. const meta = getContextMeta(event, type === 'default' ? alias : type);
  82. const contextItems = getFormattedContextData({
  83. event,
  84. contextValue: value,
  85. contextType: getContextType({alias, type}),
  86. organization,
  87. project,
  88. location,
  89. });
  90. const contentItems = contextItems.map<KeyValueDataContentProps>(item => {
  91. const itemMeta: KeyValueDataContentProps['meta'] = meta?.[item?.key];
  92. const itemErrors: KeyValueDataContentProps['errors'] = itemMeta?.['']?.err ?? [];
  93. return {
  94. item,
  95. meta: itemMeta,
  96. errors: itemErrors,
  97. };
  98. });
  99. return (
  100. <KeyValueData.Card
  101. contentItems={contentItems}
  102. title={
  103. <Title>
  104. <div>{getContextTitle({alias, type, value})}</div>
  105. <div style={{minWidth: 14}}>
  106. <ErrorBoundary customComponent={null}>
  107. {getContextIcon({
  108. alias,
  109. type,
  110. value,
  111. contextIconProps: {
  112. size: 'sm',
  113. },
  114. })}
  115. </ErrorBoundary>
  116. </div>
  117. </Title>
  118. }
  119. sortAlphabetically
  120. />
  121. );
  122. }
  123. const Title = styled('div')`
  124. display: flex;
  125. justify-content: space-between;
  126. align-items: center;
  127. `;