projectGeneralSettings.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import {createFilter} from 'react-select';
  2. import styled from '@emotion/styled';
  3. import {PlatformIcon} from 'platformicons';
  4. import {hasEveryAccess} from 'sentry/components/acl/access';
  5. import type {Field} from 'sentry/components/forms/types';
  6. import platforms from 'sentry/data/platforms';
  7. import {t, tct, tn} from 'sentry/locale';
  8. import {space} from 'sentry/styles/space';
  9. import {convertMultilineFieldValue, extractMultilineFields} from 'sentry/utils';
  10. import getDynamicText from 'sentry/utils/getDynamicText';
  11. import slugify from 'sentry/utils/slugify';
  12. // Export route to make these forms searchable by label/help
  13. export const route = '/settings/:orgId/projects/:projectId/';
  14. const getResolveAgeAllowedValues = () => {
  15. let i = 0;
  16. const values: number[] = [];
  17. while (i <= 720) {
  18. values.push(i);
  19. if (i < 12) {
  20. i += 1;
  21. } else if (i < 24) {
  22. i += 3;
  23. } else if (i < 36) {
  24. i += 6;
  25. } else if (i < 48) {
  26. i += 12;
  27. } else {
  28. i += 24;
  29. }
  30. }
  31. return values;
  32. };
  33. const RESOLVE_AGE_ALLOWED_VALUES = getResolveAgeAllowedValues();
  34. const ORG_DISABLED_REASON = t(
  35. "This option is enforced by your organization's settings and cannot be customized per-project."
  36. );
  37. const PlatformWrapper = styled('div')`
  38. display: flex;
  39. align-items: center;
  40. `;
  41. const StyledPlatformIcon = styled(PlatformIcon)`
  42. margin-right: ${space(1)};
  43. `;
  44. export const fields: Record<string, Field> = {
  45. name: {
  46. name: 'name',
  47. type: 'string',
  48. required: true,
  49. label: t('Name'),
  50. placeholder: t('my-awesome-project'),
  51. help: t('A name for this project'),
  52. transformInput: slugify,
  53. getData: (data: {name?: string}) => {
  54. return {
  55. name: data.name,
  56. slug: data.name,
  57. };
  58. },
  59. saveOnBlur: false,
  60. saveMessageAlertType: 'warning',
  61. saveMessage: t(
  62. "Changing a project's name will also change the project slug. This can break your build scripts! Please proceed carefully."
  63. ),
  64. },
  65. platform: {
  66. name: 'platform',
  67. type: 'select',
  68. label: t('Platform'),
  69. options: platforms.map(({id, name}) => ({
  70. value: id,
  71. label: (
  72. <PlatformWrapper key={id}>
  73. <StyledPlatformIcon platform={id} />
  74. {name}
  75. </PlatformWrapper>
  76. ),
  77. })),
  78. help: t('The primary platform for this project'),
  79. filterOption: createFilter({
  80. stringify: option => {
  81. const matchedPlatform = platforms.find(({id}) => id === option.value);
  82. return `${matchedPlatform?.name} ${option.value}`;
  83. },
  84. }),
  85. },
  86. subjectPrefix: {
  87. name: 'subjectPrefix',
  88. type: 'string',
  89. label: t('Subject Prefix'),
  90. placeholder: t('e.g. [my-org]'),
  91. help: t('Choose a custom prefix for emails from this project'),
  92. },
  93. resolveAge: {
  94. name: 'resolveAge',
  95. type: 'range',
  96. allowedValues: RESOLVE_AGE_ALLOWED_VALUES,
  97. label: t('Auto Resolve'),
  98. help: t(
  99. "Automatically resolve an issue if it hasn't been seen for this amount of time"
  100. ),
  101. formatLabel: val => {
  102. val = Number(val);
  103. if (val === 0) {
  104. return t('Disabled');
  105. }
  106. if (val > 23 && val % 24 === 0) {
  107. // Based on allowed values, val % 24 should always be true
  108. val = val / 24;
  109. return tn('%s day', '%s days', val);
  110. }
  111. return tn('%s hour', '%s hours', val);
  112. },
  113. saveOnBlur: false,
  114. saveMessage: tct(
  115. '[strong:Caution]: Enabling auto resolve will immediately resolve anything that has not been seen within this period of time. There is no undo!',
  116. {
  117. strong: <strong />,
  118. }
  119. ),
  120. saveMessageAlertType: 'warning',
  121. },
  122. allowedDomains: {
  123. name: 'allowedDomains',
  124. type: 'string',
  125. multiline: true,
  126. autosize: true,
  127. maxRows: 10,
  128. rows: 1,
  129. placeholder: t('https://example.com or example.com'),
  130. label: t('Allowed Domains'),
  131. help: t(
  132. 'Examples: https://example.com, *, *.example.com, *:80. Separate multiple entries with a newline'
  133. ),
  134. getValue: val => extractMultilineFields(val),
  135. setValue: val => convertMultilineFieldValue(val),
  136. },
  137. scrapeJavaScript: {
  138. name: 'scrapeJavaScript',
  139. type: 'boolean',
  140. // if this is off for the organization, it cannot be enabled for the project
  141. disabled: ({organization, project, name}) =>
  142. !organization[name] || !hasEveryAccess(['project:write'], {organization, project}),
  143. disabledReason: ORG_DISABLED_REASON,
  144. // `props` are the props given to FormField
  145. setValue: (val, props) => props.organization?.[props.name] && val,
  146. label: t('Enable JavaScript source fetching'),
  147. help: t('Allow Sentry to scrape missing JavaScript source context when possible'),
  148. },
  149. securityToken: {
  150. name: 'securityToken',
  151. type: 'string',
  152. label: t('Security Token'),
  153. help: t(
  154. 'Outbound requests matching Allowed Domains will have the header "{token_header}: {token}" appended'
  155. ),
  156. setValue: value => getDynamicText({value, fixed: '__SECURITY_TOKEN__'}),
  157. },
  158. securityTokenHeader: {
  159. name: 'securityTokenHeader',
  160. type: 'string',
  161. placeholder: t('X-Sentry-Token'),
  162. label: t('Security Token Header'),
  163. help: t(
  164. 'Outbound requests matching Allowed Domains will have the header "{token_header}: {token}" appended'
  165. ),
  166. },
  167. verifySSL: {
  168. name: 'verifySSL',
  169. type: 'boolean',
  170. label: t('Verify TLS/SSL'),
  171. help: t('Outbound requests will verify TLS (sometimes known as SSL) connections'),
  172. },
  173. };