Переглянути джерело

SONAR-8300 Create new "Projects" page [first iter]

tags/6.2-RC1
Stas Vilchik 7 роки тому
джерело
коміт
b712967932
77 змінених файлів з 2791 додано та 110 видалено
  1. 1
    0
      server/sonar-web/package.json
  2. 5
    0
      server/sonar-web/src/main/js/api/components.js
  3. 7
    0
      server/sonar-web/src/main/js/api/measures.js
  4. 3
    1
      server/sonar-web/src/main/js/app/components/App.js
  5. 28
    0
      server/sonar-web/src/main/js/app/components/GlobalMessagesContainer.js
  6. 3
    1
      server/sonar-web/src/main/js/app/index.js
  7. 25
    0
      server/sonar-web/src/main/js/app/store/components/actions.js
  8. 47
    0
      server/sonar-web/src/main/js/app/store/components/reducer.js
  9. 25
    0
      server/sonar-web/src/main/js/app/store/languages/actions.js
  10. 35
    0
      server/sonar-web/src/main/js/app/store/languages/reducer.js
  11. 15
    0
      server/sonar-web/src/main/js/app/store/measures/actions.js
  12. 23
    2
      server/sonar-web/src/main/js/app/store/measures/reducer.js
  13. 34
    0
      server/sonar-web/src/main/js/app/store/rootActions.js
  14. 44
    1
      server/sonar-web/src/main/js/app/store/rootReducer.js
  15. 0
    0
      server/sonar-web/src/main/js/app/styles/boxed-group.css
  16. 21
    0
      server/sonar-web/src/main/js/app/styles/index.js
  17. 26
    0
      server/sonar-web/src/main/js/app/styles/page.css
  18. 5
    0
      server/sonar-web/src/main/js/apps/component-measures/components/Measure.js
  19. 10
    0
      server/sonar-web/src/main/js/apps/component-measures/styles.css
  20. 1
    1
      server/sonar-web/src/main/js/apps/overview/main/enhance.js
  21. 2
    3
      server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGate.js
  22. 1
    6
      server/sonar-web/src/main/js/apps/overview/styles.css
  23. 82
    0
      server/sonar-web/src/main/js/apps/projects/components/App.js
  24. 27
    0
      server/sonar-web/src/main/js/apps/projects/components/AppContainer.js
  25. 52
    0
      server/sonar-web/src/main/js/apps/projects/components/PageHeader.js
  26. 26
    0
      server/sonar-web/src/main/js/apps/projects/components/PageHeaderContainer.js
  27. 37
    0
      server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js
  28. 47
    0
      server/sonar-web/src/main/js/apps/projects/components/ProjectCard.js
  29. 29
    0
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardContainer.js
  30. 55
    0
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.js
  31. 129
    0
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardMeasures.js
  32. 49
    0
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardQualityGate.js
  33. 75
    0
      server/sonar-web/src/main/js/apps/projects/components/ProjectsList.js
  34. 28
    0
      server/sonar-web/src/main/js/apps/projects/components/ProjectsListContainer.js
  35. 35
    0
      server/sonar-web/src/main/js/apps/projects/components/ProjectsListFooter.js
  36. 42
    0
      server/sonar-web/src/main/js/apps/projects/components/ProjectsListFooterContainer.js
  37. 126
    0
      server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.js
  38. 29
    0
      server/sonar-web/src/main/js/apps/projects/filters/CoverageFilterContainer.js
  39. 126
    0
      server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.js
  40. 29
    0
      server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilterContainer.js
  41. 70
    0
      server/sonar-web/src/main/js/apps/projects/filters/Filter.js
  42. 76
    0
      server/sonar-web/src/main/js/apps/projects/filters/QualityGateFilter.js
  43. 25
    0
      server/sonar-web/src/main/js/apps/projects/filters/QualityGateFilterContainer.js
  44. 127
    0
      server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.js
  45. 29
    0
      server/sonar-web/src/main/js/apps/projects/filters/SizeFilterContainer.js
  46. 44
    0
      server/sonar-web/src/main/js/apps/projects/filters/connectFilter.js
  47. 26
    0
      server/sonar-web/src/main/js/apps/projects/routes.js
  48. 119
    0
      server/sonar-web/src/main/js/apps/projects/store/actions.js
  49. 29
    0
      server/sonar-web/src/main/js/apps/projects/store/filters/reducer.js
  50. 25
    0
      server/sonar-web/src/main/js/apps/projects/store/filters/statuses/actions.js
  51. 44
    0
      server/sonar-web/src/main/js/apps/projects/store/filters/statuses/reducer.js
  52. 32
    0
      server/sonar-web/src/main/js/apps/projects/store/projects/actions.js
  53. 37
    0
      server/sonar-web/src/main/js/apps/projects/store/projects/reducer.js
  54. 37
    0
      server/sonar-web/src/main/js/apps/projects/store/reducer.js
  55. 25
    0
      server/sonar-web/src/main/js/apps/projects/store/state/actions.js
  56. 30
    0
      server/sonar-web/src/main/js/apps/projects/store/state/reducer.js
  57. 79
    0
      server/sonar-web/src/main/js/apps/projects/store/utils.js
  58. 147
    0
      server/sonar-web/src/main/js/apps/projects/styles.css
  59. 1
    1
      server/sonar-web/src/main/js/components/shared/drilldown-link.js
  60. 47
    0
      server/sonar-web/src/main/js/components/ui/CoverageRating.js
  61. 56
    0
      server/sonar-web/src/main/js/components/ui/DuplicationsRating.css
  62. 44
    0
      server/sonar-web/src/main/js/components/ui/DuplicationsRating.js
  63. 45
    0
      server/sonar-web/src/main/js/components/ui/Level.css
  64. 1
    0
      server/sonar-web/src/main/js/components/ui/Level.js
  65. 57
    0
      server/sonar-web/src/main/js/components/ui/Rating.css
  66. 1
    0
      server/sonar-web/src/main/js/components/ui/Rating.js
  67. 16
    0
      server/sonar-web/src/main/js/components/ui/SizeRating.css
  68. 55
    0
      server/sonar-web/src/main/js/components/ui/SizeRating.js
  69. 1
    1
      server/sonar-web/src/main/js/helpers/urls.js
  70. 1
    0
      server/sonar-web/src/main/js/main/app.js
  71. 25
    0
      server/sonar-web/src/main/js/main/common-styles.js
  72. 10
    0
      server/sonar-web/src/main/js/main/nav/global/global-nav-menu.js
  73. 10
    8
      server/sonar-web/src/main/js/main/nav/global/search-view.js
  74. 0
    84
      server/sonar-web/src/main/less/components/ui.less
  75. 26
    0
      server/sonar-web/src/main/webapp/WEB-INF/app/controllers/projects_controller.rb
  76. 3
    0
      server/sonar-web/src/main/webapp/WEB-INF/app/views/projects/index.html.erb
  77. 7
    1
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 1
- 0
server/sonar-web/package.json Переглянути файл

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

+ 5
- 0
server/sonar-web/src/main/js/api/components.js Переглянути файл

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

+ 7
- 0
server/sonar-web/src/main/js/api/measures.js Переглянути файл

@@ -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()
})
);

+ 3
- 1
server/sonar-web/src/main/js/app/components/App.js Переглянути файл

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

+ 28
- 0
server/sonar-web/src/main/js/app/components/GlobalMessagesContainer.js Переглянути файл

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

+ 3
- 1
server/sonar-web/src/main/js/app/index.js Переглянути файл

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

+ 25
- 0
server/sonar-web/src/main/js/app/store/components/actions.js Переглянути файл

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

+ 47
- 0
server/sonar-web/src/main/js/app/store/components/reducer.js Переглянути файл

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

+ 25
- 0
server/sonar-web/src/main/js/app/store/languages/actions.js Переглянути файл

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

+ 35
- 0
server/sonar-web/src/main/js/app/store/languages/reducer.js Переглянути файл

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

+ 15
- 0
server/sonar-web/src/main/js/app/store/measures/actions.js Переглянути файл

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

+ 23
- 2
server/sonar-web/src/main/js/app/store/measures/reducer.js Переглянути файл

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

+ 34
- 0
server/sonar-web/src/main/js/app/store/rootActions.js Переглянути файл

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

+ 44
- 1
server/sonar-web/src/main/js/app/store/rootReducer.js Переглянути файл

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

server/sonar-web/src/main/js/app/styles.css → server/sonar-web/src/main/js/app/styles/boxed-group.css Переглянути файл


+ 21
- 0
server/sonar-web/src/main/js/app/styles/index.js Переглянути файл

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

+ 26
- 0
server/sonar-web/src/main/js/app/styles/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;
}

+ 5
- 0
server/sonar-web/src/main/js/apps/component-measures/components/Measure.js Переглянути файл

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

+ 10
- 0
server/sonar-web/src/main/js/apps/component-measures/styles.css Переглянути файл

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

+ 1
- 1
server/sonar-web/src/main/js/apps/overview/main/enhance.js Переглянути файл

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

+ 2
- 3
server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGate.js Переглянути файл

@@ -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 && (

+ 1
- 6
server/sonar-web/src/main/js/apps/overview/styles.css Переглянути файл

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

+ 82
- 0
server/sonar-web/src/main/js/apps/projects/components/App.js Переглянути файл

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

+ 27
- 0
server/sonar-web/src/main/js/apps/projects/components/AppContainer.js Переглянути файл

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

+ 52
- 0
server/sonar-web/src/main/js/apps/projects/components/PageHeader.js Переглянути файл

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

+ 26
- 0
server/sonar-web/src/main/js/apps/projects/components/PageHeaderContainer.js Переглянути файл

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

+ 37
- 0
server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js Переглянути файл

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

+ 47
- 0
server/sonar-web/src/main/js/apps/projects/components/ProjectCard.js Переглянути файл

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

+ 29
- 0
server/sonar-web/src/main/js/apps/projects/components/ProjectCardContainer.js Переглянути файл

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

+ 55
- 0
server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.js Переглянути файл

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

+ 129
- 0
server/sonar-web/src/main/js/apps/projects/components/ProjectCardMeasures.js Переглянути файл

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

+ 49
- 0
server/sonar-web/src/main/js/apps/projects/components/ProjectCardQualityGate.js Переглянути файл

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

+ 75
- 0
server/sonar-web/src/main/js/apps/projects/components/ProjectsList.js Переглянути файл

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

+ 28
- 0
server/sonar-web/src/main/js/apps/projects/components/ProjectsListContainer.js Переглянути файл

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

+ 35
- 0
server/sonar-web/src/main/js/apps/projects/components/ProjectsListFooter.js Переглянути файл

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

+ 42
- 0
server/sonar-web/src/main/js/apps/projects/components/ProjectsListFooterContainer.js Переглянути файл

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

+ 126
- 0
server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.js Переглянути файл

@@ -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}/>
);
}
}

+ 29
- 0
server/sonar-web/src/main/js/apps/projects/filters/CoverageFilterContainer.js Переглянути файл

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

+ 126
- 0
server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.js Переглянути файл

@@ -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}/>
);
}
}

+ 29
- 0
server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilterContainer.js Переглянути файл

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

+ 70
- 0
server/sonar-web/src/main/js/apps/projects/filters/Filter.js Переглянути файл

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

+ 76
- 0
server/sonar-web/src/main/js/apps/projects/filters/QualityGateFilter.js Переглянути файл

@@ -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}/>
);
}
}

+ 25
- 0
server/sonar-web/src/main/js/apps/projects/filters/QualityGateFilterContainer.js Переглянути файл

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

+ 127
- 0
server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.js Переглянути файл

@@ -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}/>
);
}
}

+ 29
- 0
server/sonar-web/src/main/js/apps/projects/filters/SizeFilterContainer.js Переглянути файл

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

+ 44
- 0
server/sonar-web/src/main/js/apps/projects/filters/connectFilter.js Переглянути файл

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

+ 26
- 0
server/sonar-web/src/main/js/apps/projects/routes.js Переглянути файл

@@ -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}/>
);

+ 119
- 0
server/sonar-web/src/main/js/apps/projects/store/actions.js Переглянути файл

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

+ 29
- 0
server/sonar-web/src/main/js/apps/projects/store/filters/reducer.js Переглянути файл

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

+ 25
- 0
server/sonar-web/src/main/js/apps/projects/store/filters/statuses/actions.js Переглянути файл

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

+ 44
- 0
server/sonar-web/src/main/js/apps/projects/store/filters/statuses/reducer.js Переглянути файл

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

+ 32
- 0
server/sonar-web/src/main/js/apps/projects/store/projects/actions.js Переглянути файл

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

+ 37
- 0
server/sonar-web/src/main/js/apps/projects/store/projects/reducer.js Переглянути файл

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

+ 37
- 0
server/sonar-web/src/main/js/apps/projects/store/reducer.js Переглянути файл

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

+ 25
- 0
server/sonar-web/src/main/js/apps/projects/store/state/actions.js Переглянути файл

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

+ 30
- 0
server/sonar-web/src/main/js/apps/projects/store/state/reducer.js Переглянути файл

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

+ 79
- 0
server/sonar-web/src/main/js/apps/projects/store/utils.js Переглянути файл

@@ -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 ');
};

+ 147
- 0
server/sonar-web/src/main/js/apps/projects/styles.css Переглянути файл

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

+ 1
- 1
server/sonar-web/src/main/js/components/shared/drilldown-link.js Переглянути файл

@@ -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>
);
},


+ 47
- 0
server/sonar-web/src/main/js/components/ui/CoverageRating.js Переглянути файл

@@ -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}/>
);
}
}

+ 56
- 0
server/sonar-web/src/main/js/components/ui/DuplicationsRating.css Переглянути файл

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

+ 44
- 0
server/sonar-web/src/main/js/components/ui/DuplicationsRating.js Переглянути файл

@@ -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}/>
);
}
}

+ 45
- 0
server/sonar-web/src/main/js/components/ui/Level.css Переглянути файл

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

+ 1
- 0
server/sonar-web/src/main/js/components/ui/Level.js Переглянути файл

@@ -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 = {

+ 57
- 0
server/sonar-web/src/main/js/components/ui/Rating.css Переглянути файл

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

+ 1
- 0
server/sonar-web/src/main/js/components/ui/Rating.js Переглянути файл

@@ -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 = {

+ 16
- 0
server/sonar-web/src/main/js/components/ui/SizeRating.css Переглянути файл

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

+ 55
- 0
server/sonar-web/src/main/js/components/ui/SizeRating.js Переглянути файл

@@ -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">&nbsp;</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>
);
}
}

+ 1
- 1
server/sonar-web/src/main/js/helpers/urls.js Переглянути файл

@@ -115,5 +115,5 @@ export function getDeprecatedActiveRulesUrl (query = {}) {
}

export const getProjectsUrl = () => {
return window.baseUrl + '/measures/search?qualifiers[]=TRK';
return window.baseUrl + '/projects';
};

+ 1
- 0
server/sonar-web/src/main/js/main/app.js Переглянути файл

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


+ 25
- 0
server/sonar-web/src/main/js/main/common-styles.js Переглянути файл

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

+ 10
- 0
server/sonar-web/src/main/js/main/nav/global/global-nav-menu.js Переглянути файл

@@ -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()}

+ 10
- 8
server/sonar-web/src/main/js/main/nav/global/search-view.js Переглянути файл

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

+ 0
- 84
server/sonar-web/src/main/less/components/ui.less Переглянути файл

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

+ 26
- 0
server/sonar-web/src/main/webapp/WEB-INF/app/controllers/projects_controller.rb Переглянути файл

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

+ 3
- 0
server/sonar-web/src/main/webapp/WEB-INF/app/views/projects/index.html.erb Переглянути файл

@@ -0,0 +1,3 @@
<% content_for :extra_script do %>
<script src="<%= ApplicationController.root_context -%>/js/bundles/app.js?v=<%= sonar_version -%>"></script>
<% end %>

+ 7
- 1
sonar-core/src/main/resources/org/sonar/l10n/core.properties Переглянути файл

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


#------------------------------------------------------------------------------
#

Завантаження…
Відмінити
Зберегти