contextCard.tsx 3.1 KB

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