@@ -59,6 +59,7 @@ | |||
"react-router-redux": "4.0.2", | |||
"react-select": "1.0.0-beta12", | |||
"react-transform-hmr": "1.0.4", | |||
"react-virtualized": "8.1.1", | |||
"redux": "3.3.1", | |||
"redux-logger": "2.2.1", | |||
"redux-simple-router": "1.0.1", |
@@ -110,6 +110,11 @@ export function getMyProjects (data) { | |||
return getJSON(url, data); | |||
} | |||
export function searchProjects (data) { | |||
const url = '/api/components/search_projects'; | |||
return getJSON(url, data); | |||
} | |||
/** | |||
* Change component's key | |||
* @param {string} key |
@@ -33,3 +33,10 @@ export function getMeasuresAndMeta (componentKey, metrics, additional = {}) { | |||
}); | |||
return getJSON(url, data); | |||
} | |||
export const getMeasuresForComponents = (componentKeys, metricKeys) => ( | |||
getJSON('/api/measures/search', { | |||
componentKeys: componentKeys.join(), | |||
metricKeys: metricKeys.join() | |||
}) | |||
); |
@@ -20,6 +20,7 @@ | |||
import React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import { fetchCurrentUser } from '../store/users/actions'; | |||
import { fetchLanguages } from '../store/rootActions'; | |||
class App extends React.Component { | |||
static propTypes = { | |||
@@ -28,6 +29,7 @@ class App extends React.Component { | |||
componentDidMount () { | |||
this.props.fetchCurrentUser(); | |||
this.props.fetchLanguages(); | |||
} | |||
render () { | |||
@@ -37,5 +39,5 @@ class App extends React.Component { | |||
export default connect( | |||
() => ({}), | |||
{ fetchCurrentUser } | |||
{ fetchCurrentUser, fetchLanguages } | |||
)(App); |
@@ -0,0 +1,28 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import { connect } from 'react-redux'; | |||
import GlobalMessages from '../../components/controls/GlobalMessages'; | |||
import { getGlobalMessages } from '../store/rootReducer'; | |||
const mapStateToProps = state => ({ | |||
messages: getGlobalMessages(state) | |||
}); | |||
export default connect(mapStateToProps)(GlobalMessages); |
@@ -24,9 +24,10 @@ import { createHistory } from 'history'; | |||
import { Provider } from 'react-redux'; | |||
import App from './components/App'; | |||
import accountRoutes from '../apps/account/routes'; | |||
import projectsRouters from '../apps/projects/routes'; | |||
import configureStore from '../components/store/configureStore'; | |||
import rootReducer from './store/rootReducer'; | |||
import './styles.css'; | |||
import './styles/index'; | |||
window.sonarqube.appStarted.then(options => { | |||
const el = document.querySelector(options.el); | |||
@@ -42,6 +43,7 @@ window.sonarqube.appStarted.then(options => { | |||
<Router history={history}> | |||
<Route path="/" component={App}> | |||
{accountRoutes} | |||
{projectsRouters} | |||
</Route> | |||
</Router> | |||
</Provider> |
@@ -0,0 +1,25 @@ | |||
/* | |||
* 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 const RECEIVE_COMPONENTS = 'RECEIVE_COMPONENTS'; | |||
export const receiveComponents = components => ({ | |||
type: RECEIVE_COMPONENTS, | |||
components | |||
}); |
@@ -0,0 +1,47 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import { combineReducers } from 'redux'; | |||
import keyBy from 'lodash/keyBy'; | |||
import uniq from 'lodash/uniq'; | |||
import { RECEIVE_COMPONENTS } from './actions'; | |||
const byKey = (state = {}, action = {}) => { | |||
if (action.type === RECEIVE_COMPONENTS) { | |||
const changes = keyBy(action.components, 'key'); | |||
return { ...state, ...changes }; | |||
} | |||
return state; | |||
}; | |||
const keys = (state = [], action = {}) => { | |||
if (action.type === RECEIVE_COMPONENTS) { | |||
const changes = action.components.map(f => f.key); | |||
return uniq([...state, ...changes]); | |||
} | |||
return state; | |||
}; | |||
export default combineReducers({ byKey, keys }); | |||
export const getComponent = (state, key) => ( | |||
state.byKey[key] | |||
); |
@@ -0,0 +1,25 @@ | |||
/* | |||
* 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 const RECEIVE_LANGUAGES = 'RECEIVE_LANGUAGES'; | |||
export const receiveLanguages = languages => ({ | |||
type: RECEIVE_LANGUAGES, | |||
languages | |||
}); |
@@ -0,0 +1,35 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import keyBy from 'lodash/keyBy'; | |||
import { RECEIVE_LANGUAGES } from './actions'; | |||
const reducer = (state = {}, action = {}) => { | |||
if (action.type === RECEIVE_LANGUAGES) { | |||
return keyBy(action.languages, 'key'); | |||
} | |||
return state; | |||
}; | |||
export default reducer; | |||
export const getLanguages = state => ( | |||
state | |||
); |
@@ -25,3 +25,18 @@ export const receiveComponentMeasure = (componentKey, metricKey, value) => ({ | |||
metricKey, | |||
value | |||
}); | |||
export const RECEIVE_COMPONENT_MEASURES = 'RECEIVE_COMPONENT_MEASURES'; | |||
export const receiveComponentMeasures = (componentKey, measures) => ({ | |||
type: RECEIVE_COMPONENT_MEASURES, | |||
componentKey, | |||
measures | |||
}); | |||
export const RECEIVE_COMPONENTS_MEASURES = 'RECEIVE_COMPONENTS_MEASURES'; | |||
export const receiveComponentsMeasures = componentsWithMeasures => ({ | |||
type: RECEIVE_COMPONENTS_MEASURES, | |||
componentsWithMeasures | |||
}); |
@@ -17,22 +17,39 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { RECEIVE_COMPONENT_MEASURE } from './actions'; | |||
import { RECEIVE_COMPONENT_MEASURE, RECEIVE_COMPONENT_MEASURES, RECEIVE_COMPONENTS_MEASURES } from './actions'; | |||
const byMetricKey = (state = {}, action = {}) => { | |||
if (action.type === RECEIVE_COMPONENT_MEASURE) { | |||
return { ...state, [action.metricKey]: action.value }; | |||
} | |||
if (action.type === RECEIVE_COMPONENT_MEASURES) { | |||
return { ...state, ...action.measures }; | |||
} | |||
return state; | |||
}; | |||
const reducer = (state = {}, action = {}) => { | |||
if (action.type === RECEIVE_COMPONENT_MEASURE) { | |||
if ([RECEIVE_COMPONENT_MEASURE, RECEIVE_COMPONENT_MEASURES].includes(action.type)) { | |||
const component = state[action.componentKey]; | |||
return { ...state, [action.componentKey]: byMetricKey(component, action) }; | |||
} | |||
if (action.type === RECEIVE_COMPONENTS_MEASURES) { | |||
const newState = { ...state }; | |||
Object.keys(action.componentsWithMeasures).forEach(componentKey => { | |||
Object.assign(newState, { | |||
[componentKey]: byMetricKey(state[componentKey], { | |||
type: RECEIVE_COMPONENT_MEASURES, | |||
measures: action.componentsWithMeasures[componentKey] | |||
}) | |||
}); | |||
}); | |||
return newState; | |||
} | |||
return state; | |||
}; | |||
@@ -42,3 +59,7 @@ export const getComponentMeasure = (state, componentKey, metricKey) => { | |||
const component = state[componentKey]; | |||
return component && component[metricKey]; | |||
}; | |||
export const getComponentMeasures = (state, componentKey) => ( | |||
state[componentKey] | |||
); |
@@ -0,0 +1,34 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import { getLanguages } from '../../api/languages'; | |||
import { receiveLanguages } from './languages/actions'; | |||
import { addGlobalErrorMessage } from '../../components/store/globalMessages'; | |||
import { parseError } from '../../apps/code/utils'; | |||
const onFail = dispatch => error => { | |||
parseError(error).then(message => dispatch(addGlobalErrorMessage(message))); | |||
}; | |||
export const fetchLanguages = () => dispatch => { | |||
return getLanguages().then( | |||
languages => dispatch(receiveLanguages(languages)), | |||
onFail(dispatch) | |||
); | |||
}; |
@@ -18,13 +18,40 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { combineReducers } from 'redux'; | |||
import components, * as fromComponents from './components/reducer'; | |||
import users, * as fromUsers from './users/reducer'; | |||
import favorites, * as fromFavorites from './favorites/reducer'; | |||
import languages, * as fromLanguages from './languages/reducer'; | |||
import measures, * as fromMeasures from './measures/reducer'; | |||
import globalMessages, * as fromGlobalMessages from '../../components/store/globalMessages'; | |||
import issuesActivity, * as fromIssuesActivity from '../../apps/account/home/store/reducer'; | |||
import projectsApp, * as fromProjectsApp from '../../apps/projects/store/reducer'; | |||
export default combineReducers({ users, favorites, issuesActivity, measures }); | |||
export default combineReducers({ | |||
components, | |||
globalMessages, | |||
favorites, | |||
languages, | |||
measures, | |||
users, | |||
// apps | |||
issuesActivity, | |||
projectsApp | |||
}); | |||
export const getComponent = (state, key) => ( | |||
fromComponents.getComponent(state.components, key) | |||
); | |||
export const getGlobalMessages = state => ( | |||
fromGlobalMessages.getGlobalMessages(state.globalMessages) | |||
); | |||
export const getLanguages = (state, key) => ( | |||
fromLanguages.getLanguages(state.languages, key) | |||
); | |||
export const getCurrentUser = state => ( | |||
fromUsers.getCurrentUser(state.users) | |||
@@ -41,3 +68,19 @@ export const getIssuesActivity = state => ( | |||
export const getComponentMeasure = (state, componentKey, metricKey) => ( | |||
fromMeasures.getComponentMeasure(state.measures, componentKey, metricKey) | |||
); | |||
export const getComponentMeasures = (state, componentKey) => ( | |||
fromMeasures.getComponentMeasures(state.measures, componentKey) | |||
); | |||
export const getProjects = state => ( | |||
fromProjectsApp.getProjects(state.projectsApp) | |||
); | |||
export const getProjectsAppState = state => ( | |||
fromProjectsApp.getState(state.projectsApp) | |||
); | |||
export const getProjectsAppFilterStatus = (state, key) => ( | |||
fromProjectsApp.getFilterStatus(state.projectsApp, key) | |||
); |
@@ -0,0 +1,21 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import './boxed-group.css'; | |||
import './page.css'; |
@@ -0,0 +1,26 @@ | |||
.page-head { | |||
margin-bottom: 20px; | |||
padding-bottom: 15px; | |||
border-bottom: 1px solid #e6e6e6; | |||
background-color: #f3f3f3; | |||
} | |||
.page-head .page-actions { | |||
margin-bottom: 0; | |||
} | |||
.page-head .spinner { | |||
top: 4px; | |||
margin-left: 8px; | |||
} | |||
.page-with-left-sidebar .page-main { | |||
order: 2; | |||
} | |||
.page-with-left-sidebar .page-sidebar, | |||
.page-with-left-sidebar .page-sidebar-fixed { | |||
order: 1; | |||
padding-left: 0; | |||
padding-right: 40px; | |||
} |
@@ -25,6 +25,11 @@ import { TooltipsContainer } from '../../../components/mixins/tooltips-mixin'; | |||
import { formatLeak, isDiffMetric, getRatingTooltip } from '../utils'; | |||
export default class Measure extends React.Component { | |||
static propTypes = { | |||
measure: React.PropTypes.object, | |||
metric: React.PropTypes.object | |||
}; | |||
renderRating (measure, metric) { | |||
const value = isDiffMetric(metric) ? measure.leak : measure.value; | |||
const tooltip = getRatingTooltip(metric.key, value); |
@@ -54,6 +54,16 @@ | |||
font-weight: 300; | |||
} | |||
.main-domain-measures .domain-measures-value .rating, | |||
.measure-details-value .rating { | |||
vertical-align: top; | |||
width: 40px; | |||
height: 40px; | |||
line-height: 40px; | |||
border-radius: 40px; | |||
font-size: 24px; | |||
} | |||
.main-domain-measures .domain-measures-name { | |||
margin-top: 8px; | |||
} |
@@ -146,7 +146,7 @@ export default function enhance (ComposedComponent) { | |||
<div className="overview-domain-measure-sup" | |||
title={title} | |||
data-toggle="tooltip"> | |||
<DrilldownLink component={component.key} metric={metricKey}> | |||
<DrilldownLink className="link-no-underline" component={component.key} metric={metricKey}> | |||
<Rating value={measure.value}/> | |||
</DrilldownLink> | |||
</div> |
@@ -23,6 +23,7 @@ import QualityGateConditions from './QualityGateConditions'; | |||
import EmptyQualityGate from './EmptyQualityGate'; | |||
import { ComponentType, MeasuresListType, PeriodsListType } from '../propTypes'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import Level from '../../../components/ui/Level'; | |||
function parseQualityGateDetails (rawDetails) { | |||
return JSON.parse(rawDetails); | |||
@@ -53,9 +54,7 @@ const QualityGate = ({ component, measures, periods }) => { | |||
<div className="overview-quality-gate" id="overview-quality-gate"> | |||
<h2 className="overview-title"> | |||
{translate('overview.quality_gate')} | |||
<span className={'badge badge-' + level.toLowerCase()}> | |||
{translate('overview.gate', level)} | |||
</span> | |||
<Level level={level}/> | |||
</h2> | |||
{conditions.length > 0 && ( |
@@ -16,13 +16,8 @@ | |||
font-weight: 400; | |||
} | |||
.overview-title > .badge { | |||
position: relative; | |||
top: -2px; | |||
.overview-title > .level { | |||
margin-left: 15px; | |||
padding: 6px 12px; | |||
font-size: 14px; | |||
letter-spacing: 0.05em; | |||
} | |||
.overview-title > a { |
@@ -0,0 +1,82 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import React from 'react'; | |||
import Helmet from 'react-helmet'; | |||
import PageHeaderContainer from './PageHeaderContainer'; | |||
import ProjectsListContainer from './ProjectsListContainer'; | |||
import ProjectsListFooterContainer from './ProjectsListFooterContainer'; | |||
import PageSidebar from './PageSidebar'; | |||
import GlobalMessagesContainer from '../../../app/components/GlobalMessagesContainer'; | |||
import { parseUrlQuery } from '../store/utils'; | |||
import '../styles.css'; | |||
import { translate } from '../../../helpers/l10n'; | |||
export default class App extends React.Component { | |||
static propTypes = { | |||
fetchProjects: React.PropTypes.func.isRequired | |||
}; | |||
state = { | |||
query: {} | |||
}; | |||
componentDidMount () { | |||
document.querySelector('html').classList.add('dashboard-page'); | |||
this.handleQueryChange(); | |||
} | |||
componentDidUpdate (prevProps) { | |||
if (prevProps.location.query !== this.props.location.query) { | |||
this.handleQueryChange(); | |||
} | |||
} | |||
componentWillUnmount () { | |||
document.querySelector('html').classList.remove('dashboard-page'); | |||
} | |||
handleQueryChange () { | |||
const query = parseUrlQuery(this.props.location.query); | |||
this.setState({ query }); | |||
this.props.fetchProjects(query); | |||
} | |||
render () { | |||
return ( | |||
<div id="projects-page" className="page page-limited"> | |||
<Helmet title={translate('projects.page')} titleTemplate="SonarQube - %s"/> | |||
<PageHeaderContainer/> | |||
<GlobalMessagesContainer/> | |||
<div className="page-with-sidebar page-with-left-sidebar"> | |||
<div className="page-main"> | |||
<ProjectsListContainer/> | |||
<ProjectsListFooterContainer query={this.state.query}/> | |||
</div> | |||
<aside className="page-sidebar-fixed"> | |||
<PageSidebar query={this.state.query}/> | |||
</aside> | |||
</div> | |||
</div> | |||
); | |||
} | |||
} |
@@ -0,0 +1,27 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import { connect } from 'react-redux'; | |||
import App from './App'; | |||
import { fetchProjects } from '../store/actions'; | |||
export default connect( | |||
() => ({}), | |||
{ fetchProjects } | |||
)(App); |
@@ -0,0 +1,52 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import React from 'react'; | |||
import { translate } from '../../../helpers/l10n'; | |||
export default class PageHeader extends React.Component { | |||
static propTypes = { | |||
total: React.PropTypes.number, | |||
loading: React.PropTypes.bool | |||
}; | |||
render () { | |||
const { total, loading } = this.props; | |||
return ( | |||
<header className="page-header"> | |||
<h1 className="page-title">{translate('projects.page')}</h1> | |||
{!!loading && ( | |||
<i className="spinner"/> | |||
)} | |||
<div className="page-actions"> | |||
{total != null && ( | |||
<span><strong>{total}</strong> {translate('projects._projects')}</span> | |||
)} | |||
</div> | |||
<div className="page-description"> | |||
{translate('projects.page.description')} | |||
</div> | |||
</header> | |||
); | |||
} | |||
} |
@@ -0,0 +1,26 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import { connect } from 'react-redux'; | |||
import PageHeader from './PageHeader'; | |||
import { getProjectsAppState } from '../../../app/store/rootReducer'; | |||
export default connect( | |||
state => getProjectsAppState(state) | |||
)(PageHeader); |
@@ -0,0 +1,37 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import React from 'react'; | |||
import CoverageFilterContainer from '../filters/CoverageFilterContainer'; | |||
import DuplicationsFilterContainer from '../filters/DuplicationsFilterContainer'; | |||
import SizeFilterContainer from '../filters/SizeFilterContainer'; | |||
import QualityGateFilterContainer from '../filters/QualityGateFilterContainer'; | |||
export default class PageSidebar extends React.Component { | |||
render () { | |||
return ( | |||
<div> | |||
<CoverageFilterContainer query={this.props.query}/> | |||
<DuplicationsFilterContainer query={this.props.query}/> | |||
<SizeFilterContainer query={this.props.query}/> | |||
<QualityGateFilterContainer query={this.props.query}/> | |||
</div> | |||
); | |||
} | |||
} |
@@ -0,0 +1,47 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import React from 'react'; | |||
import ProjectCardMeasures from './ProjectCardMeasures'; | |||
import { getComponentUrl } from '../../../helpers/urls'; | |||
export default class ProjectCard extends React.Component { | |||
static propTypes = { | |||
project: React.PropTypes.object | |||
}; | |||
render () { | |||
const { project } = this.props; | |||
if (project == null) { | |||
return null; | |||
} | |||
return ( | |||
<div className="boxed-group project-card"> | |||
<h2 className="project-card-name"> | |||
<a className="link-base-color" href={getComponentUrl(project.key)}>{project.name}</a> | |||
</h2> | |||
<div className="boxed-group-inner"> | |||
<ProjectCardMeasures measures={this.props.measures}/> | |||
</div> | |||
</div> | |||
); | |||
} | |||
} |
@@ -0,0 +1,29 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import { connect } from 'react-redux'; | |||
import ProjectCard from './ProjectCard'; | |||
import { getComponent, getComponentMeasures } from '../../../app/store/rootReducer'; | |||
export default connect( | |||
(state, ownProps) => ({ | |||
project: getComponent(state, ownProps.projectKey), | |||
measures: getComponentMeasures(state, ownProps.projectKey), | |||
}) | |||
)(ProjectCard); |
@@ -0,0 +1,55 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import React from 'react'; | |||
import sortBy from 'lodash/sortBy'; | |||
import { connect } from 'react-redux'; | |||
import { getLanguages } from '../../../app/store/rootReducer'; | |||
import { translate } from '../../../helpers/l10n'; | |||
class ProjectCardLanguages extends React.Component { | |||
getLanguageName (key) { | |||
if (key === '<null>') { | |||
return translate('unknown'); | |||
} | |||
const language = this.props.languages[key]; | |||
return language != null ? language.name : key; | |||
} | |||
render () { | |||
const { distribution } = this.props; | |||
if (distribution == null) { | |||
return null; | |||
} | |||
const parsedLanguages = distribution.split(';').map(item => item.split('=')); | |||
const finalLanguages = sortBy(parsedLanguages, l => -1 * Number(l[1])) | |||
.slice(0, 2) | |||
.map(l => this.getLanguageName(l[0])); | |||
return <span>{finalLanguages.join(', ')}</span>; | |||
} | |||
} | |||
const mapStateToProps = state => ({ | |||
languages: getLanguages(state) | |||
}); | |||
export default connect(mapStateToProps)(ProjectCardLanguages); |
@@ -0,0 +1,129 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import React from 'react'; | |||
import ProjectCardLanguages from './ProjectCardLanguages'; | |||
import ProjectCardQualityGate from './ProjectCardQualityGate'; | |||
import Measure from '../../component-measures/components/Measure'; | |||
import Rating from '../../../components/ui/Rating'; | |||
import CoverageRating from '../../../components/ui/CoverageRating'; | |||
import DuplicationsRating from '../../../components/ui/DuplicationsRating'; | |||
import SizeRating from '../../../components/ui/SizeRating'; | |||
import { translate } from '../../../helpers/l10n'; | |||
export default class ProjectCardMeasures extends React.Component { | |||
static propTypes = { | |||
measures: React.PropTypes.object, | |||
languages: React.PropTypes.array | |||
}; | |||
render () { | |||
const { measures } = this.props; | |||
if (measures == null) { | |||
return null; | |||
} | |||
return ( | |||
<div className="project-card-measures"> | |||
<div className="project-card-measure"> | |||
<div className="project-card-measure-inner"> | |||
<div className="project-card-measure-number"> | |||
<Rating value={measures['reliability_rating']}/> | |||
</div> | |||
<div className="project-card-measure-label"> | |||
{translate('metric_domain.Reliability')} | |||
</div> | |||
</div> | |||
</div> | |||
<div className="project-card-measure"> | |||
<div className="project-card-measure-inner"> | |||
<div className="project-card-measure-number"> | |||
<Rating value={measures['security_rating']}/> | |||
</div> | |||
<div className="project-card-measure-label"> | |||
{translate('metric_domain.Security')} | |||
</div> | |||
</div> | |||
</div> | |||
<div className="project-card-measure"> | |||
<div className="project-card-measure-inner"> | |||
<div className="project-card-measure-number"> | |||
<Rating value={measures['sqale_rating']}/> | |||
</div> | |||
<div className="project-card-measure-label"> | |||
{translate('metric_domain.Maintainability')} | |||
</div> | |||
</div> | |||
</div> | |||
<div className="project-card-measure"> | |||
<div className="project-card-measure-inner"> | |||
<div className="project-card-measure-number"> | |||
{measures['coverage'] != null && ( | |||
<span className="spacer-right"> | |||
<CoverageRating value={measures['coverage']}/> | |||
</span> | |||
)} | |||
<Measure measure={{ value: measures['coverage'] }} | |||
metric={{ key: 'coverage', type: 'PERCENT' }}/> | |||
</div> | |||
<div className="project-card-measure-label"> | |||
{translate('metric.coverage.name')} | |||
</div> | |||
</div> | |||
</div> | |||
<div className="project-card-measure"> | |||
<div className="project-card-measure-inner"> | |||
<div className="project-card-measure-number"> | |||
<span className="spacer-right"> | |||
<DuplicationsRating value={measures['duplicated_lines_density']}/> | |||
</span> | |||
<Measure measure={{ value: measures['duplicated_lines_density'] }} | |||
metric={{ key: 'duplicated_lines_density', type: 'PERCENT' }}/> | |||
</div> | |||
<div className="project-card-measure-label"> | |||
{translate('metric.duplicated_lines_density.short_name')} | |||
</div> | |||
</div> | |||
</div> | |||
<div className="project-card-measure"> | |||
<div className="project-card-measure-inner"> | |||
<div className="project-card-measure-number"> | |||
<span className="spacer-right"> | |||
<SizeRating value={measures['ncloc']}/> | |||
</span> | |||
<Measure measure={{ value: measures['ncloc'] }} | |||
metric={{ key: 'ncloc', type: 'SHORT_INT' }}/> | |||
</div> | |||
<div className="project-card-measure-label"> | |||
<ProjectCardLanguages distribution={measures['ncloc_language_distribution']}/> | |||
</div> | |||
</div> | |||
</div> | |||
<ProjectCardQualityGate status={measures['alert_status']}/> | |||
</div> | |||
); | |||
} | |||
} |
@@ -0,0 +1,49 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import React from 'react'; | |||
import Level from '../../../components/ui/Level'; | |||
import { translate } from '../../../helpers/l10n'; | |||
export default class ProjectCardQualityGate extends React.Component { | |||
static propTypes = { | |||
status: React.PropTypes.string | |||
}; | |||
render () { | |||
const { status } = this.props; | |||
if (!status) { | |||
return null; | |||
} | |||
return ( | |||
<div className="project-card-measure pull-right"> | |||
<div className="project-card-measure-inner"> | |||
<div> | |||
<Level level={status}/> | |||
</div> | |||
<div className="project-card-measure-label"> | |||
{translate('overview.quality_gate')} | |||
</div> | |||
</div> | |||
</div> | |||
); | |||
} | |||
} |
@@ -0,0 +1,75 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import React from 'react'; | |||
import { List, AutoSizer, WindowScroller } from 'react-virtualized'; | |||
import ProjectCardContainer from './ProjectCardContainer'; | |||
import { translate } from '../../../helpers/l10n'; | |||
export default class ProjectsList extends React.Component { | |||
static propTypes = { | |||
projects: React.PropTypes.arrayOf(React.PropTypes.string) | |||
}; | |||
render () { | |||
const { projects } = this.props; | |||
if (projects == null) { | |||
return null; | |||
} | |||
if (projects.length === 0) { | |||
return ( | |||
<div className="projects-empty-list"> | |||
<h3>{translate('projects.no_projects.1')}</h3> | |||
<p className="big-spacer-top">{translate('projects.no_projects.2')}</p> | |||
</div> | |||
); | |||
} | |||
const rowRenderer = ({ key, index, style }) => { | |||
const projectKey = projects[index]; | |||
return ( | |||
<div key={key} style={style}> | |||
<ProjectCardContainer projectKey={projectKey}/> | |||
</div> | |||
); | |||
}; | |||
return ( | |||
<WindowScroller> | |||
{({ height, scrollTop }) => ( | |||
<AutoSizer disableHeight> | |||
{({ width }) => ( | |||
<List | |||
className="projects-list" | |||
autoHeight | |||
width={width} | |||
height={height} | |||
rowCount={projects.length} | |||
rowHeight={135} | |||
rowRenderer={rowRenderer} | |||
scrollTop={scrollTop}/> | |||
)} | |||
</AutoSizer> | |||
)} | |||
</WindowScroller> | |||
); | |||
} | |||
} |
@@ -0,0 +1,28 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import { connect } from 'react-redux'; | |||
import ProjectsList from './ProjectsList'; | |||
import { getProjects } from '../../../app/store/rootReducer'; | |||
export default connect( | |||
state => ({ | |||
projects: getProjects(state) | |||
}) | |||
)(ProjectsList); |
@@ -0,0 +1,35 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import React from 'react'; | |||
import ListFooter from '../../../components/controls/ListFooter'; | |||
export default class ProjectsListFooter extends React.Component { | |||
static propTypes = { | |||
total: React.PropTypes.number.isRequired, | |||
}; | |||
render () { | |||
if (!this.props.total) { | |||
return null; | |||
} | |||
return <ListFooter {...this.props}/>; | |||
} | |||
} |
@@ -0,0 +1,42 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import { connect } from 'react-redux'; | |||
import { getProjects, getProjectsAppState } from '../../../app/store/rootReducer'; | |||
import { fetchMoreProjects } from '../store/actions'; | |||
import ProjectsListFooter from './ProjectsListFooter'; | |||
const mapStateToProps = state => { | |||
const projects = getProjects(state); | |||
const appState = getProjectsAppState(state); | |||
return { | |||
count: projects != null ? projects.length : 0, | |||
total: appState.total != null ? appState.total : 0, | |||
ready: !appState.loading | |||
}; | |||
}; | |||
const mapDispatchToProps = (dispatch, ownProps) => ({ | |||
loadMore: () => dispatch(fetchMoreProjects(ownProps.query)) | |||
}); | |||
export default connect( | |||
mapStateToProps, | |||
mapDispatchToProps | |||
)(ProjectsListFooter); |
@@ -0,0 +1,126 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import React from 'react'; | |||
import { Link } from 'react-router'; | |||
import Filter from './Filter'; | |||
import CoverageRating from '../../../components/ui/CoverageRating'; | |||
import { translate } from '../../../helpers/l10n'; | |||
export default class CoverageFilter extends React.Component { | |||
static propTypes = { | |||
value: React.PropTypes.shape({ | |||
from: React.PropTypes.number, | |||
to: React.PropTypes.number | |||
}), | |||
getFilterUrl: React.PropTypes.func.isRequired, | |||
toggleFilter: React.PropTypes.func.isRequired | |||
}; | |||
isOptionAction (from, to) { | |||
const { value } = this.props; | |||
if (value == null) { | |||
return false; | |||
} | |||
return value.from === from && value.to === to; | |||
} | |||
renderLabel (value) { | |||
let label; | |||
if (value.to == null) { | |||
label = '>' + value.from; | |||
} else if (value.from == null) { | |||
label = '<' + value.to; | |||
} else { | |||
label = value.from + '–' + value.to; | |||
} | |||
return label + '%'; | |||
} | |||
renderValue () { | |||
const { value } = this.props; | |||
let average; | |||
if (value.to == null) { | |||
average = value.from; | |||
} else if (value.from == null) { | |||
average = value.to / 2; | |||
} else { | |||
average = (value.from + value.to) / 2; | |||
} | |||
const label = this.renderLabel(value); | |||
return ( | |||
<div className="projects-filter-value"> | |||
<CoverageRating value={average}/> | |||
<div className="projects-filter-hint note"> | |||
{label} | |||
</div> | |||
</div> | |||
); | |||
} | |||
renderOptions () { | |||
const options = [ | |||
[null, 30, 15], | |||
[30, 50, 40], | |||
[50, 70, 60], | |||
[70, 80, 75], | |||
[80, null, 90], | |||
]; | |||
return ( | |||
<div> | |||
{options.map(option => ( | |||
<Link key={option[2]} | |||
className={this.isOptionAction(option[0], option[1]) ? 'active' : ''} | |||
to={this.props.getFilterUrl({ 'coverage__gte': option[0], 'coverage__lt': option[1] })} | |||
onClick={this.props.toggleFilter}> | |||
<CoverageRating value={option[2]}/> | |||
<span className="spacer-left">{this.renderLabel({ from: option[0], to: option[1] })}</span> | |||
</Link> | |||
))} | |||
{this.props.value != null && ( | |||
<div> | |||
<hr/> | |||
<Link className="text-center" | |||
to={this.props.getFilterUrl({ 'coverage__gte': null, 'coverage__lt': null })} | |||
onClick={this.props.toggleFilter}> | |||
<span className="text-danger">{translate('reset_verb')}</span> | |||
</Link> | |||
</div> | |||
)} | |||
</div> | |||
); | |||
} | |||
render () { | |||
return ( | |||
<Filter | |||
renderName={() => 'Coverage'} | |||
renderOptions={() => this.renderOptions()} | |||
renderValue={() => this.renderValue()} | |||
{...this.props}/> | |||
); | |||
} | |||
} |
@@ -0,0 +1,29 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import CoverageFilter from './CoverageFilter'; | |||
import connectFilter from './connectFilter'; | |||
const getValue = query => { | |||
const from = query['coverage__gte']; | |||
const to = query['coverage__lt']; | |||
return from == null && to == null ? null : { from, to }; | |||
}; | |||
export default connectFilter('coverage', getValue)(CoverageFilter); |
@@ -0,0 +1,126 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import React from 'react'; | |||
import { Link } from 'react-router'; | |||
import Filter from './Filter'; | |||
import DuplicationsRating from '../../../components/ui/DuplicationsRating'; | |||
import { translate } from '../../../helpers/l10n'; | |||
export default class DuplicationsFilter extends React.Component { | |||
static propTypes = { | |||
value: React.PropTypes.shape({ | |||
from: React.PropTypes.number, | |||
to: React.PropTypes.number | |||
}), | |||
getFilterUrl: React.PropTypes.func.isRequired, | |||
toggleFilter: React.PropTypes.func.isRequired | |||
}; | |||
isOptionAction (from, to) { | |||
const { value } = this.props; | |||
if (value == null) { | |||
return false; | |||
} | |||
return value.from === from && value.to === to; | |||
} | |||
renderLabel (value) { | |||
let label; | |||
if (value.to == null) { | |||
label = '>' + value.from; | |||
} else if (value.from == null) { | |||
label = '<' + value.to; | |||
} else { | |||
label = value.from + '–' + value.to; | |||
} | |||
return label + '%'; | |||
} | |||
renderValue () { | |||
const { value } = this.props; | |||
let average; | |||
if (value.to == null) { | |||
average = value.from; | |||
} else if (value.from == null) { | |||
average = value.to / 2; | |||
} else { | |||
average = (value.from + value.to) / 2; | |||
} | |||
const label = this.renderLabel(value); | |||
return ( | |||
<div className="projects-filter-value"> | |||
<DuplicationsRating value={average}/> | |||
<div className="projects-filter-hint note"> | |||
{label} | |||
</div> | |||
</div> | |||
); | |||
} | |||
renderOptions () { | |||
const options = [ | |||
[null, 3, 1.5], | |||
[3, 5, 4], | |||
[5, 10, 7.5], | |||
[10, 20, 15], | |||
[20, null, 30], | |||
]; | |||
return ( | |||
<div> | |||
{options.map(option => ( | |||
<Link key={option[2]} | |||
className={this.isOptionAction(option[0], option[1]) ? 'active' : ''} | |||
to={this.props.getFilterUrl({ 'duplications__gte': option[0], 'duplications__lt': option[1] })} | |||
onClick={this.props.toggleFilter}> | |||
<DuplicationsRating value={option[2]}/> | |||
<span className="spacer-left">{this.renderLabel({ from: option[0], to: option[1] })}</span> | |||
</Link> | |||
))} | |||
{this.props.value != null && ( | |||
<div> | |||
<hr/> | |||
<Link className="text-center" | |||
to={this.props.getFilterUrl({ 'duplications__gte': null, 'duplications__lt': null })} | |||
onClick={this.props.toggleFilter}> | |||
<span className="text-danger">{translate('reset_verb')}</span> | |||
</Link> | |||
</div> | |||
)} | |||
</div> | |||
); | |||
} | |||
render () { | |||
return ( | |||
<Filter | |||
renderName={() => 'Duplications'} | |||
renderOptions={() => this.renderOptions()} | |||
renderValue={() => this.renderValue()} | |||
{...this.props}/> | |||
); | |||
} | |||
} |
@@ -0,0 +1,29 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import DuplicationsFilter from './DuplicationsFilter'; | |||
import connectFilter from './connectFilter'; | |||
const getValue = query => { | |||
const from = query['duplications__gte']; | |||
const to = query['duplications__lt']; | |||
return from == null && to == null ? null : { from, to }; | |||
}; | |||
export default connectFilter('duplications', getValue)(DuplicationsFilter); |
@@ -0,0 +1,70 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import React from 'react'; | |||
import classNames from 'classnames'; | |||
export default class Filter extends React.Component { | |||
static propTypes = { | |||
getFilterUrl: React.PropTypes.func.isRequired, | |||
isOpen: React.PropTypes.bool.isRequired, | |||
renderName: React.PropTypes.func.isRequired, | |||
renderOptions: React.PropTypes.func.isRequired, | |||
renderValue: React.PropTypes.func.isRequired, | |||
toggleFilter: React.PropTypes.func.isRequired, | |||
value: React.PropTypes.any | |||
}; | |||
handleClick (e) { | |||
e.preventDefault(); | |||
e.target.blur(); | |||
this.props.toggleFilter(); | |||
} | |||
render () { | |||
const { value, isOpen } = this.props; | |||
const { renderName, renderOptions, renderValue } = this.props; | |||
const className = classNames('projects-filter', { | |||
'projects-filter-active': value != null, | |||
'projects-filter-open': isOpen | |||
}); | |||
return ( | |||
<div className={className}> | |||
<a className="projects-filter-header clearfix" href="#" onClick={e => this.handleClick(e)}> | |||
<div className="projects-filter-name"> | |||
{renderName()} | |||
{' '} | |||
{!isOpen && ( | |||
<i className="icon-dropdown"/> | |||
)} | |||
</div> | |||
{value != null && renderValue()} | |||
</a> | |||
{isOpen && ( | |||
<div className="projects-filter-options"> | |||
{renderOptions()} | |||
</div> | |||
)} | |||
</div> | |||
); | |||
} | |||
} |
@@ -0,0 +1,76 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import React from 'react'; | |||
import { Link } from 'react-router'; | |||
import Filter from './Filter'; | |||
import Level from '../../../components/ui/Level'; | |||
import { translate } from '../../../helpers/l10n'; | |||
export default class QualityGateFilter extends React.Component { | |||
static propTypes = { | |||
value: React.PropTypes.any, | |||
getFilterUrl: React.PropTypes.func.isRequired, | |||
toggleFilter: React.PropTypes.func.isRequired | |||
}; | |||
renderValue () { | |||
return ( | |||
<div className="projects-filter-value"> | |||
<Level level={this.props.value}/> | |||
</div> | |||
); | |||
} | |||
renderOptions () { | |||
const options = ['ERROR', 'WARN', 'OK']; | |||
return ( | |||
<div> | |||
{options.map(option => ( | |||
<Link key={option} | |||
className={option === this.props.value ? 'active' : ''} | |||
to={this.props.getFilterUrl({ gate: option })} | |||
onClick={this.props.toggleFilter}> | |||
<Level level={option}/> | |||
</Link> | |||
))} | |||
{this.props.value != null && ( | |||
<div> | |||
<hr/> | |||
<Link className="text-center" to={this.props.getFilterUrl({ gate: null })} | |||
onClick={this.props.toggleFilter}> | |||
<span className="text-danger">{translate('reset_verb')}</span> | |||
</Link> | |||
</div> | |||
)} | |||
</div> | |||
); | |||
} | |||
render () { | |||
return ( | |||
<Filter | |||
renderName={() => 'Quality Gate'} | |||
renderOptions={() => this.renderOptions()} | |||
renderValue={() => this.renderValue()} | |||
{...this.props}/> | |||
); | |||
} | |||
} |
@@ -0,0 +1,25 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import QualityGateFilter from './QualityGateFilter'; | |||
import connectFilter from './connectFilter'; | |||
const getValue = query => query.gate; | |||
export default connectFilter('gate', getValue)(QualityGateFilter); |
@@ -0,0 +1,127 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import React from 'react'; | |||
import { Link } from 'react-router'; | |||
import Filter from './Filter'; | |||
import SizeRating from '../../../components/ui/SizeRating'; | |||
import { formatMeasure } from '../../../helpers/measures'; | |||
import { translate } from '../../../helpers/l10n'; | |||
export default class SizeFilter extends React.Component { | |||
static propTypes = { | |||
value: React.PropTypes.shape({ | |||
from: React.PropTypes.number, | |||
to: React.PropTypes.number | |||
}), | |||
getFilterUrl: React.PropTypes.func.isRequired, | |||
toggleFilter: React.PropTypes.func.isRequired | |||
}; | |||
isOptionAction (from, to) { | |||
const { value } = this.props; | |||
if (value == null) { | |||
return false; | |||
} | |||
return value.from === from && value.to === to; | |||
} | |||
renderLabel (value) { | |||
let label; | |||
if (value.to == null) { | |||
label = '>' + formatMeasure(value.from, 'SHORT_INT'); | |||
} else if (value.from == null) { | |||
label = '<' + formatMeasure(value.to, 'SHORT_INT'); | |||
} else { | |||
label = formatMeasure(value.from, 'SHORT_INT') + '–' + formatMeasure(value.to, 'SHORT_INT'); | |||
} | |||
return label; | |||
} | |||
renderValue () { | |||
const { value } = this.props; | |||
let average; | |||
if (value.to == null) { | |||
average = value.from; | |||
} else if (value.from == null) { | |||
average = value.to / 2; | |||
} else { | |||
average = (value.from + value.to) / 2; | |||
} | |||
const label = this.renderLabel(value); | |||
return ( | |||
<div className="projects-filter-value"> | |||
<SizeRating value={average}/> | |||
<div className="projects-filter-hint note"> | |||
{label} | |||
</div> | |||
</div> | |||
); | |||
} | |||
renderOptions () { | |||
const options = [ | |||
[null, 1000, 0], | |||
[1000, 10000, 1000], | |||
[10000, 100000, 10000], | |||
[100000, 500000, 100000], | |||
[500000, null, 500000], | |||
]; | |||
return ( | |||
<div> | |||
{options.map(option => ( | |||
<Link key={option[2]} | |||
className={this.isOptionAction(option[0], option[1]) ? 'active' : ''} | |||
to={this.props.getFilterUrl({ 'size__gte': option[0], 'size__lt': option[1] })} | |||
onClick={this.props.toggleFilter}> | |||
<SizeRating value={option[2]}/> | |||
<span className="spacer-left">{this.renderLabel({ from: option[0], to: option[1] })}</span> | |||
</Link> | |||
))} | |||
{this.props.value != null && ( | |||
<div> | |||
<hr/> | |||
<Link className="text-center" | |||
to={this.props.getFilterUrl({ 'size__gte': null, 'size__lt': null })} | |||
onClick={this.props.toggleFilter}> | |||
<span className="text-danger">{translate('reset_verb')}</span> | |||
</Link> | |||
</div> | |||
)} | |||
</div> | |||
); | |||
} | |||
render () { | |||
return ( | |||
<Filter | |||
renderName={() => 'Size'} | |||
renderOptions={() => this.renderOptions()} | |||
renderValue={() => this.renderValue()} | |||
{...this.props}/> | |||
); | |||
} | |||
} |
@@ -0,0 +1,29 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import SizeFilter from './SizeFilter'; | |||
import connectFilter from './connectFilter'; | |||
const getValue = query => { | |||
const from = query['size__gte']; | |||
const to = query['size__lt']; | |||
return from == null && to == null ? null : { from, to }; | |||
}; | |||
export default connectFilter('size', getValue)(SizeFilter); |
@@ -0,0 +1,44 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import { connect } from 'react-redux'; | |||
import omitBy from 'lodash/omitBy'; | |||
import isNil from 'lodash/isNil'; | |||
import { getProjectsAppFilterStatus } from '../../../app/store/rootReducer'; | |||
import { toggleFilter } from '../store/filters/statuses/actions'; | |||
import { OPEN } from '../store/filters/statuses/reducer'; | |||
const connectFilter = (key, getValue) => Component => { | |||
const mapStateToProps = (state, ownProps) => ({ | |||
isOpen: getProjectsAppFilterStatus(state, key) === OPEN, | |||
value: getValue(ownProps.query), | |||
getFilterUrl: part => { | |||
const query = omitBy({ ...ownProps.query, ...part }, isNil); | |||
return { pathname: '/projects', query }; | |||
} | |||
}); | |||
const mapDispatchToProps = dispatch => ({ | |||
toggleFilter: () => dispatch(toggleFilter(key)) | |||
}); | |||
return connect(mapStateToProps, mapDispatchToProps)(Component); | |||
}; | |||
export default connectFilter; |
@@ -0,0 +1,26 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import React from 'react'; | |||
import { Route } from 'react-router'; | |||
import AppContainer from './components/AppContainer'; | |||
export default ( | |||
<Route path="projects" component={AppContainer}/> | |||
); |
@@ -0,0 +1,119 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import groupBy from 'lodash/groupBy'; | |||
import keyBy from 'lodash/keyBy'; | |||
import { searchProjects } from '../../../api/components'; | |||
import { addGlobalErrorMessage } from '../../../components/store/globalMessages'; | |||
import { parseError } from '../../code/utils'; | |||
import { receiveComponents } from '../../../app/store/components/actions'; | |||
import { receiveProjects, receiveMoreProjects } from './projects/actions'; | |||
import { updateState } from './state/actions'; | |||
import { getProjectsAppState } from '../../../app/store/rootReducer'; | |||
import { getMeasuresForComponents } from '../../../api/measures'; | |||
import { receiveComponentsMeasures } from '../../../app/store/measures/actions'; | |||
import { convertToFilter } from './utils'; | |||
const PAGE_SIZE = 50; | |||
const METRICS = [ | |||
'alert_status', | |||
'reliability_rating', | |||
'security_rating', | |||
'sqale_rating', | |||
'duplicated_lines_density', | |||
'coverage', | |||
'ncloc', | |||
'ncloc_language_distribution' | |||
]; | |||
const onFail = dispatch => error => { | |||
parseError(error).then(message => dispatch(addGlobalErrorMessage(message))); | |||
dispatch(updateState({ loading: false })); | |||
}; | |||
const onReceiveMeasures = (dispatch, projects) => response => { | |||
const projectsById = keyBy(projects, 'id'); | |||
const byComponentId = groupBy(response.measures, 'component'); | |||
const toStore = {}; | |||
Object.keys(byComponentId).forEach(componentId => { | |||
const componentKey = projectsById[componentId].key; | |||
const measures = {}; | |||
byComponentId[componentId].forEach(measure => { | |||
measures[measure.metric] = measure.value; | |||
}); | |||
toStore[componentKey] = measures; | |||
}); | |||
dispatch(receiveComponentsMeasures(toStore)); | |||
}; | |||
const fetchProjectMeasures = projects => dispatch => { | |||
if (!projects.length) { | |||
return Promise.resolve(); | |||
} | |||
const projectKeys = projects.map(project => project.key); | |||
return getMeasuresForComponents(projectKeys, METRICS).then(onReceiveMeasures(dispatch, projects), onFail(dispatch)); | |||
}; | |||
const onReceiveProjects = dispatch => response => { | |||
dispatch(receiveComponents(response.components)); | |||
dispatch(receiveProjects(response.components)); | |||
dispatch(fetchProjectMeasures(response.components)).then(() => { | |||
dispatch(updateState({ loading: false })); | |||
}); | |||
dispatch(updateState({ | |||
total: response.paging.total, | |||
pageIndex: response.paging.pageIndex, | |||
})); | |||
}; | |||
const onReceiveMoreProjects = dispatch => response => { | |||
dispatch(receiveComponents(response.components)); | |||
dispatch(receiveMoreProjects(response.components)); | |||
dispatch(fetchProjectMeasures(response.components)).then(() => { | |||
dispatch(updateState({ loading: false })); | |||
}); | |||
dispatch(updateState({ pageIndex: response.paging.pageIndex })); | |||
}; | |||
export const fetchProjects = query => dispatch => { | |||
dispatch(updateState({ loading: true })); | |||
const data = { ps: PAGE_SIZE }; | |||
const filter = convertToFilter(query); | |||
if (filter) { | |||
data.filter = filter; | |||
} | |||
return searchProjects(data).then(onReceiveProjects(dispatch), onFail(dispatch)); | |||
}; | |||
export const fetchMoreProjects = query => (dispatch, getState) => { | |||
dispatch(updateState({ loading: true })); | |||
const state = getState(); | |||
const { pageIndex } = getProjectsAppState(state); | |||
const data = { ps: PAGE_SIZE, p: pageIndex + 1 }; | |||
const filter = convertToFilter(query); | |||
if (filter) { | |||
data.filter = filter; | |||
} | |||
return searchProjects(data).then(onReceiveMoreProjects(dispatch), onFail(dispatch)); | |||
}; |
@@ -0,0 +1,29 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import { combineReducers } from 'redux'; | |||
import statuses, * as fromStatuses from './statuses/reducer'; | |||
const reducer = combineReducers({ statuses }); | |||
export default reducer; | |||
export const getFilterStatus = (state, key) => ( | |||
fromStatuses.getStatus(state.statuses, key) | |||
); |
@@ -0,0 +1,25 @@ | |||
/* | |||
* 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 const TOGGLE_FILTER = 'projects/TOGGLE_FILTER'; | |||
export const toggleFilter = key => ({ | |||
type: TOGGLE_FILTER, | |||
key | |||
}); |
@@ -0,0 +1,44 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import { TOGGLE_FILTER } from './actions'; | |||
export const OPEN = 'OPEN'; | |||
export const CLOSED = 'CLOSED'; | |||
const allClosedState = { | |||
coverage: CLOSED, | |||
duplications: CLOSED, | |||
size: CLOSED | |||
}; | |||
const reducer = (state = allClosedState, action = {}) => { | |||
if (action.type === TOGGLE_FILTER) { | |||
const newStatus = state[action.key] === OPEN ? CLOSED : OPEN; | |||
return { ...allClosedState, [action.key]: newStatus }; | |||
} | |||
return state; | |||
}; | |||
export default reducer; | |||
export const getStatus = (state, key) => ( | |||
state[key] | |||
); |
@@ -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 const RECEIVE_PROJECTS = 'projects/RECEIVE_PROJECTS'; | |||
export const receiveProjects = projects => ({ | |||
type: RECEIVE_PROJECTS, | |||
projects | |||
}); | |||
export const RECEIVE_MORE_PROJECTS = 'projects/RECEIVE_MORE_PROJECTS'; | |||
export const receiveMoreProjects = projects => ({ | |||
type: RECEIVE_MORE_PROJECTS, | |||
projects | |||
}); |
@@ -0,0 +1,37 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import { RECEIVE_PROJECTS, RECEIVE_MORE_PROJECTS } from './actions'; | |||
const reducer = (state = null, action = {}) => { | |||
if (action.type === RECEIVE_PROJECTS) { | |||
return action.projects.map(project => project.key); | |||
} | |||
if (action.type === RECEIVE_MORE_PROJECTS) { | |||
const keys = action.projects.map(project => project.key); | |||
return state != null ? [...state, ...keys] : keys; | |||
} | |||
return state; | |||
}; | |||
export default reducer; | |||
export const getProjects = state => state; |
@@ -0,0 +1,37 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import { combineReducers } from 'redux'; | |||
import projects, * as fromProjects from './projects/reducer'; | |||
import state from './state/reducer'; | |||
import filters, * as fromFilters from './filters/reducer'; | |||
export default combineReducers({ projects, state, filters }); | |||
export const getProjects = state => ( | |||
fromProjects.getProjects(state.projects) | |||
); | |||
export const getState = state => ( | |||
state.state | |||
); | |||
export const getFilterStatus = (state, key) => ( | |||
fromFilters.getFilterStatus(state.filters, key) | |||
); |
@@ -0,0 +1,25 @@ | |||
/* | |||
* 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 const UPDATE_STATE = 'projects/UPDATE_STATE'; | |||
export const updateState = changes => ({ | |||
type: UPDATE_STATE, | |||
changes | |||
}); |
@@ -0,0 +1,30 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import { UPDATE_STATE } from './actions'; | |||
const reducer = (state = {}, action = {}) => { | |||
if (action.type === UPDATE_STATE) { | |||
return { ...state, ...action.changes }; | |||
} | |||
return state; | |||
}; | |||
export default reducer; |
@@ -0,0 +1,79 @@ | |||
/* | |||
* 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. | |||
*/ | |||
const getAsNumber = value => { | |||
if (value === '' || value == null) { | |||
return null; | |||
} | |||
return isNaN(value) ? null : Number(value); | |||
}; | |||
const getAsLevel = value => { | |||
if (value === 'ERROR' || value === 'WARN' || value === 'OK') { | |||
return value; | |||
} | |||
return null; | |||
}; | |||
export const parseUrlQuery = urlQuery => ({ | |||
'gate': getAsLevel(urlQuery['gate']), | |||
'coverage__gte': getAsNumber(urlQuery['coverage__gte']), | |||
'coverage__lt': getAsNumber(urlQuery['coverage__lt']), | |||
'duplications__gte': getAsNumber(urlQuery['duplications__gte']), | |||
'duplications__lt': getAsNumber(urlQuery['duplications__lt']), | |||
'size__gte': getAsNumber(urlQuery['size__gte']), | |||
'size__lt': getAsNumber(urlQuery['size__lt']) | |||
}); | |||
export const convertToFilter = query => { | |||
const conditions = []; | |||
if (query['gate'] != null) { | |||
conditions.push('alert_status = ' + query['gate']); | |||
} | |||
if (query['coverage__gte'] != null) { | |||
conditions.push('coverage >= ' + query['coverage__gte']); | |||
} | |||
if (query['coverage__lt'] != null) { | |||
conditions.push('coverage < ' + query['coverage__lt']); | |||
} | |||
if (query['duplications__gte'] != null) { | |||
conditions.push('duplicated_lines_density >= ' + query['duplications__gte']); | |||
} | |||
if (query['duplications__lt'] != null) { | |||
conditions.push('duplicated_lines_density < ' + query['duplications__lt']); | |||
} | |||
if (query['size__gte'] != null) { | |||
conditions.push('ncloc >= ' + query['size__gte']); | |||
} | |||
if (query['size__lt'] != null) { | |||
conditions.push('ncloc < ' + query['size__lt']); | |||
} | |||
return conditions.join(' and '); | |||
}; |
@@ -0,0 +1,147 @@ | |||
.projects-list { | |||
outline: none; | |||
} | |||
.projects-empty-list { | |||
padding: 60px 0; | |||
border: 1px solid #e6e6e6; | |||
border-radius: 2px; | |||
text-align: center; | |||
color: #777; | |||
} | |||
.project-card { | |||
height: 115px; | |||
box-sizing: border-box; | |||
} | |||
.project-card-name { | |||
font-weight: 600; | |||
} | |||
.project-card-measures { | |||
margin: 0 -20px; | |||
} | |||
.project-card-measure { | |||
position: relative; | |||
display: inline-block; | |||
vertical-align: top; | |||
padding: 0 15px; | |||
text-align: center; | |||
} | |||
.project-card-measure + .project-card-measure:before { | |||
position: absolute; | |||
top: 50%; | |||
left: 0; | |||
width: 0; | |||
height: 24px; | |||
margin-top: -12px; | |||
border-left: 1px solid #e6e6e6; | |||
content: ""; | |||
} | |||
.project-card-measure.pull-right:before { | |||
display: none; | |||
} | |||
.project-card-measure .level { | |||
margin-top: 0; | |||
margin-bottom: 0; | |||
} | |||
.project-card-measure-inner { | |||
display: inline-block; | |||
vertical-align: top; | |||
text-align: center; | |||
} | |||
.project-card-measure-number { | |||
line-height: 24px; | |||
font-size: 18px; | |||
} | |||
.project-card-measure-label { | |||
margin-top: 4px; | |||
font-size: 12px; | |||
} | |||
.projects-filter { | |||
border: 1px solid transparent; | |||
border-top-color: #e6e6e6; | |||
} | |||
.projects-filter:first-child { | |||
border-top-color: transparent; | |||
} | |||
.projects-filter-active .projects-filter-name { | |||
font-weight: 600; | |||
} | |||
.projects-filter-open { | |||
border-color: #e6e6e6 !important; | |||
background-color: #fff; | |||
} | |||
.projects-filter-open + .projects-filter { | |||
border-top-color: transparent; | |||
} | |||
.projects-filter-open .projects-filter-header { | |||
border-bottom: 1px solid #e6e6e6; | |||
transition: none; | |||
} | |||
.projects-filter-header { | |||
display: block; | |||
padding: 15px; | |||
color: #444; | |||
border: none; | |||
} | |||
.projects-filter-checkbox { | |||
float: left; | |||
padding: 4px 4px 4px 0; | |||
} | |||
.projects-filter-name { | |||
float: left; | |||
padding: 4px; | |||
line-height: 16px; | |||
} | |||
.projects-filter-hint { | |||
float: left; | |||
margin-right: 6px; | |||
padding: 4px; | |||
line-height: 16px; | |||
} | |||
.projects-filter-value { | |||
float: right; | |||
} | |||
.projects-filter-options { | |||
padding-top: 5px; | |||
padding-bottom: 5px; | |||
} | |||
.projects-filter-options a { | |||
display: block; | |||
padding: 5px 15px; | |||
line-height: 24px; | |||
border: none; | |||
color: #444; | |||
} | |||
.projects-filter-options a:hover, | |||
.projects-filter-options a:active, | |||
.projects-filter-options a:focus { | |||
background-color: #f3f3f3; | |||
} | |||
.projects-filter-options a.active { | |||
background-color: #cae3f2; | |||
} |
@@ -119,7 +119,7 @@ export const DrilldownLink = React.createClass({ | |||
this.propsToIssueParams()); | |||
return ( | |||
<a href={url}>{this.props.children}</a> | |||
<a className={this.props.className} href={url}>{this.props.children}</a> | |||
); | |||
}, | |||
@@ -0,0 +1,47 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import React from 'react'; | |||
import { DonutChart } from '../charts/donut-chart'; | |||
export default class CoverageRating extends React.Component { | |||
static propTypes = { | |||
value: React.PropTypes.oneOfType([React.PropTypes.number, React.PropTypes.string]) | |||
}; | |||
render () { | |||
let data = [{ value: 100, fill: '#ccc ' }]; | |||
if (this.props.value != null) { | |||
const value = Number(this.props.value); | |||
data = [ | |||
{ value, fill: '#85bb43' }, | |||
{ value: 100 - value, fill: '#d4333f' } | |||
]; | |||
} | |||
return ( | |||
<DonutChart | |||
data={data} | |||
width={24} | |||
height={24} | |||
thickness={3}/> | |||
); | |||
} | |||
} |
@@ -0,0 +1,56 @@ | |||
.duplications-rating { | |||
position: relative; | |||
display: inline-flex; | |||
vertical-align: top; | |||
justify-content: center; | |||
align-items: center; | |||
width: 24px; | |||
height: 24px; | |||
border: 3px solid #f3ca8e; | |||
border-radius: 24px; | |||
box-sizing: border-box; | |||
} | |||
.duplications-rating:after { | |||
border-radius: 24px; | |||
background-color: #f3ca8e; | |||
content: ""; | |||
} | |||
.duplications-rating-A { | |||
border-color: #85bb43; | |||
} | |||
.duplications-rating-A:after { | |||
display: none; | |||
} | |||
.duplications-rating-B:after { | |||
width: 6px; | |||
height: 6px; | |||
} | |||
.duplications-rating-C:after { | |||
width: 8px; | |||
height: 8px; | |||
} | |||
.duplications-rating-D { | |||
border-color: #d4333f; | |||
} | |||
.duplications-rating-D:after { | |||
width: 12px; | |||
height: 12px; | |||
background-color: #d4333f; | |||
} | |||
.duplications-rating-E { | |||
border-color: #d4333f; | |||
} | |||
.duplications-rating-E:after { | |||
width: 14px; | |||
height: 14px; | |||
background-color: #d4333f; | |||
} |
@@ -0,0 +1,44 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import React from 'react'; | |||
import classNames from 'classnames'; | |||
import inRange from 'lodash/inRange'; | |||
import './DuplicationsRating.css'; | |||
export default class DuplicationsRating extends React.Component { | |||
static propTypes = { | |||
value: React.PropTypes.oneOfType([React.PropTypes.number, React.PropTypes.string]).isRequired | |||
}; | |||
render () { | |||
const { value } = this.props; | |||
const className = classNames('duplications-rating', { | |||
'duplications-rating-A': inRange(value, 3), | |||
'duplications-rating-B': inRange(value, 3, 5), | |||
'duplications-rating-C': inRange(value, 5, 10), | |||
'duplications-rating-D': inRange(value, 10, 20), | |||
'duplications-rating-E': value >= 20 | |||
}); | |||
return ( | |||
<div className={className}/> | |||
); | |||
} | |||
} |
@@ -0,0 +1,45 @@ | |||
.level { | |||
display: inline-block; | |||
width: 80px; | |||
height: 24px; | |||
line-height: 24px; | |||
border-radius: 24px; | |||
box-sizing: border-box; | |||
color: #fff; | |||
letter-spacing: 0.02em; | |||
font-size: 13px; | |||
font-weight: 400; | |||
text-align: center; | |||
text-shadow: 0 0 1px rgba(0, 0, 0, 0.35); | |||
} | |||
.level-small { | |||
width: 64px; | |||
margin-top: -1px; | |||
margin-bottom: -1px; | |||
height: 18px; | |||
line-height: 18px; | |||
font-size: 12px; | |||
} | |||
a > .level { | |||
margin-bottom: -1px; | |||
border-bottom: 1px solid; | |||
transition: all 0.2s ease; | |||
} | |||
a > .level:hover { | |||
opacity: 0.8; | |||
} | |||
.level-OK { | |||
background-color: #85bb43; | |||
} | |||
.level-WARN { | |||
background-color: #f90; | |||
} | |||
.level-ERROR { | |||
background-color: #d4333f; | |||
} |
@@ -19,6 +19,7 @@ | |||
*/ | |||
import React from 'react'; | |||
import { formatMeasure } from '../../helpers/measures'; | |||
import './Level.css'; | |||
export default class Level extends React.Component { | |||
static propTypes = { |
@@ -0,0 +1,57 @@ | |||
.rating { | |||
display: inline-block; | |||
width: 24px; | |||
height: 24px; | |||
line-height: 23px; | |||
border-radius: 24px; | |||
box-sizing: border-box; | |||
color: #fff; | |||
font-size: 16px; | |||
font-weight: 400; | |||
text-align: center; | |||
text-shadow: 0 0 1px rgba(0, 0, 0, 0.35); | |||
} | |||
a > .rating { | |||
margin-bottom: -1px; | |||
border-bottom: 1px solid; | |||
transition: all 0.2s ease; | |||
} | |||
a > .rating:hover { | |||
opacity: 0.8; | |||
} | |||
.rating-A { | |||
background-color: #0a0; | |||
} | |||
a > .rating-A { | |||
border-bottom-color: #0a0; | |||
} | |||
.rating-B { | |||
background-color: #80cc00; | |||
} | |||
a .rating-B { border-bottom-color: #80cc00; } | |||
.rating-C { | |||
background-color: #fe0; | |||
color: #444; | |||
text-shadow: none; | |||
} | |||
a .rating-C { border-bottom-color: #fe0; } | |||
.rating-D { | |||
background-color: #f77700; | |||
} | |||
a .rating-D { border-bottom-color: #f77700; } | |||
.rating-E { | |||
background-color: #e00; | |||
} | |||
a .rating-E { border-bottom-color: #e00; } |
@@ -19,6 +19,7 @@ | |||
*/ | |||
import React from 'react'; | |||
import { formatMeasure } from '../../helpers/measures'; | |||
import './Rating.css'; | |||
export default class Rating extends React.Component { | |||
static propTypes = { |
@@ -0,0 +1,16 @@ | |||
.size-rating { | |||
display: inline-block; | |||
vertical-align: top; | |||
width: 24px; | |||
height: 24px; | |||
line-height: 24px; | |||
border-radius: 24px; | |||
background-color: #4b9fd5; | |||
color: #fff; | |||
font-size: 12px; | |||
text-align: center; | |||
} | |||
.size-rating-muted { | |||
background-color: #ccc; | |||
} |
@@ -0,0 +1,55 @@ | |||
/* | |||
* 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. | |||
*/ | |||
import React from 'react'; | |||
import inRange from 'lodash/inRange'; | |||
import './SizeRating.css'; | |||
export default class SizeRating extends React.Component { | |||
static propTypes = { | |||
value: React.PropTypes.oneOfType([React.PropTypes.number, React.PropTypes.string]) | |||
}; | |||
render () { | |||
const { value } = this.props; | |||
if (value == null) { | |||
return ( | |||
<div className="size-rating size-rating-muted"> </div> | |||
); | |||
} | |||
let letter; | |||
if (inRange(value, 1000)) { | |||
letter = 'XS'; | |||
} else if (inRange(value, 1000, 10000)) { | |||
letter = 'S'; | |||
} else if (inRange(value, 10000, 100000)) { | |||
letter = 'M'; | |||
} else if (inRange(value, 100000, 500000)) { | |||
letter = 'L'; | |||
} else if (value >= 500000) { | |||
letter = 'XL'; | |||
} | |||
return ( | |||
<div className="size-rating">{letter}</div> | |||
); | |||
} | |||
} |
@@ -115,5 +115,5 @@ export function getDeprecatedActiveRulesUrl (query = {}) { | |||
} | |||
export const getProjectsUrl = () => { | |||
return window.baseUrl + '/measures/search?qualifiers[]=TRK'; | |||
return window.baseUrl + '/projects'; | |||
}; |
@@ -28,6 +28,7 @@ import Navigation from './nav/app'; | |||
import { installGlobal, requestMessages } from '../helpers/l10n'; | |||
import * as measures from '../helpers/measures'; | |||
import * as request from '../helpers/request'; | |||
import './common-styles'; | |||
import '../../less/sonar.less'; | |||
@@ -0,0 +1,25 @@ | |||
/* | |||
* 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. | |||
*/ | |||
// this is a bad idea | |||
// TODO rework once we have single js app | |||
import '../components/ui/Level.css'; | |||
import '../components/ui/Rating.css'; |
@@ -71,6 +71,15 @@ export default React.createClass({ | |||
); | |||
}, | |||
renderProjects () { | |||
const url = window.baseUrl + '/projects'; | |||
return ( | |||
<li className={this.activeLink('/projects')}> | |||
<a href={url}>{translate('projects.page')}</a> | |||
</li> | |||
); | |||
}, | |||
renderIssuesLink () { | |||
const url = window.baseUrl + '/issues/search'; | |||
return ( | |||
@@ -159,6 +168,7 @@ export default React.createClass({ | |||
return ( | |||
<ul className="nav navbar-nav"> | |||
{this.renderDashboards()} | |||
{this.renderProjects()} | |||
{this.renderIssuesLink()} | |||
{this.renderMeasuresLink()} | |||
{this.renderRulesLink()} |
@@ -179,13 +179,15 @@ export default Marionette.LayoutView.extend({ | |||
const favorite = _.first(this.favorite, 6).map(function (f, index) { | |||
return _.extend(f, { extra: index === 0 ? translate('favorite') : null }); | |||
}); | |||
const qualifiers = this.model.get('qualifiers').map(function (q, index) { | |||
return { | |||
url: window.baseUrl + '/all_projects?qualifier=' + encodeURIComponent(q), | |||
name: translate('qualifiers.all', q), | |||
extra: index === 0 ? '' : null | |||
}; | |||
}); | |||
const qualifiers = this.model.get('qualifiers') | |||
.filter(q => q !== 'TRK') | |||
.map(function (q, index) { | |||
return { | |||
url: window.baseUrl + '/all_projects?qualifier=' + encodeURIComponent(q), | |||
name: translate('qualifiers.all', q), | |||
extra: index === 0 ? '' : null | |||
}; | |||
}); | |||
this.results.reset([].concat(history, favorite, qualifiers)); | |||
}, | |||
@@ -211,7 +213,7 @@ export default Marionette.LayoutView.extend({ | |||
q: domain.q, | |||
extra: index === 0 ? domain.name : null, | |||
url: window.baseUrl + '/dashboard/index?id=' + encodeURIComponent(item.key) + | |||
window.dashboardParameters(true) | |||
window.dashboardParameters(true) | |||
})); | |||
}); | |||
}); |
@@ -20,90 +20,6 @@ | |||
@import (reference) "../variables"; | |||
@import (reference) "../mixins"; | |||
/* | |||
* Display Maintainability Rating (also used by the SQALE plugin) | |||
*/ | |||
.rating { | |||
display: inline-block; | |||
width: 1em; | |||
height: 1.3em; | |||
line-height: 1.3; | |||
color: #fff; | |||
font-weight: 300; | |||
text-align: center; | |||
a > & { | |||
margin-bottom: -1px; | |||
border-bottom: 1px solid; | |||
transition: all 0.2s ease; | |||
&:hover { opacity: 0.8; } | |||
} | |||
} | |||
.rating-A { | |||
background-color: #00AA00; | |||
a & { border-bottom-color: #00AA00; } | |||
} | |||
.rating-B { | |||
background-color: #80CC00; | |||
a & { border-bottom-color: #80CC00; } | |||
} | |||
.rating-C { | |||
background-color: #FFEE00; | |||
color: @baseFontColor; | |||
a & { border-bottom-color: #FFEE00; } | |||
} | |||
.rating-D { | |||
background-color: #F77700; | |||
a & { border-bottom-color: #F77700; } | |||
} | |||
.rating-E { | |||
background-color: #EE0000; | |||
a & { border-bottom-color: #EE0000; } | |||
} | |||
.level { | |||
display: inline-block; | |||
height: 1.6em; | |||
line-height: 1.7; | |||
margin: -0.3em 0; | |||
padding: 0 0.5em; | |||
border-radius: 3px; | |||
color: #fff; | |||
letter-spacing: 0.02em; | |||
font-weight: 400; | |||
text-align: center; | |||
text-shadow: 0 0 1px rgba(0, 0, 0, 0.35); | |||
a > & { | |||
margin-bottom: -1px; | |||
border-bottom: 1px solid; | |||
transition: all 0.2s ease; | |||
&:hover { opacity: 0.8; } | |||
} | |||
} | |||
.level-OK { | |||
background-color: @green; | |||
} | |||
.level-WARN { | |||
background-color: @orange; | |||
} | |||
.level-ERROR { | |||
background-color: @red; | |||
} | |||
.processes-container { | |||
position: fixed; | |||
z-index: @process-container-z-index; |
@@ -0,0 +1,26 @@ | |||
# | |||
# SonarQube, open source software quality management tool. | |||
# Copyright (C) 2008-2014 SonarSource | |||
# mailto:contact AT sonarsource DOT com | |||
# | |||
# SonarQube 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. | |||
# | |||
# SonarQube 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. | |||
# | |||
class ProjectsController < ApplicationController | |||
def index | |||
end | |||
end |
@@ -0,0 +1,3 @@ | |||
<% content_for :extra_script do %> | |||
<script src="<%= ApplicationController.root_context -%>/js/bundles/app.js?v=<%= sonar_version -%>"></script> | |||
<% end %> |
@@ -876,13 +876,19 @@ issue_bulk_change.error.need_one_action=At least one action must be provided | |||
#------------------------------------------------------------------------------ | |||
# | |||
# ALL PROJECTS PAGE | |||
# PROJECTS PAGE | |||
# | |||
#------------------------------------------------------------------------------ | |||
all-projects.cols.name=Name | |||
all-projects.results_not_display_due_to_security=Due to security settings, some results are not being displayed. | |||
projects.page=Projects | |||
projects.page.description=Explore projects by coverage, duplications or size. | |||
projects._projects=projects | |||
projects.no_projects.1=We couldn't find any projects matching selected criteria. | |||
projects.no_projects.2=Try to change filters to get some results. | |||
#------------------------------------------------------------------------------ | |||
# |