integrationUtil.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. import capitalize from 'lodash/capitalize';
  2. import * as qs from 'query-string';
  3. import {
  4. IconBitbucket,
  5. IconGeneric,
  6. IconGithub,
  7. IconGitlab,
  8. IconJira,
  9. IconVsts,
  10. } from 'app/icons';
  11. import HookStore from 'app/stores/hookStore';
  12. import {
  13. AppOrProviderOrPlugin,
  14. DocumentIntegration,
  15. IntegrationFeature,
  16. IntegrationInstallationStatus,
  17. IntegrationType,
  18. LightWeightOrganization,
  19. PluginWithProjectList,
  20. SentryApp,
  21. SentryAppInstallation,
  22. } from 'app/types';
  23. import {Hooks} from 'app/types/hooks';
  24. import {EventParameters, trackAdvancedAnalyticsEvent} from 'app/utils/advancedAnalytics';
  25. import {IntegrationAnalyticsKey} from 'app/utils/integrationEvents';
  26. const mapIntegrationParams = analyticsParams => {
  27. // Reload expects integration_status even though it's not relevant for non-sentry apps
  28. // Passing in a dummy value of published in those cases
  29. const fullParams = {...analyticsParams};
  30. if (analyticsParams.integration && analyticsParams.integration_type !== 'sentry_app') {
  31. fullParams.integration_status = 'published';
  32. }
  33. return fullParams;
  34. };
  35. // wrapper around trackAdvancedAnalyticsEvent which has some extra
  36. // data massaging above
  37. export function trackIntegrationEvent<T extends IntegrationAnalyticsKey>(
  38. eventKey: T,
  39. analyticsParams: EventParameters[T] & {organization: LightWeightOrganization}, // integration events should always be tied to an org
  40. options?: Parameters<typeof trackAdvancedAnalyticsEvent>[2]
  41. ) {
  42. options = options || {};
  43. options.mapValuesFn = mapIntegrationParams;
  44. return trackAdvancedAnalyticsEvent(eventKey, analyticsParams, options);
  45. }
  46. /**
  47. * In sentry.io the features list supports rendering plan details. If the hook
  48. * is not registered for rendering the features list like this simply show the
  49. * features as a normal list.
  50. */
  51. const generateFeaturesList = p => (
  52. <ul>
  53. {p.features.map((f, i) => (
  54. <li key={i}>{f.description}</li>
  55. ))}
  56. </ul>
  57. );
  58. const generateIntegrationFeatures = p =>
  59. p.children({
  60. disabled: false,
  61. disabledReason: null,
  62. ungatedFeatures: p.features,
  63. gatedFeatureGroups: [],
  64. });
  65. const defaultFeatureGateComponents = {
  66. IntegrationFeatures: generateIntegrationFeatures,
  67. IntegrationDirectoryFeatures: generateIntegrationFeatures,
  68. FeatureList: generateFeaturesList,
  69. IntegrationDirectoryFeatureList: generateFeaturesList,
  70. } as ReturnType<Hooks['integrations:feature-gates']>;
  71. export const getIntegrationFeatureGate = () => {
  72. const defaultHook = () => defaultFeatureGateComponents;
  73. const featureHook = HookStore.get('integrations:feature-gates')[0] || defaultHook;
  74. return featureHook();
  75. };
  76. export const getSentryAppInstallStatus = (install: SentryAppInstallation | undefined) => {
  77. if (install) {
  78. return capitalize(install.status) as IntegrationInstallationStatus;
  79. }
  80. return 'Not Installed';
  81. };
  82. export const getCategories = (features: IntegrationFeature[]): string[] => {
  83. const transform = features.map(({featureGate}) => {
  84. const feature = featureGate
  85. .replace(/integrations/g, '')
  86. .replace(/-/g, ' ')
  87. .trim();
  88. switch (feature) {
  89. case 'actionable notification':
  90. return 'notification action';
  91. case 'issue basic':
  92. case 'issue link':
  93. case 'issue sync':
  94. return 'project management';
  95. case 'commits':
  96. return 'source code management';
  97. case 'chat unfurl':
  98. return 'chat';
  99. default:
  100. return feature;
  101. }
  102. });
  103. return [...new Set(transform)];
  104. };
  105. export const getCategoriesForIntegration = (
  106. integration: AppOrProviderOrPlugin
  107. ): string[] => {
  108. if (isSentryApp(integration)) {
  109. return ['internal', 'unpublished'].includes(integration.status)
  110. ? [integration.status]
  111. : getCategories(integration.featureData);
  112. }
  113. if (isPlugin(integration)) {
  114. return getCategories(integration.featureDescriptions);
  115. }
  116. if (isDocumentIntegration(integration)) {
  117. return getCategories(integration.features);
  118. }
  119. return getCategories(integration.metadata.features);
  120. };
  121. export function isSentryApp(
  122. integration: AppOrProviderOrPlugin
  123. ): integration is SentryApp {
  124. return !!(integration as SentryApp).uuid;
  125. }
  126. export function isPlugin(
  127. integration: AppOrProviderOrPlugin
  128. ): integration is PluginWithProjectList {
  129. return integration.hasOwnProperty('shortName');
  130. }
  131. export function isDocumentIntegration(
  132. integration: AppOrProviderOrPlugin
  133. ): integration is DocumentIntegration {
  134. return integration.hasOwnProperty('docUrl');
  135. }
  136. export const getIntegrationType = (
  137. integration: AppOrProviderOrPlugin
  138. ): IntegrationType => {
  139. if (isSentryApp(integration)) {
  140. return 'sentry_app';
  141. }
  142. if (isPlugin(integration)) {
  143. return 'plugin';
  144. }
  145. if (isDocumentIntegration(integration)) {
  146. return 'document';
  147. }
  148. return 'first_party';
  149. };
  150. export const convertIntegrationTypeToSnakeCase = (
  151. type: 'plugin' | 'firstParty' | 'sentryApp' | 'documentIntegration'
  152. ) => {
  153. switch (type) {
  154. case 'firstParty':
  155. return 'first_party';
  156. case 'sentryApp':
  157. return 'sentry_app';
  158. case 'documentIntegration':
  159. return 'document';
  160. default:
  161. return type;
  162. }
  163. };
  164. export const safeGetQsParam = (param: string) => {
  165. try {
  166. const query = qs.parse(window.location.search) || {};
  167. return query[param];
  168. } catch {
  169. return undefined;
  170. }
  171. };
  172. export const getIntegrationIcon = (integrationType?: string, size?: string) => {
  173. const iconSize = size || 'md';
  174. switch (integrationType) {
  175. case 'bitbucket':
  176. return <IconBitbucket size={iconSize} />;
  177. case 'gitlab':
  178. return <IconGitlab size={iconSize} />;
  179. case 'github':
  180. case 'github_enterprise':
  181. return <IconGithub size={iconSize} />;
  182. case 'jira':
  183. case 'jira_server':
  184. return <IconJira size={iconSize} />;
  185. case 'vsts':
  186. return <IconVsts size={iconSize} />;
  187. default:
  188. return <IconGeneric size={iconSize} />;
  189. }
  190. };
  191. // used for project creation and onboarding
  192. // determines what integration maps to what project platform
  193. export const platfromToIntegrationMap = {
  194. 'node-awslambda': 'aws_lambda',
  195. 'python-awslambda': 'aws_lambda',
  196. };