addIntegration.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import {Component} from 'react';
  2. import * as qs from 'query-string';
  3. import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
  4. import {t} from 'sentry/locale';
  5. import ConfigStore from 'sentry/stores/configStore';
  6. import type {IntegrationProvider, IntegrationWithConfig} from 'sentry/types/integrations';
  7. import type {Organization} from 'sentry/types/organization';
  8. import {trackIntegrationAnalytics} from 'sentry/utils/integrationUtil';
  9. import type {MessagingIntegrationAnalyticsView} from 'sentry/views/alerts/rules/issue/setupMessagingIntegrationButton';
  10. type Props = {
  11. children: (
  12. openDialog: (urlParams?: {[key: string]: string}) => void
  13. ) => React.ReactNode;
  14. onInstall: (data: IntegrationWithConfig) => void;
  15. organization: Organization;
  16. provider: IntegrationProvider;
  17. account?: string | null; // for analytics
  18. analyticsParams?: {
  19. already_installed: boolean;
  20. view:
  21. | MessagingIntegrationAnalyticsView
  22. | 'integrations_directory_integration_detail'
  23. | 'integrations_directory'
  24. | 'onboarding'
  25. | 'project_creation';
  26. };
  27. modalParams?: {[key: string]: string};
  28. };
  29. export default class AddIntegration extends Component<Props> {
  30. componentDidMount() {
  31. window.addEventListener('message', this.didReceiveMessage);
  32. }
  33. componentWillUnmount() {
  34. window.removeEventListener('message', this.didReceiveMessage);
  35. this.dialog?.close();
  36. }
  37. dialog: Window | null = null;
  38. computeCenteredWindow(width: number, height: number) {
  39. // Taken from: https://stackoverflow.com/questions/4068373/center-a-popup-window-on-screen
  40. const screenLeft =
  41. window.screenLeft !== undefined ? window.screenLeft : window.screenX;
  42. const screenTop = window.screenTop !== undefined ? window.screenTop : window.screenY;
  43. const innerWidth = window.innerWidth
  44. ? window.innerWidth
  45. : document.documentElement.clientWidth
  46. ? document.documentElement.clientWidth
  47. : screen.width;
  48. const innerHeight = window.innerHeight
  49. ? window.innerHeight
  50. : document.documentElement.clientHeight
  51. ? document.documentElement.clientHeight
  52. : screen.height;
  53. const left = innerWidth / 2 - width / 2 + screenLeft;
  54. const top = innerHeight / 2 - height / 2 + screenTop;
  55. return {left, top};
  56. }
  57. openDialog = (urlParams?: {[key: string]: string}) => {
  58. const {account, analyticsParams, modalParams, organization, provider} = this.props;
  59. trackIntegrationAnalytics('integrations.installation_start', {
  60. integration: provider.key,
  61. integration_type: 'first_party',
  62. organization,
  63. ...analyticsParams,
  64. });
  65. const name = 'sentryAddIntegration';
  66. const {url, width, height} = provider.setupDialog;
  67. const {left, top} = this.computeCenteredWindow(width, height);
  68. let query: {[key: string]: string} = {...urlParams};
  69. if (account) {
  70. query.account = account;
  71. }
  72. if (modalParams) {
  73. query = {...query, ...modalParams};
  74. }
  75. const installUrl = `${url}?${qs.stringify(query)}`;
  76. const opts = `scrollbars=yes,width=${width},height=${height},top=${top},left=${left}`;
  77. this.dialog = window.open(installUrl, name, opts);
  78. this.dialog?.focus();
  79. };
  80. didReceiveMessage = (message: MessageEvent) => {
  81. const {analyticsParams, onInstall, organization, provider} = this.props;
  82. const validOrigins = [
  83. ConfigStore.get('links').sentryUrl,
  84. ConfigStore.get('links').organizationUrl,
  85. document.location.origin,
  86. ];
  87. if (!validOrigins.includes(message.origin)) {
  88. return;
  89. }
  90. if (message.source !== this.dialog) {
  91. return;
  92. }
  93. const {success, data} = message.data;
  94. this.dialog = null;
  95. if (!success) {
  96. addErrorMessage(data?.error ?? t('An unknown error occurred'));
  97. return;
  98. }
  99. if (!data) {
  100. return;
  101. }
  102. trackIntegrationAnalytics('integrations.installation_complete', {
  103. integration: provider.key,
  104. integration_type: 'first_party',
  105. organization,
  106. ...analyticsParams,
  107. });
  108. addSuccessMessage(t('%s added', provider.name));
  109. onInstall(data);
  110. };
  111. render() {
  112. const {children} = this.props;
  113. return children(this.openDialog);
  114. }
  115. }