From 3fc77718cf0e14555367dd8f3492205db3f5ce8c Mon Sep 17 00:00:00 2001 From: Grégoire Aubert Date: Tue, 18 Dec 2018 11:53:09 +0100 Subject: SONARCLOUD-264 Use same empty organization space in projects page --- .../create/organization/AutoOrganizationCreate.tsx | 4 +- .../create/organization/CreateOrganization.tsx | 7 +- .../__tests__/AutoOrganizationCreate-test.tsx | 2 +- .../__tests__/CreateOrganization-test.tsx | 11 +- .../organizations/components/OrganizationEmpty.css | 23 +++ .../organizations/components/OrganizationEmpty.tsx | 69 +++++++ .../components/OrganizationJustCreated.css | 23 --- .../components/OrganizationJustCreated.tsx | 69 ------- .../organizations/components/OrganizationPage.tsx | 21 +-- .../__tests__/OrganizationEmpty-test.tsx | 63 +++++++ .../__tests__/OrganizationJustCreated-test.tsx | 63 ------- .../__snapshots__/OrganizationEmpty-test.tsx.snap | 41 +++++ .../OrganizationJustCreated-test.tsx.snap | 41 ----- .../src/main/js/apps/organizations/routes.ts | 7 +- .../js/apps/projects/components/AllProjects.tsx | 115 ++++++++---- .../components/__tests__/AllProjects-test.tsx | 36 +++- .../__snapshots__/AllProjects-test.tsx.snap | 200 +++++++++++++++++++++ 17 files changed, 520 insertions(+), 275 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.css create mode 100644 server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.tsx delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/OrganizationJustCreated.css delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/OrganizationJustCreated.tsx create mode 100644 server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationEmpty-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationJustCreated-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEmpty-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationJustCreated-test.tsx.snap (limited to 'server/sonar-web/src') diff --git a/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx b/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx index 11f6fe3fbc5..7a805ec17ba 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx @@ -48,7 +48,7 @@ interface Props { handleOrgDetailsFinish: (organization: T.Organization) => Promise; handleOrgDetailsStepOpen: () => void; onDone: () => void; - onOrgCreated: (organization: string, justCreated?: boolean) => void; + onOrgCreated: (organization: string) => void; onUpgradeFail: () => void; organization?: T.Organization; step: Step; @@ -72,7 +72,7 @@ export default class AutoOrganizationCreate extends React.PureComponent this.props.onOrgCreated(organization, false)); + }).then(() => this.props.onOrgCreated(organization)); }; handleCreateOrganization = () => { diff --git a/server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx b/server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx index 7ccc09db294..e368675861c 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx +++ b/server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx @@ -205,7 +205,7 @@ export class CreateOrganization extends React.PureComponent { + handleOrgCreated = (organization: string) => { this.props.skipOnboarding(); if (this.isStoredTimestampValid(ORGANIZATION_IMPORT_REDIRECT_TO_PROJECT_TIMESTAMP)) { this.props.router.push({ @@ -213,10 +213,7 @@ export class CreateOrganization extends React.PureComponent { organization: 'foo' }); await waitAndUpdate(wrapper); - expect(onOrgCreated).toHaveBeenCalledWith('foo', false); + expect(onOrgCreated).toHaveBeenCalledWith('foo'); }); function shallowRender(props: Partial = {}) { diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/CreateOrganization-test.tsx b/server/sonar-web/src/main/js/apps/create/organization/__tests__/CreateOrganization-test.tsx index 4da19584a2e..ddd5ebcd85f 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/CreateOrganization-test.tsx +++ b/server/sonar-web/src/main/js/apps/create/organization/__tests__/CreateOrganization-test.tsx @@ -226,16 +226,7 @@ it('should redirect to organization page after creation', async () => { wrapper.setState({ organization: boundOrganization }); wrapper.instance().handleOrgCreated('foo'); - expect(push).toHaveBeenCalledWith({ - pathname: '/organizations/foo', - state: { justCreated: true } - }); - - wrapper.instance().handleOrgCreated('foo', false); - expect(push).toHaveBeenCalledWith({ - pathname: '/organizations/foo', - state: { justCreated: false } - }); + expect(push).toHaveBeenCalledWith({ pathname: '/organizations/foo' }); }); it('should redirect to projects creation page after creation', async () => { diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.css b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.css new file mode 100644 index 00000000000..55f794f1fc5 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.css @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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. + */ +.organization-empty { + margin: 100px auto 0; + width: 800px; +} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.tsx new file mode 100644 index 00000000000..d47f5675f1c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.tsx @@ -0,0 +1,69 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 { Button } from '../../../components/ui/buttons'; +import OnboardingProjectIcon from '../../../components/icons-components/OnboardingProjectIcon'; +import OnboardingAddMembersIcon from '../../../components/icons-components/OnboardingAddMembersIcon'; +import { translate } from '../../../helpers/l10n'; +import { OnboardingContextShape } from '../../../app/components/OnboardingContext'; +import { withRouter, Router } from '../../../components/hoc/withRouter'; +import '../../tutorials/styles.css'; +import './OrganizationEmpty.css'; + +interface Props { + openProjectOnboarding: OnboardingContextShape; + organization: T.Organization; + router: Pick; +} + +export class OrganizationEmpty extends React.PureComponent { + handleNewProjectClick = () => { + this.props.openProjectOnboarding(this.props.organization); + }; + + handleAddMembersClick = () => { + const { organization } = this.props; + this.props.router.push(`/organizations/${organization.key}/members`); + }; + + render() { + return ( +
+

{translate('onboarding.create_organization.ready')}

+
+ + +
+
+ ); + } +} + +export default withRouter(OrganizationEmpty); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationJustCreated.css b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationJustCreated.css deleted file mode 100644 index 2779ccbce5c..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationJustCreated.css +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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. - */ -.organization-just-created { - margin: 120px auto 0; - width: 800px; -} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationJustCreated.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationJustCreated.tsx deleted file mode 100644 index 89f1d217de5..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationJustCreated.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 { Button } from '../../../components/ui/buttons'; -import OnboardingProjectIcon from '../../../components/icons-components/OnboardingProjectIcon'; -import OnboardingAddMembersIcon from '../../../components/icons-components/OnboardingAddMembersIcon'; -import { translate } from '../../../helpers/l10n'; -import { OnboardingContextShape } from '../../../app/components/OnboardingContext'; -import { withRouter, Router } from '../../../components/hoc/withRouter'; -import '../../tutorials/styles.css'; -import './OrganizationJustCreated.css'; - -interface Props { - openProjectOnboarding: OnboardingContextShape; - organization: T.Organization; - router: Pick; -} - -export class OrganizationJustCreated extends React.PureComponent { - handleNewProjectClick = () => { - this.props.openProjectOnboarding(this.props.organization); - }; - - handleAddMembersClick = () => { - const { organization } = this.props; - this.props.router.push(`/organizations/${organization.key}/members`); - }; - - render() { - return ( -
-

{translate('onboarding.create_organization.ready')}

-
- - -
-
- ); - } -} - -export default withRouter(OrganizationJustCreated); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.tsx index d8770331308..e74b3a8b651 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.tsx +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.tsx @@ -21,7 +21,6 @@ import * as React from 'react'; import Helmet from 'react-helmet'; import { connect } from 'react-redux'; import { Location } from 'history'; -import OrganizationJustCreated from './OrganizationJustCreated'; import OrganizationNavigation from '../navigation/OrganizationNavigation'; import { fetchOrganization } from '../actions'; import NotFound from '../../../app/components/NotFound'; @@ -32,7 +31,6 @@ import { getMyOrganizations, Store } from '../../../store/rootReducer'; -import { OnboardingContext } from '../../../app/components/OnboardingContext'; interface OwnProps { children?: React.ReactNode; @@ -86,23 +84,6 @@ export class OrganizationPage extends React.PureComponent { this.props.fetchOrganization(organizationKey).then(this.stopLoading, this.stopLoading); }; - renderChildren(organization: T.Organization) { - const { location } = this.props; - const justCreated = Boolean(location.state && location.state.justCreated); - return justCreated ? ( - - {openProjectOnboarding => ( - - )} - - ) : ( - this.props.children - ); - } - render() { const { organization } = this.props; @@ -124,7 +105,7 @@ export class OrganizationPage extends React.PureComponent { organization={organization} userOrganizations={this.props.userOrganizations} /> - {this.renderChildren(organization)} + {this.props.children} ); } diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationEmpty-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationEmpty-test.tsx new file mode 100644 index 00000000000..257fa9e9e27 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationEmpty-test.tsx @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 { shallow } from 'enzyme'; +import { OrganizationEmpty } from '../OrganizationEmpty'; +import { click } from '../../../../helpers/testUtils'; + +const organization: T.Organization = { key: 'foo', name: 'Foo' }; + +it('should render', () => { + expect( + shallow( + + ) + ).toMatchSnapshot(); +}); + +it('should create new project', () => { + const openProjectOnboarding = jest.fn(); + const wrapper = shallow( + + ); + click(wrapper.find('Button').first()); + expect(openProjectOnboarding).toBeCalledWith({ key: 'foo', name: 'Foo' }); +}); + +it('should add members', () => { + const router = { push: jest.fn() }; + const wrapper = shallow( + + ); + click(wrapper.find('Button').last()); + expect(router.push).toBeCalledWith('/organizations/foo/members'); +}); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationJustCreated-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationJustCreated-test.tsx deleted file mode 100644 index 0bcc3631ed5..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationJustCreated-test.tsx +++ /dev/null @@ -1,63 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 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 { shallow } from 'enzyme'; -import { OrganizationJustCreated } from '../OrganizationJustCreated'; -import { click } from '../../../../helpers/testUtils'; - -const organization: T.Organization = { key: 'foo', name: 'Foo' }; - -it('should render', () => { - expect( - shallow( - - ) - ).toMatchSnapshot(); -}); - -it('should create new project', () => { - const openProjectOnboarding = jest.fn(); - const wrapper = shallow( - - ); - click(wrapper.find('Button').first()); - expect(openProjectOnboarding).toBeCalledWith({ key: 'foo', name: 'Foo' }); -}); - -it('should add members', () => { - const router = { push: jest.fn() }; - const wrapper = shallow( - - ); - click(wrapper.find('Button').last()); - expect(router.push).toBeCalledWith('/organizations/foo/members'); -}); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEmpty-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEmpty-test.tsx.snap new file mode 100644 index 00000000000..32086688e74 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEmpty-test.tsx.snap @@ -0,0 +1,41 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render 1`] = ` +
+

+ onboarding.create_organization.ready +

+
+ + +
+
+`; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationJustCreated-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationJustCreated-test.tsx.snap deleted file mode 100644 index 0ce7ab7b2fa..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationJustCreated-test.tsx.snap +++ /dev/null @@ -1,41 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render 1`] = ` -
-

- onboarding.create_organization.ready -

-
- - -
-
-`; diff --git a/server/sonar-web/src/main/js/apps/organizations/routes.ts b/server/sonar-web/src/main/js/apps/organizations/routes.ts index e3279292d9c..d78634091ec 100644 --- a/server/sonar-web/src/main/js/apps/organizations/routes.ts +++ b/server/sonar-web/src/main/js/apps/organizations/routes.ts @@ -34,11 +34,8 @@ const routes = [ { indexRoute: { onEnter(nextState: RouterState, replace: RedirectFunction) { - const { location, params } = nextState; - const justCreated = Boolean(location.state && location.state.justCreated); - if (!justCreated) { - replace(`/organizations/${params.organizationKey}/projects`); - } + const { params } = nextState; + replace(`/organizations/${params.organizationKey}/projects`); } } }, diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx index 94f52fe4461..1c141b7e6f5 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx @@ -26,6 +26,7 @@ import PageSidebar from './PageSidebar'; import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication'; import DeferredSpinner from '../../../components/common/DeferredSpinner'; import ListFooter from '../../../components/controls/ListFooter'; +import OrganizationEmpty from '../../organizations/components/OrganizationEmpty'; import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper'; import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; import Visualizations from '../visualizations/Visualizations'; @@ -33,11 +34,12 @@ import { Project, Facets } from '../types'; import { fetchProjects, parseSorting, SORTING_SWITCH } from '../utils'; import { parseUrlQuery, Query, hasFilterParams, hasVisualizationParams } from '../query'; import { translate } from '../../../helpers/l10n'; -import { addSideBarFooterClass, removeSideBarFooterClass } from '../../../helpers/pages'; +import { addSideBarClass, removeSideBarClass } from '../../../helpers/pages'; import { RawQuery } from '../../../helpers/query'; import { get, save } from '../../../helpers/storage'; import { isSonarCloud } from '../../../helpers/system'; import { isLoggedIn } from '../../../helpers/users'; +import { OnboardingContext } from '../../../app/components/OnboardingContext'; import { withRouter, Location, Router } from '../../../components/hoc/withRouter'; import '../../../components/search-navigator.css'; import '../styles.css'; @@ -47,13 +49,13 @@ interface Props { isFavorite: boolean; location: Pick; organization: T.Organization | undefined; - organizationsEnabled?: boolean; router: Pick; storageOptionsSuffix?: string; } interface State { facets?: Facets; + initialLoading: boolean; loading: boolean; pageIndex?: number; projects?: Project[]; @@ -70,7 +72,7 @@ export class AllProjects extends React.PureComponent { constructor(props: Props) { super(props); - this.state = { loading: true, query: {} }; + this.state = { initialLoading: true, loading: true, query: {} }; } componentDidMount() { @@ -81,38 +83,37 @@ export class AllProjects extends React.PureComponent { return; } this.handleQueryChange(true); - addSideBarFooterClass(); + this.updateFooterClass(); } componentDidUpdate(prevProps: Props) { if (prevProps.location.query !== this.props.location.query) { this.handleQueryChange(false); } + + if ( + prevProps.organization && + this.props.organization && + prevProps.organization.key !== this.props.organization.key + ) { + this.setState({ initialLoading: true }); + } + + this.updateFooterClass(); } componentWillUnmount() { this.mounted = false; - removeSideBarFooterClass(); + removeSideBarClass(); } - getView = () => this.state.query.view || 'overall'; - - getVisualization = () => this.state.query.visualization || 'risk'; - - getSort = () => this.state.query.sort || 'name'; - - stopLoading = () => { - if (this.mounted) { - this.setState({ loading: false }); - } - }; - fetchProjects = (query: any) => { this.setState({ loading: true, query }); fetchProjects(query, this.props.isFavorite, this.props.organization).then(response => { if (this.mounted) { this.setState({ facets: response.facets, + initialLoading: false, loading: false, pageIndex: 1, projects: response.projects, @@ -141,6 +142,8 @@ export class AllProjects extends React.PureComponent { } }; + getSort = () => this.state.query.sort || 'name'; + getStorageOptions = () => { const { storageOptionsSuffix } = this.props; const options: { @@ -160,6 +163,14 @@ export class AllProjects extends React.PureComponent { return options; }; + getView = () => this.state.query.view || 'overall'; + + getVisualization = () => this.state.query.visualization || 'risk'; + + handleClearAll = () => { + this.props.router.push({ pathname: this.props.location.pathname }); + }; + handlePerspectiveChange = ({ view, visualization }: { view: string; visualization?: string }) => { const { storageOptionsSuffix } = this.props; const query: { @@ -188,12 +199,6 @@ export class AllProjects extends React.PureComponent { save(PROJECTS_VISUALIZATION, visualization, storageOptionsSuffix); }; - handleSortChange = (sort: string, desc: boolean) => { - const asString = (desc ? '-' : '') + sort; - this.updateLocationQuery({ sort: asString }); - save(PROJECTS_SORT, asString, this.props.storageOptionsSuffix); - }; - handleQueryChange(initialMount: boolean) { const query = parseUrlQuery(this.props.location.query); const savedOptions = this.getStorageOptions(); @@ -207,13 +212,34 @@ export class AllProjects extends React.PureComponent { } } + handleSortChange = (sort: string, desc: boolean) => { + const asString = (desc ? '-' : '') + sort; + this.updateLocationQuery({ sort: asString }); + save(PROJECTS_SORT, asString, this.props.storageOptionsSuffix); + }; + + stopLoading = () => { + if (this.mounted) { + this.setState({ initialLoading: false, loading: false }); + } + }; + updateLocationQuery = (newQuery: RawQuery) => { const query = omitBy({ ...this.props.location.query, ...newQuery }, x => !x); this.props.router.push({ pathname: this.props.location.pathname, query }); }; - handleClearAll = () => { - this.props.router.push({ pathname: this.props.location.pathname }); + updateFooterClass = () => { + const { organization } = this.props; + const { initialLoading, projects } = this.state; + const isOrganizationContext = isSonarCloud() && organization; + const isEmpty = projects && projects.length === 0; + + if (isOrganizationContext && (initialLoading || isEmpty)) { + removeSideBarClass(); + } else { + addSideBarClass(); + } }; renderSide = () => ( @@ -270,7 +296,7 @@ export class AllProjects extends React.PureComponent {
{this.state.projects && ( { }; render() { + const { organization } = this.props; + const { projects } = this.state; + const isOrganizationContext = isSonarCloud() && organization; + const initialLoading = isOrganizationContext && this.state.initialLoading; + const organizationEmpty = isOrganizationContext && projects && projects.length === 0; + return (
- {this.renderSide()} + {initialLoading ? ( +
+ +
+ ) : ( + <> + {!organizationEmpty && this.renderSide()} -
- {this.renderHeader()} - {this.renderMain()} -
+
+ {organizationEmpty && organization ? ( + + {openProjectOnboarding => ( + + )} + + ) : ( + <> + {this.renderHeader()} + {this.renderMain()} + + )} +
+ + )}
); } diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx index 12e7e5d81e5..d1ac2e535f0 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx @@ -22,6 +22,8 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import { AllProjects } from '../AllProjects'; import { get, save } from '../../../../helpers/storage'; +import { isSonarCloud } from '../../../../helpers/system'; +import { waitAndUpdate } from '../../../../helpers/testUtils'; jest.mock('../ProjectsList', () => ({ // eslint-disable-next-line @@ -55,11 +57,14 @@ jest.mock('../../../../helpers/storage', () => ({ save: jest.fn() })); -const fetchProjects = require('../../utils').fetchProjects as jest.Mock; +jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn() })); + +const fetchProjects = require('../../utils').fetchProjects as jest.Mock; beforeEach(() => { - (get as jest.Mock).mockImplementation(() => null); - (save as jest.Mock).mockClear(); + (get as jest.Mock).mockImplementation(() => null); + (save as jest.Mock).mockClear(); + (isSonarCloud as jest.Mock).mockReturnValue(false); fetchProjects.mockClear(); }); @@ -100,7 +105,7 @@ it('fetches projects', () => { }); it('redirects to the saved search', () => { - (get as jest.Mock).mockImplementation( + (get as jest.Mock).mockImplementation( (key: string) => (key === 'sonarqube.projects.view' ? 'leak' : null) ); const replace = jest.fn(); @@ -161,6 +166,28 @@ it('changes perspective to risk visualization', () => { expect(save).toHaveBeenCalledWith('sonarqube.projects.visualization', 'risk', undefined); }); +it('renders correctly empty organization', async () => { + (isSonarCloud as jest.Mock).mockReturnValue(true); + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); + await waitAndUpdate(wrapper); + expect(wrapper).toMatchSnapshot(); + wrapper.setState({ + loading: false, + projects: [{ key: 'foo', measures: {}, name: 'Foo' }], + total: 0 + }); + expect(wrapper).toMatchSnapshot(); +}); + function shallowRender( props: Partial = {}, push = jest.fn(), @@ -172,7 +199,6 @@ function shallowRender( isFavorite={false} location={{ pathname: '/projects', query: {} }} organization={undefined} - organizationsEnabled={false} router={{ push, replace }} {...props} /> diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap index e0873b4d900..1c09c537190 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap @@ -231,3 +231,203 @@ exports[`renders 2`] = `
`; + +exports[`renders correctly empty organization 1`] = ` +
+ + +
+ +
+
+`; + +exports[`renders correctly empty organization 2`] = ` +
+ + +
+ + + +
+
+`; + +exports[`renders correctly empty organization 3`] = ` +
+ + + + + +
+
+
+
+ +
+
+
+ +
+ + +
+
+
+
+`; -- cgit v1.2.3