@@ -44,13 +44,18 @@ | |||
"devDependencies": { | |||
"@types/classnames": "2.2.0", | |||
"@types/enzyme": "2.8.6", | |||
"@types/escape-html": "0.0.19", | |||
"@types/jest": "20.0.7", | |||
"@types/jquery": "3.2.11", | |||
"@types/lodash": "4.14.73", | |||
"@types/prop-types": "15.5.1", | |||
"@types/react": "16.0.2", | |||
"@types/react-dom": "15.5.2", | |||
"@types/react-helmet": "5.0.3", | |||
"@types/react-modal": "2.2.0", | |||
"@types/react-redux": "5.0.3", | |||
"@types/react-router": "3.0.5", | |||
"@types/react-select": "1.0.51", | |||
"autoprefixer": "7.1.1", | |||
"awesome-typescript-loader": "3.2.3", | |||
"babel-core": "^6.22.1", | |||
@@ -122,8 +127,16 @@ | |||
], | |||
"jest": { | |||
"coverageDirectory": "<rootDir>/target/coverage", | |||
"coveragePathIgnorePatterns": ["<rootDir>/node_modules", "<rootDir>/tests"], | |||
"moduleFileExtensions": ["ts", "tsx", "js", "json"], | |||
"coveragePathIgnorePatterns": [ | |||
"<rootDir>/node_modules", | |||
"<rootDir>/tests" | |||
], | |||
"moduleFileExtensions": [ | |||
"ts", | |||
"tsx", | |||
"js", | |||
"json" | |||
], | |||
"moduleNameMapper": { | |||
"^.+\\.(hbs|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/config/jest/FileStub.js", | |||
"^.+\\.css$": "<rootDir>/config/jest/CSSStub.js" | |||
@@ -132,7 +145,9 @@ | |||
"<rootDir>/config/polyfills.js", | |||
"<rootDir>/config/jest/SetupTestEnvironment.js" | |||
], | |||
"snapshotSerializers": ["enzyme-to-json/serializer"], | |||
"snapshotSerializers": [ | |||
"enzyme-to-json/serializer" | |||
], | |||
"testPathIgnorePatterns": [ | |||
"<rootDir>/node_modules", | |||
"<rootDir>/src/main/webapp", |
@@ -17,9 +17,11 @@ | |||
* 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'; | |||
const routes = [ | |||
{ | |||
getIndexRoute(_, callback) { | |||
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { | |||
import('./components/AboutApp').then(i => callback(null, { component: i.default })); | |||
} | |||
} |
@@ -1,94 +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 React from 'react'; | |||
import moment from 'moment'; | |||
import { sortBy } from 'lodash'; | |||
import { Link } from 'react-router'; | |||
import Level from '../../../components/ui/Level'; | |||
import { projectType } from './propTypes'; | |||
import { translateWithParameters, translate } from '../../../helpers/l10n'; | |||
export default class ProjectCard extends React.PureComponent { | |||
static propTypes = { | |||
project: projectType.isRequired | |||
}; | |||
render() { | |||
const { project } = this.props; | |||
const isAnalyzed = project.lastAnalysisDate != null; | |||
const analysisMoment = isAnalyzed && moment(project.lastAnalysisDate); | |||
const links = sortBy(project.links, 'type'); | |||
return ( | |||
<div className="account-project-card clearfix"> | |||
<aside className="account-project-side"> | |||
{isAnalyzed | |||
? <div className="account-project-analysis" title={analysisMoment.format('LLL')}> | |||
{translateWithParameters( | |||
'my_account.projects.analyzed_x', | |||
analysisMoment.fromNow() | |||
)} | |||
</div> | |||
: <div className="account-project-analysis"> | |||
{translate('my_account.projects.never_analyzed')} | |||
</div>} | |||
{project.qualityGate != null && | |||
<div className="account-project-quality-gate"> | |||
<Level level={project.qualityGate} /> | |||
</div>} | |||
</aside> | |||
<h3 className="account-project-name"> | |||
<Link to={{ pathname: '/dashboard', query: { id: project.key } }}> | |||
{project.name} | |||
</Link> | |||
</h3> | |||
{links.length > 0 && | |||
<div className="account-project-links"> | |||
<ul className="list-inline"> | |||
{links.map(link => | |||
<li key={link.type}> | |||
<a | |||
className="link-with-icon" | |||
href={link.href} | |||
title={link.name} | |||
target="_blank" | |||
rel="nofollow"> | |||
<i className={`icon-color-link icon-${link.type}`} /> | |||
</a> | |||
</li> | |||
)} | |||
</ul> | |||
</div>} | |||
<div className="account-project-key"> | |||
{project.key} | |||
</div> | |||
{!!project.description && | |||
<div className="account-project-description"> | |||
{project.description} | |||
</div>} | |||
</div> | |||
); | |||
} | |||
} |
@@ -0,0 +1,94 @@ | |||
/* | |||
* 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 moment from 'moment'; | |||
import { sortBy } from 'lodash'; | |||
import { Link } from 'react-router'; | |||
import { IProject } from './types'; | |||
import Level from '../../../components/ui/Level'; | |||
import { translateWithParameters, translate } from '../../../helpers/l10n'; | |||
interface Props { | |||
project: IProject; | |||
} | |||
export default function ProjectCard(props: Props) { | |||
const { project } = props; | |||
const isAnalyzed = project.lastAnalysisDate != null; | |||
const analysisMoment = isAnalyzed && moment(project.lastAnalysisDate); | |||
const links = sortBy(project.links, 'type'); | |||
return ( | |||
<div className="account-project-card clearfix"> | |||
<aside className="account-project-side"> | |||
{isAnalyzed | |||
? <div | |||
className="account-project-analysis" | |||
title={analysisMoment ? analysisMoment.format('LLL') : undefined}> | |||
{translateWithParameters( | |||
'my_account.projects.analyzed_x', | |||
analysisMoment ? analysisMoment.fromNow() : undefined | |||
)} | |||
</div> | |||
: <div className="account-project-analysis"> | |||
{translate('my_account.projects.never_analyzed')} | |||
</div>} | |||
{project.qualityGate != null && | |||
<div className="account-project-quality-gate"> | |||
<Level level={project.qualityGate} /> | |||
</div>} | |||
</aside> | |||
<h3 className="account-project-name"> | |||
<Link to={{ pathname: '/dashboard', query: { id: project.key } }}> | |||
{project.name} | |||
</Link> | |||
</h3> | |||
{links.length > 0 && | |||
<div className="account-project-links"> | |||
<ul className="list-inline"> | |||
{links.map(link => | |||
<li key={link.type}> | |||
<a | |||
className="link-with-icon" | |||
href={link.href} | |||
title={link.name} | |||
target="_blank" | |||
rel="nofollow"> | |||
<i className={`icon-color-link icon-${link.type}`} /> | |||
</a> | |||
</li> | |||
)} | |||
</ul> | |||
</div>} | |||
<div className="account-project-key"> | |||
{project.key} | |||
</div> | |||
{!!project.description && | |||
<div className="account-project-description"> | |||
{project.description} | |||
</div>} | |||
</div> | |||
); | |||
} |
@@ -1,67 +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 React from 'react'; | |||
import PropTypes from 'prop-types'; | |||
import ProjectCard from './ProjectCard'; | |||
import ListFooter from '../../../components/controls/ListFooter'; | |||
import { projectsListType } from './propTypes'; | |||
import { translate } from '../../../helpers/l10n'; | |||
export default class Projects extends React.PureComponent { | |||
static propTypes = { | |||
projects: projectsListType.isRequired, | |||
total: PropTypes.number.isRequired, | |||
loading: PropTypes.bool.isRequired, | |||
loadMore: PropTypes.func.isRequired | |||
}; | |||
render() { | |||
const { projects } = this.props; | |||
return ( | |||
<div id="account-projects"> | |||
{projects.length === 0 | |||
? <div className="js-no-results"> | |||
{translate('my_account.projects.no_results')} | |||
</div> | |||
: <p> | |||
{translate('my_account.projects.description')} | |||
</p>} | |||
{projects.length > 0 && | |||
<ul className="account-projects-list"> | |||
{projects.map(project => | |||
<li key={project.key}> | |||
<ProjectCard project={project} /> | |||
</li> | |||
)} | |||
</ul>} | |||
{projects.length > 0 && | |||
<ListFooter | |||
count={projects.length} | |||
total={this.props.total} | |||
ready={!this.props.loading} | |||
loadMore={this.props.loadMore} | |||
/>} | |||
</div> | |||
); | |||
} | |||
} |
@@ -0,0 +1,65 @@ | |||
/* | |||
* 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 ProjectCard from './ProjectCard'; | |||
import { IProject } from './types'; | |||
import ListFooter from '../../../components/controls/ListFooter'; | |||
import { translate } from '../../../helpers/l10n'; | |||
interface Props { | |||
loading: boolean; | |||
loadMore: () => void; | |||
projects: IProject[]; | |||
search: (query: string) => void; | |||
total?: number; | |||
} | |||
export default function Projects(props: Props) { | |||
const { projects } = props; | |||
return ( | |||
<div id="account-projects"> | |||
{projects.length === 0 | |||
? <div className="js-no-results"> | |||
{translate('my_account.projects.no_results')} | |||
</div> | |||
: <p> | |||
{translate('my_account.projects.description')} | |||
</p>} | |||
{projects.length > 0 && | |||
<ul className="account-projects-list"> | |||
{projects.map(project => | |||
<li key={project.key}> | |||
<ProjectCard project={project} /> | |||
</li> | |||
)} | |||
</ul>} | |||
{projects.length > 0 && | |||
<ListFooter | |||
count={projects.length} | |||
total={props.total || 0} | |||
ready={!props.loading} | |||
loadMore={props.loadMore} | |||
/>} | |||
</div> | |||
); | |||
} |
@@ -17,24 +17,28 @@ | |||
* 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 * as React from 'react'; | |||
import Helmet from 'react-helmet'; | |||
import Projects from './Projects'; | |||
import { getMyProjects } from '../../../api/components'; | |||
import { translate } from '../../../helpers/l10n'; | |||
export default class ProjectsContainer extends React.PureComponent { | |||
state = { | |||
interface State { | |||
loading: boolean; | |||
page: number; | |||
projects?: any[]; | |||
query: string; | |||
total?: number; | |||
} | |||
export default class ProjectsContainer extends React.PureComponent<{}, State> { | |||
mounted: boolean; | |||
state: State = { | |||
loading: true, | |||
page: 1, | |||
query: '' | |||
}; | |||
componentWillMount() { | |||
this.loadMore = this.loadMore.bind(this); | |||
this.search = this.search.bind(this); | |||
} | |||
componentDidMount() { | |||
this.mounted = true; | |||
this.loadProjects(); | |||
@@ -46,15 +50,15 @@ export default class ProjectsContainer extends React.PureComponent { | |||
loadProjects(page = this.state.page, query = this.state.query) { | |||
this.setState({ loading: true }); | |||
const data = { ps: 100 }; | |||
const data: { [p: string]: any } = { ps: 100 }; | |||
if (page > 1) { | |||
data.p = page; | |||
} | |||
if (query) { | |||
data.q = query; | |||
} | |||
return getMyProjects(data).then(r => { | |||
const projects = page > 1 ? [...this.state.projects, ...r.projects] : r.projects; | |||
return getMyProjects(data).then((r: any) => { | |||
const projects = page > 1 ? [...(this.state.projects || []), ...r.projects] : r.projects; | |||
this.setState({ | |||
projects, | |||
query, | |||
@@ -65,13 +69,9 @@ export default class ProjectsContainer extends React.PureComponent { | |||
}); | |||
} | |||
loadMore() { | |||
return this.loadProjects(this.state.page + 1); | |||
} | |||
loadMore = () => this.loadProjects(this.state.page + 1); | |||
search(query) { | |||
return this.loadProjects(1, query); | |||
} | |||
search = (query: string) => this.loadProjects(1, query); | |||
render() { | |||
const helmet = <Helmet title={translate('my_account.projects')} />; |
@@ -1,34 +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 PropTypes from 'prop-types'; | |||
const { shape, string, array, arrayOf } = PropTypes; | |||
export const projectType = shape({ | |||
id: string.isRequired, | |||
key: string.isRequired, | |||
name: string.isRequired, | |||
lastAnalysisDate: string, | |||
description: string, | |||
links: array.isRequired, | |||
qualityGate: string | |||
}); | |||
export const projectsListType = arrayOf(projectType); |
@@ -0,0 +1,32 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 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. | |||
*/ | |||
export interface IProject { | |||
id: string; | |||
key: string; | |||
name: string; | |||
lastAnalysisDate: string; | |||
description: string; | |||
links: Array<{ | |||
href: string; | |||
name: string; | |||
type: string; | |||
}>; | |||
qualityGate: string; | |||
} |
@@ -17,44 +17,46 @@ | |||
* 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, RouteComponent } from 'react-router'; | |||
const routes = [ | |||
{ | |||
getComponent(_, callback) { | |||
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) { | |||
import('./components/Account').then(i => callback(null, i.default)); | |||
}, | |||
childRoutes: [ | |||
{ | |||
getIndexRoute(_, callback) { | |||
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { | |||
import('./profile/Profile').then(i => callback(null, { component: i.default })); | |||
} | |||
}, | |||
{ | |||
path: 'security', | |||
getComponent(_, callback) { | |||
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) { | |||
import('./components/Security').then(i => callback(null, i.default)); | |||
} | |||
}, | |||
{ | |||
path: 'projects', | |||
getComponent(_, callback) { | |||
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) { | |||
import('./projects/ProjectsContainer').then(i => callback(null, i.default)); | |||
} | |||
}, | |||
{ | |||
path: 'notifications', | |||
getComponent(_, callback) { | |||
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) { | |||
import('./notifications/Notifications').then(i => callback(null, i.default)); | |||
} | |||
}, | |||
{ | |||
path: 'organizations', | |||
getComponent(_, callback) { | |||
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) { | |||
import('./organizations/UserOrganizations').then(i => callback(null, i.default)); | |||
}, | |||
childRoutes: [ | |||
{ | |||
path: 'create', | |||
getComponent(_, callback) { | |||
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) { | |||
import('./organizations/CreateOrganizationForm').then(i => callback(null, i.default)); | |||
} | |||
} |
@@ -17,9 +17,11 @@ | |||
* 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'; | |||
const routes = [ | |||
{ | |||
getIndexRoute(_, callback) { | |||
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { | |||
import('./components/BackgroundTasksApp').then(i => callback(null, { component: i.default })); | |||
} | |||
} |
@@ -17,9 +17,11 @@ | |||
* 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'; | |||
const routes = [ | |||
{ | |||
getIndexRoute(_, callback) { | |||
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { | |||
import('./components/App').then(i => callback(null, { component: i.default })); | |||
} | |||
} |
@@ -17,10 +17,12 @@ | |||
* 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, RouteComponent } from 'react-router'; | |||
const routes = [ | |||
{ | |||
indexRoute: { | |||
getComponent(_, callback) { | |||
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) { | |||
import('./components/CodingRulesAppContainer').then(i => callback(null, i.default)); | |||
} | |||
} |
@@ -17,15 +17,17 @@ | |||
* 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, RedirectFunction } from 'react-router'; | |||
const routes = [ | |||
{ | |||
getIndexRoute(_, callback) { | |||
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { | |||
import('./components/AppContainer').then(i => callback(null, { component: i.default })); | |||
} | |||
}, | |||
{ | |||
path: 'domain/:domainName', | |||
onEnter(nextState, replace) { | |||
onEnter(nextState: RouterState, replace: RedirectFunction) { | |||
replace({ | |||
pathname: '/component_measures', | |||
query: { | |||
@@ -37,7 +39,7 @@ const routes = [ | |||
}, | |||
{ | |||
path: 'metric/:metricKey(/:view)', | |||
onEnter(nextState, replace) { | |||
onEnter(nextState: RouterState, replace: RedirectFunction) { | |||
if (nextState.params.view === 'history') { | |||
replace({ | |||
pathname: '/project/activity', |
@@ -17,20 +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 SourceViewer from '../../../components/SourceViewer/SourceViewer'; | |||
export default class App extends React.PureComponent { | |||
/*:: props: { | |||
location: { | |||
query: { | |||
id: string, | |||
line?: string | |||
} | |||
} | |||
interface Props { | |||
location: { | |||
query: { | |||
id: string; | |||
line?: string; | |||
}; | |||
}; | |||
*/ | |||
} | |||
export default class App extends React.PureComponent<Props> { | |||
scrollToLine = () => { | |||
const { line } = this.props.location.query; | |||
if (line) { |
@@ -17,10 +17,12 @@ | |||
* 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, RouteComponent } from 'react-router'; | |||
const routes = [ | |||
{ | |||
indexRoute: { | |||
getComponent(_, callback) { | |||
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) { | |||
import('./components/App').then(i => callback(null, i.default)); | |||
} | |||
} |
@@ -17,9 +17,11 @@ | |||
* 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'; | |||
const routes = [ | |||
{ | |||
getIndexRoute(_, callback) { | |||
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { | |||
import('./components/CustomMeasuresAppContainer').then(i => | |||
callback(null, { component: i.default }) | |||
); |
@@ -17,9 +17,11 @@ | |||
* 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'; | |||
const routes = [ | |||
{ | |||
getIndexRoute(_, callback) { | |||
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { | |||
Promise.all([ | |||
import('./components/GroupsAppContainer').then(i => i.default), | |||
import('../organizations/forSingleOrganization').then(i => i.default) |
@@ -17,11 +17,12 @@ | |||
* 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(_, callback) { | |||
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { | |||
import('./components/AppContainer').then(i => | |||
callback(null, { component: i.default, onEnter }) | |||
); |
@@ -17,7 +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. | |||
*/ | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import init from '../init'; | |||
export default class MaintenanceAppContainer extends React.PureComponent { |
@@ -17,7 +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. | |||
*/ | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import init from '../init'; | |||
export default class SetupAppContainer extends React.PureComponent { |
@@ -17,7 +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. | |||
*/ | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { IndexRoute } from 'react-router'; | |||
import MaintenanceAppContainer from './components/MaintenanceAppContainer'; | |||
import SetupAppContainer from './components/SetupAppContainer'; |
@@ -17,7 +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. | |||
*/ | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import Helmet from 'react-helmet'; | |||
import init from '../init'; | |||
import { translate } from '../../../helpers/l10n'; |
@@ -17,9 +17,11 @@ | |||
* 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'; | |||
const routes = [ | |||
{ | |||
getIndexRoute(_, callback) { | |||
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { | |||
import('./components/MetricsAppContainer').then(i => | |||
callback(null, { component: i.default }) | |||
); |
@@ -27,8 +27,6 @@ exports[`renders 2`] = ` | |||
overview.quality_gate | |||
<Level | |||
level="ERROR" | |||
muted={false} | |||
small={false} | |||
/> | |||
</h2> | |||
<div |
@@ -17,9 +17,11 @@ | |||
* 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'; | |||
const routes = [ | |||
{ | |||
getIndexRoute(_, callback) { | |||
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { | |||
import('./components/AppContainer').then(i => callback(null, { component: i.default })); | |||
} | |||
} |
@@ -17,9 +17,11 @@ | |||
* 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'; | |||
const routes = [ | |||
{ | |||
getIndexRoute(_, callback) { | |||
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { | |||
Promise.all([ | |||
import('./components/AppContainer').then(i => i.default), | |||
import('../organizations/forSingleOrganization').then(i => i.default) |
@@ -17,9 +17,11 @@ | |||
* 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'; | |||
export const globalPermissionsRoutes = [ | |||
{ | |||
getIndexRoute(_, callback) { | |||
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { | |||
Promise.all([ | |||
import('./global/components/App').then(i => i.default), | |||
import('../organizations/forSingleOrganization').then(i => i.default) | |||
@@ -32,7 +34,7 @@ export const globalPermissionsRoutes = [ | |||
export const projectPermissionsRoutes = [ | |||
{ | |||
getIndexRoute(_, callback) { | |||
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { | |||
import('./project/components/AppContainer').then(i => | |||
callback(null, { component: i.default }) | |||
); |
@@ -17,9 +17,11 @@ | |||
* 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'; | |||
const routes = [ | |||
{ | |||
getIndexRoute(_, callback) { | |||
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { | |||
import('./components/ProjectActivityAppContainer').then(i => | |||
callback(null, { component: i.default }) | |||
); |
@@ -17,9 +17,11 @@ | |||
* 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'; | |||
const routes = [ | |||
{ | |||
getIndexRoute(_, callback) { | |||
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { | |||
Promise.all([ | |||
import('./AppContainer').then(i => i.default), | |||
import('../organizations/forSingleOrganization').then(i => i.default) |
@@ -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'; | |||
export default class App extends React.PureComponent { | |||
componentDidMount() { |
@@ -17,16 +17,17 @@ | |||
* 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, RouteComponent, RedirectFunction } from 'react-router'; | |||
import { saveAll } from '../../helpers/storage'; | |||
const routes = [ | |||
{ | |||
getComponent(_, callback) { | |||
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) { | |||
import('./components/App').then(i => callback(null, i.default)); | |||
}, | |||
childRoutes: [ | |||
{ | |||
getIndexRoute(_, callback) { | |||
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { | |||
import('./components/DefaultPageSelector').then(i => | |||
callback(null, { component: i.default }) | |||
); | |||
@@ -34,14 +35,14 @@ const routes = [ | |||
}, | |||
{ | |||
path: 'all', | |||
onEnter(_, replace) { | |||
onEnter(_: RouterState, replace: RedirectFunction) { | |||
saveAll(); | |||
replace('/projects'); | |||
} | |||
}, | |||
{ | |||
path: 'favorite', | |||
getComponent(_, callback) { | |||
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) { | |||
import('./components/FavoriteProjectsContainer').then(i => callback(null, i.default)); | |||
} | |||
} |
@@ -17,20 +17,22 @@ | |||
* 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, RouteComponent } from 'react-router'; | |||
const routes = [ | |||
{ | |||
getComponent(_, callback) { | |||
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) { | |||
import('./containers/QualityGatesAppContainer').then(i => callback(null, i.default)); | |||
}, | |||
childRoutes: [ | |||
{ | |||
getIndexRoute(_, callback) { | |||
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { | |||
import('./components/Intro').then(i => callback(null, { component: i.default })); | |||
} | |||
}, | |||
{ | |||
path: 'show/:id', | |||
getComponent(_, callback) { | |||
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) { | |||
import('./containers/DetailsContainer').then(i => callback(null, i.default)); | |||
} | |||
} |
@@ -105,6 +105,18 @@ Array [ | |||
`; | |||
exports[`#sortProfiles sorts partial set of inherited profiles 1`] = ` | |||
Array [ | |||
Object { | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"key": "foo", | |||
"name": "foo", | |||
"parentKey": "bar", | |||
}, | |||
] | |||
`; | |||
exports[`#sortProfiles sorts partial set of inherited profiles 2`] = ` | |||
Array [ | |||
Object { | |||
"childrenCount": 0, |
@@ -18,9 +18,10 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { sortProfiles } from '../utils'; | |||
import { IProfile } from '../types'; | |||
function createProfile(key, parentKey) { | |||
return { name: key, key, parentKey }; | |||
function createProfile(key: string, parentKey?: string) { | |||
return { name: key, key, parentKey } as IProfile; | |||
} | |||
describe('#sortProfiles', () => { | |||
@@ -54,7 +55,7 @@ describe('#sortProfiles', () => { | |||
it('sorts partial set of inherited profiles', () => { | |||
const foo = createProfile('foo', 'bar'); | |||
expect(sortProfiles([foo]), ['foo']); | |||
expect(sortProfiles([foo])).toMatchSnapshot(); | |||
const profile1 = createProfile('profile1', 'x'); | |||
const profile2 = createProfile('profile2'); |
@@ -1,122 +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 moment from 'moment'; | |||
import ChangesList from './ChangesList'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { getRulesUrl } from '../../../helpers/urls'; | |||
/*:: | |||
type Props = { | |||
events: Array<{ | |||
action: string, | |||
authorName: string, | |||
date: string, | |||
params?: {}, | |||
ruleKey: string, | |||
ruleName: string | |||
}>, | |||
organization: ?string | |||
}; | |||
*/ | |||
export default class Changelog extends React.PureComponent { | |||
/*:: props: Props; */ | |||
render() { | |||
let isEvenRow = false; | |||
const rows = this.props.events.map((event, index) => { | |||
const prev = index > 0 ? this.props.events[index - 1] : null; | |||
const isSameDate = prev != null && moment(prev.date).diff(event.date, 'seconds') < 10; | |||
const isBulkChange = | |||
prev != null && | |||
isSameDate && | |||
prev.authorName === event.authorName && | |||
prev.action === event.action; | |||
if (!isBulkChange) { | |||
isEvenRow = !isEvenRow; | |||
} | |||
const className = 'js-profile-changelog-event ' + (isEvenRow ? 'even' : 'odd'); | |||
return ( | |||
<tr key={index} className={className}> | |||
<td className="thin nowrap"> | |||
{!isBulkChange && moment(event.date).format('LLL')} | |||
</td> | |||
<td className="thin nowrap"> | |||
{!isBulkChange && | |||
(event.authorName | |||
? <span> | |||
{event.authorName} | |||
</span> | |||
: <span className="note">System</span>)} | |||
</td> | |||
<td className="thin nowrap"> | |||
{!isBulkChange && translate('quality_profiles.changelog', event.action)} | |||
</td> | |||
<td style={{ lineHeight: '1.5' }}> | |||
<Link to={getRulesUrl({ rule_key: event.ruleKey }, this.props.organization)}> | |||
{event.ruleName} | |||
</Link> | |||
</td> | |||
<td className="thin nowrap"> | |||
<ChangesList changes={event.params} /> | |||
</td> | |||
</tr> | |||
); | |||
}); | |||
return ( | |||
<table className="data zebra-hover"> | |||
<thead> | |||
<tr> | |||
<th className="thin nowrap"> | |||
{translate('date')} <i className="icon-sort-desc" /> | |||
</th> | |||
<th className="thin nowrap"> | |||
{translate('user')} | |||
</th> | |||
<th className="thin nowrap"> | |||
{translate('action')} | |||
</th> | |||
<th> | |||
{translate('rule')} | |||
</th> | |||
<th className="thin nowrap"> | |||
{translate('parameters')} | |||
</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
{rows} | |||
</tbody> | |||
</table> | |||
); | |||
} | |||
} |
@@ -0,0 +1,109 @@ | |||
/* | |||
* 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 * as moment from 'moment'; | |||
import ChangesList from './ChangesList'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { getRulesUrl } from '../../../helpers/urls'; | |||
import { IProfileChangelogEvent } from '../types'; | |||
interface Props { | |||
events: IProfileChangelogEvent[]; | |||
organization: string | null; | |||
} | |||
export default function Changelog(props: Props) { | |||
let isEvenRow = false; | |||
const rows = props.events.map((event, index) => { | |||
const prev = index > 0 ? props.events[index - 1] : null; | |||
const isSameDate = prev != null && moment(prev.date).diff(event.date, 'seconds') < 10; | |||
const isBulkChange = | |||
prev != null && | |||
isSameDate && | |||
prev.authorName === event.authorName && | |||
prev.action === event.action; | |||
if (!isBulkChange) { | |||
isEvenRow = !isEvenRow; | |||
} | |||
const className = 'js-profile-changelog-event ' + (isEvenRow ? 'even' : 'odd'); | |||
return ( | |||
<tr key={index} className={className}> | |||
<td className="thin nowrap"> | |||
{!isBulkChange && moment(event.date).format('LLL')} | |||
</td> | |||
<td className="thin nowrap"> | |||
{!isBulkChange && | |||
(event.authorName | |||
? <span> | |||
{event.authorName} | |||
</span> | |||
: <span className="note">System</span>)} | |||
</td> | |||
<td className="thin nowrap"> | |||
{!isBulkChange && translate('quality_profiles.changelog', event.action)} | |||
</td> | |||
<td style={{ lineHeight: '1.5' }}> | |||
<Link to={getRulesUrl({ rule_key: event.ruleKey }, props.organization)}> | |||
{event.ruleName} | |||
</Link> | |||
</td> | |||
<td className="thin nowrap"> | |||
{event.params && <ChangesList changes={event.params} />} | |||
</td> | |||
</tr> | |||
); | |||
}); | |||
return ( | |||
<table className="data zebra-hover"> | |||
<thead> | |||
<tr> | |||
<th className="thin nowrap"> | |||
{translate('date')} <i className="icon-sort-desc" /> | |||
</th> | |||
<th className="thin nowrap"> | |||
{translate('user')} | |||
</th> | |||
<th className="thin nowrap"> | |||
{translate('action')} | |||
</th> | |||
<th> | |||
{translate('rule')} | |||
</th> | |||
<th className="thin nowrap"> | |||
{translate('parameters')} | |||
</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
{rows} | |||
</tbody> | |||
</table> | |||
); | |||
} |
@@ -17,48 +17,42 @@ | |||
* 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 PropTypes from 'prop-types'; | |||
import * as React from 'react'; | |||
import * as PropTypes from 'prop-types'; | |||
import Changelog from './Changelog'; | |||
import ChangelogSearch from './ChangelogSearch'; | |||
import ChangelogEmpty from './ChangelogEmpty'; | |||
import { getProfileChangelog } from '../../../api/quality-profiles'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { getProfileChangelogPath } from '../utils'; | |||
/*:: import type { Profile } from '../propTypes'; */ | |||
import { IProfile, IProfileChangelogEvent } from '../types'; | |||
/*:: | |||
type Props = { | |||
interface Props { | |||
location: { | |||
query: { | |||
since?: string, | |||
to?: string | |||
} | |||
}, | |||
organization: ?string, | |||
profile: Profile | |||
}; | |||
*/ | |||
/*:: | |||
type State = { | |||
events?: Array<*>, | |||
loading: boolean, | |||
page?: number, | |||
total?: number | |||
}; | |||
*/ | |||
export default class ChangelogContainer extends React.PureComponent { | |||
/*:: mounted: boolean; */ | |||
/*:: props: Props; */ | |||
since?: string; | |||
to?: string; | |||
}; | |||
}; | |||
organization: string | null; | |||
profile: IProfile; | |||
} | |||
interface State { | |||
events?: IProfileChangelogEvent[]; | |||
loading: boolean; | |||
page?: number; | |||
total?: number; | |||
} | |||
export default class ChangelogContainer extends React.PureComponent<Props, State> { | |||
mounted: boolean; | |||
static contextTypes = { | |||
router: PropTypes.object | |||
}; | |||
state /*: State */ = { | |||
state: State = { | |||
loading: true | |||
}; | |||
@@ -67,7 +61,7 @@ export default class ChangelogContainer extends React.PureComponent { | |||
this.loadChangelog(); | |||
} | |||
componentDidUpdate(prevProps /*: Props */) { | |||
componentDidUpdate(prevProps: Props) { | |||
if (prevProps.location !== this.props.location) { | |||
this.loadChangelog(); | |||
} | |||
@@ -80,7 +74,7 @@ export default class ChangelogContainer extends React.PureComponent { | |||
loadChangelog() { | |||
this.setState({ loading: true }); | |||
const { query } = this.props.location; | |||
const data /*: Object */ = { profileKey: this.props.profile.key }; | |||
const data: any = { profileKey: this.props.profile.key }; | |||
if (query.since) { | |||
data.since = query.since; | |||
} | |||
@@ -88,7 +82,7 @@ export default class ChangelogContainer extends React.PureComponent { | |||
data.to = query.to; | |||
} | |||
getProfileChangelog(data).then(r => { | |||
getProfileChangelog(data).then((r: any) => { | |||
if (this.mounted) { | |||
this.setState({ | |||
events: r.events, | |||
@@ -100,36 +94,38 @@ export default class ChangelogContainer extends React.PureComponent { | |||
}); | |||
} | |||
loadMore(e /*: SyntheticInputEvent */) { | |||
e.preventDefault(); | |||
e.target.blur(); | |||
loadMore(event: React.SyntheticEvent<HTMLElement>) { | |||
event.preventDefault(); | |||
event.currentTarget.blur(); | |||
if (this.state.page != null) { | |||
this.setState({ loading: true }); | |||
const { query } = this.props.location; | |||
const data: any = { | |||
profileKey: this.props.profile.key, | |||
p: this.state.page + 1 | |||
}; | |||
if (query.since) { | |||
data.since = query.since; | |||
} | |||
if (query.to) { | |||
data.to = query.to; | |||
} | |||
this.setState({ loading: true }); | |||
const { query } = this.props.location; | |||
const data /*: Object */ = { | |||
profileKey: this.props.profile.key, | |||
p: this.state.page + 1 | |||
}; | |||
if (query.since) { | |||
data.since = query.since; | |||
} | |||
if (query.to) { | |||
data.to = query.to; | |||
getProfileChangelog(data).then((r: any) => { | |||
if (this.mounted && this.state.events) { | |||
this.setState({ | |||
events: [...this.state.events, ...r.events], | |||
total: r.total, | |||
page: r.p, | |||
loading: false | |||
}); | |||
} | |||
}); | |||
} | |||
getProfileChangelog(data).then(r => { | |||
if (this.mounted && this.state.events) { | |||
this.setState({ | |||
events: [...this.state.events, ...r.events], | |||
total: r.total, | |||
page: r.p, | |||
loading: false | |||
}); | |||
} | |||
}); | |||
} | |||
handleFromDateChange = (fromDate /*: string | void */) => { | |||
handleFromDateChange = (fromDate?: string) => { | |||
const path = getProfileChangelogPath( | |||
this.props.profile.name, | |||
this.props.profile.language, | |||
@@ -142,7 +138,7 @@ export default class ChangelogContainer extends React.PureComponent { | |||
this.context.router.push(path); | |||
}; | |||
handleToDateChange = (toDate /*: string | void */) => { | |||
handleToDateChange = (toDate?: string) => { | |||
const path = getProfileChangelogPath( | |||
this.props.profile.name, | |||
this.props.profile.language, |
@@ -17,16 +17,13 @@ | |||
* 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 class ChangelogEmpty extends React.PureComponent { | |||
render() { | |||
return ( | |||
<div className="big-spacer-top"> | |||
{translate('no_results')} | |||
</div> | |||
); | |||
} | |||
export default function ChangelogEmpty() { | |||
return ( | |||
<div className="big-spacer-top"> | |||
{translate('no_results')} | |||
</div> | |||
); | |||
} |
@@ -17,27 +17,22 @@ | |||
* 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 DateInput from '../../../components/controls/DateInput'; | |||
import { translate } from '../../../helpers/l10n'; | |||
/*:: | |||
type Props = { | |||
fromDate?: string, | |||
toDate?: string, | |||
onFromDateChange: () => void, | |||
onReset: () => void, | |||
onToDateChange: () => void | |||
}; | |||
*/ | |||
export default class ChangelogSearch extends React.PureComponent { | |||
/*:: props: Props; */ | |||
interface Props { | |||
fromDate?: string; | |||
toDate?: string; | |||
onFromDateChange: () => void; | |||
onReset: () => void; | |||
onToDateChange: () => void; | |||
} | |||
handleResetClick(e /*: SyntheticInputEvent */) { | |||
e.preventDefault(); | |||
e.target.blur(); | |||
export default class ChangelogSearch extends React.PureComponent<Props> { | |||
handleResetClick(event: React.SyntheticEvent<HTMLElement>) { | |||
event.preventDefault(); | |||
event.currentTarget.blur(); | |||
this.props.onReset(); | |||
} | |||
@@ -17,33 +17,24 @@ | |||
* 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 SeverityChange from './SeverityChange'; | |||
import ParameterChange from './ParameterChange'; | |||
/*:: | |||
type Props = { | |||
changes: { [string]: ?string } | |||
}; | |||
*/ | |||
export default class ChangesList extends React.PureComponent { | |||
/*:: props: Props; */ | |||
render() { | |||
const { changes } = this.props; | |||
interface Props { | |||
changes: { [change: string]: string | null }; | |||
} | |||
return ( | |||
<ul> | |||
{Object.keys(changes).map(key => | |||
<li key={key}> | |||
{key === 'severity' | |||
? <SeverityChange severity={changes[key]} /> | |||
: <ParameterChange name={key} value={changes[key]} />} | |||
</li> | |||
)} | |||
</ul> | |||
); | |||
} | |||
export default function ChangesList({ changes }: Props) { | |||
return ( | |||
<ul> | |||
{Object.keys(changes).map(key => | |||
<li key={key}> | |||
{key === 'severity' | |||
? <SeverityChange severity={changes[key]} /> | |||
: <ParameterChange name={key} value={changes[key]} />} | |||
</li> | |||
)} | |||
</ul> | |||
); | |||
} |
@@ -17,38 +17,29 @@ | |||
* 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 { translateWithParameters } from '../../../helpers/l10n'; | |||
/*:: | |||
type Props = { | |||
name: string, | |||
value: ?string | |||
}; | |||
*/ | |||
export default class ParameterChange extends React.PureComponent { | |||
/*:: props: Props; */ | |||
render() { | |||
const { name, value } = this.props; | |||
if (value == null) { | |||
return ( | |||
<div style={{ whiteSpace: 'normal' }}> | |||
{translateWithParameters( | |||
'quality_profiles.changelog.parameter_reset_to_default_value', | |||
name | |||
)} | |||
</div> | |||
); | |||
} | |||
interface Props { | |||
name: string; | |||
value: string | null; | |||
} | |||
export default function ParameterChange({ name, value }: Props) { | |||
if (value == null) { | |||
return ( | |||
<div style={{ whiteSpace: 'normal' }}> | |||
{translateWithParameters('quality_profiles.parameter_set_to', name, value)} | |||
{translateWithParameters( | |||
'quality_profiles.changelog.parameter_reset_to_default_value', | |||
name | |||
)} | |||
</div> | |||
); | |||
} | |||
return ( | |||
<div style={{ whiteSpace: 'normal' }}> | |||
{translateWithParameters('quality_profiles.parameter_set_to', name, value)} | |||
</div> | |||
); | |||
} |
@@ -17,26 +17,18 @@ | |||
* 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 SeverityHelper from '../../../components/shared/SeverityHelper'; | |||
import { translate } from '../../../helpers/l10n'; | |||
/*:: | |||
type Props = { | |||
severity: ?string | |||
}; | |||
*/ | |||
export default class SeverityChange extends React.PureComponent { | |||
/*:: props: Props; */ | |||
interface Props { | |||
severity: string | null; | |||
} | |||
render() { | |||
return ( | |||
<div> | |||
{translate('quality_profiles.severity_set_to')}{' '} | |||
<SeverityHelper severity={this.props.severity} /> | |||
</div> | |||
); | |||
} | |||
export default function SeverityChange({ severity }: Props) { | |||
return ( | |||
<div> | |||
{translate('quality_profiles.severity_set_to')} <SeverityHelper severity={severity} /> | |||
</div> | |||
); | |||
} |
@@ -18,11 +18,12 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import Changelog from '../Changelog'; | |||
import ChangesList from '../ChangesList'; | |||
import { IProfileChangelogEvent } from '../../types'; | |||
function createEvent(overrides) { | |||
function createEvent(overrides?: { [p: string]: any }): IProfileChangelogEvent { | |||
return { | |||
date: '2016-01-01', | |||
authorName: 'John', | |||
@@ -30,50 +31,51 @@ function createEvent(overrides) { | |||
ruleKey: 'squid1234', | |||
ruleName: 'Do not do this', | |||
params: {}, | |||
organization: null, | |||
...overrides | |||
}; | |||
} | |||
it('should render events', () => { | |||
const events = [createEvent(), createEvent()]; | |||
const changelog = shallow(<Changelog events={events} />); | |||
const changelog = shallow(<Changelog events={events} organization={null} />); | |||
expect(changelog.find('tbody').find('tr').length).toBe(2); | |||
}); | |||
it('should render event date', () => { | |||
const events = [createEvent()]; | |||
const changelog = shallow(<Changelog events={events} />); | |||
const changelog = shallow(<Changelog events={events} organization={null} />); | |||
expect(changelog.text()).toContain('2016'); | |||
}); | |||
it('should render author', () => { | |||
const events = [createEvent()]; | |||
const changelog = shallow(<Changelog events={events} />); | |||
const changelog = shallow(<Changelog events={events} organization={null} />); | |||
expect(changelog.text()).toContain('John'); | |||
}); | |||
it('should render system author', () => { | |||
const events = [createEvent({ authorName: undefined })]; | |||
const changelog = shallow(<Changelog events={events} />); | |||
const changelog = shallow(<Changelog events={events} organization={null} />); | |||
expect(changelog.text()).toContain('System'); | |||
}); | |||
it('should render action', () => { | |||
const events = [createEvent()]; | |||
const changelog = shallow(<Changelog events={events} />); | |||
const changelog = shallow(<Changelog events={events} organization={null} />); | |||
expect(changelog.text()).toContain('ACTIVATED'); | |||
}); | |||
it('should render rule', () => { | |||
const events = [createEvent()]; | |||
const changelog = shallow(<Changelog events={events} />); | |||
const changelog = shallow(<Changelog events={events} organization={null} />); | |||
expect(changelog.find('Link').prop('to')).toContain('rule_key=squid1234'); | |||
}); | |||
it('should render ChangesList', () => { | |||
const params = { severity: 'BLOCKER' }; | |||
const events = [createEvent({ params })]; | |||
const changelog = shallow(<Changelog events={events} />); | |||
const changelog = shallow(<Changelog events={events} organization={null} />); | |||
const changesList = changelog.find(ChangesList); | |||
expect(changesList.length).toBe(1); | |||
expect(changesList.prop('changes')).toBe(params); |
@@ -18,7 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import ChangelogSearch from '../ChangelogSearch'; | |||
import DateInput from '../../../../components/controls/DateInput'; | |||
import { click } from '../../../../helpers/testUtils'; |
@@ -18,7 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import ChangesList from '../ChangesList'; | |||
import SeverityChange from '../SeverityChange'; | |||
import ParameterChange from '../ParameterChange'; |
@@ -18,11 +18,11 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import ParameterChange from '../ParameterChange'; | |||
it('should render different messages', () => { | |||
const first = shallow(<ParameterChange name="foo" />); | |||
const first = shallow(<ParameterChange name="foo" value={null} />); | |||
const second = shallow(<ParameterChange name="foo" value="bar" />); | |||
expect(first.text()).not.toBe(second.text()); | |||
}); |
@@ -18,7 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import SeverityChange from '../SeverityChange'; | |||
import SeverityHelper from '../../../../components/shared/SeverityHelper'; | |||
@@ -17,45 +17,45 @@ | |||
* 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 PropTypes from 'prop-types'; | |||
import * as React from 'react'; | |||
import * as PropTypes from 'prop-types'; | |||
import ComparisonForm from './ComparisonForm'; | |||
import ComparisonResults from './ComparisonResults'; | |||
import { compareProfiles } from '../../../api/quality-profiles'; | |||
import { getProfileComparePath } from '../utils'; | |||
/*:: import type { Profile } from '../propTypes'; */ | |||
/*:: | |||
type Props = { | |||
location: { query: { withKey?: string } }, | |||
organization: ?string, | |||
profile: Profile, | |||
profiles: Array<Profile> | |||
}; | |||
*/ | |||
/*:: | |||
type State = { | |||
loading: boolean, | |||
left?: { name: string }, | |||
right?: { name: string }, | |||
inLeft?: Array<*>, | |||
inRight?: Array<*>, | |||
modified?: Array<*> | |||
}; | |||
*/ | |||
export default class ComparisonContainer extends React.PureComponent { | |||
/*:: mounted: boolean; */ | |||
/*:: props: Props; */ | |||
/*:: state: State; */ | |||
import { IProfile } from '../types'; | |||
interface Props { | |||
location: { query: { withKey?: string } }; | |||
organization: string | null; | |||
profile: IProfile; | |||
profiles: IProfile[]; | |||
} | |||
type Params = { [p: string]: string }; | |||
interface State { | |||
loading: boolean; | |||
left?: { name: string }; | |||
right?: { name: string }; | |||
inLeft?: Array<{ key: string; name: string; severity: string }>; | |||
inRight?: Array<{ key: string; name: string; severity: string }>; | |||
modified?: Array<{ | |||
key: string; | |||
name: string; | |||
left: { params: Params; severity: string }; | |||
right: { params: Params; severity: string }; | |||
}>; | |||
} | |||
export default class ComparisonContainer extends React.PureComponent<Props, State> { | |||
mounted: boolean; | |||
static contextTypes = { | |||
router: PropTypes.object | |||
}; | |||
constructor(props /*: Props */) { | |||
constructor(props: Props) { | |||
super(props); | |||
this.state = { loading: false }; | |||
} | |||
@@ -65,7 +65,7 @@ export default class ComparisonContainer extends React.PureComponent { | |||
this.loadResults(); | |||
} | |||
componentDidUpdate(prevProps /*: Props */) { | |||
componentDidUpdate(prevProps: Props) { | |||
if (prevProps.profile !== this.props.profile || prevProps.location !== this.props.location) { | |||
this.loadResults(); | |||
} | |||
@@ -83,7 +83,7 @@ export default class ComparisonContainer extends React.PureComponent { | |||
} | |||
this.setState({ loading: true }); | |||
compareProfiles(this.props.profile.key, withKey).then(r => { | |||
compareProfiles(this.props.profile.key, withKey).then((r: any) => { | |||
if (this.mounted) { | |||
this.setState({ | |||
left: r.left, | |||
@@ -97,7 +97,7 @@ export default class ComparisonContainer extends React.PureComponent { | |||
}); | |||
} | |||
handleCompare = (withKey /*: string */) => { | |||
handleCompare = (withKey: string) => { | |||
const path = getProfileComparePath( | |||
this.props.profile.name, | |||
this.props.profile.language, | |||
@@ -126,6 +126,10 @@ export default class ComparisonContainer extends React.PureComponent { | |||
</header> | |||
{left != null && | |||
inLeft != null && | |||
right != null && | |||
inRight != null && | |||
modified != null && | |||
<ComparisonResults | |||
left={left} | |||
right={right} |
@@ -17,16 +17,13 @@ | |||
* 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 class ComparisonEmpty extends React.PureComponent { | |||
render() { | |||
return ( | |||
<div className="big-spacer-top"> | |||
{translate('quality_profile.empty_comparison')} | |||
</div> | |||
); | |||
} | |||
export default function ComparisonEmpty() { | |||
return ( | |||
<div className="big-spacer-top"> | |||
{translate('quality_profile.empty_comparison')} | |||
</div> | |||
); | |||
} |
@@ -17,25 +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. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import Select from 'react-select'; | |||
import * as React from 'react'; | |||
import * as Select from 'react-select'; | |||
import { translate } from '../../../helpers/l10n'; | |||
/*:: import type { Profile } from '../propTypes'; */ | |||
import { IProfile } from '../types'; | |||
/*:: | |||
type Props = { | |||
profile: Profile, | |||
profiles: Array<Profile>, | |||
onCompare: string => void, | |||
withKey: string | |||
}; | |||
*/ | |||
export default class ComparisonForm extends React.PureComponent { | |||
/*:: props: Props; */ | |||
interface Props { | |||
profile: IProfile; | |||
profiles: IProfile[]; | |||
onCompare: (rule: string) => void; | |||
withKey?: string; | |||
} | |||
handleChange(option /*: { value: string } */) { | |||
export default class ComparisonForm extends React.PureComponent<Props> { | |||
handleChange(option: { value: string }) { | |||
this.props.onCompare(option.value); | |||
} | |||
@@ -17,33 +17,31 @@ | |||
* 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 { Link } from 'react-router'; | |||
import ComparisonEmpty from './ComparisonEmpty'; | |||
import SeverityIcon from '../../../components/shared/SeverityIcon'; | |||
import { translateWithParameters } from '../../../helpers/l10n'; | |||
import { getRulesUrl } from '../../../helpers/urls'; | |||
/*:: | |||
type Params = { [string]: string }; | |||
*/ | |||
type Params = { [p: string]: string }; | |||
/*:: | |||
type Props = { | |||
left: { name: string }, | |||
right: { name: string }, | |||
inLeft: Array<*>, | |||
inRight: Array<*>, | |||
modified: Array<*>, | |||
organization: ?string | |||
}; | |||
*/ | |||
export default class ComparisonResults extends React.PureComponent { | |||
/*:: props: Props; */ | |||
interface Props { | |||
left: { name: string }; | |||
right: { name: string }; | |||
inLeft: Array<{ key: string; name: string; severity: string }>; | |||
inRight: Array<{ key: string; name: string; severity: string }>; | |||
modified: Array<{ | |||
key: string; | |||
name: string; | |||
left: { params: Params; severity: string }; | |||
right: { params: Params; severity: string }; | |||
}>; | |||
organization: string | null; | |||
} | |||
renderRule(rule /*: { key: string, name: string } */, severity /*: string */) { | |||
export default class ComparisonResults extends React.PureComponent<Props> { | |||
renderRule(rule: { key: string; name: string }, severity: string) { | |||
return ( | |||
<div> | |||
<SeverityIcon severity={severity} />{' '} | |||
@@ -52,7 +50,7 @@ export default class ComparisonResults extends React.PureComponent { | |||
); | |||
} | |||
renderParameters(params /*: Params */) { | |||
renderParameters(params: Params) { | |||
if (!params) { | |||
return null; | |||
} | |||
@@ -135,7 +133,7 @@ export default class ComparisonResults extends React.PureComponent { | |||
} | |||
const header = ( | |||
<tr key="modified-header"> | |||
<td colSpan="2" className="text-center"> | |||
<td colSpan={2} className="text-center"> | |||
<h6> | |||
{translateWithParameters( | |||
'quality_profiles.x_rules_have_different_configuration', |
@@ -18,8 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import React from 'react'; | |||
import Select from 'react-select'; | |||
import * as React from 'react'; | |||
import ComparisonForm from '../ComparisonForm'; | |||
import { createFakeProfile } from '../../utils'; | |||
@@ -38,7 +37,7 @@ it('should render Select with right options', () => { | |||
profiles={profiles} | |||
onCompare={() => true} | |||
/> | |||
).find(Select); | |||
).find('Select'); | |||
expect(output.length).toBe(1); | |||
expect(output.prop('value')).toBe('another'); | |||
expect(output.prop('options')).toEqual([{ value: 'another', label: 'another name' }]); |
@@ -18,7 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
import ComparisonResults from '../ComparisonResults'; | |||
import ComparisonEmpty from '../ComparisonEmpty'; | |||
@@ -32,6 +32,7 @@ it('should render ComparisonEmpty', () => { | |||
inLeft={[]} | |||
inRight={[]} | |||
modified={[]} | |||
organization={null} | |||
/> | |||
); | |||
expect(output.is(ComparisonEmpty)).toBe(true); | |||
@@ -65,6 +66,7 @@ it('should compare', () => { | |||
inLeft={inLeft} | |||
inRight={inRight} | |||
modified={modified} | |||
organization={null} | |||
/> | |||
); | |||
@@ -17,37 +17,31 @@ | |||
* 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 { searchQualityProfiles, getExporters } from '../../../api/quality-profiles'; | |||
import { sortProfiles } from '../utils'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import OrganizationHelmet from '../../../components/common/OrganizationHelmet'; | |||
/*:: import type { Exporter } from '../propTypes'; */ | |||
import '../styles.css'; | |||
import { IExporter, IProfile } from '../types'; | |||
/*:: | |||
type Props = { | |||
children: React.Element<*>, | |||
currentUser: { permissions: { global: Array<string> } }, | |||
languages: Array<*>, | |||
onRequestFail: Object => void, | |||
organization: { name: string, canAdmin?: boolean, key: string } | null | |||
}; | |||
*/ | |||
interface Props { | |||
children: React.ReactElement<any>; | |||
currentUser: { permissions: { global: Array<string> } }; | |||
languages: Array<{}>; | |||
onRequestFail: (reasong: any) => void; | |||
organization: { name: string; canAdmin?: boolean; key: string } | null; | |||
} | |||
/*:: | |||
type State = { | |||
loading: boolean, | |||
exporters?: Array<Exporter>, | |||
profiles?: Array<*> | |||
}; | |||
*/ | |||
interface State { | |||
loading: boolean; | |||
exporters?: IExporter[]; | |||
profiles?: IProfile[]; | |||
} | |||
export default class App extends React.PureComponent { | |||
/*:: mounted: boolean; */ | |||
/*:: props: Props; */ | |||
state /*: State */ = { loading: true }; | |||
export default class App extends React.PureComponent<Props, State> { | |||
mounted: boolean; | |||
state: State = { loading: true }; | |||
componentWillMount() { | |||
const html = document.querySelector('html'); | |||
@@ -90,7 +84,7 @@ export default class App extends React.PureComponent { | |||
} | |||
updateProfiles = () => { | |||
return this.fetchProfiles().then(profiles => { | |||
return this.fetchProfiles().then((profiles: any) => { | |||
if (this.mounted) { | |||
this.setState({ profiles: sortProfiles(profiles) }); | |||
} |
@@ -22,7 +22,7 @@ import App from './App'; | |||
import { getLanguages, getCurrentUser, getOrganizationByKey } from '../../../store/rootReducer'; | |||
import { onFail } from '../../../store/rootActions'; | |||
const mapStateToProps = (state, ownProps) => ({ | |||
const mapStateToProps = (state: any, ownProps: any) => ({ | |||
currentUser: getCurrentUser(state), | |||
languages: getLanguages(state), | |||
organization: ownProps.params.organizationKey | |||
@@ -30,8 +30,8 @@ const mapStateToProps = (state, ownProps) => ({ | |||
: null | |||
}); | |||
const mapDispatchToProps = dispatch => ({ | |||
onRequestFail: error => onFail(dispatch)(error) | |||
const mapDispatchToProps = (dispatch: any) => ({ | |||
onRequestFail: (error: any) => onFail(dispatch)(error) | |||
}); | |||
export default connect(mapStateToProps, mapDispatchToProps)(App); | |||
export default connect(mapStateToProps, mapDispatchToProps)(App as any); |
@@ -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 classNames from 'classnames'; | |||
import * as React from 'react'; | |||
import * as classNames from 'classnames'; | |||
import Tooltip from '../../../components/controls/Tooltip'; | |||
import { translate } from '../../../helpers/l10n'; | |||
/*:: | |||
type Props = {| | |||
className?: string, | |||
tooltip?: boolean | |||
|}; | |||
*/ | |||
interface Props { | |||
className?: string; | |||
tooltip?: boolean; | |||
} | |||
export default function BuiltInBadge(props /*: Props */) { | |||
export default function BuiltInBadge({ className, tooltip = true }: Props) { | |||
const badge = ( | |||
<div className={classNames('outline-badge', props.className)}> | |||
<div className={classNames('outline-badge', className)}> | |||
{translate('quality_profiles.built_in')} | |||
</div> | |||
); | |||
@@ -44,13 +41,9 @@ export default function BuiltInBadge(props /*: Props */) { | |||
</span> | |||
); | |||
return props.tooltip | |||
return tooltip | |||
? <Tooltip overlay={overlay}> | |||
{badge} | |||
</Tooltip> | |||
: badge; | |||
} | |||
BuiltInBadge.defaultProps = { | |||
tooltip: true | |||
}; |
@@ -17,33 +17,27 @@ | |||
* 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 Modal from 'react-modal'; | |||
/*:: import type { Profile } from '../propTypes'; */ | |||
import { copyProfile } from '../../../api/quality-profiles'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { IProfile } from '../types'; | |||
/*:: | |||
type Props = { | |||
onClose: () => void, | |||
onCopy: string => void, | |||
onRequestFail: Object => void, | |||
profile: Profile | |||
}; | |||
*/ | |||
interface Props { | |||
onClose: () => void; | |||
onCopy: (name: string) => void; | |||
onRequestFail: (reasong: any) => void; | |||
profile: IProfile; | |||
} | |||
/*:: | |||
type State = { | |||
loading: boolean, | |||
name: ?string | |||
}; | |||
*/ | |||
interface State { | |||
loading: boolean; | |||
name: string | null; | |||
} | |||
export default class CopyProfileForm extends React.PureComponent { | |||
/*:: mounted: boolean; */ | |||
/*:: props: Props; */ | |||
state /*: State */ = { loading: false, name: null }; | |||
export default class CopyProfileForm extends React.PureComponent<Props, State> { | |||
mounted: boolean; | |||
state: State = { loading: false, name: null }; | |||
componentDidMount() { | |||
this.mounted = true; | |||
@@ -53,16 +47,16 @@ export default class CopyProfileForm extends React.PureComponent { | |||
this.mounted = false; | |||
} | |||
handleCancelClick = (event /*: Event */) => { | |||
handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => { | |||
event.preventDefault(); | |||
this.props.onClose(); | |||
}; | |||
handleNameChange = (event /*: { currentTarget: HTMLInputElement } */) => { | |||
handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => { | |||
this.setState({ name: event.currentTarget.value }); | |||
}; | |||
handleFormSubmit = (event /*: Event */) => { | |||
handleFormSubmit = (event: React.SyntheticEvent<HTMLElement>) => { | |||
event.preventDefault(); | |||
const { name } = this.state; | |||
@@ -70,8 +64,8 @@ export default class CopyProfileForm extends React.PureComponent { | |||
if (name != null) { | |||
this.setState({ loading: true }); | |||
copyProfile(this.props.profile.key, name).then( | |||
profile => this.props.onCopy(profile.name), | |||
error => { | |||
(profile: any) => this.props.onCopy(profile.name), | |||
(error: any) => { | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
@@ -113,11 +107,11 @@ export default class CopyProfileForm extends React.PureComponent { | |||
<input | |||
autoFocus={true} | |||
id="copy-profile-name" | |||
maxLength="100" | |||
maxLength={100} | |||
name="name" | |||
onChange={this.handleNameChange} | |||
required={true} | |||
size="50" | |||
size={50} | |||
type="text" | |||
value={this.state.name != null ? this.state.name : profile.name} | |||
/> |
@@ -17,32 +17,27 @@ | |||
* 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 Modal from 'react-modal'; | |||
/*:: import type { Profile } from '../propTypes'; */ | |||
import { deleteProfile } from '../../../api/quality-profiles'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { IProfile } from '../types'; | |||
/*:: | |||
type Props = { | |||
onClose: () => void, | |||
onDelete: () => void, | |||
onRequestFail: Object => void, | |||
profile: Profile | |||
}; | |||
*/ | |||
interface Props { | |||
onClose: () => void; | |||
onDelete: () => void; | |||
onRequestFail: (reason: any) => void; | |||
profile: IProfile; | |||
} | |||
/*:: | |||
type State = { | |||
loading: boolean | |||
}; | |||
*/ | |||
interface State { | |||
loading: boolean; | |||
name: string | null; | |||
} | |||
export default class DeleteProfileForm extends React.PureComponent { | |||
/*:: mounted: boolean; */ | |||
/*:: props: Props; */ | |||
state /*: State */ = { loading: false, name: null }; | |||
export default class DeleteProfileForm extends React.PureComponent<Props, State> { | |||
mounted: boolean; | |||
state: State = { loading: false, name: null }; | |||
componentDidMount() { | |||
this.mounted = true; | |||
@@ -52,15 +47,15 @@ export default class DeleteProfileForm extends React.PureComponent { | |||
this.mounted = false; | |||
} | |||
handleCancelClick = (event /*: Event */) => { | |||
handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => { | |||
event.preventDefault(); | |||
this.props.onClose(); | |||
}; | |||
handleFormSubmit = (event /*: Event */) => { | |||
handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => { | |||
event.preventDefault(); | |||
this.setState({ loading: true }); | |||
deleteProfile(this.props.profile.key).then(this.props.onDelete, error => { | |||
deleteProfile(this.props.profile.key).then(this.props.onDelete, (error: any) => { | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} |
@@ -17,9 +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. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import PropTypes from 'prop-types'; | |||
import * as React from 'react'; | |||
import * as PropTypes from 'prop-types'; | |||
import { Link } from 'react-router'; | |||
import RenameProfileForm from './RenameProfileForm'; | |||
import CopyProfileForm from './CopyProfileForm'; | |||
@@ -28,31 +27,24 @@ import { translate } from '../../../helpers/l10n'; | |||
import { getRulesUrl } from '../../../helpers/urls'; | |||
import { setDefaultProfile } from '../../../api/quality-profiles'; | |||
import { getProfilePath, getProfileComparePath, getProfilesPath } from '../utils'; | |||
/*:: import type { Profile } from '../propTypes'; */ | |||
/*:: | |||
type Props = { | |||
canAdmin: boolean, | |||
fromList: boolean, | |||
onRequestFail: Object => void, | |||
organization: ?string, | |||
profile: Profile, | |||
updateProfiles: () => Promise<*> | |||
}; | |||
*/ | |||
/*:: | |||
type State = { | |||
copyFormOpen: boolean, | |||
deleteFormOpen: boolean, | |||
renameFormOpen: boolean | |||
}; | |||
*/ | |||
export default class ProfileActions extends React.PureComponent { | |||
/*:: props: Props; */ | |||
/*:: state: State; */ | |||
import { IProfile } from '../types'; | |||
interface Props { | |||
canAdmin: boolean; | |||
fromList?: boolean; | |||
onRequestFail: (reasong: any) => void; | |||
organization: string | null; | |||
profile: IProfile; | |||
updateProfiles: () => Promise<void>; | |||
} | |||
interface State { | |||
copyFormOpen: boolean; | |||
deleteFormOpen: boolean; | |||
renameFormOpen: boolean; | |||
} | |||
export default class ProfileActions extends React.PureComponent<Props, State> { | |||
static defaultProps = { | |||
fromList: false | |||
}; | |||
@@ -61,7 +53,7 @@ export default class ProfileActions extends React.PureComponent { | |||
router: PropTypes.object | |||
}; | |||
constructor(props /*: Props */) { | |||
constructor(props: Props) { | |||
super(props); | |||
this.state = { | |||
copyFormOpen: false, | |||
@@ -70,12 +62,12 @@ export default class ProfileActions extends React.PureComponent { | |||
}; | |||
} | |||
handleRenameClick = (event /*: Event */) => { | |||
handleRenameClick = (event: React.SyntheticEvent<HTMLElement>) => { | |||
event.preventDefault(); | |||
this.setState({ renameFormOpen: true }); | |||
}; | |||
handleProfileRename = (name /*: string */) => { | |||
handleProfileRename = (name: string) => { | |||
this.closeRenameForm(); | |||
this.props.updateProfiles().then(() => { | |||
if (!this.props.fromList) { | |||
@@ -90,12 +82,12 @@ export default class ProfileActions extends React.PureComponent { | |||
this.setState({ renameFormOpen: false }); | |||
}; | |||
handleCopyClick = (event /*: Event */) => { | |||
handleCopyClick = (event: React.SyntheticEvent<HTMLElement>) => { | |||
event.preventDefault(); | |||
this.setState({ copyFormOpen: true }); | |||
}; | |||
handleProfileCopy = (name /*: string */) => { | |||
handleProfileCopy = (name: string) => { | |||
this.props.updateProfiles().then(() => { | |||
this.context.router.push( | |||
getProfilePath(name, this.props.profile.language, this.props.organization) | |||
@@ -107,12 +99,12 @@ export default class ProfileActions extends React.PureComponent { | |||
this.setState({ copyFormOpen: false }); | |||
}; | |||
handleSetDefaultClick = (e /*: SyntheticInputEvent */) => { | |||
handleSetDefaultClick = (e: React.SyntheticEvent<HTMLElement>) => { | |||
e.preventDefault(); | |||
setDefaultProfile(this.props.profile.key).then(this.props.updateProfiles); | |||
}; | |||
handleDeleteClick = (event /*: Event */) => { | |||
handleDeleteClick = (event: React.SyntheticEvent<HTMLElement>) => { | |||
event.preventDefault(); | |||
this.setState({ deleteFormOpen: true }); | |||
}; | |||
@@ -131,7 +123,9 @@ export default class ProfileActions extends React.PureComponent { | |||
// FIXME use org, name and lang | |||
const backupUrl = | |||
window.baseUrl + '/api/qualityprofiles/backup?profileKey=' + encodeURIComponent(profile.key); | |||
(window as any).baseUrl + | |||
'/api/qualityprofiles/backup?profileKey=' + | |||
encodeURIComponent(profile.key); | |||
const activateMoreUrl = getRulesUrl( | |||
{ |
@@ -17,32 +17,27 @@ | |||
* 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 Helmet from 'react-helmet'; | |||
import ProfileNotFound from './ProfileNotFound'; | |||
import ProfileHeader from '../details/ProfileHeader'; | |||
/*:: import type { Profile } from '../propTypes'; */ | |||
import { IProfile } from '../types'; | |||
/*:: | |||
type Props = { | |||
canAdmin: boolean, | |||
children: React.Element<*>, | |||
interface Props { | |||
canAdmin: boolean; | |||
children: React.ReactElement<any>; | |||
location: { | |||
pathname: string, | |||
query: { key?: string, language: string, name: string } | |||
}, | |||
onRequestFail: Object => void, | |||
organization: ?string, | |||
profiles: Array<Profile>, | |||
router: { replace: ({}) => void }, | |||
updateProfiles: () => Promise<*> | |||
}; | |||
*/ | |||
export default class ProfileContainer extends React.PureComponent { | |||
/*:: props: Props; */ | |||
pathname: string; | |||
query: { key?: string; language: string; name: string }; | |||
}; | |||
onRequestFail: (reasong: any) => void; | |||
organization: string | null; | |||
profiles: IProfile[]; | |||
router: { replace: ({}) => void }; | |||
updateProfiles: () => Promise<void>; | |||
} | |||
export default class ProfileContainer extends React.PureComponent<Props> { | |||
componentDidMount() { | |||
const { location, profiles, router } = this.props; | |||
if (location.query.key) { |
@@ -17,35 +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. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import moment from 'moment'; | |||
import * as React from 'react'; | |||
import * as moment from 'moment'; | |||
import { translate } from '../../../helpers/l10n'; | |||
/*:: | |||
type Props = { | |||
date?: string | |||
}; | |||
*/ | |||
export default class ProfileDate extends React.PureComponent { | |||
/*:: props: Props; */ | |||
render() { | |||
const { date } = this.props; | |||
if (!date) { | |||
return ( | |||
<span> | |||
{translate('never')} | |||
</span> | |||
); | |||
} | |||
interface Props { | |||
date?: string; | |||
} | |||
return ( | |||
<span title={moment(date).format('LLL')} data-toggle="tooltip"> | |||
export default function ProfileDate({ date }: Props) { | |||
return date | |||
? <span title={moment(date).format('LLL')} data-toggle="tooltip"> | |||
{moment(date).fromNow()} | |||
</span> | |||
); | |||
} | |||
: <span> | |||
{translate('never')} | |||
</span>; | |||
} |
@@ -17,32 +17,25 @@ | |||
* 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 { Link } from 'react-router'; | |||
import { getProfilePath } from '../utils'; | |||
/*:: | |||
type Props = { | |||
children?: React.Element<*>, | |||
language: string, | |||
name: string, | |||
organization: ?string | |||
}; | |||
*/ | |||
export default class ProfileLink extends React.PureComponent { | |||
/*:: props: Props; */ | |||
interface Props { | |||
className?: string; | |||
children?: React.ReactElement<any> | string; | |||
language: string; | |||
name: string; | |||
organization: string | null; | |||
} | |||
render() { | |||
const { name, language, organization, children, ...other } = this.props; | |||
return ( | |||
<Link | |||
to={getProfilePath(name, language, organization)} | |||
activeClassName="link-no-underline" | |||
{...other}> | |||
{children} | |||
</Link> | |||
); | |||
} | |||
export default function ProfileLink({ name, language, organization, children, ...other }: Props) { | |||
return ( | |||
<Link | |||
to={getProfilePath(name, language, organization)} | |||
activeClassName="link-no-underline" | |||
{...other}> | |||
{children} | |||
</Link> | |||
); | |||
} |
@@ -17,34 +17,27 @@ | |||
* 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 { IndexLink } from 'react-router'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { getProfilesPath } from '../utils'; | |||
/*:: | |||
type Props = { | |||
organization: ?string | |||
}; | |||
*/ | |||
export default class ProfileNotFound extends React.PureComponent { | |||
/*:: props: Props; */ | |||
interface Props { | |||
organization: string | null; | |||
} | |||
render() { | |||
return ( | |||
<div className="quality-profile-not-found"> | |||
<div className="note spacer-bottom"> | |||
<IndexLink to={getProfilesPath(this.props.organization)} className="text-muted"> | |||
{translate('quality_profiles.page')} | |||
</IndexLink> | |||
</div> | |||
export default function ProfileNotFound(props: Props) { | |||
return ( | |||
<div className="quality-profile-not-found"> | |||
<div className="note spacer-bottom"> | |||
<IndexLink to={getProfilesPath(props.organization)} className="text-muted"> | |||
{translate('quality_profiles.page')} | |||
</IndexLink> | |||
</div> | |||
<div> | |||
{translate('quality_profiles.not_found')} | |||
</div> | |||
<div> | |||
{translate('quality_profiles.not_found')} | |||
</div> | |||
); | |||
} | |||
</div> | |||
); | |||
} |
@@ -17,33 +17,27 @@ | |||
* 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 Modal from 'react-modal'; | |||
/*:: import type { Profile } from '../propTypes'; */ | |||
import { renameProfile } from '../../../api/quality-profiles'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { IProfile } from '../types'; | |||
/*:: | |||
type Props = { | |||
onClose: () => void, | |||
onRename: string => void, | |||
onRequestFail: Object => void, | |||
profile: Profile | |||
}; | |||
*/ | |||
interface Props { | |||
onClose: () => void; | |||
onRename: (name: string) => void; | |||
onRequestFail: (reason: any) => void; | |||
profile: IProfile; | |||
} | |||
/*:: | |||
type State = { | |||
loading: boolean, | |||
name: ?string | |||
}; | |||
*/ | |||
interface State { | |||
loading: boolean; | |||
name: string | null; | |||
} | |||
export default class RenameProfileForm extends React.PureComponent { | |||
/*:: mounted: boolean; */ | |||
/*:: props: Props; */ | |||
state /*: State */ = { loading: false, name: null }; | |||
export default class RenameProfileForm extends React.PureComponent<Props, State> { | |||
mounted: boolean; | |||
state: State = { loading: false, name: null }; | |||
componentDidMount() { | |||
this.mounted = true; | |||
@@ -53,16 +47,16 @@ export default class RenameProfileForm extends React.PureComponent { | |||
this.mounted = false; | |||
} | |||
handleCancelClick = (event /*: Event */) => { | |||
handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => { | |||
event.preventDefault(); | |||
this.props.onClose(); | |||
}; | |||
handleNameChange = (event /*: { currentTarget: HTMLInputElement } */) => { | |||
handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => { | |||
this.setState({ name: event.currentTarget.value }); | |||
}; | |||
handleFormSubmit = (event /*: Event */) => { | |||
handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => { | |||
event.preventDefault(); | |||
const { name } = this.state; | |||
@@ -71,7 +65,7 @@ export default class RenameProfileForm extends React.PureComponent { | |||
this.setState({ loading: true }); | |||
renameProfile(this.props.profile.key, name).then( | |||
() => this.props.onRename(name), | |||
error => { | |||
(error: any) => { | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
@@ -113,11 +107,11 @@ export default class RenameProfileForm extends React.PureComponent { | |||
<input | |||
autoFocus={true} | |||
id="rename-profile-name" | |||
maxLength="100" | |||
maxLength={100} | |||
name="name" | |||
onChange={this.handleNameChange} | |||
required={true} | |||
size="50" | |||
size={50} | |||
type="text" | |||
value={this.state.name != null ? this.state.name : profile.name} | |||
/> |
@@ -18,7 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import Helmet from 'react-helmet'; | |||
import ProfileContainer from '../ProfileContainer'; | |||
import ProfileNotFound from '../ProfileNotFound'; | |||
@@ -31,9 +31,12 @@ it('should render ProfileHeader', () => { | |||
const updateProfiles = jest.fn(); | |||
const output = shallow( | |||
<ProfileContainer | |||
location={{ query: { language: 'js', name: 'fake' } }} | |||
profiles={profiles} | |||
canAdmin={false} | |||
location={{ pathname: '', query: { language: 'js', name: 'fake' } }} | |||
onRequestFail={jest.fn()} | |||
organization={null} | |||
profiles={profiles} | |||
router={{} as any} | |||
updateProfiles={updateProfiles}> | |||
<div /> | |||
</ProfileContainer> | |||
@@ -52,10 +55,13 @@ it('should render ProfileNotFound', () => { | |||
]; | |||
const output = shallow( | |||
<ProfileContainer | |||
location={{ query: { language: 'js', name: 'random' } }} | |||
profiles={profiles} | |||
canAdmin={false} | |||
updateProfiles={() => true}> | |||
location={{ pathname: '', query: { language: 'js', name: 'random' } }} | |||
onRequestFail={jest.fn()} | |||
organization={null} | |||
profiles={profiles} | |||
router={{} as any} | |||
updateProfiles={jest.fn()}> | |||
<div /> | |||
</ProfileContainer> | |||
); | |||
@@ -67,9 +73,12 @@ it('should render Helmet', () => { | |||
const updateProfiles = jest.fn(); | |||
const output = shallow( | |||
<ProfileContainer | |||
location={{ query: { language: 'js', name: 'First Profile' } }} | |||
profiles={profiles} | |||
canAdmin={false} | |||
location={{ pathname: '', query: { language: 'js', name: 'First Profile' } }} | |||
onRequestFail={jest.fn()} | |||
organization={null} | |||
profiles={profiles} | |||
router={{} as any} | |||
updateProfiles={updateProfiles}> | |||
<div /> | |||
</ProfileContainer> |
@@ -17,36 +17,30 @@ | |||
* 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 Modal from 'react-modal'; | |||
import Select from 'react-select'; | |||
import * as Select from 'react-select'; | |||
import { sortBy } from 'lodash'; | |||
import { changeProfileParent } from '../../../api/quality-profiles'; | |||
import { translate } from '../../../helpers/l10n'; | |||
/*:: import type { Profile } from '../propTypes'; */ | |||
import { IProfile } from '../types'; | |||
/*:: | |||
type Props = { | |||
onChange: () => void, | |||
onClose: () => void, | |||
onRequestFail: Object => void, | |||
profile: Profile, | |||
profiles: Array<Profile> | |||
}; | |||
*/ | |||
interface Props { | |||
onChange: () => void; | |||
onClose: () => void; | |||
onRequestFail: (reasong: any) => void; | |||
profile: IProfile; | |||
profiles: IProfile[]; | |||
} | |||
/*:: | |||
type State = { | |||
loading: boolean, | |||
selected: ?string | |||
}; | |||
*/ | |||
interface State { | |||
loading: boolean; | |||
selected: string | null; | |||
} | |||
export default class ChangeParentForm extends React.PureComponent { | |||
/*:: mounted: boolean; */ | |||
/*:: props: Props; */ | |||
state /*: State */ = { | |||
export default class ChangeParentForm extends React.PureComponent<Props, State> { | |||
mounted: boolean; | |||
state: State = { | |||
loading: false, | |||
selected: null | |||
}; | |||
@@ -59,28 +53,30 @@ export default class ChangeParentForm extends React.PureComponent { | |||
this.mounted = false; | |||
} | |||
handleCancelClick = (event /*: Event */) => { | |||
handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => { | |||
event.preventDefault(); | |||
this.props.onClose(); | |||
}; | |||
handleSelectChange = (option /*: { value: string } */) => { | |||
handleSelectChange = (option: { value: string }) => { | |||
this.setState({ selected: option.value }); | |||
}; | |||
handleFormSubmit = (event /*: Event */) => { | |||
handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => { | |||
event.preventDefault(); | |||
const parent = this.state.selected; | |||
if (parent != null) { | |||
this.setState({ loading: true }); | |||
changeProfileParent(this.props.profile.key, parent).then(this.props.onChange).catch(error => { | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
this.props.onRequestFail(error); | |||
}); | |||
changeProfileParent(this.props.profile.key, parent) | |||
.then(this.props.onChange) | |||
.catch((error: any) => { | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
this.props.onRequestFail(error); | |||
}); | |||
} | |||
}; | |||
@@ -122,7 +118,6 @@ export default class ChangeParentForm extends React.PureComponent { | |||
</label> | |||
<Select | |||
clearable={false} | |||
id="change-profile-parent" | |||
name="parentKey" | |||
onChange={this.handleSelectChange} | |||
options={options} |
@@ -17,27 +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. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import Modal from 'react-modal'; | |||
import escapeHtml from 'escape-html'; | |||
/*:: import type { Profile } from '../propTypes'; */ | |||
import * as escapeHtml from 'escape-html'; | |||
import SelectList from '../../../components/SelectList'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { IProfile } from '../types'; | |||
/*:: | |||
type Props = { | |||
onClose: () => void, | |||
organization: ?string, | |||
profile: Profile | |||
}; | |||
*/ | |||
interface Props { | |||
onClose: () => void; | |||
organization: string | null; | |||
profile: IProfile; | |||
} | |||
export default class ChangeProjectsForm extends React.PureComponent { | |||
/*:: container: HTMLElement; */ | |||
/*:: props: Props; */ | |||
export default class ChangeProjectsForm extends React.PureComponent<Props> { | |||
container: HTMLElement; | |||
handleCloseClick = (event /*: Event */) => { | |||
handleCloseClick = (event: React.SyntheticEvent<HTMLElement>) => { | |||
event.preventDefault(); | |||
this.props.onClose(); | |||
}; | |||
@@ -46,17 +42,17 @@ export default class ChangeProjectsForm extends React.PureComponent { | |||
const { key } = this.props.profile; | |||
const searchUrl = | |||
window.baseUrl + '/api/qualityprofiles/projects?key=' + encodeURIComponent(key); | |||
(window as any).baseUrl + '/api/qualityprofiles/projects?key=' + encodeURIComponent(key); | |||
new SelectList({ | |||
new (SelectList as any)({ | |||
searchUrl, | |||
el: this.container, | |||
width: '100%', | |||
readOnly: false, | |||
focusSearch: false, | |||
dangerouslyUnescapedHtmlFormat: item => escapeHtml(item.name), | |||
selectUrl: window.baseUrl + '/api/qualityprofiles/add_project', | |||
deselectUrl: window.baseUrl + '/api/qualityprofiles/remove_project', | |||
dangerouslyUnescapedHtmlFormat: (item: { name: string }) => escapeHtml(item.name), | |||
selectUrl: (window as any).baseUrl + '/api/qualityprofiles/add_project', | |||
deselectUrl: (window as any).baseUrl + '/api/qualityprofiles/remove_project', | |||
extra: { profileKey: key }, | |||
selectParameter: 'projectUuid', | |||
selectParameterValue: 'uuid', | |||
@@ -91,7 +87,7 @@ export default class ChangeProjectsForm extends React.PureComponent { | |||
</div> | |||
<div className="modal-body"> | |||
<div id="profile-projects" ref={node => (this.container = node)} /> | |||
<div id="profile-projects" ref={node => (this.container = node as HTMLElement)} /> | |||
</div> | |||
<div className="modal-foot"> |
@@ -17,43 +17,36 @@ | |||
* 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 ProfileRules from './ProfileRules'; | |||
import ProfileProjects from './ProfileProjects'; | |||
import ProfileInheritance from './ProfileInheritance'; | |||
import ProfileExporters from './ProfileExporters'; | |||
/*:: import type { Profile, Exporter } from '../propTypes'; */ | |||
import { IExporter, IProfile } from '../types'; | |||
/*:: | |||
type Props = { | |||
canAdmin: boolean, | |||
exporters: Array<Exporter>, | |||
onRequestFail: Object => void, | |||
organization: ?string, | |||
profile: Profile, | |||
profiles: Array<Profile>, | |||
updateProfiles: () => Promise<*> | |||
}; | |||
*/ | |||
export default class ProfileDetails extends React.PureComponent { | |||
/*:: props: Props; */ | |||
interface Props { | |||
canAdmin: boolean; | |||
exporters: IExporter[]; | |||
onRequestFail: (reasong: any) => void; | |||
organization: string | null; | |||
profile: IProfile; | |||
profiles: IProfile[]; | |||
updateProfiles: () => Promise<void>; | |||
} | |||
render() { | |||
return ( | |||
<div> | |||
<div className="quality-profile-grid"> | |||
<div className="quality-profile-grid-left"> | |||
<ProfileRules {...this.props} /> | |||
<ProfileExporters {...this.props} /> | |||
</div> | |||
<div className="quality-profile-grid-right"> | |||
<ProfileInheritance {...this.props} /> | |||
<ProfileProjects {...this.props} /> | |||
</div> | |||
export default function ProfileDetails(props: Props) { | |||
return ( | |||
<div> | |||
<div className="quality-profile-grid"> | |||
<div className="quality-profile-grid-left"> | |||
<ProfileRules {...props} /> | |||
<ProfileExporters {...props} /> | |||
</div> | |||
<div className="quality-profile-grid-right"> | |||
<ProfileInheritance {...props} /> | |||
<ProfileProjects {...props} /> | |||
</div> | |||
</div> | |||
); | |||
} | |||
</div> | |||
); | |||
} |
@@ -17,28 +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. | |||
*/ | |||
// @flow | |||
import { stringify } from 'querystring'; | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { translate } from '../../../helpers/l10n'; | |||
/*:: import type { Profile, Exporter } from '../propTypes'; */ | |||
import { IProfile, IExporter } from '../types'; | |||
/*:: | |||
type Props = { | |||
exporters: Array<Exporter>, | |||
organization: ?string, | |||
profile: Profile | |||
}; | |||
*/ | |||
export default class ProfileExporters extends React.PureComponent { | |||
/*:: props: Props; */ | |||
interface Props { | |||
exporters: IExporter[]; | |||
organization: string | null; | |||
profile: IProfile; | |||
} | |||
getExportUrl(exporter /*: Exporter */) { | |||
export default class ProfileExporters extends React.PureComponent<Props> { | |||
getExportUrl(exporter: IExporter) { | |||
const { organization, profile } = this.props; | |||
const path = '/api/qualityprofiles/export'; | |||
const parameters /*: { [string]: string } */ = { | |||
const parameters = { | |||
exporterKey: exporter.key, | |||
language: profile.language, | |||
name: profile.name | |||
@@ -46,7 +41,7 @@ export default class ProfileExporters extends React.PureComponent { | |||
if (organization) { | |||
Object.assign(parameters, { organization }); | |||
} | |||
return window.baseUrl + path + '?' + stringify(parameters); | |||
return (window as any).baseUrl + path + '?' + stringify(parameters); | |||
} | |||
render() { |
@@ -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 { Link, IndexLink } from 'react-router'; | |||
import ProfileLink from '../components/ProfileLink'; | |||
import ProfileActions from '../components/ProfileActions'; | |||
@@ -31,21 +30,17 @@ import { | |||
getProfilesForLanguagePath, | |||
getProfileChangelogPath | |||
} from '../utils'; | |||
/*:: import type { Profile } from '../propTypes'; */ | |||
import { IProfile } from '../types'; | |||
/*:: | |||
type Props = { | |||
canAdmin: boolean, | |||
onRequestFail: Object => void, | |||
organization: ?string, | |||
profile: Profile, | |||
updateProfiles: () => Promise<*> | |||
}; | |||
*/ | |||
export default class ProfileHeader extends React.PureComponent { | |||
/*:: props: Props; */ | |||
interface Props { | |||
canAdmin: boolean; | |||
onRequestFail: (reasong: any) => void; | |||
profile: IProfile; | |||
organization: string | null; | |||
updateProfiles: () => Promise<void>; | |||
} | |||
export default class ProfileHeader extends React.PureComponent<Props> { | |||
renderUpdateDate() { | |||
const { profile } = this.props; | |||
let inner = ( |
@@ -17,51 +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 classNames from 'classnames'; | |||
import * as React from 'react'; | |||
import * as classNames from 'classnames'; | |||
import ProfileInheritanceBox from './ProfileInheritanceBox'; | |||
import ChangeParentForm from './ChangeParentForm'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { getProfileInheritance } from '../../../api/quality-profiles'; | |||
/*:: import type { Profile } from '../propTypes'; */ | |||
/*:: | |||
type Props = { | |||
canAdmin: boolean, | |||
onRequestFail: Object => void, | |||
organization: ?string, | |||
profile: Profile, | |||
profiles: Array<Profile>, | |||
updateProfiles: () => Promise<*> | |||
}; | |||
*/ | |||
/*:: | |||
type ProfileInheritanceDetails = { | |||
activeRuleCount: number, | |||
isBuiltIn: boolean, | |||
key: string, | |||
language: string, | |||
name: string, | |||
overridingRuleCount?: number | |||
}; | |||
*/ | |||
/*:: | |||
type State = { | |||
ancestors?: Array<ProfileInheritanceDetails>, | |||
children?: Array<ProfileInheritanceDetails>, | |||
formOpen: boolean, | |||
loading: boolean, | |||
profile?: ProfileInheritanceDetails | |||
}; | |||
*/ | |||
export default class ProfileInheritance extends React.PureComponent { | |||
/*:: mounted: boolean; */ | |||
/*:: props: Props; */ | |||
state /*: State */ = { | |||
import { IProfile } from '../types'; | |||
interface Props { | |||
canAdmin: boolean; | |||
onRequestFail: (reason: any) => void; | |||
organization: string | null; | |||
profile: IProfile; | |||
profiles: IProfile[]; | |||
updateProfiles: () => Promise<void>; | |||
} | |||
interface ProfileInheritanceDetails { | |||
activeRuleCount: number; | |||
isBuiltIn: boolean; | |||
key: string; | |||
language: string; | |||
name: string; | |||
overridingRuleCount?: number; | |||
} | |||
interface State { | |||
ancestors?: Array<ProfileInheritanceDetails>; | |||
children?: Array<ProfileInheritanceDetails>; | |||
formOpen: boolean; | |||
loading: boolean; | |||
profile?: ProfileInheritanceDetails; | |||
} | |||
export default class ProfileInheritance extends React.PureComponent<Props, State> { | |||
mounted: boolean; | |||
state: State = { | |||
formOpen: false, | |||
loading: true | |||
}; | |||
@@ -71,7 +64,7 @@ export default class ProfileInheritance extends React.PureComponent { | |||
this.loadData(); | |||
} | |||
componentDidUpdate(prevProps /*: Props */) { | |||
componentDidUpdate(prevProps: Props) { | |||
if (prevProps.profile !== this.props.profile) { | |||
this.loadData(); | |||
} | |||
@@ -82,7 +75,7 @@ export default class ProfileInheritance extends React.PureComponent { | |||
} | |||
loadData() { | |||
getProfileInheritance(this.props.profile.key).then(r => { | |||
getProfileInheritance(this.props.profile.key).then((r: any) => { | |||
if (this.mounted) { | |||
const { ancestors, children } = r; | |||
this.setState({ | |||
@@ -95,7 +88,7 @@ export default class ProfileInheritance extends React.PureComponent { | |||
}); | |||
} | |||
handleChangeParentClick = (event /*: Event */) => { | |||
handleChangeParentClick = (event: React.SyntheticEvent<HTMLElement>) => { | |||
event.preventDefault(); | |||
this.setState({ formOpen: true }); | |||
}; | |||
@@ -153,15 +146,16 @@ export default class ProfileInheritance extends React.PureComponent { | |||
/> | |||
)} | |||
<ProfileInheritanceBox | |||
className={currentClassName} | |||
depth={ancestors ? ancestors.length : 0} | |||
displayLink={false} | |||
extendsBuiltIn={extendsBuiltIn} | |||
language={profile.language} | |||
organization={this.props.organization} | |||
profile={this.state.profile} | |||
/> | |||
{this.state.profile != null && | |||
<ProfileInheritanceBox | |||
className={currentClassName} | |||
depth={ancestors ? ancestors.length : 0} | |||
displayLink={false} | |||
extendsBuiltIn={extendsBuiltIn} | |||
language={profile.language} | |||
organization={this.props.organization} | |||
profile={this.state.profile} | |||
/>} | |||
{this.state.children != null && | |||
this.state.children.map(child => |
@@ -1,93 +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 ProfileLink from '../components/ProfileLink'; | |||
import BuiltInBadge from '../components/BuiltInBadge'; | |||
import Tooltip from '../../../components/controls/Tooltip'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
/*:: | |||
type Props = {| | |||
className?: string, | |||
depth: number, | |||
displayLink?: boolean, | |||
extendsBuiltIn?: boolean, | |||
language: string, | |||
organization: ?string, | |||
profile: { | |||
activeRuleCount: number, | |||
isBuiltIn: boolean, | |||
key: string, | |||
language: string, | |||
name: string, | |||
overridingRuleCount?: number | |||
} | |||
|}; | |||
*/ | |||
export default class ProfileInheritanceBox extends React.PureComponent { | |||
/*:: props: Props; */ | |||
static defaultProps = { | |||
displayLink: true | |||
}; | |||
render() { | |||
const { profile, className, extendsBuiltIn } = this.props; | |||
const offset = 25 * this.props.depth; | |||
return ( | |||
<tr className={className}> | |||
<td> | |||
<div style={{ paddingLeft: offset }}> | |||
{this.props.displayLink | |||
? <ProfileLink | |||
language={this.props.language} | |||
name={profile.name} | |||
organization={this.props.organization}> | |||
{profile.name} | |||
</ProfileLink> | |||
: profile.name} | |||
{profile.isBuiltIn && <BuiltInBadge className="spacer-left" />} | |||
{extendsBuiltIn && | |||
<Tooltip overlay={translate('quality_profiles.extends_built_in')}> | |||
<i className="icon-help spacer-left" /> | |||
</Tooltip>} | |||
</div> | |||
</td> | |||
<td> | |||
{translateWithParameters('quality_profile.x_active_rules', profile.activeRuleCount)} | |||
</td> | |||
<td> | |||
{profile.overridingRuleCount != null && | |||
<p> | |||
{translateWithParameters( | |||
'quality_profiles.x_overridden_rules', | |||
profile.overridingRuleCount | |||
)} | |||
</p>} | |||
</td> | |||
</tr> | |||
); | |||
} | |||
} |
@@ -0,0 +1,82 @@ | |||
/* | |||
* 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 ProfileLink from '../components/ProfileLink'; | |||
import BuiltInBadge from '../components/BuiltInBadge'; | |||
import Tooltip from '../../../components/controls/Tooltip'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
interface Props { | |||
className?: string; | |||
depth: number; | |||
displayLink?: boolean; | |||
extendsBuiltIn?: boolean; | |||
language: string; | |||
organization: string | null; | |||
profile: { | |||
activeRuleCount: number; | |||
isBuiltIn: boolean; | |||
key: string; | |||
language: string; | |||
name: string; | |||
overridingRuleCount?: number; | |||
}; | |||
} | |||
export default function ProfileInheritanceBox({ displayLink = true, ...props }: Props) { | |||
const { profile, className, extendsBuiltIn } = props; | |||
const offset = 25 * props.depth; | |||
return ( | |||
<tr className={className}> | |||
<td> | |||
<div style={{ paddingLeft: offset }}> | |||
{displayLink | |||
? <ProfileLink | |||
language={props.language} | |||
name={profile.name} | |||
organization={props.organization}> | |||
{profile.name} | |||
</ProfileLink> | |||
: profile.name} | |||
{profile.isBuiltIn && <BuiltInBadge className="spacer-left" />} | |||
{extendsBuiltIn && | |||
<Tooltip overlay={translate('quality_profiles.extends_built_in')}> | |||
<i className="icon-help spacer-left" /> | |||
</Tooltip>} | |||
</div> | |||
</td> | |||
<td> | |||
{translateWithParameters('quality_profile.x_active_rules', profile.activeRuleCount)} | |||
</td> | |||
<td> | |||
{profile.overridingRuleCount != null && | |||
<p> | |||
{translateWithParameters( | |||
'quality_profiles.x_overridden_rules', | |||
profile.overridingRuleCount | |||
)} | |||
</p>} | |||
</td> | |||
</tr> | |||
); | |||
} |
@@ -17,37 +17,32 @@ | |||
* 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 { Link } from 'react-router'; | |||
import ChangeProjectsForm from './ChangeProjectsForm'; | |||
import QualifierIcon from '../../../components/shared/QualifierIcon'; | |||
import { getProfileProjects } from '../../../api/quality-profiles'; | |||
import { translate } from '../../../helpers/l10n'; | |||
/*:: import type { Profile } from '../propTypes'; */ | |||
/*:: | |||
type Props = { | |||
canAdmin: boolean, | |||
organization: ?string, | |||
profile: Profile, | |||
updateProfiles: () => Promise<*> | |||
}; | |||
*/ | |||
/*:: | |||
type State = { | |||
formOpen: boolean, | |||
loading: boolean, | |||
more?: boolean, | |||
projects: ?Array<*> | |||
}; | |||
*/ | |||
export default class ProfileProjects extends React.PureComponent { | |||
/*:: mounted: boolean; */ | |||
/*:: props: Props; */ | |||
state /*: State */ = { | |||
import { IProfile } from '../types'; | |||
interface Props { | |||
canAdmin: boolean; | |||
organization: string | null; | |||
profile: IProfile; | |||
updateProfiles: () => Promise<void>; | |||
} | |||
interface State { | |||
formOpen: boolean; | |||
loading: boolean; | |||
more?: boolean; | |||
projects: Array<{ key: string; name: string; uuid: string }> | null; | |||
} | |||
export default class ProfileProjects extends React.PureComponent<Props, State> { | |||
mounted: boolean; | |||
state: State = { | |||
formOpen: false, | |||
loading: true, | |||
projects: null | |||
@@ -58,7 +53,7 @@ export default class ProfileProjects extends React.PureComponent { | |||
this.loadProjects(); | |||
} | |||
componentDidUpdate(prevProps /*: Props */) { | |||
componentDidUpdate(prevProps: Props) { | |||
if (prevProps.profile !== this.props.profile) { | |||
this.loadProjects(); | |||
} | |||
@@ -74,7 +69,7 @@ export default class ProfileProjects extends React.PureComponent { | |||
} | |||
const data = { key: this.props.profile.key }; | |||
getProfileProjects(data).then(r => { | |||
getProfileProjects(data).then((r: any) => { | |||
if (this.mounted) { | |||
this.setState({ | |||
projects: r.results, | |||
@@ -85,7 +80,7 @@ export default class ProfileProjects extends React.PureComponent { | |||
}); | |||
} | |||
handleChangeClick = (event /*: Event */) => { | |||
handleChangeClick = (event: React.SyntheticEvent<HTMLElement>) => { | |||
event.preventDefault(); | |||
this.setState({ formOpen: true }); | |||
}; |
@@ -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 { Link } from 'react-router'; | |||
import { keyBy } from 'lodash'; | |||
import ProfileRulesRowOfType from './ProfileRulesRowOfType'; | |||
@@ -29,33 +28,34 @@ import { searchRules, takeFacet } from '../../../api/rules'; | |||
import { getQualityProfiles } from '../../../api/quality-profiles'; | |||
import { getRulesUrl } from '../../../helpers/urls'; | |||
import { translate } from '../../../helpers/l10n'; | |||
/*:: import type { Profile } from '../propTypes'; */ | |||
import { IProfile } from '../types'; | |||
const TYPES = ['BUG', 'VULNERABILITY', 'CODE_SMELL']; | |||
/*:: | |||
type Props = { | |||
canAdmin: boolean, | |||
organization: ?string, | |||
profile: Profile | |||
}; | |||
*/ | |||
/*:: | |||
type State = { | |||
activatedTotal: ?number, | |||
activatedByType?: { [string]: ?{ val: string, count: ?number } }, | |||
allByType?: { [string]: ?{ val: string, count: ?number } }, | |||
compareToSonarWay: ?{ profile: string, profileName: string, missingRuleCount: number }, | |||
loading: boolean, | |||
total: ?number | |||
}; | |||
*/ | |||
export default class ProfileRules extends React.PureComponent { | |||
/*:: mounted: boolean; */ | |||
/*:: props: Props; */ | |||
state /*: State */ = { | |||
interface Props { | |||
canAdmin: boolean; | |||
organization: string | null; | |||
profile: IProfile; | |||
} | |||
interface ByType { | |||
val: string; | |||
count: number | null; | |||
} | |||
interface State { | |||
activatedTotal: number | null; | |||
activatedByType: { [type: string]: ByType }; | |||
allByType: { [type: string]: ByType }; | |||
compareToSonarWay: { profile: string; profileName: string; missingRuleCount: number } | null; | |||
loading: boolean; | |||
total: number | null; | |||
} | |||
export default class ProfileRules extends React.PureComponent<Props, State> { | |||
mounted: boolean; | |||
state: State = { | |||
activatedTotal: null, | |||
activatedByType: keyBy(TYPES.map(t => ({ val: t, count: null })), 'val'), | |||
allByType: keyBy(TYPES.map(t => ({ val: t, count: null })), 'val'), | |||
@@ -69,7 +69,7 @@ export default class ProfileRules extends React.PureComponent { | |||
this.loadRules(); | |||
} | |||
componentDidUpdate(prevProps /*: Props */) { | |||
componentDidUpdate(prevProps: Props) { | |||
if (prevProps.profile !== this.props.profile) { | |||
this.loadRules(); | |||
} | |||
@@ -117,8 +117,8 @@ export default class ProfileRules extends React.PureComponent { | |||
const [allRules, activatedRules, showProfile] = responses; | |||
this.setState({ | |||
activatedTotal: activatedRules.total, | |||
allByType: keyBy(takeFacet(allRules, 'types'), 'val'), | |||
activatedByType: keyBy(takeFacet(activatedRules, 'types'), 'val'), | |||
allByType: keyBy<ByType>(takeFacet(allRules, 'types'), 'val'), | |||
activatedByType: keyBy<ByType>(takeFacet(activatedRules, 'types'), 'val'), | |||
compareToSonarWay: showProfile && showProfile.compareToSonarWay, | |||
loading: false, | |||
total: allRules.total | |||
@@ -127,13 +127,13 @@ export default class ProfileRules extends React.PureComponent { | |||
}); | |||
} | |||
getRulesCountForType(type /*: string */) /*: ?number */ { | |||
getRulesCountForType(type: string) { | |||
return this.state.activatedByType && this.state.activatedByType[type] | |||
? this.state.activatedByType[type].count | |||
: null; | |||
} | |||
getRulesTotalForType(type /*: string */) /*: ?number */ { | |||
getRulesTotalForType(type: string) { | |||
return this.state.allByType && this.state.allByType[type] | |||
? this.state.allByType[type].count | |||
: null; |
@@ -17,18 +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 { Link } from 'react-router'; | |||
import Tooltip from '../../../components/controls/Tooltip'; | |||
import { getDeprecatedActiveRulesUrl } from '../../../helpers/urls'; | |||
import { translate } from '../../../helpers/l10n'; | |||
/*:: | |||
type Props = { activeDeprecatedRules: number, organization: ?string, profile: string }; | |||
*/ | |||
interface Props { | |||
activeDeprecatedRules: number; | |||
organization: string | null; | |||
profile: string; | |||
} | |||
export default function ProfileRulesDeprecatedWarning(props /*: Props */) { | |||
export default function ProfileRulesDeprecatedWarning(props: Props) { | |||
const url = getDeprecatedActiveRulesUrl({ qprofile: props.profile }, props.organization); | |||
return ( | |||
<div className="quality-profile-rules-deprecated clearfix"> |
@@ -17,25 +17,22 @@ | |||
* 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 { Link } from 'react-router'; | |||
import IssueTypeIcon from '../../../components/ui/IssueTypeIcon'; | |||
import { formatMeasure } from '../../../helpers/measures'; | |||
import { getRulesUrl } from '../../../helpers/urls'; | |||
import { translate } from '../../../helpers/l10n'; | |||
/*:: | |||
type Props = { | |||
count: ?number, | |||
organization: ?string, | |||
qprofile: string, | |||
total: ?number, | |||
type: string | |||
}; | |||
*/ | |||
interface Props { | |||
count: number | null; | |||
organization: string | null; | |||
qprofile: string; | |||
total: number | null; | |||
type: string; | |||
} | |||
export default function ProfileRulesRowOfType(props /*: Props */) { | |||
export default function ProfileRulesRowOfType(props: Props) { | |||
const activeRulesUrl = getRulesUrl( | |||
{ qprofile: props.qprofile, activation: 'true', types: props.type }, | |||
props.organization | |||
@@ -60,14 +57,14 @@ export default function ProfileRulesRowOfType(props /*: Props */) { | |||
<td className="thin nowrap text-right"> | |||
{props.count != null && | |||
<Link to={activeRulesUrl}> | |||
{formatMeasure(props.count, 'SHORT_INT')} | |||
{formatMeasure(props.count, 'SHORT_INT', null)} | |||
</Link>} | |||
</td> | |||
<td className="thin nowrap text-right"> | |||
{inactiveCount != null && | |||
(inactiveCount > 0 | |||
? <Link to={inactiveRulesUrl} className="small text-muted"> | |||
{formatMeasure(inactiveCount, 'SHORT_INT')} | |||
{formatMeasure(inactiveCount, 'SHORT_INT', null)} | |||
</Link> | |||
: <span className="note text-muted">0</span>)} | |||
</td> |
@@ -17,23 +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. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
import { formatMeasure } from '../../../helpers/measures'; | |||
import { getRulesUrl } from '../../../helpers/urls'; | |||
import { translate } from '../../../helpers/l10n'; | |||
/*:: | |||
type Props = { | |||
count: ?number, | |||
organization: ?string, | |||
qprofile: string, | |||
total: ?number | |||
}; | |||
*/ | |||
interface Props { | |||
count: number | null; | |||
organization: string | null; | |||
qprofile: string; | |||
total: number | null; | |||
} | |||
export default function ProfileRulesRowTotal(props /*: Props */) { | |||
export default function ProfileRulesRowTotal(props: Props) { | |||
const activeRulesUrl = getRulesUrl( | |||
{ qprofile: props.qprofile, activation: 'true' }, | |||
props.organization | |||
@@ -58,7 +55,7 @@ export default function ProfileRulesRowTotal(props /*: Props */) { | |||
{props.count != null && | |||
<Link to={activeRulesUrl}> | |||
<strong> | |||
{formatMeasure(props.count, 'SHORT_INT')} | |||
{formatMeasure(props.count, 'SHORT_INT', null)} | |||
</strong> | |||
</Link>} | |||
</td> | |||
@@ -67,7 +64,7 @@ export default function ProfileRulesRowTotal(props /*: Props */) { | |||
(inactiveCount > 0 | |||
? <Link to={inactiveRulesUrl} className="small text-muted"> | |||
<strong> | |||
{formatMeasure(inactiveCount, 'SHORT_INT')} | |||
{formatMeasure(inactiveCount, 'SHORT_INT', null)} | |||
</strong> | |||
</Link> | |||
: <span className="note text-muted">0</span>)} |
@@ -17,24 +17,21 @@ | |||
* 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 { Link } from 'react-router'; | |||
import Tooltip from '../../../components/controls/Tooltip'; | |||
import { getRulesUrl } from '../../../helpers/urls'; | |||
import { translate } from '../../../helpers/l10n'; | |||
/*:: | |||
type Props = { | |||
language: string, | |||
organization: ?string, | |||
profile: string, | |||
sonarway: string, | |||
sonarWayMissingRules: number | |||
}; | |||
*/ | |||
interface Props { | |||
language: string; | |||
organization: string | null; | |||
profile: string; | |||
sonarway: string; | |||
sonarWayMissingRules: number; | |||
} | |||
export default function ProfileRulesSonarWayComparison(props /*: Props */) { | |||
export default function ProfileRulesSonarWayComparison(props: Props) { | |||
const url = getRulesUrl( | |||
{ | |||
qprofile: props.profile, |
@@ -17,7 +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. | |||
*/ | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import ProfileRules from '../ProfileRules'; | |||
import { doAsync } from '../../../../helpers/testUtils'; | |||
@@ -68,11 +68,9 @@ const apiResponseActive = { | |||
}; | |||
// Mock api some api functions | |||
// eslint-disable-next-line | |||
apiRules.searchRules = data => | |||
(apiRules as any).searchRules = (data: any) => | |||
Promise.resolve(data.activation === 'true' ? apiResponseActive : apiResponseAll); | |||
// eslint-disable-next-line | |||
apiQP.getQualityProfiles = () => | |||
(apiQP as any).getQualityProfiles = () => | |||
Promise.resolve({ | |||
compareToSonarWay: { | |||
profile: 'sonarway', | |||
@@ -83,8 +81,9 @@ apiQP.getQualityProfiles = () => | |||
it('should render the quality profiles rules with sonarway comparison', () => { | |||
const wrapper = shallow(<ProfileRules canAdmin={false} organization="foo" profile={PROFILE} />); | |||
wrapper.instance().mounted = true; | |||
wrapper.instance().loadRules(); | |||
const instance = wrapper.instance() as any; | |||
instance.mounted = true; | |||
instance.loadRules(); | |||
return doAsync(() => { | |||
wrapper.update(); | |||
expect(wrapper.find('ProfileRulesSonarWayComparison')).toHaveLength(1); | |||
@@ -123,13 +122,13 @@ it('should not show a button to activate more rules on built in profiles', () => | |||
}); | |||
it('should not show sonarway comparison for built in profiles', () => { | |||
// eslint-disable-next-line | |||
apiQP.getQualityProfiles = jest.fn(() => Promise.resolve()); | |||
(apiQP as any).getQualityProfiles = jest.fn(() => Promise.resolve()); | |||
const wrapper = shallow( | |||
<ProfileRules canAdmin={true} organization={null} profile={{ ...PROFILE, isBuiltIn: true }} /> | |||
); | |||
wrapper.instance().mounted = true; | |||
wrapper.instance().loadRules(); | |||
const instance = wrapper.instance() as any; | |||
instance.mounted = true; | |||
instance.loadRules(); | |||
return doAsync(() => { | |||
wrapper.update(); | |||
expect(apiQP.getQualityProfiles).toHaveBeenCalledTimes(0); | |||
@@ -138,8 +137,7 @@ it('should not show sonarway comparison for built in profiles', () => { | |||
}); | |||
it('should not show sonarway comparison if there is no missing rules', () => { | |||
// eslint-disable-next-line | |||
apiQP.getQualityProfiles = jest.fn(() => | |||
(apiQP as any).getQualityProfiles = jest.fn(() => | |||
Promise.resolve({ | |||
compareToSonarWay: { | |||
profile: 'sonarway', | |||
@@ -149,8 +147,9 @@ it('should not show sonarway comparison if there is no missing rules', () => { | |||
}) | |||
); | |||
const wrapper = shallow(<ProfileRules canAdmin={true} organization={null} profile={PROFILE} />); | |||
wrapper.instance().mounted = true; | |||
wrapper.instance().loadRules(); | |||
const instance = wrapper.instance() as any; | |||
instance.mounted = true; | |||
instance.loadRules(); | |||
return doAsync(() => { | |||
wrapper.update(); | |||
expect(apiQP.getQualityProfiles).toHaveBeenCalledTimes(1); |
@@ -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 { shallow } from 'enzyme'; | |||
import ProfileRulesDeprecatedWarning from '../ProfileRulesDeprecatedWarning'; | |||
@@ -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 { shallow } from 'enzyme'; | |||
import ProfileRulesRowOfType from '../ProfileRulesRowOfType'; | |||
@@ -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 { shallow } from 'enzyme'; | |||
import ProfileRulesRowTotal from '../ProfileRulesRowTotal'; | |||
@@ -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 { shallow } from 'enzyme'; | |||
import ProfileRulesSonarWayComparison from '../ProfileRulesSonarWayComparison'; | |||
@@ -17,39 +17,33 @@ | |||
* 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 Modal from 'react-modal'; | |||
import Select from 'react-select'; | |||
import * as Select from 'react-select'; | |||
import { sortBy } from 'lodash'; | |||
import { getImporters, createQualityProfile } from '../../../api/quality-profiles'; | |||
import { translate } from '../../../helpers/l10n'; | |||
/*:: | |||
type Props = { | |||
languages: Array<{ key: string, name: string }>, | |||
onClose: () => void, | |||
onCreate: Function, | |||
onRequestFail: Object => void, | |||
organization: ?string | |||
}; | |||
*/ | |||
/*:: | |||
type State = { | |||
importers: Array<{ key: string, languages: Array<string>, name: string }>, | |||
language?: string, | |||
loading: boolean, | |||
name: string, | |||
preloading: boolean | |||
}; | |||
*/ | |||
export default class CreateProfileForm extends React.PureComponent { | |||
interface Props { | |||
languages: Array<{ key: string; name: string }>; | |||
onClose: () => void; | |||
onCreate: Function; | |||
onRequestFail: (reasong: any) => void; | |||
organization: string | null; | |||
} | |||
interface State { | |||
importers: Array<{ key: string; languages: Array<string>; name: string }>; | |||
language?: string; | |||
loading: boolean; | |||
name: string; | |||
preloading: boolean; | |||
} | |||
export default class CreateProfileForm extends React.PureComponent<Props, State> { | |||
/*:: form: HTMLFormElement; */ | |||
/*:: mounted: boolean; */ | |||
/*:: props: Props; */ | |||
state /*: State */ = { importers: [], loading: false, name: '', preloading: true }; | |||
mounted: boolean; | |||
state: State = { importers: [], loading: false, name: '', preloading: true }; | |||
componentDidMount() { | |||
this.mounted = true; | |||
@@ -61,39 +55,41 @@ export default class CreateProfileForm extends React.PureComponent { | |||
} | |||
fetchImporters() { | |||
getImporters().then(importers => { | |||
if (this.mounted) { | |||
this.setState({ importers, preloading: false }); | |||
getImporters().then( | |||
(importers: Array<{ key: string; languages: Array<string>; name: string }>) => { | |||
if (this.mounted) { | |||
this.setState({ importers, preloading: false }); | |||
} | |||
} | |||
}); | |||
); | |||
} | |||
handleCancelClick = (event /*: Event */) => { | |||
handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => { | |||
event.preventDefault(); | |||
this.props.onClose(); | |||
}; | |||
handleNameChange = (event /*: { currentTarget: HTMLInputElement } */) => { | |||
handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => { | |||
this.setState({ name: event.currentTarget.value }); | |||
}; | |||
handleLanguageChange = (option /*: { value: string } */) => { | |||
handleLanguageChange = (option: { value: string }) => { | |||
this.setState({ language: option.value }); | |||
}; | |||
handleFormSubmit = (event /*: Event */) => { | |||
handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => { | |||
event.preventDefault(); | |||
this.setState({ loading: true }); | |||
const data = new FormData(this.form); | |||
const data = new FormData(event.currentTarget); | |||
if (this.props.organization) { | |||
data.append('organization', this.props.organization); | |||
} | |||
createQualityProfile(data).then( | |||
response => this.props.onCreate(response.profile), | |||
error => { | |||
(response: any) => this.props.onCreate(response.profile), | |||
(error: any) => { | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
@@ -118,10 +114,7 @@ export default class CreateProfileForm extends React.PureComponent { | |||
className="modal" | |||
overlayClassName="modal-overlay" | |||
onRequestClose={this.props.onClose}> | |||
<form | |||
id="create-profile-form" | |||
onSubmit={this.handleFormSubmit} | |||
ref={node => (this.form = node)}> | |||
<form id="create-profile-form" onSubmit={this.handleFormSubmit}> | |||
<div className="modal-head"> | |||
<h2> | |||
{header} | |||
@@ -141,11 +134,11 @@ export default class CreateProfileForm extends React.PureComponent { | |||
<input | |||
autoFocus={true} | |||
id="create-profile-name" | |||
maxLength="100" | |||
maxLength={100} | |||
name="name" | |||
onChange={this.handleNameChange} | |||
required={true} | |||
size="50" | |||
size={50} | |||
type="text" | |||
value={this.state.name} | |||
/> | |||
@@ -157,7 +150,6 @@ export default class CreateProfileForm extends React.PureComponent { | |||
</label> | |||
<Select | |||
clearable={false} | |||
id="create-profile-language" | |||
name="language" | |||
onChange={this.handleLanguageChange} | |||
options={languages.map(language => ({ |
@@ -17,32 +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. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import EvolutionDeprecated from './EvolutionDeprecated'; | |||
import EvolutionStagnant from './EvolutionStagnant'; | |||
import EvolutionRules from './EvolutionRules'; | |||
/*:: import type { Profile } from '../propTypes'; */ | |||
import { IProfile } from '../types'; | |||
/*:: | |||
type Props = { | |||
organization: ?string, | |||
profiles: Array<Profile> | |||
}; | |||
*/ | |||
export default class Evolution extends React.PureComponent { | |||
/*:: props: Props; */ | |||
render() { | |||
const { organization, profiles } = this.props; | |||
interface Props { | |||
organization: string | null; | |||
profiles: IProfile[]; | |||
} | |||
return ( | |||
<div className="quality-profiles-evolution"> | |||
<EvolutionDeprecated organization={organization} profiles={profiles} /> | |||
<EvolutionStagnant organization={organization} profiles={profiles} /> | |||
<EvolutionRules organization={organization} /> | |||
</div> | |||
); | |||
} | |||
export default function Evolution({ organization, profiles }: Props) { | |||
return ( | |||
<div className="quality-profiles-evolution"> | |||
<EvolutionDeprecated organization={organization} profiles={profiles} /> | |||
<EvolutionStagnant organization={organization} profiles={profiles} /> | |||
<EvolutionRules organization={organization} /> | |||
</div> | |||
); | |||
} |
@@ -1,96 +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 { sortBy } from 'lodash'; | |||
import ProfileLink from '../components/ProfileLink'; | |||
import { getDeprecatedActiveRulesUrl } from '../../../helpers/urls'; | |||
import { translateWithParameters, translate } from '../../../helpers/l10n'; | |||
/*:: import type { Profile } from '../propTypes'; */ | |||
/*:: | |||
type Props = { | |||
organization: ?string, | |||
profiles: Array<Profile> | |||
}; | |||
*/ | |||
export default class EvolutionDeprecated extends React.PureComponent { | |||
/*:: props: Props; */ | |||
render() { | |||
const profilesWithDeprecations = this.props.profiles.filter( | |||
profile => profile.activeDeprecatedRuleCount > 0 | |||
); | |||
if (profilesWithDeprecations.length === 0) { | |||
return null; | |||
} | |||
const sortedProfiles = sortBy(profilesWithDeprecations, p => -p.activeDeprecatedRuleCount); | |||
return ( | |||
<div className="quality-profile-box quality-profiles-evolution-deprecated"> | |||
<div className="spacer-bottom"> | |||
<strong> | |||
{translate('quality_profiles.deprecated_rules')} | |||
</strong> | |||
</div> | |||
<div className="spacer-bottom"> | |||
{translateWithParameters( | |||
'quality_profiles.deprecated_rules_are_still_activated', | |||
profilesWithDeprecations.length | |||
)} | |||
</div> | |||
<ul> | |||
{sortedProfiles.map(profile => | |||
<li key={profile.key} className="spacer-top"> | |||
<div className="text-ellipsis"> | |||
<ProfileLink | |||
className="link-no-underline" | |||
language={profile.language} | |||
name={profile.name} | |||
organization={this.props.organization}> | |||
{profile.name} | |||
</ProfileLink> | |||
</div> | |||
<div className="note"> | |||
{profile.languageName} | |||
{', '} | |||
<Link | |||
to={getDeprecatedActiveRulesUrl( | |||
{ qprofile: profile.key }, | |||
this.props.organization | |||
)} | |||
className="text-muted"> | |||
{translateWithParameters( | |||
'quality_profile.x_rules', | |||
profile.activeDeprecatedRuleCount | |||
)} | |||
</Link> | |||
</div> | |||
</li> | |||
)} | |||
</ul> | |||
</div> | |||
); | |||
} | |||
} |
@@ -0,0 +1,86 @@ | |||
/* | |||
* 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 { sortBy } from 'lodash'; | |||
import ProfileLink from '../components/ProfileLink'; | |||
import { getDeprecatedActiveRulesUrl } from '../../../helpers/urls'; | |||
import { translateWithParameters, translate } from '../../../helpers/l10n'; | |||
import { IProfile } from '../types'; | |||
interface Props { | |||
organization: string | null; | |||
profiles: IProfile[]; | |||
} | |||
export default function EvolutionDeprecated(props: Props) { | |||
const profilesWithDeprecations = props.profiles.filter( | |||
profile => profile.activeDeprecatedRuleCount > 0 | |||
); | |||
if (profilesWithDeprecations.length === 0) { | |||
return null; | |||
} | |||
const sortedProfiles = sortBy(profilesWithDeprecations, p => -p.activeDeprecatedRuleCount); | |||
return ( | |||
<div className="quality-profile-box quality-profiles-evolution-deprecated"> | |||
<div className="spacer-bottom"> | |||
<strong> | |||
{translate('quality_profiles.deprecated_rules')} | |||
</strong> | |||
</div> | |||
<div className="spacer-bottom"> | |||
{translateWithParameters( | |||
'quality_profiles.deprecated_rules_are_still_activated', | |||
profilesWithDeprecations.length | |||
)} | |||
</div> | |||
<ul> | |||
{sortedProfiles.map(profile => | |||
<li key={profile.key} className="spacer-top"> | |||
<div className="text-ellipsis"> | |||
<ProfileLink | |||
className="link-no-underline" | |||
language={profile.language} | |||
name={profile.name} | |||
organization={props.organization}> | |||
{profile.name} | |||
</ProfileLink> | |||
</div> | |||
<div className="note"> | |||
{profile.languageName} | |||
{', '} | |||
<Link | |||
to={getDeprecatedActiveRulesUrl({ qprofile: profile.key }, props.organization)} | |||
className="text-muted"> | |||
{translateWithParameters( | |||
'quality_profile.x_rules', | |||
profile.activeDeprecatedRuleCount | |||
)} | |||
</Link> | |||
</div> | |||
</li> | |||
)} | |||
</ul> | |||
</div> | |||
); | |||
} |
@@ -17,10 +17,9 @@ | |||
* 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 { Link } from 'react-router'; | |||
import moment from 'moment'; | |||
import * as moment from 'moment'; | |||
import { sortBy } from 'lodash'; | |||
import { searchRules } from '../../../api/rules'; | |||
import { translateWithParameters, translate } from '../../../helpers/l10n'; | |||
@@ -31,24 +30,33 @@ const RULES_LIMIT = 10; | |||
const PERIOD_START_MOMENT = moment().subtract(1, 'year'); | |||
function parseRules(r) { | |||
function parseRules(r: any) { | |||
const { rules, actives } = r; | |||
return rules.map(rule => { | |||
return rules.map((rule: any) => { | |||
const activations = actives[rule.key]; | |||
return { ...rule, activations: activations ? activations.length : 0 }; | |||
}); | |||
} | |||
/*:: | |||
type Props = { | |||
organization: ?string | |||
}; | |||
*/ | |||
interface Props { | |||
organization: string | null; | |||
} | |||
interface IRule { | |||
activations: number; | |||
key: string; | |||
langName: string; | |||
name: string; | |||
} | |||
interface State { | |||
latestRules?: Array<IRule>; | |||
latestRulesTotal?: number; | |||
} | |||
export default class EvolutionRules extends React.PureComponent { | |||
/*:: mounted: boolean; */ | |||
/*:: props: Props; */ | |||
state = {}; | |||
export default class EvolutionRules extends React.PureComponent<Props, State> { | |||
mounted: boolean; | |||
state: State = {}; | |||
componentDidMount() { | |||
this.mounted = true; | |||
@@ -68,10 +76,10 @@ export default class EvolutionRules extends React.PureComponent { | |||
f: 'name,langName,actives' | |||
}; | |||
searchRules(data).then(r => { | |||
searchRules(data).then((r: any) => { | |||
if (this.mounted) { | |||
this.setState({ | |||
latestRules: sortBy(parseRules(r), 'langName'), | |||
latestRules: sortBy<IRule>(parseRules(r), 'langName'), | |||
latestRulesTotal: r.total | |||
}); | |||
} | |||
@@ -79,7 +87,7 @@ export default class EvolutionRules extends React.PureComponent { | |||
} | |||
render() { | |||
if (!this.state.latestRulesTotal) { | |||
if (!this.state.latestRulesTotal || !this.state.latestRules) { | |||
return null; | |||
} | |||
@@ -125,7 +133,7 @@ export default class EvolutionRules extends React.PureComponent { | |||
{this.state.latestRulesTotal > RULES_LIMIT && | |||
<div className="spacer-top"> | |||
<Link to={newRulesUrl} className="small"> | |||
{translate('see_all')} {formatMeasure(this.state.latestRulesTotal, 'SHORT_INT')} | |||
{translate('see_all')} {formatMeasure(this.state.latestRulesTotal, 'SHORT_INT', null)} | |||
</Link> | |||
</div>} | |||
</div> |
@@ -1,80 +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 moment from 'moment'; | |||
import ProfileLink from '../components/ProfileLink'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { isStagnant } from '../utils'; | |||
/*:: import type { Profile } from '../propTypes'; */ | |||
/*:: | |||
type Props = { | |||
organization: ?string, | |||
profiles: Array<Profile> | |||
}; | |||
*/ | |||
export default class EvolutionStagnant extends React.PureComponent { | |||
/*:: props: Props; */ | |||
render() { | |||
// TODO filter built-in out | |||
const outdated = this.props.profiles.filter(isStagnant); | |||
if (outdated.length === 0) { | |||
return null; | |||
} | |||
return ( | |||
<div className="quality-profile-box quality-profiles-evolution-stagnant"> | |||
<div className="spacer-bottom"> | |||
<strong> | |||
{translate('quality_profiles.stagnant_profiles')} | |||
</strong> | |||
</div> | |||
<div className="spacer-bottom"> | |||
{translate('quality_profiles.not_updated_more_than_year')} | |||
</div> | |||
<ul> | |||
{outdated.map(profile => | |||
<li key={profile.key} className="spacer-top"> | |||
<div className="text-ellipsis"> | |||
<ProfileLink | |||
className="link-no-underline" | |||
language={profile.language} | |||
name={profile.name} | |||
organization={this.props.organization}> | |||
{profile.name} | |||
</ProfileLink> | |||
</div> | |||
<div className="note"> | |||
{profile.languageName} | |||
{', '} | |||
updated on {moment(profile.rulesUpdatedAt).format('LL')} | |||
</div> | |||
</li> | |||
)} | |||
</ul> | |||
</div> | |||
); | |||
} | |||
} |
@@ -0,0 +1,73 @@ | |||
/* | |||
* 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 moment from 'moment'; | |||
import ProfileLink from '../components/ProfileLink'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { isStagnant } from '../utils'; | |||
import { IProfile } from '../types'; | |||
interface Props { | |||
organization: string | null; | |||
profiles: IProfile[]; | |||
} | |||
export default function EvolutionStagnan(props: Props) { | |||
// TODO filter built-in out | |||
const outdated = props.profiles.filter(isStagnant); | |||
if (outdated.length === 0) { | |||
return null; | |||
} | |||
return ( | |||
<div className="quality-profile-box quality-profiles-evolution-stagnant"> | |||
<div className="spacer-bottom"> | |||
<strong> | |||
{translate('quality_profiles.stagnant_profiles')} | |||
</strong> | |||
</div> | |||
<div className="spacer-bottom"> | |||
{translate('quality_profiles.not_updated_more_than_year')} | |||
</div> | |||
<ul> | |||
{outdated.map(profile => | |||
<li key={profile.key} className="spacer-top"> | |||
<div className="text-ellipsis"> | |||
<ProfileLink | |||
className="link-no-underline" | |||
language={profile.language} | |||
name={profile.name} | |||
organization={props.organization}> | |||
{profile.name} | |||
</ProfileLink> | |||
</div> | |||
<div className="note"> | |||
{profile.languageName} | |||
{', '} | |||
updated on {moment(profile.rulesUpdatedAt).format('LL')} | |||
</div> | |||
</li> | |||
)} | |||
</ul> | |||
</div> | |||
); | |||
} |
@@ -17,42 +17,35 @@ | |||
* 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 PageHeader from './PageHeader'; | |||
import Evolution from './Evolution'; | |||
import ProfilesList from './ProfilesList'; | |||
/*:: import type { Profile } from '../propTypes'; */ | |||
import { IProfile } from '../types'; | |||
/*:: | |||
type Props = { | |||
canAdmin: boolean, | |||
languages: Array<{ key: string, name: string }>, | |||
location: { query: { [string]: string } }, | |||
onRequestFail: Object => void, | |||
organization?: string, | |||
profiles: Array<Profile>, | |||
updateProfiles: () => Promise<*> | |||
}; | |||
*/ | |||
export default class HomeContainer extends React.PureComponent { | |||
/*:: props: Props; */ | |||
interface Props { | |||
canAdmin: boolean; | |||
languages: Array<{ key: string; name: string }>; | |||
location: { query: { [p: string]: string } }; | |||
onRequestFail: (reason: any) => void; | |||
organization: string | null; | |||
profiles: Array<IProfile>; | |||
updateProfiles: () => Promise<void>; | |||
} | |||
render() { | |||
return ( | |||
<div> | |||
<PageHeader {...this.props} /> | |||
export default function HomeContainer(props: Props) { | |||
return ( | |||
<div> | |||
<PageHeader {...props} /> | |||
<div className="page-with-sidebar"> | |||
<div className="page-main"> | |||
<ProfilesList {...this.props} /> | |||
</div> | |||
<div className="page-sidebar"> | |||
<Evolution {...this.props} /> | |||
</div> | |||
<div className="page-with-sidebar"> | |||
<div className="page-main"> | |||
<ProfilesList {...props} /> | |||
</div> | |||
<div className="page-sidebar"> | |||
<Evolution {...props} /> | |||
</div> | |||
</div> | |||
); | |||
} | |||
</div> | |||
); | |||
} |
@@ -17,51 +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 PropTypes from 'prop-types'; | |||
import * as React from 'react'; | |||
import * as PropTypes from 'prop-types'; | |||
import CreateProfileForm from './CreateProfileForm'; | |||
import RestoreProfileForm from './RestoreProfileForm'; | |||
/*:: import type { Profile } from '../propTypes'; */ | |||
import { getProfilePath } from '../utils'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { IProfile } from '../types'; | |||
/*:: | |||
type Props = { | |||
canAdmin: boolean, | |||
languages: Array<{ key: string, name: string }>, | |||
onRequestFail: Object => void, | |||
organization: ?string, | |||
updateProfiles: () => Promise<*> | |||
}; | |||
*/ | |||
/*:: | |||
type State = { | |||
createFormOpen: boolean, | |||
restoreFormOpen: boolean | |||
}; | |||
*/ | |||
interface Props { | |||
canAdmin: boolean; | |||
languages: Array<{ key: string; name: string }>; | |||
onRequestFail: (reason: any) => void; | |||
organization: string | null; | |||
updateProfiles: () => Promise<void>; | |||
} | |||
export default class PageHeader extends React.PureComponent { | |||
/*:: props: Props; */ | |||
interface State { | |||
createFormOpen: boolean; | |||
restoreFormOpen: boolean; | |||
} | |||
export default class PageHeader extends React.PureComponent<Props, State> { | |||
static contextTypes = { | |||
router: PropTypes.object | |||
}; | |||
state /*: State */ = { | |||
state = { | |||
createFormOpen: false, | |||
restoreFormOpen: false | |||
}; | |||
handleCreateClick = (event /*: Event & { currentTarget: HTMLButtonElement } */) => { | |||
handleCreateClick = (event: React.SyntheticEvent<HTMLElement>) => { | |||
event.preventDefault(); | |||
event.currentTarget.blur(); | |||
this.setState({ createFormOpen: true }); | |||
}; | |||
handleCreate = (profile /*: Profile */) => { | |||
handleCreate = (profile: IProfile) => { | |||
this.props.updateProfiles().then(() => { | |||
this.context.router.push( | |||
getProfilePath(profile.name, profile.language, this.props.organization) | |||
@@ -73,7 +66,7 @@ export default class PageHeader extends React.PureComponent { | |||
this.setState({ createFormOpen: false }); | |||
}; | |||
handleRestoreClick = (event /*: Event */) => { | |||
handleRestoreClick = (event: React.SyntheticEvent<HTMLElement>) => { | |||
event.preventDefault(); | |||
this.setState({ restoreFormOpen: true }); | |||
}; |