From cd9f8d636f2b057b4c7fb6f0846e5843d44b7956 Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Tue, 28 Nov 2017 14:00:11 +0100 Subject: SONAR-10080 turn Issues to My Issues --- server/sonar-web/src/main/js/api/issues.ts | 3 +- .../js/app/components/nav/global/GlobalNavMenu.js | 15 +++- .../src/main/js/app/utils/startReactApp.js | 7 +- .../src/main/js/apps/issues/IssuesPageSelector.tsx | 44 ++++++++++ .../src/main/js/apps/issues/components/App.d.ts | 36 ++++++++ .../src/main/js/apps/issues/components/App.js | 42 +++++----- .../main/js/apps/issues/components/AppContainer.js | 72 ---------------- .../js/apps/issues/components/AppContainer.tsx | 96 ++++++++++++++++++++++ server/sonar-web/src/main/js/apps/issues/routes.ts | 33 -------- .../src/main/js/apps/organizations/routes.js | 4 +- server/sonar-web/src/main/js/helpers/issues.ts | 2 +- 11 files changed, 222 insertions(+), 132 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/issues/IssuesPageSelector.tsx create mode 100644 server/sonar-web/src/main/js/apps/issues/components/App.d.ts delete mode 100644 server/sonar-web/src/main/js/apps/issues/components/AppContainer.js create mode 100644 server/sonar-web/src/main/js/apps/issues/components/AppContainer.tsx delete mode 100644 server/sonar-web/src/main/js/apps/issues/routes.ts (limited to 'server/sonar-web/src/main/js') diff --git a/server/sonar-web/src/main/js/api/issues.ts b/server/sonar-web/src/main/js/api/issues.ts index 730292aead3..516fe67ce48 100644 --- a/server/sonar-web/src/main/js/api/issues.ts +++ b/server/sonar-web/src/main/js/api/issues.ts @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { getJSON, post, postJSON, RequestData } from '../helpers/request'; +import { RawIssue } from '../helpers/issues'; export interface IssueResponse { components?: Array<{}>; @@ -30,7 +31,7 @@ interface IssuesResponse { components?: Array<{}>; debtTotal?: number; facets: Array<{}>; - issues: Array<{}>; + issues: RawIssue[]; paging: { pageIndex: number; pageSize: number; 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.js index 02b3ab3ce11..27702bf87f7 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.js @@ -64,11 +64,24 @@ export default class GlobalNavMenu extends React.PureComponent { } renderIssuesLink() { + const active = this.props.location.pathname === 'issues'; + + if (this.props.sonarCloud) { + return ( +
  • + + {translate('my_issues')} + +
  • + ); + } + const query = this.props.currentUser.isLoggedIn && isMySet() ? { resolved: 'false', myIssues: 'true' } : { resolved: 'false' }; - const active = this.props.location.pathname === 'issues'; return (
  • diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.js b/server/sonar-web/src/main/js/app/utils/startReactApp.js index 0350b525fd4..3488208f508 100644 --- a/server/sonar-web/src/main/js/app/utils/startReactApp.js +++ b/server/sonar-web/src/main/js/app/utils/startReactApp.js @@ -47,7 +47,8 @@ import componentRoutes from '../../apps/component/routes'; import componentMeasuresRoutes from '../../apps/component-measures/routes'; import customMeasuresRoutes from '../../apps/custom-measures/routes'; import groupsRoutes from '../../apps/groups/routes'; -import issuesRoutes from '../../apps/issues/routes'; +import Issues from '../../apps/issues/components/AppContainer'; +import IssuesPageSelector from '../../apps/issues/IssuesPageSelector'; import marketplaceRoutes from '../../apps/marketplace/routes'; import metricsRoutes from '../../apps/metrics/routes'; import overviewRoutes from '../../apps/overview/routes'; @@ -167,7 +168,7 @@ const startReactApp = () => { path="extension/:pluginKey/:extensionKey" component={GlobalPageExtension} /> - + @@ -187,7 +188,7 @@ const startReactApp = () => { path="project/extension/:pluginKey/:extensionKey" component={ProjectPageExtension} /> - + ; +} + +const stateToProps = (state: any) => { + const onSonarCloudSetting = getGlobalSettingValue(state, 'sonar.sonarcloud.enabled'); + return { + currentUser: getCurrentUser(state), + onSonarCloud: Boolean(onSonarCloudSetting && onSonarCloudSetting.value === 'true') + }; +}; + +export default connect(stateToProps)(IssuesPage); diff --git a/server/sonar-web/src/main/js/apps/issues/components/App.d.ts b/server/sonar-web/src/main/js/apps/issues/components/App.d.ts new file mode 100644 index 00000000000..d80df8ebb32 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/components/App.d.ts @@ -0,0 +1,36 @@ +/* + * 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 { Component, CurrentUser } from '../../../app/types'; +import { RawQuery } from '../../../helpers/query'; + +interface Props { + branch?: { name: string }; + component?: Component; + currentUser: CurrentUser; + fetchIssues: (query: RawQuery, requestOrganizations?: boolean) => Promise; + location: { pathname: string; query: RawQuery }; + myIssues?: boolean; + onBranchesChange: () => void; + onSonarCloud: boolean; + organization?: { key: string }; +} + +export default class App extends React.Component {} diff --git a/server/sonar-web/src/main/js/apps/issues/components/App.js b/server/sonar-web/src/main/js/apps/issues/components/App.js index 94b226cb89d..46cb1d23952 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/App.js +++ b/server/sonar-web/src/main/js/apps/issues/components/App.js @@ -22,6 +22,7 @@ import React from 'react'; import Helmet from 'react-helmet'; import key from 'keymaster'; import { keyBy, without } from 'lodash'; +import PropTypes from 'prop-types'; import PageActions from './PageActions'; import FiltersHeader from './FiltersHeader'; import MyIssuesFilter from './MyIssuesFilter'; @@ -71,12 +72,10 @@ export type Props = { currentUser: CurrentUser, fetchIssues: (query: RawQuery, requestOrganizations?: boolean) => Promise<*>, location: { pathname: string, query: RawQuery }, + myIssues?: bool; onBranchesChange: () => void, + onSonarCloud: bool, organization?: { key: string }, - router: { - push: ({ pathname: string, query?: RawQuery }) => void, - replace: ({ pathname: string, query?: RawQuery }) => void - } }; */ @@ -114,6 +113,10 @@ export default class App extends React.PureComponent { /*:: props: Props; */ /*:: state: State; */ + static contextTypes = { + router: PropTypes.object.isRequired + }; + constructor(props /*: Props */) { super(props); this.state = { @@ -123,7 +126,7 @@ export default class App extends React.PureComponent { issues: [], loading: true, locationsNavigator: false, - myIssues: areMyIssuesSelected(props.location.query), + myIssues: props.myIssues || areMyIssuesSelected(props.location.query), openFacets: { severities: true, types: true }, openIssue: null, openPopup: null, @@ -172,7 +175,7 @@ export default class App extends React.PureComponent { } this.setState({ - myIssues: areMyIssuesSelected(nextProps.location.query), + myIssues: nextProps.myIssues || areMyIssuesSelected(nextProps.location.query), openIssue, query: parseQuery(nextProps.location.query) }); @@ -329,15 +332,15 @@ export default class App extends React.PureComponent { } }; if (this.state.openIssue) { - this.props.router.replace(path); + this.context.router.replace(path); } else { - this.props.router.push(path); + this.context.router.push(path); } }; closeIssue = () => { if (this.state.query) { - this.props.router.push({ + this.context.router.push({ pathname: this.props.location.pathname, query: { ...serializeQuery(this.state.query), @@ -575,7 +578,7 @@ export default class App extends React.PureComponent { }; handleFilterChange = (changes /*: {} */) => { - this.props.router.push({ + this.context.router.push({ pathname: this.props.location.pathname, query: { ...serializeQuery({ ...this.state.query, ...changes }), @@ -591,7 +594,7 @@ export default class App extends React.PureComponent { if (!this.props.component) { saveMyIssues(myIssues); } - this.props.router.push({ + this.context.router.push({ pathname: this.props.location.pathname, query: { ...serializeQuery({ ...this.state.query, assigned: true, assignees: [] }), @@ -618,7 +621,7 @@ export default class App extends React.PureComponent { }; handleReset = () => { - this.props.router.push({ + this.context.router.push({ pathname: this.props.location.pathname, query: { ...DEFAULT_QUERY, @@ -754,17 +757,18 @@ export default class App extends React.PureComponent { } renderFacets() { - const { component, currentUser } = this.props; + const { component, currentUser, onSonarCloud } = this.props; const { query } = this.state; return (
    - {currentUser.isLoggedIn && ( - - )} + {currentUser.isLoggedIn && + !onSonarCloud && ( + + )} ({ - currentUser: getCurrentUser(state) -}); - -const fetchIssueOrganizations = issues => dispatch => { - if (!issues.length) { - return Promise.resolve(); - } - - const organizationKeys = uniq(issues.map(issue => issue.organization)); - return getOrganizations({ organizations: organizationKeys.join() }).then( - response => dispatch(receiveOrganizations(response.organizations)), - throwGlobalError - ); -}; - -const fetchIssues = (query /*: RawQuery */, requestOrganizations /*: boolean */ = true) => ( - dispatch, - getState -) => { - const organizationsEnabled = areThereCustomOrganizations(getState()); - return searchIssues({ ...query, additionalFields: '_all' }) - .then(response => { - const parsedIssues = response.issues.map(issue => - parseIssueFromResponse(issue, response.components, response.users, response.rules) - ); - return { ...response, issues: parsedIssues }; - }) - .then(response => { - return organizationsEnabled && requestOrganizations - ? dispatch(fetchIssueOrganizations(response.issues)).then(() => response) - : response; - }) - .catch(throwGlobalError); -}; - -const mapDispatchToProps = { fetchIssues }; - -export default connect(mapStateToProps, mapDispatchToProps)(withRouter(App)); diff --git a/server/sonar-web/src/main/js/apps/issues/components/AppContainer.tsx b/server/sonar-web/src/main/js/apps/issues/components/AppContainer.tsx new file mode 100644 index 00000000000..7747e8aa148 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/components/AppContainer.tsx @@ -0,0 +1,96 @@ +/* + * 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 { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { uniq } from 'lodash'; +import throwGlobalError from '../../../app/utils/throwGlobalError'; +import { + getCurrentUser, + areThereCustomOrganizations, + getGlobalSettingValue +} from '../../../store/rootReducer'; +import { getOrganizations } from '../../../api/organizations'; +import { receiveOrganizations } from '../../../store/organizations/duck'; +import { searchIssues } from '../../../api/issues'; +import { parseIssueFromResponse } from '../../../helpers/issues'; +import { RawQuery } from '../../../helpers/query'; +import { CurrentUser } from '../../../app/types'; +import { lazyLoad } from '../../../components/lazyLoad'; + +interface StateProps { + currentUser: CurrentUser; + onSonarCloud: boolean; +} + +const mapStateToProps = (state: any): StateProps => { + const onSonarCloudSetting = getGlobalSettingValue(state, 'sonar.sonarcloud.enabled'); + return { + currentUser: getCurrentUser(state), + onSonarCloud: Boolean(onSonarCloudSetting && onSonarCloudSetting.value === 'true') + }; +}; + +const fetchIssueOrganizations = (issues: any[]) => (dispatch: Dispatch) => { + if (!issues.length) { + return Promise.resolve(); + } + + const organizationKeys = uniq(issues.map(issue => issue.organization)); + return getOrganizations({ organizations: organizationKeys.join() }).then( + response => dispatch(receiveOrganizations(response.organizations)), + throwGlobalError + ); +}; + +const fetchIssues = (query: RawQuery, requestOrganizations = true) => ( + // use `Function` to be able to do `dispatch(...).then(...)` + dispatch: Function, + getState: () => any +) => { + const organizationsEnabled = areThereCustomOrganizations(getState()); + return searchIssues({ ...query, additionalFields: '_all' }) + .then(response => { + const parsedIssues = response.issues.map(issue => + parseIssueFromResponse(issue, response.components, response.users, response.rules) + ); + return { ...response, issues: parsedIssues }; + }) + .then(response => { + return organizationsEnabled && requestOrganizations + ? dispatch(fetchIssueOrganizations(response.issues)).then(() => response) + : response; + }) + .catch(throwGlobalError); +}; + +interface DispatchProps { + fetchIssues: (query: RawQuery, requestOrganizations?: boolean) => Promise; +} + +// have to type cast this, because of async action +const mapDispatchToProps = { fetchIssues: fetchIssues as any } as DispatchProps; + +interface OwnProps { + myIssues?: boolean; +} + +export default connect(mapStateToProps, mapDispatchToProps)( + lazyLoad(() => import('./App')) +); diff --git a/server/sonar-web/src/main/js/apps/issues/routes.ts b/server/sonar-web/src/main/js/apps/issues/routes.ts deleted file mode 100644 index e915d90da0a..00000000000 --- a/server/sonar-web/src/main/js/apps/issues/routes.ts +++ /dev/null @@ -1,33 +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. - */ -import { RouterState, IndexRouteProps } from 'react-router'; -import { onEnter } from './redirects'; - -const routes = [ - { - getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { - import('./components/AppContainer').then(i => - callback(null, { component: i.default, onEnter }) - ); - } - } -]; - -export default routes; diff --git a/server/sonar-web/src/main/js/apps/organizations/routes.js b/server/sonar-web/src/main/js/apps/organizations/routes.js index b0010c5e416..d4cbdaa8a9c 100644 --- a/server/sonar-web/src/main/js/apps/organizations/routes.js +++ b/server/sonar-web/src/main/js/apps/organizations/routes.js @@ -32,7 +32,7 @@ import OrganizationProjectsManagement from './components/OrganizationProjectsMan import OrganizationDelete from './components/OrganizationDelete'; import qualityGatesRoutes from '../quality-gates/routes'; import qualityProfilesRoutes from '../quality-profiles/routes'; -import issuesRoutes from '../issues/routes'; +import Issues from '../issues/components/AppContainer'; const routes = [ { @@ -55,7 +55,7 @@ const routes = [ { path: 'issues', component: OrganizationContainer, - childRoutes: issuesRoutes + childRoutes: [{ indexRoute: { component: Issues } }] }, { path: 'members', diff --git a/server/sonar-web/src/main/js/helpers/issues.ts b/server/sonar-web/src/main/js/helpers/issues.ts index 33f8792bab6..c3b6b644c08 100644 --- a/server/sonar-web/src/main/js/helpers/issues.ts +++ b/server/sonar-web/src/main/js/helpers/issues.ts @@ -50,7 +50,7 @@ interface IssueBase { [x: string]: any; } -interface RawIssue extends IssueBase { +export interface RawIssue extends IssueBase { assignee?: string; author?: string; comments?: Array; -- cgit v1.2.3