Bladeren bron

SONAR-10080 turn Issues to My Issues

tags/7.0-RC1
Stas Vilchik 6 jaren geleden
bovenliggende
commit
cd9f8d636f

+ 2
- 1
server/sonar-web/src/main/js/api/issues.ts Bestand weergeven

@@ -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;

+ 14
- 1
server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js Bestand weergeven

@@ -64,11 +64,24 @@ export default class GlobalNavMenu extends React.PureComponent {
}

renderIssuesLink() {
const active = this.props.location.pathname === 'issues';

if (this.props.sonarCloud) {
return (
<li>
<Link
to={{ pathname: '/issues', query: { resolved: 'false' } }}
className={active ? 'active' : undefined}>
{translate('my_issues')}
</Link>
</li>
);
}

const query =
this.props.currentUser.isLoggedIn && isMySet()
? { resolved: 'false', myIssues: 'true' }
: { resolved: 'false' };
const active = this.props.location.pathname === 'issues';
return (
<li>
<Link to={{ pathname: '/issues', query }} className={active ? 'active' : undefined}>

+ 4
- 3
server/sonar-web/src/main/js/app/utils/startReactApp.js Bestand weergeven

@@ -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}
/>
<Route path="issues" childRoutes={issuesRoutes} />
<Route path="issues" component={IssuesPageSelector} />
<Route path="organizations" childRoutes={organizationsRoutes} />
<Route path="projects" childRoutes={projectsRoutes} />
<Route path="quality_gates" childRoutes={qualityGatesRoutes} />
@@ -187,7 +188,7 @@ const startReactApp = () => {
path="project/extension/:pluginKey/:extensionKey"
component={ProjectPageExtension}
/>
<Route path="project/issues" childRoutes={issuesRoutes} />
<Route path="project/issues" component={Issues} />
<Route path="project/quality_gate" childRoutes={projectQualityGateRoutes} />
<Route
path="project/quality_profiles"

+ 44
- 0
server/sonar-web/src/main/js/apps/issues/IssuesPageSelector.tsx Bestand weergeven

@@ -0,0 +1,44 @@
/*
* 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 { connect } from 'react-redux';
import AppContainer from './components/AppContainer';
import { CurrentUser, isLoggedIn } from '../../app/types';
import { getCurrentUser, getGlobalSettingValue } from '../../store/rootReducer';

interface StateProps {
currentUser: CurrentUser;
onSonarCloud: boolean;
}

function IssuesPage({ currentUser, onSonarCloud, ...props }: StateProps) {
const myIssues = (isLoggedIn(currentUser) && onSonarCloud) || undefined;
return <AppContainer myIssues={myIssues} {...props} />;
}

const stateToProps = (state: any) => {
const onSonarCloudSetting = getGlobalSettingValue(state, 'sonar.sonarcloud.enabled');
return {
currentUser: getCurrentUser(state),
onSonarCloud: Boolean(onSonarCloudSetting && onSonarCloudSetting.value === 'true')
};
};

export default connect<StateProps>(stateToProps)(IssuesPage);

server/sonar-web/src/main/js/apps/issues/routes.ts → server/sonar-web/src/main/js/apps/issues/components/App.d.ts Bestand weergeven

@@ -1,7 +1,7 @@
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
* 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
@@ -17,17 +17,20 @@
* 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';
import * as React from 'react';
import { Component, CurrentUser } from '../../../app/types';
import { RawQuery } from '../../../helpers/query';

const routes = [
{
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
import('./components/AppContainer').then(i =>
callback(null, { component: i.default, onEnter })
);
}
}
];
interface Props {
branch?: { name: string };
component?: Component;
currentUser: CurrentUser;
fetchIssues: (query: RawQuery, requestOrganizations?: boolean) => Promise<any>;
location: { pathname: string; query: RawQuery };
myIssues?: boolean;
onBranchesChange: () => void;
onSonarCloud: boolean;
organization?: { key: string };
}

export default routes;
export default class App extends React.Component<Props> {}

+ 23
- 19
server/sonar-web/src/main/js/apps/issues/components/App.js Bestand weergeven

@@ -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 (
<div className="layout-page-filters">
{currentUser.isLoggedIn && (
<MyIssuesFilter
myIssues={this.state.myIssues}
onMyIssuesChange={this.handleMyIssuesChange}
/>
)}
{currentUser.isLoggedIn &&
!onSonarCloud && (
<MyIssuesFilter
myIssues={this.state.myIssues}
onMyIssuesChange={this.handleMyIssuesChange}
/>
)}
<FiltersHeader displayReset={this.isFiltered()} onReset={this.handleReset} />
<Sidebar
component={component}

server/sonar-web/src/main/js/apps/issues/components/AppContainer.js → server/sonar-web/src/main/js/apps/issues/components/AppContainer.tsx Bestand weergeven

@@ -17,25 +17,37 @@
* 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 { connect } from 'react-redux';
import { withRouter } from 'react-router';
/*:: import type { Dispatch } from 'redux'; */
import { Dispatch } from 'redux';
import { uniq } from 'lodash';
import App from './App';
import throwGlobalError from '../../../app/utils/throwGlobalError';
import { getCurrentUser, areThereCustomOrganizations } from '../../../store/rootReducer';
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 type { RawQuery } from '../../../helpers/query'; */
import { RawQuery } from '../../../helpers/query';
import { CurrentUser } from '../../../app/types';
import { lazyLoad } from '../../../components/lazyLoad';

const mapStateToProps = state => ({
currentUser: getCurrentUser(state)
});
interface StateProps {
currentUser: CurrentUser;
onSonarCloud: boolean;
}

const fetchIssueOrganizations = issues => dispatch => {
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<any>) => {
if (!issues.length) {
return Promise.resolve();
}
@@ -47,9 +59,10 @@ const fetchIssueOrganizations = issues => dispatch => {
);
};

const fetchIssues = (query /*: RawQuery */, requestOrganizations /*: boolean */ = true) => (
dispatch,
getState
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' })
@@ -67,6 +80,17 @@ const fetchIssues = (query /*: RawQuery */, requestOrganizations /*: boolean */
.catch(throwGlobalError);
};

const mapDispatchToProps = { fetchIssues };
interface DispatchProps {
fetchIssues: (query: RawQuery, requestOrganizations?: boolean) => Promise<void>;
}

// 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)(withRouter(App));
export default connect<StateProps, DispatchProps, OwnProps>(mapStateToProps, mapDispatchToProps)(
lazyLoad(() => import('./App'))
);

+ 2
- 2
server/sonar-web/src/main/js/apps/organizations/routes.js Bestand weergeven

@@ -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',

+ 1
- 1
server/sonar-web/src/main/js/helpers/issues.ts Bestand weergeven

@@ -50,7 +50,7 @@ interface IssueBase {
[x: string]: any;
}

interface RawIssue extends IssueBase {
export interface RawIssue extends IssueBase {
assignee?: string;
author?: string;
comments?: Array<Comment>;

+ 1
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties Bestand weergeven

@@ -95,6 +95,7 @@ minor=Minor
more=More
more_x={0} more
more_actions=More Actions
my_issues=My Issues
my_favorite=My Favorite
my_favorites=My Favorites
my_projects=My Projects

Laden…
Annuleren
Opslaan