You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

GlobalNavPlus.tsx 6.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2020 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 Dropdown from 'sonar-ui-common/components/controls/Dropdown';
  22. import PlusIcon from 'sonar-ui-common/components/icons/PlusIcon';
  23. import { translate } from 'sonar-ui-common/helpers/l10n';
  24. import { getAlmSettings } from '../../../../api/alm-settings';
  25. import { getComponentNavigation } from '../../../../api/nav';
  26. import CreateFormShim from '../../../../apps/portfolio/components/CreateFormShim';
  27. import { Router, withRouter } from '../../../../components/hoc/withRouter';
  28. import { getExtensionStart } from '../../../../helpers/extensions';
  29. import { getComponentAdminUrl, getComponentOverviewUrl } from '../../../../helpers/urls';
  30. import { hasGlobalPermission } from '../../../../helpers/users';
  31. import { AlmKeys, AlmSettingsInstance } from '../../../../types/alm-settings';
  32. import { ComponentQualifier } from '../../../../types/component';
  33. import CreateApplicationForm from '../../extensions/CreateApplicationForm';
  34. import GlobalNavPlusMenu from './GlobalNavPlusMenu';
  35. interface Props {
  36. appState: Pick<T.AppState, 'branchesEnabled' | 'qualifiers'>;
  37. currentUser: T.LoggedInUser;
  38. router: Router;
  39. }
  40. interface State {
  41. boundAlms: Array<string>;
  42. creatingComponent?: ComponentQualifier;
  43. governanceReady: boolean;
  44. }
  45. /*
  46. * ALMs for which the import feature has been implemented
  47. */
  48. const IMPORT_COMPATIBLE_ALMS = [AlmKeys.Bitbucket, AlmKeys.GitHub, AlmKeys.GitLab];
  49. const almSettingsValidators = {
  50. [AlmKeys.Azure]: (_: AlmSettingsInstance) => true,
  51. [AlmKeys.Bitbucket]: (_: AlmSettingsInstance) => true,
  52. [AlmKeys.GitHub]: (_: AlmSettingsInstance) => true,
  53. [AlmKeys.GitLab]: (settings: AlmSettingsInstance) => !!settings.url
  54. };
  55. export class GlobalNavPlus extends React.PureComponent<Props, State> {
  56. mounted = false;
  57. state: State = { boundAlms: [], governanceReady: false };
  58. componentDidMount() {
  59. this.mounted = true;
  60. this.fetchAlmBindings();
  61. if (this.props.appState.qualifiers.includes(ComponentQualifier.Portfolio)) {
  62. getExtensionStart('governance/console').then(
  63. () => {
  64. if (this.mounted) {
  65. this.setState({ governanceReady: true });
  66. }
  67. },
  68. () => {}
  69. );
  70. }
  71. }
  72. componentWillUnmount() {
  73. this.mounted = false;
  74. }
  75. closeComponentCreationForm = () => {
  76. this.setState({ creatingComponent: undefined });
  77. };
  78. almSettingIsValid = (settings: AlmSettingsInstance) => {
  79. return almSettingsValidators[settings.alm](settings);
  80. };
  81. fetchAlmBindings = async () => {
  82. const {
  83. appState: { branchesEnabled },
  84. currentUser
  85. } = this.props;
  86. const canCreateProject = hasGlobalPermission(currentUser, 'provisioning');
  87. // getAlmSettings requires branchesEnabled
  88. if (!canCreateProject || !branchesEnabled) {
  89. return;
  90. }
  91. const almSettings = await getAlmSettings();
  92. // Import is only available if exactly one binding is configured
  93. const boundAlms = IMPORT_COMPATIBLE_ALMS.filter(key => {
  94. const currentAlmSettings = almSettings.filter(s => s.alm === key);
  95. return currentAlmSettings.length === 1 && this.almSettingIsValid(currentAlmSettings[0]);
  96. });
  97. if (this.mounted) {
  98. this.setState({
  99. boundAlms
  100. });
  101. }
  102. };
  103. handleComponentCreationClick = (qualifier: ComponentQualifier) => {
  104. this.setState({ creatingComponent: qualifier });
  105. };
  106. handleComponentCreate = ({ key, qualifier }: { key: string; qualifier: ComponentQualifier }) => {
  107. return getComponentNavigation({ component: key }).then(({ configuration }) => {
  108. if (configuration && configuration.showSettings) {
  109. this.props.router.push(getComponentAdminUrl(key, qualifier));
  110. } else {
  111. this.props.router.push(getComponentOverviewUrl(key, qualifier));
  112. }
  113. this.closeComponentCreationForm();
  114. });
  115. };
  116. render() {
  117. const { appState, currentUser } = this.props;
  118. const { boundAlms, governanceReady, creatingComponent } = this.state;
  119. const canCreateApplication =
  120. appState.qualifiers.includes(ComponentQualifier.Application) &&
  121. hasGlobalPermission(currentUser, 'applicationcreator');
  122. const canCreatePortfolio =
  123. appState.qualifiers.includes(ComponentQualifier.Portfolio) &&
  124. hasGlobalPermission(currentUser, 'portfoliocreator');
  125. const canCreateProject = hasGlobalPermission(currentUser, 'provisioning');
  126. if (!canCreateProject && !canCreateApplication && !canCreatePortfolio) {
  127. return null;
  128. }
  129. return (
  130. <>
  131. <Dropdown
  132. onOpen={this.fetchAlmBindings}
  133. overlay={
  134. <GlobalNavPlusMenu
  135. canCreateApplication={canCreateApplication}
  136. canCreatePortfolio={canCreatePortfolio}
  137. canCreateProject={canCreateProject}
  138. compatibleAlms={boundAlms}
  139. onComponentCreationClick={this.handleComponentCreationClick}
  140. />
  141. }
  142. tagName="li">
  143. <a
  144. className="navbar-icon navbar-plus"
  145. href="#"
  146. title={translate('my_account.create_new_project_portfolio_or_application')}>
  147. <PlusIcon />
  148. </a>
  149. </Dropdown>
  150. {canCreateApplication && creatingComponent === ComponentQualifier.Application && (
  151. <CreateApplicationForm
  152. onClose={this.closeComponentCreationForm}
  153. onCreate={this.handleComponentCreate}
  154. />
  155. )}
  156. {governanceReady && creatingComponent === ComponentQualifier.Portfolio && (
  157. <CreateFormShim
  158. defaultQualifier={creatingComponent}
  159. onClose={this.closeComponentCreationForm}
  160. onCreate={this.handleComponentCreate}
  161. />
  162. )}
  163. </>
  164. );
  165. }
  166. }
  167. export default withRouter(GlobalNavPlus);