import * as React from 'react';
import { get } from 'sonar-ui-common/helpers/storage';
import { searchProjects } from '../../../api/components';
+import { withCurrentUser } from '../../../components/hoc/withCurrentUser';
import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
-import { isLoggedIn } from '../../../helpers/users';
+import { hasGlobalPermission, isLoggedIn } from '../../../helpers/users';
import { PROJECTS_ALL, PROJECTS_DEFAULT_FILTER, PROJECTS_FAVORITE } from '../utils';
import AllProjectsContainer from './AllProjectsContainer';
}
interface State {
- shouldBeRedirected?: boolean;
- shouldForceSorting?: string;
+ checking: boolean;
}
export class DefaultPageSelector extends React.PureComponent<Props, State> {
- state: State = {};
+ state: State = { checking: true };
componentDidMount() {
- this.defineIfShouldBeRedirected();
+ this.checkIfNeedsRedirecting();
}
- componentDidUpdate(prevProps: Props) {
- if (prevProps.location !== this.props.location) {
- this.defineIfShouldBeRedirected();
- } else if (this.state.shouldBeRedirected === true) {
- this.props.router.replace({ ...this.props.location, pathname: '/projects/favorite' });
- } else if (this.state.shouldForceSorting != null) {
- this.props.router.replace({
- ...this.props.location,
- query: {
- ...this.props.location.query,
- sort: this.state.shouldForceSorting
- }
- });
- }
- }
-
- isFavoriteSet = (): boolean => {
- const setting = get(PROJECTS_DEFAULT_FILTER);
- return setting === PROJECTS_FAVORITE;
- };
-
- isAllSet = (): boolean => {
+ checkIfNeedsRedirecting = async () => {
+ const { currentUser, router, location } = this.props;
const setting = get(PROJECTS_DEFAULT_FILTER);
- return setting === PROJECTS_ALL;
- };
- defineIfShouldBeRedirected() {
- if (Object.keys(this.props.location.query).length > 0) {
- // show ALL projects when there are some filters
- this.setState({ shouldBeRedirected: false, shouldForceSorting: undefined });
- } else if (!isLoggedIn(this.props.currentUser)) {
- // show ALL projects if user is anonymous
- if (!this.props.location.query || !this.props.location.query.sort) {
- // force default sorting to last analysis date
- this.setState({ shouldBeRedirected: false, shouldForceSorting: '-analysis_date' });
- } else {
- this.setState({ shouldBeRedirected: false, shouldForceSorting: undefined });
- }
- } else if (this.isFavoriteSet()) {
- // show FAVORITE projects if "favorite" setting is explicitly set
- this.setState({ shouldBeRedirected: true, shouldForceSorting: undefined });
- } else if (this.isAllSet()) {
- // show ALL projects if "all" setting is explicitly set
- this.setState({ shouldBeRedirected: false, shouldForceSorting: undefined });
- } else {
- // otherwise, request favorites
- this.setState({ shouldBeRedirected: undefined, shouldForceSorting: undefined });
- searchProjects({ filter: 'isFavorite', ps: 1 }).then(r => {
- // show FAVORITE projects if there are any
- this.setState({ shouldBeRedirected: r.paging.total > 0, shouldForceSorting: undefined });
- });
+ // 1. Don't have to redirect if:
+ // 1.1 User is anonymous
+ // 1.2 There's a query, which means the user is interacting with the current page
+ // 1.3 The last interaction with the filter was to set it to "all"
+ if (
+ !isLoggedIn(currentUser) ||
+ Object.keys(location.query).length > 0 ||
+ setting === PROJECTS_ALL
+ ) {
+ this.setState({ checking: false });
+ return;
}
- }
- render() {
- const { shouldBeRedirected, shouldForceSorting } = this.state;
+ // 2. Redirect to the favorites page if:
+ // 2.1 The last interaction with the filter was to set it to "favorites"
+ // 2.2 The user has starred some projects
+ if (
+ setting === PROJECTS_FAVORITE ||
+ (await searchProjects({ filter: 'isFavorite', ps: 1 })).paging.total > 0
+ ) {
+ router.replace('/projects/favorite');
+ return;
+ }
+ // 3. Redirect to the create project page if:
+ // 3.1 The user has permission to provision projects, AND there are 0 projects on the instance
if (
- shouldBeRedirected !== undefined &&
- shouldBeRedirected !== true &&
- shouldForceSorting === undefined
+ hasGlobalPermission(currentUser, 'provisioning') &&
+ (await searchProjects({ ps: 1 })).paging.total === 0
) {
- return <AllProjectsContainer isFavorite={false} />;
+ this.props.router.replace('/projects/create');
+ }
+
+ // None of the above apply. Do not redirect, and stay on this page.
+ this.setState({ checking: false });
+ };
+
+ render() {
+ const { checking } = this.state;
+
+ if (checking) {
+ // We don't return a loader here, on purpose. We don't want to show anything
+ // just yet.
+ return null;
}
- return null;
+ return <AllProjectsContainer isFavorite={false} />;
}
}
-export default withRouter(DefaultPageSelector);
+export default withCurrentUser(withRouter(DefaultPageSelector));
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2021 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 { connect } from 'react-redux';
-import { getCurrentUser, Store } from '../../../store/rootReducer';
-import DefaultPageSelector from './DefaultPageSelector';
-
-const stateToProps = (state: Store) => ({
- currentUser: getCurrentUser(state)
-});
-
-export default connect(stateToProps)(DefaultPageSelector);
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { mount } from 'enzyme';
+import { shallow } from 'enzyme';
import * as React from 'react';
import { get } from 'sonar-ui-common/helpers/storage';
-import { doAsync } from 'sonar-ui-common/helpers/testUtils';
+import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { searchProjects } from '../../../../api/components';
+import {
+ mockCurrentUser,
+ mockLocation,
+ mockLoggedInUser,
+ mockRouter
+} from '../../../../helpers/testMocks';
+import { hasGlobalPermission } from '../../../../helpers/users';
import { DefaultPageSelector } from '../DefaultPageSelector';
jest.mock('../AllProjectsContainer', () => ({
}));
jest.mock('sonar-ui-common/helpers/storage', () => ({
- get: jest.fn()
+ get: jest.fn().mockReturnValue(undefined)
+}));
+
+jest.mock('../../../../helpers/users', () => ({
+ hasGlobalPermission: jest.fn().mockReturnValue(false),
+ isLoggedIn: jest.fn((u: T.CurrentUser) => u.isLoggedIn)
}));
jest.mock('../../../../api/components', () => ({
- searchProjects: jest.fn()
+ searchProjects: jest.fn().mockResolvedValue({ paging: { total: 0 } })
}));
-beforeEach(() => {
- (get as jest.Mock).mockImplementation(() => '').mockClear();
+beforeEach(jest.clearAllMocks);
+
+it('renders correctly', () => {
+ expect(shallowRender({ currentUser: mockLoggedInUser() }).type()).toBeNull(); // checking
+ expect(shallowRender({ currentUser: mockCurrentUser() })).toMatchSnapshot('default');
+});
+
+it("1.1 doesn't redirect for anonymous users", async () => {
+ const replace = jest.fn();
+ const wrapper = shallowRender({
+ currentUser: mockCurrentUser(),
+ router: mockRouter({ replace })
+ });
+ await waitAndUpdate(wrapper);
+ expect(replace).not.toBeCalled();
+});
+
+it("1.2 doesn't redirect if there's an existing filter in location", async () => {
+ const replace = jest.fn();
+ const wrapper = shallowRender({
+ location: mockLocation({ query: { size: '1' } }),
+ router: mockRouter({ replace })
+ });
+
+ await waitAndUpdate(wrapper);
+
+ expect(replace).not.toBeCalled();
});
-it('shows all projects with existing filter', () => {
+it("1.3 doesn't redirect if the user previously used the 'all' filter", async () => {
+ (get as jest.Mock).mockReturnValueOnce('all');
const replace = jest.fn();
- mountRender(undefined, { size: '1' }, replace);
+ const wrapper = shallowRender({ router: mockRouter({ replace }) });
+
+ await waitAndUpdate(wrapper);
+
expect(replace).not.toBeCalled();
});
-it('shows all projects sorted by analysis date for anonymous', () => {
+it('2.1 redirects to favorites if the user previously used the "favorites" filter', async () => {
+ (get as jest.Mock).mockReturnValueOnce('favorite');
+ const replace = jest.fn();
+ const wrapper = shallowRender({ router: mockRouter({ replace }) });
+
+ await waitAndUpdate(wrapper);
+
+ expect(replace).toBeCalledWith('/projects/favorite');
+});
+
+it('2.2 redirects to favorites if the user has starred projects', async () => {
+ (searchProjects as jest.Mock).mockResolvedValueOnce({ paging: { total: 3 } });
const replace = jest.fn();
- mountRender({ isLoggedIn: false }, undefined, replace);
- expect(replace).lastCalledWith({ pathname: '/projects', query: { sort: '-analysis_date' } });
+ const wrapper = shallowRender({ router: mockRouter({ replace }) });
+
+ await waitAndUpdate(wrapper);
+
+ expect(searchProjects).toHaveBeenLastCalledWith({ filter: 'isFavorite', ps: 1 });
+ expect(replace).toBeCalledWith('/projects/favorite');
});
-it('shows favorite projects', () => {
- (get as jest.Mock).mockImplementation(() => 'favorite');
+it('3.1 redirects to create project page, if user has correct permissions AND there are 0 projects', async () => {
+ (hasGlobalPermission as jest.Mock).mockReturnValueOnce(true);
const replace = jest.fn();
- mountRender(undefined, undefined, replace);
- expect(replace).lastCalledWith({ pathname: '/projects/favorite', query: {} });
+ const wrapper = shallowRender({ router: mockRouter({ replace }) });
+
+ await waitAndUpdate(wrapper);
+
+ expect(replace).toBeCalledWith('/projects/create');
});
-it('shows all projects', () => {
- (get as jest.Mock).mockImplementation(() => 'all');
+it("3.1 doesn't redirect to create project page, if user has no permissions", async () => {
const replace = jest.fn();
- mountRender(undefined, undefined, replace);
+ const wrapper = shallowRender({ router: mockRouter({ replace }) });
+
+ await waitAndUpdate(wrapper);
+
expect(replace).not.toBeCalled();
});
-it('fetches favorites', () => {
- (searchProjects as jest.Mock).mockImplementation(() => Promise.resolve({ paging: { total: 3 } }));
+it("3.1 doesn't redirect to create project page, if there's existing projects", async () => {
+ (searchProjects as jest.Mock)
+ .mockResolvedValueOnce({ paging: { total: 0 } }) // no favorites
+ .mockResolvedValueOnce({ paging: { total: 3 } }); // existing projects
const replace = jest.fn();
- mountRender(undefined, undefined, replace);
- return doAsync().then(() => {
- expect(searchProjects).toHaveBeenLastCalledWith({ filter: 'isFavorite', ps: 1 });
- expect(replace).toBeCalledWith({ pathname: '/projects/favorite', query: {} });
- });
+ const wrapper = shallowRender({ router: mockRouter({ replace }) });
+
+ await waitAndUpdate(wrapper);
+
+ expect(replace).not.toBeCalled();
});
-function mountRender(
- currentUser: T.CurrentUser = { isLoggedIn: true },
- query: any = {},
- replace: any = jest.fn()
-) {
- return mount(
+function shallowRender(props: Partial<DefaultPageSelector['props']> = {}) {
+ return shallow<DefaultPageSelector>(
<DefaultPageSelector
- currentUser={currentUser}
- location={{ pathname: '/projects', query }}
- router={{ replace }}
+ currentUser={mockLoggedInUser()}
+ location={mockLocation({ pathname: '/projects' })}
+ router={mockRouter()}
+ {...props}
/>
);
}
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly: default 1`] = `
+<AllProjectsContainer
+ isFavorite={false}
+/>
+`;
import { RedirectFunction, RouterState } from 'react-router';
import { lazyLoadComponent } from 'sonar-ui-common/components/lazyLoadComponent';
import { save } from 'sonar-ui-common/helpers/storage';
-import { isDefined } from 'sonar-ui-common/helpers/types';
-import DefaultPageSelectorContainer from './components/DefaultPageSelectorContainer';
-import FavoriteProjectsContainer from './components/FavoriteProjectsContainer';
import { PROJECTS_ALL, PROJECTS_DEFAULT_FILTER } from './utils';
const routes = [
- { indexRoute: { component: DefaultPageSelectorContainer } },
+ {
+ indexRoute: { component: lazyLoadComponent(() => import('./components/DefaultPageSelector')) }
+ },
{
path: 'all',
onEnter(_: RouterState, replace: RedirectFunction) {
replace('/projects');
}
},
- { path: 'favorite', component: FavoriteProjectsContainer },
+ {
+ path: 'favorite',
+ component: lazyLoadComponent(() => import('./components/FavoriteProjectsContainer'))
+ },
{
path: 'create',
component: lazyLoadComponent(() => import('../create/project/CreateProjectPage'))
}
-].filter(isDefined);
+];
export default routes;