From 82d363f3bc37245ea92c77b5c1b0ea418f91dbb5 Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Tue, 12 Dec 2017 14:37:19 +0100 Subject: [PATCH] SONAR-10187 Provide more options to populate empty "My Projects" page --- .../js/app/components/GlobalContainer.tsx | 78 ++++++ .../main/js/app/components/GlobalFooter.js | 83 ------ .../main/js/app/components/GlobalFooter.tsx | 97 +++++++ .../app/components/GlobalFooterContainer.tsx | 8 +- ...narCloud.js => GlobalFooterSonarCloud.tsx} | 36 +-- ...alFooter-test.js => GlobalFooter-test.tsx} | 2 +- ...est.js => GlobalFooterSonarCloud-test.tsx} | 2 +- .../__snapshots__/GlobalFooter-test.js.snap | 173 ------------- .../__snapshots__/GlobalFooter-test.tsx.snap | 242 ++++++++++++++++++ .../GlobalFooterSonarCloud-test.js.snap | 57 ----- .../GlobalFooterSonarCloud-test.tsx.snap | 78 ++++++ .../js/app/components/help/GlobalHelp.d.ts | 30 +++ .../global/{GlobalNav.js => GlobalNav.tsx} | 82 +++--- .../{GlobalNavMenu.js => GlobalNavMenu.tsx} | 36 +-- .../components/nav/global/GlobalNavUser.tsx | 148 ++++------- .../global/__tests__/GlobalNavUser-test.tsx | 6 +- .../__snapshots__/GlobalNavMenu-test.js.snap | 2 - .../__snapshots__/GlobalNavUser-test.tsx.snap | 127 ++------- .../main/js/app/components/search/Search.d.ts | 28 ++ .../main/js/app/styles/components/page.css | 20 +- .../src/main/js/app/styles/init/forms.css | 6 +- server/sonar-web/src/main/js/app/types.ts | 10 + ...ganizationCard.js => OrganizationCard.tsx} | 59 ++--- ...nizationsList.js => OrganizationsList.tsx} | 17 +- .../OrganizationNavigationAdministration.tsx | 2 +- .../OrganizationNavigationHeader.tsx | 15 +- ...tionNavigationAdministration-test.tsx.snap | 4 +- ...OrganizationNavigationHeader-test.tsx.snap | 87 ++----- .../components/NoFavoriteProjects.tsx | 94 ++++++- .../__tests__/NoFavoriteProjects-test.tsx | 15 +- .../NoFavoriteProjects-test.tsx.snap | 69 +++-- .../__snapshots__/ProjectsList-test.tsx.snap | 2 +- .../src/main/js/apps/projects/styles.css | 5 + .../tutorials/onboarding/OnboardingModal.d.ts | 26 ++ .../icons-components/DropdownIcon.tsx | 6 +- .../src/main/js/components/nav/NavBar.tsx | 1 + .../ui/OrganizationListItem.tsx} | 35 +-- .../__tests__/OrganizationListItem-test.tsx | 38 +++ .../OrganizationListItem-test.tsx.snap | 41 +++ .../src/main/js/store/appState/duck.ts | 13 +- .../src/main/js/store/organizations/duck.js | 2 +- .../resources/org/sonar/l10n/core.properties | 5 +- 42 files changed, 1082 insertions(+), 805 deletions(-) create mode 100644 server/sonar-web/src/main/js/app/components/GlobalContainer.tsx delete mode 100644 server/sonar-web/src/main/js/app/components/GlobalFooter.js create mode 100644 server/sonar-web/src/main/js/app/components/GlobalFooter.tsx rename server/sonar-web/src/main/js/app/components/{GlobalFooterSonarCloud.js => GlobalFooterSonarCloud.tsx} (55%) rename server/sonar-web/src/main/js/app/components/__tests__/{GlobalFooter-test.js => GlobalFooter-test.tsx} (98%) rename server/sonar-web/src/main/js/app/components/__tests__/{GlobalFooterSonarCloud-test.js => GlobalFooterSonarCloud-test.tsx} (97%) delete mode 100644 server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooter-test.js.snap create mode 100644 server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooter-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooterSonarCloud-test.js.snap create mode 100644 server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooterSonarCloud-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/app/components/help/GlobalHelp.d.ts rename server/sonar-web/src/main/js/app/components/nav/global/{GlobalNav.js => GlobalNav.tsx} (77%) rename server/sonar-web/src/main/js/app/components/nav/global/{GlobalNavMenu.js => GlobalNavMenu.tsx} (85%) create mode 100644 server/sonar-web/src/main/js/app/components/search/Search.d.ts rename server/sonar-web/src/main/js/apps/account/organizations/{OrganizationCard.js => OrganizationCard.tsx} (50%) rename server/sonar-web/src/main/js/apps/account/organizations/{OrganizationsList.js => OrganizationsList.tsx} (77%) create mode 100644 server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.d.ts rename server/sonar-web/src/main/js/{app/components/GlobalContainer.js => components/ui/OrganizationListItem.tsx} (53%) create mode 100644 server/sonar-web/src/main/js/components/ui/__tests__/OrganizationListItem-test.tsx create mode 100644 server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/OrganizationListItem-test.tsx.snap diff --git a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx new file mode 100644 index 00000000000..cbab4678fd5 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx @@ -0,0 +1,78 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import * as PropTypes from 'prop-types'; +import GlobalNav from './nav/global/GlobalNav'; +import GlobalFooterContainer from './GlobalFooterContainer'; +import GlobalMessagesContainer from './GlobalMessagesContainer'; + +interface Props { + children: React.ReactNode; + location: { pathname: string }; +} + +interface State { + isOnboardingTutorialOpen: boolean; +} + +export default class GlobalContainer extends React.PureComponent { + static childContextTypes = { + closeOnboardingTutorial: PropTypes.func, + openOnboardingTutorial: PropTypes.func + }; + + constructor(props: Props) { + super(props); + this.state = { isOnboardingTutorialOpen: false }; + } + + getChildContext() { + return { + closeOnboardingTutorial: this.closeOnboardingTutorial, + openOnboardingTutorial: this.openOnboardingTutorial + }; + } + + openOnboardingTutorial = () => this.setState({ isOnboardingTutorialOpen: true }); + + closeOnboardingTutorial = () => this.setState({ isOnboardingTutorialOpen: false }); + + render() { + // it is important to pass `location` down to `GlobalNav` to trigger render on url change + + return ( +
+
+
+ + + {this.props.children} +
+
+ +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/app/components/GlobalFooter.js b/server/sonar-web/src/main/js/app/components/GlobalFooter.js deleted file mode 100644 index 6cd008827fa..00000000000 --- a/server/sonar-web/src/main/js/app/components/GlobalFooter.js +++ /dev/null @@ -1,83 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -// @flow -import React from 'react'; -import { Link } from 'react-router'; -import GlobalFooterSonarCloud from './GlobalFooterSonarCloud'; -import GlobalFooterBranding from './GlobalFooterBranding'; -import { translate, translateWithParameters } from '../../helpers/l10n'; - -/*:: -type Props = { - hideLoggedInInfo?: boolean, - productionDatabase: boolean, - onSonarCloud?: { value: string }, - sonarqubeVersion?: string -}; -*/ - -export default function GlobalFooter( - { hideLoggedInInfo, productionDatabase, onSonarCloud, sonarqubeVersion } /*: Props */ -) { - if (onSonarCloud && onSonarCloud.value === 'true') { - return ; - } - - return ( - - ); -} diff --git a/server/sonar-web/src/main/js/app/components/GlobalFooter.tsx b/server/sonar-web/src/main/js/app/components/GlobalFooter.tsx new file mode 100644 index 00000000000..39422c1e761 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/GlobalFooter.tsx @@ -0,0 +1,97 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { Link } from 'react-router'; +import GlobalFooterSonarCloud from './GlobalFooterSonarCloud'; +import GlobalFooterBranding from './GlobalFooterBranding'; +import { translate, translateWithParameters } from '../../helpers/l10n'; + +interface Props { + hideLoggedInInfo?: boolean; + productionDatabase: boolean; + onSonarCloud?: { value: string }; + sonarqubeVersion?: string; +} + +export default function GlobalFooter({ + hideLoggedInInfo, + productionDatabase, + onSonarCloud, + sonarqubeVersion +}: Props) { + if (onSonarCloud && onSonarCloud.value === 'true') { + return ; + } + + return ( + + ); +} diff --git a/server/sonar-web/src/main/js/app/components/GlobalFooterContainer.tsx b/server/sonar-web/src/main/js/app/components/GlobalFooterContainer.tsx index e9b31253cdd..1e85753db06 100644 --- a/server/sonar-web/src/main/js/app/components/GlobalFooterContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/GlobalFooterContainer.tsx @@ -21,7 +21,13 @@ import { connect } from 'react-redux'; import { getAppState, getGlobalSettingValue } from '../../store/rootReducer'; import GlobalFooter from './GlobalFooter'; -const mapStateToProps = (state: any) => ({ +interface StateProps { + onSonarCloud?: { value: string }; + productionDatabase: boolean; + sonarqubeVersion?: string; +} + +const mapStateToProps = (state: any): StateProps => ({ sonarqubeVersion: getAppState(state).version, productionDatabase: getAppState(state).productionDatabase, onSonarCloud: getGlobalSettingValue(state, 'sonar.sonarcloud.enabled') diff --git a/server/sonar-web/src/main/js/app/components/GlobalFooterSonarCloud.js b/server/sonar-web/src/main/js/app/components/GlobalFooterSonarCloud.tsx similarity index 55% rename from server/sonar-web/src/main/js/app/components/GlobalFooterSonarCloud.js rename to server/sonar-web/src/main/js/app/components/GlobalFooterSonarCloud.tsx index 9c601855952..1ccd6d5c9ff 100644 --- a/server/sonar-web/src/main/js/app/components/GlobalFooterSonarCloud.js +++ b/server/sonar-web/src/main/js/app/components/GlobalFooterSonarCloud.tsx @@ -17,8 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { translate } from '../../helpers/l10n'; export default function GlobalFooterSonarCloud() { @@ -32,19 +31,26 @@ export default function GlobalFooterSonarCloud() { . All rights reserved. -
- {translate('footer.news')} - {' - '} - {translate('footer.terms')} - {' - '} - {translate('footer.twitter')} - {' - '} - {translate('footer.get_started')} - {' - '} - {translate('footer.help')} - {' - '} - {translate('footer.about')} -
+ ); } diff --git a/server/sonar-web/src/main/js/app/components/__tests__/GlobalFooter-test.js b/server/sonar-web/src/main/js/app/components/__tests__/GlobalFooter-test.tsx similarity index 98% rename from server/sonar-web/src/main/js/app/components/__tests__/GlobalFooter-test.js rename to server/sonar-web/src/main/js/app/components/__tests__/GlobalFooter-test.tsx index 74ef097213f..f8325090a85 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/GlobalFooter-test.js +++ b/server/sonar-web/src/main/js/app/components/__tests__/GlobalFooter-test.tsx @@ -17,8 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import * as React from 'react'; import { shallow } from 'enzyme'; -import React from 'react'; import GlobalFooter from '../GlobalFooter'; it('should render the only logged in information', () => { diff --git a/server/sonar-web/src/main/js/app/components/__tests__/GlobalFooterSonarCloud-test.js b/server/sonar-web/src/main/js/app/components/__tests__/GlobalFooterSonarCloud-test.tsx similarity index 97% rename from server/sonar-web/src/main/js/app/components/__tests__/GlobalFooterSonarCloud-test.js rename to server/sonar-web/src/main/js/app/components/__tests__/GlobalFooterSonarCloud-test.tsx index 2f41d408c61..98644558082 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/GlobalFooterSonarCloud-test.js +++ b/server/sonar-web/src/main/js/app/components/__tests__/GlobalFooterSonarCloud-test.tsx @@ -17,8 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import * as React from 'react'; import { shallow } from 'enzyme'; -import React from 'react'; import GlobalFooterSonarCloud from '../GlobalFooterSonarCloud'; it('should render correctly', () => { diff --git a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooter-test.js.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooter-test.js.snap deleted file mode 100644 index 438127ce0bc..00000000000 --- a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooter-test.js.snap +++ /dev/null @@ -1,173 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should display the sq version 1`] = ` - -`; - -exports[`should not render the only logged in information 1`] = ` - -`; - -exports[`should render SonarCloud footer 1`] = ``; - -exports[`should render the only logged in information 1`] = ` - -`; - -exports[`should show the db warning message 1`] = ` -
-

- footer.production_database_warning -

-

- footer.production_database_explanation -

-
-`; diff --git a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooter-test.tsx.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooter-test.tsx.snap new file mode 100644 index 00000000000..b7069f5b1cb --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooter-test.tsx.snap @@ -0,0 +1,242 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should display the sq version 1`] = ` + +`; + +exports[`should not render the only logged in information 1`] = ` + +`; + +exports[`should render SonarCloud footer 1`] = ``; + +exports[`should render the only logged in information 1`] = ` + +`; + +exports[`should show the db warning message 1`] = ` +
+

+ footer.production_database_warning +

+

+ footer.production_database_explanation +

+
+`; diff --git a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooterSonarCloud-test.js.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooterSonarCloud-test.js.snap deleted file mode 100644 index 0a3aa1953b1..00000000000 --- a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooterSonarCloud-test.js.snap +++ /dev/null @@ -1,57 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` - -`; diff --git a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooterSonarCloud-test.tsx.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooterSonarCloud-test.tsx.snap new file mode 100644 index 00000000000..f7a6f4b0228 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooterSonarCloud-test.tsx.snap @@ -0,0 +1,78 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` + +`; diff --git a/server/sonar-web/src/main/js/app/components/help/GlobalHelp.d.ts b/server/sonar-web/src/main/js/app/components/help/GlobalHelp.d.ts new file mode 100644 index 00000000000..ea8fb503279 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/help/GlobalHelp.d.ts @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { CurrentUser, AppState } from '../../types'; + +export interface Props { + currentUser: CurrentUser; + onClose: () => void; + onSonarCloud?: boolean; + onTutorialSelect: () => void; +} + +export default class GlobalHelp extends React.PureComponent {} diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.js b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx similarity index 77% rename from server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.js rename to server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx index 564775c0179..04404a1fe1e 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.js +++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx @@ -17,8 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { connect } from 'react-redux'; import GlobalNavBranding from './GlobalNavBranding'; import GlobalNavMenu from './GlobalNavMenu'; @@ -28,42 +27,41 @@ import GlobalNavPlus from './GlobalNavPlus'; import Search from '../../search/Search'; import GlobalHelp from '../../help/GlobalHelp'; import * as theme from '../../../theme'; -import { isLoggedIn } from '../../../types'; +import { isLoggedIn, CurrentUser, AppState } from '../../../types'; +import OnboardingModal from '../../../../apps/tutorials/onboarding/OnboardingModal'; import NavBar from '../../../../components/nav/NavBar'; import Tooltip from '../../../../components/controls/Tooltip'; import HelpIcon from '../../../../components/icons-components/HelpIcon'; -import OnboardingModal from '../../../../apps/tutorials/onboarding/OnboardingModal'; +import { translate } from '../../../../helpers/l10n'; import { getCurrentUser, getAppState, getGlobalSettingValue } from '../../../../store/rootReducer'; import { skipOnboarding } from '../../../../store/users/actions'; -import { translate } from '../../../../helpers/l10n'; import './GlobalNav.css'; -/*:: -type Props = { - appState: { organizationsEnabled: boolean }, - currentUser: { isLoggedIn: boolean, showOnboardingTutorial: boolean }, - location: { pathname: string }, - skipOnboarding: () => void, - onSonarCloud: boolean -}; -*/ +interface StateProps { + appState: AppState; + currentUser: CurrentUser; + onSonarCloud: boolean; +} -/*:: -type State = { - helpOpen: boolean, - onboardingTutorialOpen: boolean, - onboardingTutorialTooltip: boolean -}; -*/ - -class GlobalNav extends React.PureComponent { - /*:: interval: ?number; */ - /*:: props: Props; */ - state /*: State */ = { - helpOpen: false, - onboardingTutorialOpen: false, - onboardingTutorialTooltip: false - }; +interface DispatchProps { + skipOnboarding: () => void; +} + +interface Props extends StateProps, DispatchProps { + closeOnboardingTutorial: () => void; + isOnboardingTutorialOpen: boolean; + location: { pathname: string }; + openOnboardingTutorial: () => void; +} + +interface State { + helpOpen: boolean; + onboardingTutorialTooltip: boolean; +} + +class GlobalNav extends React.PureComponent { + interval?: number; + state: State = { helpOpen: false, onboardingTutorialTooltip: false }; componentDidMount() { window.addEventListener('keypress', this.onKeyPress); @@ -79,9 +77,9 @@ class GlobalNav extends React.PureComponent { window.removeEventListener('keypress', this.onKeyPress); } - onKeyPress = e => { - const tagName = e.target.tagName; - const code = e.keyCode || e.which; + onKeyPress = (event: KeyboardEvent) => { + const { tagName } = event.target as HTMLElement; + const code = event.keyCode || event.which; const isInput = tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA'; const isTriggerKey = code === 63; if (!isInput && isTriggerKey) { @@ -89,7 +87,7 @@ class GlobalNav extends React.PureComponent { } }; - handleHelpClick = event => { + handleHelpClick = (event: React.SyntheticEvent) => { event.preventDefault(); this.openHelp(); }; @@ -98,12 +96,16 @@ class GlobalNav extends React.PureComponent { closeHelp = () => this.setState({ helpOpen: false }); - openOnboardingTutorial = () => this.setState({ helpOpen: false, onboardingTutorialOpen: true }); + openOnboardingTutorial = () => { + this.setState({ helpOpen: false }); + this.props.openOnboardingTutorial(); + }; closeOnboardingTutorial = () => { - this.setState({ onboardingTutorialOpen: false, onboardingTutorialTooltip: true }); + this.setState({ onboardingTutorialTooltip: true }); this.props.skipOnboarding(); - this.interval = setInterval(() => { + this.props.closeOnboardingTutorial(); + this.interval = window.setInterval(() => { this.setState({ onboardingTutorialTooltip: false }); }, 3000); }; @@ -148,7 +150,7 @@ class GlobalNav extends React.PureComponent { /> )} - {this.state.onboardingTutorialOpen && ( + {this.props.isOnboardingTutorialOpen && ( )} @@ -156,7 +158,7 @@ class GlobalNav extends React.PureComponent { } } -const mapStateToProps = state => { +const mapStateToProps = (state: any): StateProps => { const sonarCloudSetting = getGlobalSettingValue(state, 'sonar.sonarcloud.enabled'); return { @@ -166,6 +168,6 @@ const mapStateToProps = state => { }; }; -const mapDispatchToProps = { skipOnboarding }; +const mapDispatchToProps: DispatchProps = { skipOnboarding }; export default connect(mapStateToProps, mapDispatchToProps)(GlobalNav); diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.tsx similarity index 85% rename from server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js rename to server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.tsx index 7cea4c635b2..50277e52187 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js +++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.tsx @@ -17,31 +17,23 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; -import PropTypes from 'prop-types'; +import * as React from 'react'; import { Link } from 'react-router'; -import { isLoggedIn } from '../../../../app/types'; +import { isLoggedIn, CurrentUser, AppState, Extension } from '../../../../app/types'; import { translate } from '../../../../helpers/l10n'; -import { getQualityGatesUrl } from '../../../../helpers/urls'; +import { getQualityGatesUrl, getBaseUrl } from '../../../../helpers/urls'; import { isMySet } from '../../../../apps/issues/utils'; -export default class GlobalNavMenu extends React.PureComponent { - static propTypes = { - appState: PropTypes.object.isRequired, - currentUser: PropTypes.object.isRequired, - location: PropTypes.shape({ - pathname: PropTypes.string.isRequired - }).isRequired, - onSonarCloud: PropTypes.bool - }; - - static defaultProps = { - globalDashboards: [], - globalPages: [] - }; +interface Props { + appState: AppState; + currentUser: CurrentUser; + location: { pathname: string }; + onSonarCloud: boolean; +} - activeLink(url) { - return window.location.pathname.indexOf(window.baseUrl + url) === 0 ? 'active' : null; +export default class GlobalNavMenu extends React.PureComponent { + activeLink(url: string) { + return window.location.pathname.indexOf(getBaseUrl() + url) === 0 ? 'active' : undefined; } renderProjects() { @@ -144,7 +136,7 @@ export default class GlobalNavMenu extends React.PureComponent { ); } - renderGlobalPageLink = ({ key, name }) => { + renderGlobalPageLink = ({ key, name }: Extension) => { return (
  • {name} @@ -153,7 +145,7 @@ export default class GlobalNavMenu extends React.PureComponent { }; renderMore() { - const { globalPages } = this.props.appState; + const { globalPages = [] } = this.props.appState; const withoutPortfolios = globalPages.filter(page => page.key !== 'governance/portfolios'); if (withoutPortfolios.length === 0) { return null; diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.tsx b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.tsx index 1649a5e370e..fb19ba06d40 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.tsx @@ -25,10 +25,10 @@ import { Link } from 'react-router'; import * as theme from '../../../theme'; import { CurrentUser, LoggedInUser, isLoggedIn, Organization } from '../../../types'; import Avatar from '../../../../components/ui/Avatar'; -import OrganizationLink from '../../../../components/ui/OrganizationLink'; +import OrganizationListItem from '../../../../components/ui/OrganizationListItem'; import { translate } from '../../../../helpers/l10n'; import { getBaseUrl } from '../../../../helpers/urls'; -import OrganizationAvatar from '../../../../components/common/OrganizationAvatar'; +import Dropdown from '../../../../components/controls/Dropdown'; interface Props { appState: { organizationsEnabled: boolean }; @@ -36,32 +36,11 @@ interface Props { organizations: Organization[]; } -interface State { - open: boolean; -} - -export default class GlobalNavUser extends React.PureComponent { - node?: HTMLElement | null; - +export default class GlobalNavUser extends React.PureComponent { static contextTypes = { router: PropTypes.object }; - constructor(props: Props) { - super(props); - this.state = { open: false }; - } - - componentWillUnmount() { - window.removeEventListener('click', this.handleClickOutside); - } - - handleClickOutside = (event: MouseEvent) => { - if (!this.node || !this.node.contains(event.target as Node)) { - this.closeDropdown(); - } - }; - handleLogin = (event: React.SyntheticEvent) => { event.preventDefault(); const shouldReturnToCurrentPage = window.location.pathname !== `${getBaseUrl()}/about`; @@ -76,98 +55,61 @@ export default class GlobalNavUser extends React.PureComponent { handleLogout = (event: React.SyntheticEvent) => { event.preventDefault(); - this.closeDropdown(); this.context.router.push('/sessions/logout'); }; - toggleDropdown = (event: React.SyntheticEvent) => { - event.preventDefault(); - if (this.state.open) { - this.closeDropdown(); - } else { - this.openDropdown(); - } - }; - - openDropdown = () => { - window.addEventListener('click', this.handleClickOutside, true); - this.setState({ open: true }); - }; - - closeDropdown = () => { - window.removeEventListener('click', this.handleClickOutside); - this.setState({ open: false }); - }; - renderAuthenticated() { const { organizations } = this.props; const currentUser = this.props.currentUser as LoggedInUser; const hasOrganizations = this.props.appState.organizationsEnabled && organizations.length > 0; return ( -
  • (this.node = node)}> - - - - {this.state.open && ( -
      -
    • -
      - {currentUser.name} -
      - {currentUser.email != null && ( -
      - {currentUser.email} + + {({ onToggleClick, open }) => ( +
    • + + + +
        +
      • +
        + {currentUser.name}
        - )} -
      • -
      • -
      • - - {translate('my_account.page')} - -
      • - {hasOrganizations &&
      • } - {hasOrganizations && ( + {currentUser.email != null && ( +
        + {currentUser.email} +
        + )} +
      • +
      • - - {translate('my_organizations')} - + {translate('my_account.page')}
      • - )} - {hasOrganizations && - sortBy(organizations, org => org.name.toLowerCase()).map(organization => ( -
      • - -
        - - {organization.name} -
        - {organization.isAdmin && ( - {translate('admin')} - )} -
        + {hasOrganizations &&
      • } + {hasOrganizations && ( +
      • + {translate('my_organizations')}
      • - ))} - {hasOrganizations &&
      • } -
      • - - {translate('layout.logout')} - -
      • -
      + )} + {hasOrganizations && + sortBy(organizations, org => org.name.toLowerCase()).map(organization => ( + + ))} + {hasOrganizations &&
    • } +
    • + + {translate('layout.logout')} + +
    • +
    +
  • )} - + ); } diff --git a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavUser-test.tsx b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavUser-test.tsx index 659fe6def16..dbec4b97992 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavUser-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavUser-test.tsx @@ -43,7 +43,7 @@ it('should render the right interface for logged in user', () => { ); wrapper.setState({ open: true }); - expect(wrapper).toMatchSnapshot(); + expect(wrapper.find('Dropdown').dive()).toMatchSnapshot(); }); it('should render user organizations', () => { @@ -51,7 +51,7 @@ it('should render user organizations', () => { ); wrapper.setState({ open: true }); - expect(wrapper).toMatchSnapshot(); + expect(wrapper.find('Dropdown').dive()).toMatchSnapshot(); }); it('should not render user organizations when they are not activated', () => { @@ -63,5 +63,5 @@ it('should not render user organizations when they are not activated', () => { /> ); wrapper.setState({ open: true }); - expect(wrapper).toMatchSnapshot(); + expect(wrapper.find('Dropdown').dive()).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavMenu-test.js.snap b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavMenu-test.js.snap index 673cc003e3c..34dd8f91396 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavMenu-test.js.snap +++ b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavMenu-test.js.snap @@ -32,7 +32,6 @@ exports[`should show administration menu if the user has the rights 1`] = `
  • -
  • - -
    - - - bar - -
    -
    -
  • -
  • + - -
    - - - Foo - -
    -
    -
  • -
  • + - -
    - - - MyOrg - -
    -
    -
  • + } + />
  • {} diff --git a/server/sonar-web/src/main/js/app/styles/components/page.css b/server/sonar-web/src/main/js/app/styles/components/page.css index a1a7d2b8610..53855c6747f 100644 --- a/server/sonar-web/src/main/js/app/styles/components/page.css +++ b/server/sonar-web/src/main/js/app/styles/components/page.css @@ -140,18 +140,10 @@ } .page-footer a:hover, -.page-footer a:active, -.page-footer a:focus { - color: var(--blue); -} - -.page-footer a:hover { - border-bottom-color: var(--lightBlue); -} - .page-footer a:active, .page-footer a:focus { border-bottom-color: var(--lightBlue); + color: var(--blue); } .page-footer-with-sidebar { @@ -162,6 +154,16 @@ max-width: 980px; } +.page-footer-menu-item { + display: inline-block; +} + +.page-footer-menu-item + .page-footer-menu-item::before { + content: '-'; + padding: 0 calc(0.5 * var(--gridSize)); + user-select: none; +} + .page-with-sidebar { display: flex; } diff --git a/server/sonar-web/src/main/js/app/styles/init/forms.css b/server/sonar-web/src/main/js/app/styles/init/forms.css index a9a610f6632..97287e21ac0 100644 --- a/server/sonar-web/src/main/js/app/styles/init/forms.css +++ b/server/sonar-web/src/main/js/app/styles/init/forms.css @@ -118,7 +118,7 @@ input[type='button'] { display: inline-block; vertical-align: baseline; height: var(--controlHeight); - line-height: 22px; + line-height: calc(var(--controlHeight) - 2px); padding: 0 12px; border: 1px solid var(--darkBlue); border-radius: 2px; @@ -184,6 +184,10 @@ input[type='button']:disabled:focus { box-shadow: none; } +.button svg { + padding-top: calc((var(--controlHeight) - 16px - 2px) / 2); +} + .button-red, input[type='submit'].button-red { border-color: var(--red); diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts index af3c21a26db..2b2bc7a64a8 100644 --- a/server/sonar-web/src/main/js/app/types.ts +++ b/server/sonar-web/src/main/js/app/types.ts @@ -150,3 +150,13 @@ export interface LoggedInUser extends CurrentUser { export function isLoggedIn(user: CurrentUser): user is LoggedInUser { return user.isLoggedIn; } + +export interface AppState { + adminPages?: Extension[]; + authenticationError: boolean; + authorizationError: boolean; + canAdmin?: boolean; + globalPages?: Extension[]; + organizationsEnabled: boolean; + qualifiers: string[]; +} diff --git a/server/sonar-web/src/main/js/apps/account/organizations/OrganizationCard.js b/server/sonar-web/src/main/js/apps/account/organizations/OrganizationCard.tsx similarity index 50% rename from server/sonar-web/src/main/js/apps/account/organizations/OrganizationCard.js rename to server/sonar-web/src/main/js/apps/account/organizations/OrganizationCard.tsx index 1bded7bb20f..0f8b57127a7 100644 --- a/server/sonar-web/src/main/js/apps/account/organizations/OrganizationCard.js +++ b/server/sonar-web/src/main/js/apps/account/organizations/OrganizationCard.tsx @@ -17,61 +17,44 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; +import OrganizationAvatar from '../../../components/common/OrganizationAvatar'; import OrganizationLink from '../../../components/ui/OrganizationLink'; -/*:: import type { Organization } from '../../../store/organizations/duck'; */ import { translate } from '../../../helpers/l10n'; +import { Organization } from '../../../app/types'; -/*:: -type Props = { - organization: Organization -}; -*/ - -export default function OrganizationCard(props /*: Props */) { - const { organization } = props; +interface Props { + organization: Organization; +} +export default function OrganizationCard({ organization }: Props) { return (
    -
    ); } diff --git a/server/sonar-web/src/main/js/apps/account/organizations/OrganizationsList.js b/server/sonar-web/src/main/js/apps/account/organizations/OrganizationsList.tsx similarity index 77% rename from server/sonar-web/src/main/js/apps/account/organizations/OrganizationsList.js rename to server/sonar-web/src/main/js/apps/account/organizations/OrganizationsList.tsx index 921b636dd3a..5b0b8ccc367 100644 --- a/server/sonar-web/src/main/js/apps/account/organizations/OrganizationsList.js +++ b/server/sonar-web/src/main/js/apps/account/organizations/OrganizationsList.tsx @@ -17,22 +17,19 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { sortBy } from 'lodash'; import OrganizationCard from './OrganizationCard'; -/*:: import type { Organization } from '../../../store/organizations/duck'; */ +import { Organization } from '../../../app/types'; -/*:: -type Props = { - organizations: Array -}; -*/ +interface Props { + organizations: Organization[]; +} -export default function OrganizationsList(props /*: Props */) { +export default function OrganizationsList({ organizations }: Props) { return (
      - {sortBy(props.organizations, organization => organization.name.toLocaleLowerCase()).map( + {sortBy(organizations, organization => organization.name.toLocaleLowerCase()).map( organization => (
    • diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationAdministration.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationAdministration.tsx index 98d50b19d61..accf47cbd70 100644 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationAdministration.tsx +++ b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationAdministration.tsx @@ -56,7 +56,7 @@ export default function OrganizationNavigationAdministration({ location, organiz href="#" onClick={onToggleClick}> {translate('layout.settings')} - +
        {extensions.map(extension => ( diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationHeader.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationHeader.tsx index afa4332544c..84f0256e579 100644 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationHeader.tsx +++ b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationHeader.tsx @@ -24,8 +24,7 @@ import { Organization } from '../../../app/types'; import OrganizationAvatar from '../../../components/common/OrganizationAvatar'; import Dropdown from '../../../components/controls/Dropdown'; import DropdownIcon from '../../../components/icons-components/DropdownIcon'; -import OrganizationLink from '../../../components/ui/OrganizationLink'; -import { translate } from '../../../helpers/l10n'; +import OrganizationListItem from '../../../components/ui/OrganizationListItem'; interface Props { organization: Organization; @@ -49,17 +48,7 @@ export default function OrganizationNavigationHeader({ organization, organizatio
          {sortBy(other, org => org.name.toLowerCase()).map(organization => ( -
        • - -
          - - {organization.name} -
          - {organization.isAdmin && ( - {translate('admin')} - )} -
          -
        • + ))}
        diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationAdministration-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationAdministration-test.tsx.snap index 61f766d7cf7..713261b2290 100644 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationAdministration-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationAdministration-test.tsx.snap @@ -11,7 +11,9 @@ exports[`renders 1`] = ` onClick={[Function]} > layout.settings - +
          -
        • - -
          - - - org1 - -
          - - admin - -
          -
        • -
        • + - -
          - - - org2 - -
          -
          -
        • + } + />
        `; diff --git a/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx b/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx index b562b7d46bf..d1bb1d60a34 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx @@ -19,22 +19,90 @@ */ import * as React from 'react'; import { Link } from 'react-router'; +import * as classNames from 'classnames'; +import { connect } from 'react-redux'; +import * as PropTypes from 'prop-types'; +import { sortBy } from 'lodash'; +import { Organization } from '../../../app/types'; +import DropdownIcon from '../../../components/icons-components/DropdownIcon'; +import Dropdown from '../../../components/controls/Dropdown'; +import { getMyOrganizations } from '../../../store/rootReducer'; +import OrganizationListItem from '../../../components/ui/OrganizationListItem'; import { translate } from '../../../helpers/l10n'; -interface Props { +interface StateProps { + organizations: Organization[]; +} + +interface Props extends StateProps { onSonarCloud: boolean; } -export default function NoFavoriteProjects({ onSonarCloud }: Props) { - return ( -
        -

        {translate('projects.no_favorite_projects')}

        -

        {translate('projects.no_favorite_projects.engagement')}

        -

        - - {translate('projects.explore_projects')} - -

        -
        - ); +export class NoFavoriteProjects extends React.PureComponent { + static contextTypes = { + openOnboardingTutorial: PropTypes.func + }; + + onAnalyzeProjectClick = (event: React.SyntheticEvent) => { + event.preventDefault(); + event.currentTarget.blur(); + this.context.openOnboardingTutorial(); + }; + + render() { + const { onSonarCloud, organizations } = this.props; + return ( +
        +

        {translate('projects.no_favorite_projects')}

        + {onSonarCloud ? ( +
        +

        {translate('projects.no_favorite_projects.how_to_add_projects')}

        +
        + + {translate('my_account.analyze_new_project')} + + + {({ onToggleClick, open }) => ( +
        + + {translate('projects.no_favorite_projects.favorite_projects_from_orgs')} + + +
          + {sortBy(organizations, org => org.name.toLowerCase()).map(organization => ( + + ))} +
        +
        + )} +
        + + {translate('projects.no_favorite_projects.favorite_public_projects')} + +
        +
        + ) : ( +
        +

        + {translate('projects.no_favorite_projects.engagement')} +

        +

        + + {translate('projects.explore_projects')} + +

        +
        + )} +
        + ); + } } + +const mapStateToProps = (state: any): StateProps => ({ + organizations: getMyOrganizations(state) +}); + +export default connect(mapStateToProps)(NoFavoriteProjects); diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/NoFavoriteProjects-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/NoFavoriteProjects-test.tsx index 3635c476f77..8f767812c60 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/NoFavoriteProjects-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/NoFavoriteProjects-test.tsx @@ -19,8 +19,19 @@ */ import * as React from 'react'; import { shallow } from 'enzyme'; -import NoFavoriteProjects from '../NoFavoriteProjects'; +import { NoFavoriteProjects } from '../NoFavoriteProjects'; +import { Visibility } from '../../../../app/types'; it('renders', () => { - expect(shallow()).toMatchSnapshot(); + expect(shallow()).toMatchSnapshot(); +}); + +it('renders for SonarCloud', () => { + const organizations = [ + { isAdmin: true, key: 'org1', name: 'org1', projectVisibility: Visibility.Public }, + { isAdmin: false, key: 'org2', name: 'org2', projectVisibility: Visibility.Public } + ]; + expect( + shallow() + ).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/NoFavoriteProjects-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/NoFavoriteProjects-test.tsx.snap index 3b6652f10f8..a7dede57348 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/NoFavoriteProjects-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/NoFavoriteProjects-test.tsx.snap @@ -7,22 +7,61 @@ exports[`renders 1`] = `

        projects.no_favorite_projects

        -

        - projects.no_favorite_projects.engagement -

        -

        +

        + projects.no_favorite_projects.engagement +

        +

        + + projects.explore_projects + +

        + + +`; + +exports[`renders for SonarCloud 1`] = ` +
        +

        + projects.no_favorite_projects +

        +
        - + projects.no_favorite_projects.how_to_add_projects +

        +
        - projects.explore_projects - -

        + + my_account.analyze_new_project + + + + projects.no_favorite_projects.favorite_public_projects + +
        +
        `; diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap index 93fb447b814..b2552033bef 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap @@ -47,6 +47,6 @@ exports[`renders different types of "no projects" 3`] = `
        - +
        `; diff --git a/server/sonar-web/src/main/js/apps/projects/styles.css b/server/sonar-web/src/main/js/apps/projects/styles.css index 707f0243994..e1084c7f01f 100644 --- a/server/sonar-web/src/main/js/apps/projects/styles.css +++ b/server/sonar-web/src/main/js/apps/projects/styles.css @@ -267,3 +267,8 @@ margin-left: -250px; text-align: center; } + +.projects-empty-list { + padding: calc(4 * var(--gridSize)) 0; + text-align: center; +} diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.d.ts b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.d.ts new file mode 100644 index 00000000000..d5d3100cbfa --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/OnboardingModal.d.ts @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; + +export interface Props { + onFinish: () => void; +} + +export default class OnboardingModal extends React.PureComponent {} diff --git a/server/sonar-web/src/main/js/components/icons-components/DropdownIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/DropdownIcon.tsx index 20bc9629f31..7cd3226777a 100644 --- a/server/sonar-web/src/main/js/components/icons-components/DropdownIcon.tsx +++ b/server/sonar-web/src/main/js/components/icons-components/DropdownIcon.tsx @@ -24,13 +24,13 @@ export default function DropdownIcon({ className, fill = 'currentColor', size = return ( - + -
        -
        - - - {props.children} +
      • + +
        + + {organization.name}
        -
      • - -
        + {organization.isAdmin && ( + {translate('admin')} + )} + + ); } diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/OrganizationListItem-test.tsx b/server/sonar-web/src/main/js/components/ui/__tests__/OrganizationListItem-test.tsx new file mode 100644 index 00000000000..dd340f58686 --- /dev/null +++ b/server/sonar-web/src/main/js/components/ui/__tests__/OrganizationListItem-test.tsx @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import OrganizationListItem from '../OrganizationListItem'; +import { Visibility } from '../../../app/types'; + +it('renders', () => { + expect( + shallow( + + ) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/OrganizationListItem-test.tsx.snap b/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/OrganizationListItem-test.tsx.snap new file mode 100644 index 00000000000..bd3a76ad927 --- /dev/null +++ b/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/OrganizationListItem-test.tsx.snap @@ -0,0 +1,41 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +
      • + +
        + + + org + +
        + + admin + +
        +
      • +`; diff --git a/server/sonar-web/src/main/js/store/appState/duck.ts b/server/sonar-web/src/main/js/store/appState/duck.ts index abb05d0b309..dd0f2a8c962 100644 --- a/server/sonar-web/src/main/js/store/appState/duck.ts +++ b/server/sonar-web/src/main/js/store/appState/duck.ts @@ -18,15 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Extension } from '../../app/types'; - -interface AppState { - adminPages?: Extension[]; - authenticationError: boolean; - authorizationError: boolean; - organizationsEnabled: boolean; - qualifiers?: string[]; -} +import { Extension, AppState } from '../../app/types'; interface SetAppStateAction { type: 'SET_APP_STATE'; @@ -62,7 +54,8 @@ export function requireAuthorization(): RequireAuthorizationAction { const defaultValue: AppState = { authenticationError: false, authorizationError: false, - organizationsEnabled: false + organizationsEnabled: false, + qualifiers: [] }; export default function(state: AppState = defaultValue, action: Action): AppState { diff --git a/server/sonar-web/src/main/js/store/organizations/duck.js b/server/sonar-web/src/main/js/store/organizations/duck.js index 4b4023f97ce..35578963545 100644 --- a/server/sonar-web/src/main/js/store/organizations/duck.js +++ b/server/sonar-web/src/main/js/store/organizations/duck.js @@ -199,7 +199,7 @@ function byKey(state /*: ByKeyState */ = {}, action /*: Action */) { case 'RECEIVE_MY_ORGANIZATIONS': return onReceiveOrganizations(state, action); case 'CREATE_ORGANIZATION': - return { ...state, [action.organization.key]: action.organization }; + return { ...state, [action.organization.key]: { ...action.organization, isAdmin: true } }; case 'UPDATE_ORGANIZATION': return { ...state, diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 030f1b305bf..1fa402c992e 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -685,6 +685,9 @@ projects._projects=projects projects.no_projects.empty_instance=Once you analyze some projects, they will show up here. projects.no_favorite_projects=You don't have any favorite projects yet. projects.no_favorite_projects.engagement=Discover and mark as favorites projects you are interested in to have a quick access to them. +projects.no_favorite_projects.how_to_add_projects=Here is how to add projects to this page +projects.no_favorite_projects.favorite_projects_from_orgs=Favorite projects from your orgs +projects.no_favorite_projects.favorite_public_projects=Favorite public projects projects.explore_projects=Explore Projects projects.not_analyzed=Project is not analyzed yet. projects.no_leak_period=Project has no leak data yet. @@ -2472,7 +2475,7 @@ footer.community=Community footer.documentation=Documentation footer.get_started=Get Started footer.help=Help -footer.licence=LGPL v3 +footer.license=LGPL v3 footer.news=News footer.plugins=Plugins footer.production_database_explanation=The embedded database will not scale, it will not support upgrading to newer versions of SonarQube, and there is no support for migrating your data out of it into a different database engine. -- 2.39.5