projectDebugFiles.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. import {Fragment} from 'react';
  2. import forEach from 'lodash/forEach';
  3. import isObject from 'lodash/isObject';
  4. import set from 'lodash/set';
  5. import {openDebugFileSourceModal} from 'app/actionCreators/modal';
  6. import Feature from 'app/components/acl/feature';
  7. import FeatureDisabled from 'app/components/acl/featureDisabled';
  8. import {DEBUG_SOURCE_TYPES} from 'app/data/debugFileSources';
  9. import {t} from 'app/locale';
  10. import {Choices} from 'app/types';
  11. import {BuiltinSymbolSource} from 'app/types/debugFiles';
  12. import {Field} from 'app/views/settings/components/forms/type';
  13. import TextBlock from 'app/views/settings/components/text/textBlock';
  14. type SymbolSourceOptions = {
  15. builtinSymbolSources: BuiltinSymbolSource[];
  16. };
  17. // Export route to make these forms searchable by label/help
  18. export const route = '/settings/:orgId/projects/:projectId/debug-symbols/';
  19. function flattenKeys(obj: any): Record<string, string> {
  20. const result = {};
  21. forEach(obj, (value, key) => {
  22. if (isObject(value)) {
  23. forEach(value, (innerValue, innerKey) => {
  24. result[`${key}.${innerKey}`] = innerValue;
  25. });
  26. } else {
  27. result[key] = value;
  28. }
  29. });
  30. return result;
  31. }
  32. function unflattenKeys(obj: Record<string, string>): Record<string, any> {
  33. const result = {};
  34. forEach(obj, (value, key) => {
  35. set(result, key.split('.'), value);
  36. });
  37. return result;
  38. }
  39. export const fields: Record<string, Field> = {
  40. builtinSymbolSources: {
  41. name: 'builtinSymbolSources',
  42. type: 'select',
  43. multiple: true,
  44. label: t('Built-in Repositories'),
  45. help: t(
  46. 'Configures which built-in repositories Sentry should use to resolve debug files.'
  47. ),
  48. formatMessageValue: (value, {builtinSymbolSources}: SymbolSourceOptions) => {
  49. const rv: string[] = [];
  50. value.forEach(key => {
  51. builtinSymbolSources.forEach(source => {
  52. if (source.sentry_key === key) {
  53. rv.push(source.name);
  54. }
  55. });
  56. });
  57. return rv.length ? rv.join(', ') : '\u2014';
  58. },
  59. choices: ({builtinSymbolSources}) => {
  60. return (builtinSymbolSources as BuiltinSymbolSource[])
  61. ?.filter(source => !source.hidden)
  62. .map(source => [source.sentry_key, t(source.name)]) as Choices;
  63. },
  64. getValue: value => (value === null ? [] : value),
  65. },
  66. symbolSources: {
  67. name: 'symbolSources',
  68. type: 'rich_list',
  69. label: t('Custom Repositories'),
  70. /* eslint-disable-next-line react/prop-types */
  71. help: ({organization}) => (
  72. <Feature
  73. features={['organizations:custom-symbol-sources']}
  74. hookName="feature-disabled:custom-symbol-sources"
  75. organization={organization}
  76. renderDisabled={p => (
  77. <FeatureDisabled
  78. features={p.features}
  79. message={t('Custom repositories are disabled.')}
  80. featureName={t('custom repositories')}
  81. />
  82. )}
  83. >
  84. {t('Configures custom repositories containing debug files.')}
  85. </Feature>
  86. ),
  87. disabled: ({features}) => !features.has('custom-symbol-sources'),
  88. formatMessageValue: false,
  89. addButtonText: t('Add Repository'),
  90. addDropdown: {
  91. items: [
  92. {
  93. value: 's3',
  94. label: t(DEBUG_SOURCE_TYPES.s3),
  95. searchKey: t('aws amazon s3 bucket'),
  96. },
  97. {
  98. value: 'gcs',
  99. label: t(DEBUG_SOURCE_TYPES.gcs),
  100. searchKey: t('gcs google cloud storage bucket'),
  101. },
  102. {
  103. value: 'http',
  104. label: t(DEBUG_SOURCE_TYPES.http),
  105. searchKey: t('http symbol server ssqp symstore symsrv'),
  106. },
  107. ],
  108. },
  109. getValue: sources => JSON.stringify(sources.map(unflattenKeys)),
  110. setValue: (raw: string) => {
  111. if (!raw) {
  112. return [];
  113. }
  114. return (JSON.parse(raw) || []).map(flattenKeys);
  115. },
  116. renderItem(item) {
  117. return item.name ? <span>{item.name}</span> : <em>{t('<Unnamed Repository>')}</em>;
  118. },
  119. onAddItem(item, addItem) {
  120. openDebugFileSourceModal({
  121. sourceType: item.value,
  122. onSave: addItem,
  123. });
  124. },
  125. onEditItem(item, updateItem) {
  126. openDebugFileSourceModal({
  127. sourceConfig: item,
  128. sourceType: item.type,
  129. onSave: updateItem,
  130. });
  131. },
  132. removeConfirm: {
  133. confirmText: t('Remove Repository'),
  134. message: (
  135. <Fragment>
  136. <TextBlock>
  137. <strong>
  138. {t('Removing this repository applies instantly to new events.')}
  139. </strong>
  140. </TextBlock>
  141. <TextBlock>
  142. {t(
  143. 'Debug files from this repository will not be used to symbolicate future events. This may create new issues and alert members in your organization.'
  144. )}
  145. </TextBlock>
  146. </Fragment>
  147. ),
  148. },
  149. },
  150. };