addIntegration.tsx 3.9 KB

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