選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

StartupModal.tsx 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2019 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. import * as React from 'react';
  21. import { connect } from 'react-redux';
  22. import { OnboardingContext } from './OnboardingContext';
  23. import { differenceInDays, parseDate, toShortNotSoISOString } from '../../helpers/dates';
  24. import { getCurrentUser, getAppState, Store } from '../../store/rootReducer';
  25. import { skipOnboarding } from '../../store/users';
  26. import { showLicense } from '../../api/marketplace';
  27. import { hasMessage } from '../../helpers/l10n';
  28. import { save, get } from '../../helpers/storage';
  29. import { isSonarCloud } from '../../helpers/system';
  30. import { lazyLoad } from '../../components/lazyLoad';
  31. import { isLoggedIn } from '../../helpers/users';
  32. import { withRouter, Router, Location } from '../../components/hoc/withRouter';
  33. const OnboardingModal = lazyLoad(() => import('../../apps/tutorials/onboarding/OnboardingModal'));
  34. const LicensePromptModal = lazyLoad(
  35. () => import('../../apps/marketplace/components/LicensePromptModal'),
  36. 'LicensePromptModal'
  37. );
  38. const TeamOnboardingModal = lazyLoad(() =>
  39. import('../../apps/tutorials/teamOnboarding/TeamOnboardingModal')
  40. );
  41. interface StateProps {
  42. canAdmin?: boolean;
  43. currentEdition?: T.EditionKey;
  44. currentUser: T.CurrentUser;
  45. }
  46. interface DispatchProps {
  47. skipOnboarding: () => void;
  48. }
  49. interface OwnProps {
  50. children?: React.ReactNode;
  51. }
  52. interface WithRouterProps {
  53. location: Pick<Location, 'pathname'>;
  54. router: Pick<Router, 'push'>;
  55. }
  56. type Props = StateProps & DispatchProps & OwnProps & WithRouterProps;
  57. enum ModalKey {
  58. license,
  59. onboarding,
  60. teamOnboarding
  61. }
  62. interface State {
  63. modal?: ModalKey;
  64. }
  65. const LICENSE_PROMPT = 'sonarqube.license.prompt';
  66. export class StartupModal extends React.PureComponent<Props, State> {
  67. state: State = {};
  68. componentDidMount() {
  69. this.tryAutoOpenLicense().catch(this.tryAutoOpenOnboarding);
  70. }
  71. closeOnboarding = () => {
  72. this.setState(state => {
  73. if (state.modal !== ModalKey.license) {
  74. this.props.skipOnboarding();
  75. return { modal: undefined };
  76. }
  77. return null;
  78. });
  79. };
  80. closeLicense = () => {
  81. this.setState(state => {
  82. if (state.modal === ModalKey.license) {
  83. return { modal: undefined };
  84. }
  85. return null;
  86. });
  87. };
  88. openOnboarding = () => {
  89. this.setState({ modal: ModalKey.onboarding });
  90. };
  91. openProjectOnboarding = (organization?: T.Organization) => {
  92. this.setState({ modal: undefined });
  93. const state: { organization?: string; tab?: string } = {};
  94. if (organization) {
  95. state.organization = organization.key;
  96. state.tab = organization.alm ? 'auto' : 'manual';
  97. }
  98. this.props.router.push({ pathname: `/projects/create`, state });
  99. };
  100. openTeamOnboarding = () => {
  101. this.setState({ modal: ModalKey.teamOnboarding });
  102. };
  103. tryAutoOpenLicense = () => {
  104. const { canAdmin, currentEdition, currentUser } = this.props;
  105. const hasLicenseManager = hasMessage('license.prompt.title');
  106. const hasLicensedEdition = currentEdition && currentEdition !== 'community';
  107. if (canAdmin && hasLicensedEdition && isLoggedIn(currentUser) && hasLicenseManager) {
  108. const lastPrompt = get(LICENSE_PROMPT, currentUser.login);
  109. if (!lastPrompt || differenceInDays(new Date(), parseDate(lastPrompt)) >= 1) {
  110. return showLicense().then(license => {
  111. if (!license || !license.isValidEdition) {
  112. save(LICENSE_PROMPT, toShortNotSoISOString(new Date()), currentUser.login);
  113. this.setState({ modal: ModalKey.license });
  114. return Promise.resolve();
  115. }
  116. return Promise.reject();
  117. });
  118. }
  119. }
  120. return Promise.reject();
  121. };
  122. tryAutoOpenOnboarding = () => {
  123. if (
  124. isSonarCloud() &&
  125. this.props.currentUser.showOnboardingTutorial &&
  126. !['/about', '/documentation', '/onboarding', '/projects/create', '/create-organization'].some(
  127. path => this.props.location.pathname.startsWith(path)
  128. )
  129. ) {
  130. this.openOnboarding();
  131. }
  132. };
  133. render() {
  134. const { modal } = this.state;
  135. return (
  136. <OnboardingContext.Provider value={this.openProjectOnboarding}>
  137. {this.props.children}
  138. {modal === ModalKey.license && <LicensePromptModal onClose={this.closeLicense} />}
  139. {modal === ModalKey.onboarding && (
  140. <OnboardingModal
  141. onClose={this.closeOnboarding}
  142. onOpenProjectOnboarding={this.openProjectOnboarding}
  143. onOpenTeamOnboarding={this.openTeamOnboarding}
  144. />
  145. )}
  146. {modal === ModalKey.teamOnboarding && (
  147. <TeamOnboardingModal onFinish={this.closeOnboarding} />
  148. )}
  149. </OnboardingContext.Provider>
  150. );
  151. }
  152. }
  153. const mapStateToProps = (state: Store): StateProps => ({
  154. canAdmin: getAppState(state).canAdmin,
  155. currentEdition: getAppState(state).edition,
  156. currentUser: getCurrentUser(state)
  157. });
  158. const mapDispatchToProps: DispatchProps = { skipOnboarding };
  159. export default connect(
  160. mapStateToProps,
  161. mapDispatchToProps
  162. )(withRouter(StartupModal));