utils.tsx 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. import * as Sentry from '@sentry/react';
  2. import {Field} from 'sentry/components/forms/type';
  3. import ExternalLink from 'sentry/components/links/externalLink';
  4. import {
  5. AWS_REGIONS,
  6. DEBUG_SOURCE_CASINGS,
  7. DEBUG_SOURCE_LAYOUTS,
  8. } from 'sentry/data/debugFileSources';
  9. import {t, tct} from 'sentry/locale';
  10. import {CustomRepoType} from 'sentry/types/debugFiles';
  11. import {uniqueId} from 'sentry/utils/guid';
  12. function objectToChoices(obj: Record<string, string>): [key: string, value: string][] {
  13. return Object.entries(obj).map(([key, value]) => [key, t(value)]);
  14. }
  15. type FieldMap = Record<string, Field>;
  16. const commonFields: FieldMap = {
  17. id: {
  18. name: 'id',
  19. type: 'hidden',
  20. required: true,
  21. defaultValue: uniqueId,
  22. },
  23. name: {
  24. name: 'name',
  25. type: 'string',
  26. required: true,
  27. label: t('Name'),
  28. placeholder: t('New Repository'),
  29. help: t('A display name for this repository'),
  30. },
  31. // filters are explicitly not exposed to the UI
  32. layoutType: {
  33. name: 'layout.type',
  34. type: 'select',
  35. label: t('Directory Layout'),
  36. help: t('The layout of the folder structure.'),
  37. defaultValue: 'native',
  38. choices: objectToChoices(DEBUG_SOURCE_LAYOUTS),
  39. },
  40. layoutCasing: {
  41. name: 'layout.casing',
  42. type: 'select',
  43. label: t('Path Casing'),
  44. help: t('The case of files and folders.'),
  45. defaultValue: 'default',
  46. choices: objectToChoices(DEBUG_SOURCE_CASINGS),
  47. },
  48. prefix: {
  49. name: 'prefix',
  50. type: 'string',
  51. label: 'Root Path',
  52. placeholder: '/',
  53. help: t('The path at which files are located within this repository.'),
  54. },
  55. separator: {
  56. name: '',
  57. type: 'separator',
  58. },
  59. };
  60. export function getFormFieldsAndInitialData(
  61. type: CustomRepoType,
  62. sourceConfig?: Record<string, any>
  63. ) {
  64. if (type === CustomRepoType.HTTP || type === CustomRepoType.APP_STORE_CONNECT) {
  65. return {};
  66. }
  67. const {secret_key, layout, private_key, ...config} = sourceConfig ?? {};
  68. const initialData = layout
  69. ? {...config, 'layout.casing': layout.casing, 'layout.type': layout.type}
  70. : config;
  71. switch (type) {
  72. case 's3':
  73. return {
  74. fields: [
  75. commonFields.id,
  76. commonFields.name,
  77. commonFields.separator,
  78. {
  79. name: 'bucket',
  80. type: 'string',
  81. required: true,
  82. label: t('Bucket'),
  83. placeholder: 's3-bucket-name',
  84. help: t(
  85. 'Name of the S3 bucket. Read permissions are required to download symbols.'
  86. ),
  87. },
  88. {
  89. name: 'region',
  90. type: 'select',
  91. required: true,
  92. label: t('Region'),
  93. help: t('The AWS region and availability zone of the bucket.'),
  94. choices: AWS_REGIONS.map(([k, v]) => [
  95. k,
  96. <span key={k}>
  97. <code>{k}</code> {v}
  98. </span>,
  99. ]),
  100. },
  101. {
  102. name: 'access_key',
  103. type: 'string',
  104. required: true,
  105. label: t('Access Key ID'),
  106. placeholder: 'AKIAIOSFODNN7EXAMPLE',
  107. help: tct(
  108. 'Access key to the AWS account. Credentials can be managed in the [link].',
  109. {
  110. link: (
  111. <ExternalLink href="https://console.aws.amazon.com/iam/">
  112. IAM console
  113. </ExternalLink>
  114. ),
  115. }
  116. ),
  117. },
  118. {
  119. name: 'secret_key',
  120. type: 'string',
  121. required: true,
  122. label: t('Secret Access Key'),
  123. placeholder:
  124. typeof secret_key === 'object'
  125. ? t('(Secret Access Key unchanged)')
  126. : 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
  127. },
  128. commonFields.separator,
  129. commonFields.prefix,
  130. commonFields.layoutType,
  131. commonFields.layoutCasing,
  132. ],
  133. initialData: !initialData
  134. ? undefined
  135. : {
  136. ...initialData,
  137. secret_key: undefined,
  138. },
  139. };
  140. case 'gcs':
  141. return {
  142. fields: [
  143. commonFields.id,
  144. commonFields.name,
  145. commonFields.separator,
  146. {
  147. name: 'bucket',
  148. type: 'string',
  149. required: true,
  150. label: t('Bucket'),
  151. placeholder: 'gcs-bucket-name',
  152. help: t(
  153. 'Name of the GCS bucket. Read permissions are required to download symbols.'
  154. ),
  155. },
  156. {
  157. name: 'client_email',
  158. type: 'email',
  159. required: true,
  160. label: t('Client Email'),
  161. placeholder: 'user@project.iam.gserviceaccount.com',
  162. help: t('Email address of the GCS service account.'),
  163. },
  164. {
  165. name: 'private_key',
  166. type: 'string',
  167. required: true,
  168. multiline: true,
  169. autosize: true,
  170. maxRows: 5,
  171. rows: 3,
  172. label: t('Private Key'),
  173. placeholder:
  174. typeof private_key === 'object'
  175. ? t('(Private Key unchanged)')
  176. : '-----BEGIN PRIVATE KEY-----\n[PRIVATE-KEY]\n-----END PRIVATE KEY-----',
  177. help: tct(
  178. 'The service account key. Credentials can be managed on the [link].',
  179. {
  180. link: (
  181. <ExternalLink href="https://console.cloud.google.com/project/_/iam-admin">
  182. IAM &amp; Admin Page
  183. </ExternalLink>
  184. ),
  185. }
  186. ),
  187. },
  188. commonFields.separator,
  189. commonFields.prefix,
  190. commonFields.layoutType,
  191. commonFields.layoutCasing,
  192. ],
  193. initialData: !initialData
  194. ? undefined
  195. : {
  196. ...initialData,
  197. private_key: undefined,
  198. },
  199. };
  200. default: {
  201. Sentry.captureException(new Error('Unknown custom repository type'));
  202. return {}; // this shall never happen
  203. }
  204. }
  205. }
  206. export function getFinalData(type: CustomRepoType, data: Record<string, any>) {
  207. if (type === CustomRepoType.HTTP || type === CustomRepoType.APP_STORE_CONNECT) {
  208. return data;
  209. }
  210. switch (type) {
  211. case 's3':
  212. return {
  213. ...data,
  214. secret_key: data.secret_key ?? {
  215. 'hidden-secret': true,
  216. },
  217. };
  218. case 'gcs':
  219. return {
  220. ...data,
  221. private_key: data.private_key ?? {
  222. 'hidden-secret': true,
  223. },
  224. };
  225. default: {
  226. Sentry.captureException(new Error('Unknown custom repository type'));
  227. return {}; // this shall never happen
  228. }
  229. }
  230. }