utils.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. import {Fragment} from 'react';
  2. import styled from '@emotion/styled';
  3. import startCase from 'lodash/startCase';
  4. import moment from 'moment-timezone';
  5. import ContextData from 'sentry/components/contextData';
  6. import {t} from 'sentry/locale';
  7. import plugins from 'sentry/plugins';
  8. import ConfigStore from 'sentry/stores/configStore';
  9. import space from 'sentry/styles/space';
  10. import {Event, KeyValueListData} from 'sentry/types';
  11. import {defined} from 'sentry/utils';
  12. const CONTEXT_TYPES = {
  13. default: require('sentry/components/events/contexts/default').default,
  14. app: require('sentry/components/events/contexts/app').AppEventContext,
  15. device: require('sentry/components/events/contexts/device').DeviceEventContext,
  16. browser: require('sentry/components/events/contexts/browser').BrowserEventContext,
  17. os: require('sentry/components/events/contexts/operatingSystem')
  18. .OperatingSystemEventContext,
  19. runtime: require('sentry/components/events/contexts/runtime').RuntimeEventContext,
  20. user: require('sentry/components/events/contexts/user').UserEventContext,
  21. gpu: require('sentry/components/events/contexts/gpu').GPUEventContext,
  22. trace: require('sentry/components/events/contexts/trace').TraceEventContext,
  23. // 'redux.state' will be replaced with more generic context called 'state'
  24. 'redux.state': require('sentry/components/events/contexts/redux').default,
  25. state: require('sentry/components/events/contexts/state').StateEventContext,
  26. };
  27. export function getContextComponent(type: string) {
  28. return CONTEXT_TYPES[type] || plugins.contexts[type] || CONTEXT_TYPES.default;
  29. }
  30. export function getSourcePlugin(pluginContexts: Array<any>, contextType: string) {
  31. if (CONTEXT_TYPES[contextType]) {
  32. return null;
  33. }
  34. for (const plugin of pluginContexts) {
  35. if (plugin.contexts.indexOf(contextType) >= 0) {
  36. return plugin;
  37. }
  38. }
  39. return null;
  40. }
  41. export function getRelativeTimeFromEventDateCreated(
  42. eventDateCreated: string,
  43. timestamp?: string,
  44. showTimestamp = true
  45. ) {
  46. if (!defined(timestamp)) {
  47. return timestamp;
  48. }
  49. const dateTime = moment(timestamp);
  50. if (!dateTime.isValid()) {
  51. return timestamp;
  52. }
  53. const relativeTime = `(${dateTime.from(eventDateCreated, true)} ${t(
  54. 'before this event'
  55. )})`;
  56. if (!showTimestamp) {
  57. return <RelativeTime>{relativeTime}</RelativeTime>;
  58. }
  59. return (
  60. <Fragment>
  61. {timestamp}
  62. <RelativeTime>{relativeTime}</RelativeTime>
  63. </Fragment>
  64. );
  65. }
  66. // Typescript doesn't have types for DisplayNames yet and that's why the type assertion "any" is needed below.
  67. // There is currently an open PR that intends to introduce the types https://github.com/microsoft/TypeScript/pull/44022
  68. export function getFullLanguageDescription(locale: string) {
  69. const sentryAppLanguageCode = ConfigStore.get('languageCode');
  70. const [languageAbbreviation, countryAbbreviation] = locale.includes('_')
  71. ? locale.split('_')
  72. : locale.split('-');
  73. try {
  74. const languageNames = new (Intl as any).DisplayNames(sentryAppLanguageCode, {
  75. type: 'language',
  76. });
  77. const languageName = languageNames.of(languageAbbreviation);
  78. if (countryAbbreviation) {
  79. const regionNames = new (Intl as any).DisplayNames(sentryAppLanguageCode, {
  80. type: 'region',
  81. });
  82. const countryName = regionNames.of(countryAbbreviation.toUpperCase());
  83. return `${languageName} (${countryName})`;
  84. }
  85. return languageName;
  86. } catch {
  87. return locale;
  88. }
  89. }
  90. export function geKnownData<Data, DataType>({
  91. data,
  92. knownDataTypes,
  93. meta,
  94. raw,
  95. onGetKnownDataDetails,
  96. }: {
  97. data: Data;
  98. knownDataTypes: string[];
  99. onGetKnownDataDetails: (props: {data: Data; type: DataType}) =>
  100. | {
  101. subject: string;
  102. value?: React.ReactNode;
  103. }
  104. | undefined;
  105. meta?: Record<any, any>;
  106. raw?: boolean;
  107. }): KeyValueListData {
  108. const filteredTypes = knownDataTypes.filter(knownDataType => {
  109. if (
  110. typeof data[knownDataType] !== 'number' &&
  111. typeof data[knownDataType] !== 'boolean' &&
  112. !data[knownDataType]
  113. ) {
  114. return !!meta?.[knownDataType];
  115. }
  116. return true;
  117. });
  118. return filteredTypes
  119. .map(type => {
  120. const knownDataDetails = onGetKnownDataDetails({
  121. data,
  122. type: type as unknown as DataType,
  123. });
  124. if (!knownDataDetails) {
  125. return null;
  126. }
  127. return {
  128. key: type,
  129. ...knownDataDetails,
  130. value: raw ? (
  131. knownDataDetails.value
  132. ) : (
  133. <ContextData
  134. data={knownDataDetails.value}
  135. meta={meta?.[type]}
  136. withAnnotatedText
  137. />
  138. ),
  139. };
  140. })
  141. .filter(defined);
  142. }
  143. export function getUnknownData({
  144. allData,
  145. knownKeys,
  146. meta,
  147. }: {
  148. allData: Record<string, any>;
  149. knownKeys: string[];
  150. meta?: NonNullable<Event['_meta']>[keyof Event['_meta']];
  151. }): KeyValueListData {
  152. return Object.entries(allData)
  153. .filter(
  154. ([key]) =>
  155. key !== 'type' &&
  156. key !== 'title' &&
  157. !knownKeys.includes(key) &&
  158. (typeof allData[key] !== 'number' && !allData[key] ? !!meta?.[key]?.[''] : true)
  159. )
  160. .map(([key, value]) => ({
  161. key,
  162. value,
  163. subject: startCase(key),
  164. meta: meta?.[key]?.[''],
  165. }));
  166. }
  167. const RelativeTime = styled('span')`
  168. color: ${p => p.theme.subText};
  169. margin-left: ${space(0.5)};
  170. `;