Procházet zdrojové kódy

SONAR-9531 Change the application space

tags/6.6-RC1
Stas Vilchik před 6 roky
rodič
revize
30cc3b9580
42 změnil soubory, kde provedl 914 přidání a 115 odebrání
  1. 31
    0
      server/sonar-web/src/main/js/api/application.js
  2. 4
    0
      server/sonar-web/src/main/js/api/quality-gates.js
  3. 1
    1
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.js
  4. 10
    4
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.js
  5. 4
    1
      server/sonar-web/src/main/js/app/components/search/SearchResult.js
  6. 1
    1
      server/sonar-web/src/main/js/app/components/search/utils.js
  7. 6
    8
      server/sonar-web/src/main/js/apps/code/components/App.js
  8. 5
    3
      server/sonar-web/src/main/js/apps/code/components/Component.js
  9. 5
    3
      server/sonar-web/src/main/js/apps/code/components/ComponentsHeader.js
  10. 2
    2
      server/sonar-web/src/main/js/apps/code/components/Search.js
  11. 15
    15
      server/sonar-web/src/main/js/apps/code/utils.js
  12. 10
    2
      server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.js
  13. 1
    1
      server/sonar-web/src/main/js/apps/component-measures/details/MeasureDetailsHeader.js
  14. 1
    1
      server/sonar-web/src/main/js/apps/component-measures/home/Home.js
  15. 20
    7
      server/sonar-web/src/main/js/apps/component-measures/home/actions.js
  16. 1
    1
      server/sonar-web/src/main/js/apps/issues/sidebar/ProjectFacet.js
  17. 16
    9
      server/sonar-web/src/main/js/apps/overview/components/App.js
  18. 93
    0
      server/sonar-web/src/main/js/apps/overview/components/ApplicationLeakPeriodLegend.js
  19. 9
    2
      server/sonar-web/src/main/js/apps/overview/components/OverviewApp.js
  20. 3
    9
      server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.js
  21. 35
    0
      server/sonar-web/src/main/js/apps/overview/components/__tests__/ApplicationLeakPeriodLegend-test.js
  22. 54
    0
      server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/ApplicationLeakPeriodLegend-test.js.snap
  23. 5
    2
      server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.js
  24. 5
    0
      server/sonar-web/src/main/js/apps/overview/main/enhance.js
  25. 6
    6
      server/sonar-web/src/main/js/apps/overview/meta/Meta.js
  26. 53
    18
      server/sonar-web/src/main/js/apps/overview/meta/MetaSize.js
  27. 108
    0
      server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGate.js
  28. 15
    0
      server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGateProject.css
  29. 103
    0
      server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGateProject.js
  30. 39
    0
      server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/ApplicationQualityGate-test.js
  31. 73
    0
      server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/ApplicationQualityGateProject-test.js
  32. 62
    0
      server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGate-test.js.snap
  33. 84
    0
      server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGateProject-test.js.snap
  34. 5
    0
      server/sonar-web/src/main/js/apps/overview/styles.css
  35. 1
    0
      server/sonar-web/src/main/js/apps/overview/utils.js
  36. 2
    1
      server/sonar-web/src/main/js/apps/permission-templates/components/AppContainer.js
  37. 16
    14
      server/sonar-web/src/main/js/apps/permissions/project/components/App.js
  38. 1
    1
      server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.js
  39. 1
    0
      server/sonar-web/src/main/js/apps/permissions/project/constants.js
  40. 5
    2
      server/sonar-web/src/main/js/apps/project-admin/deletion/Header.js
  41. 1
    1
      server/sonar-web/src/main/js/apps/projects-admin/constants.js
  42. 2
    0
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 31
- 0
server/sonar-web/src/main/js/api/application.js Zobrazit soubor

@@ -0,0 +1,31 @@
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import { getJSON } from '../helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';

type GetApplicationLeakResponse = Array<{
date: string,
project: string,
projectName: string
}>;

export const getApplicationLeak = (application: string): Promise<GetApplicationLeakResponse> =>
getJSON('/api/views/show_leak', { application }).then(r => r.leaks, throwGlobalError);

+ 4
- 0
server/sonar-web/src/main/js/api/quality-gates.js Zobrazit soubor

@@ -105,3 +105,7 @@ export function dissociateGateWithProject(gateId, projectKey) {
const data = { gateId, projectKey };
return post(url, data);
}

export function getApplicationQualityGate(application) {
return getJSON('/api/qualitygates/application_status', { application });
}

+ 1
- 1
server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.js Zobrazit soubor

@@ -56,7 +56,7 @@ export default class ComponentNav extends React.PureComponent {
populateRecentHistory = () => {
const { breadcrumbs } = this.props.component;
const { qualifier } = breadcrumbs[breadcrumbs.length - 1];
if (['TRK', 'VW', 'DEV'].indexOf(qualifier) !== -1) {
if (['TRK', 'VW', 'APP', 'DEV'].indexOf(qualifier) !== -1) {
RecentHistory.add(
this.props.component.key,
this.props.component.name,

+ 10
- 4
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.js Zobrazit soubor

@@ -57,6 +57,10 @@ export default class ComponentNavMenu extends React.PureComponent {
return qualifier === 'VW' || qualifier === 'SVW';
}

isApplication() {
return this.props.component.qualifier === 'APP';
}

renderDashboardLink() {
const pathname = this.isView() ? '/portfolio' : '/dashboard';
return (
@@ -78,14 +82,16 @@ export default class ComponentNavMenu extends React.PureComponent {
<Link
to={{ pathname: '/code', query: { id: this.props.component.key } }}
activeClassName="active">
{this.isView() ? translate('view_projects.page') : translate('code.page')}
{this.isView() || this.isApplication()
? translate('view_projects.page')
: translate('code.page')}
</Link>
</li>
);
}

renderActivityLink() {
if (!this.isProject()) {
if (!this.isProject() && !this.isApplication()) {
return null;
}

@@ -167,7 +173,7 @@ export default class ComponentNavMenu extends React.PureComponent {
}

renderSettingsLink() {
if (!this.props.conf.showSettings) {
if (!this.props.conf.showSettings || this.isApplication()) {
return null;
}
return (
@@ -293,7 +299,7 @@ export default class ComponentNavMenu extends React.PureComponent {
return null;
}

if (qualifier !== 'TRK' && qualifier !== 'VW') {
if (qualifier !== 'TRK' && qualifier !== 'VW' && qualifier !== 'APP') {
return null;
}


+ 4
- 1
server/sonar-web/src/main/js/app/components/search/SearchResult.js Zobrazit soubor

@@ -87,7 +87,10 @@ export default class SearchResult extends React.PureComponent {
return null;
}

if (!['VW', 'SVW', 'TRK'].includes(component.qualifier) || component.organization == null) {
if (
!['VW', 'SVW', 'APP', 'TRK'].includes(component.qualifier) ||
component.organization == null
) {
return null;
}


+ 1
- 1
server/sonar-web/src/main/js/app/components/search/utils.js Zobrazit soubor

@@ -20,7 +20,7 @@
// @flow
import { sortBy } from 'lodash';

const ORDER = ['DEV', 'VW', 'SVW', 'TRK', 'BRC', 'FIL', 'UTS'];
const ORDER = ['DEV', 'VW', 'SVW', 'APP', 'TRK', 'BRC', 'FIL', 'UTS'];

export function sortQualifiers(qualifiers: Array<string>) {
return sortBy(qualifiers, qualifier => ORDER.indexOf(qualifier));

+ 6
- 8
server/sonar-web/src/main/js/apps/code/components/App.js Zobrazit soubor

@@ -74,8 +74,8 @@ class App extends React.PureComponent {
addComponentBreadcrumbs(component.key, component.breadcrumbs);

this.setState({ loading: true });
const isView = component.qualifier === 'VW' || component.qualifier === 'SVW';
retrieveComponentChildren(component.key, isView)
const isPortfolio = ['VW', 'SVW'].includes(component.qualifier);
retrieveComponentChildren(component.key, isPortfolio)
.then(r => {
addComponent(r.baseComponent);
this.handleUpdate();
@@ -91,9 +91,8 @@ class App extends React.PureComponent {
loadComponent(componentKey) {
this.setState({ loading: true });

const isView =
this.props.component.qualifier === 'VW' || this.props.component.qualifier === 'SVW';
retrieveComponent(componentKey, isView)
const isPortfolio = ['VW', 'SVW'].includes(this.props.component.qualifier);
retrieveComponent(componentKey, isPortfolio)
.then(r => {
if (this.mounted) {
if (['FIL', 'UTS'].includes(r.component.qualifier)) {
@@ -135,9 +134,8 @@ class App extends React.PureComponent {

handleLoadMore() {
const { baseComponent, page } = this.state;
const isView =
this.props.component.qualifier === 'VW' || this.props.component.qualifier === 'SVW';
loadMoreChildren(baseComponent.key, page + 1, isView)
const isPortfolio = ['VW', 'SVW'].includes(this.props.component.qualifier);
loadMoreChildren(baseComponent.key, page + 1, isPortfolio)
.then(r => {
if (this.mounted) {
this.setState({

+ 5
- 3
server/sonar-web/src/main/js/apps/code/components/Component.js Zobrazit soubor

@@ -61,7 +61,8 @@ export default class Component extends React.PureComponent {

render() {
const { component, rootComponent, selected, previous, canBrowse } = this.props;
const isView = ['VW', 'SVW'].includes(rootComponent.qualifier);
const isPortfolio = ['VW', 'SVW'].includes(rootComponent.qualifier);
const isApplication = rootComponent.qualifier === 'APP';

let componentAction = null;

@@ -76,7 +77,7 @@ export default class Component extends React.PureComponent {
}
}

const columns = isView
const columns = isPortfolio
? [
{ metric: 'releasability_rating', type: 'RATING' },
{ metric: 'reliability_rating', type: 'RATING' },
@@ -85,13 +86,14 @@ export default class Component extends React.PureComponent {
{ metric: 'ncloc', type: 'SHORT_INT' }
]
: [
isApplication && { metric: 'alert_status', type: 'LEVEL' },
{ metric: 'ncloc', type: 'SHORT_INT' },
{ metric: 'bugs', type: 'SHORT_INT' },
{ metric: 'vulnerabilities', type: 'SHORT_INT' },
{ metric: 'code_smells', type: 'SHORT_INT' },
{ metric: 'coverage', type: 'PERCENT' },
{ metric: 'duplicated_lines_density', type: 'PERCENT' }
];
].filter(Boolean);

return (
<tr className={classNames({ selected })}>

+ 5
- 3
server/sonar-web/src/main/js/apps/code/components/ComponentsHeader.js Zobrazit soubor

@@ -21,9 +21,10 @@ import React from 'react';
import { translate } from '../../../helpers/l10n';

const ComponentsHeader = ({ baseComponent, rootComponent }) => {
const isView = rootComponent.qualifier === 'VW' || rootComponent.qualifier === 'SVW';
const isPortfolio = rootComponent.qualifier === 'VW' || rootComponent.qualifier === 'SVW';
const isApplication = rootComponent.qualifier === 'APP';

const columns = isView
const columns = isPortfolio
? [
translate('metric_domain.Releasability'),
translate('metric_domain.Reliability'),
@@ -32,13 +33,14 @@ const ComponentsHeader = ({ baseComponent, rootComponent }) => {
translate('metric', 'ncloc', 'name')
]
: [
isApplication && translate('metric.alert_status.name'),
translate('metric', 'ncloc', 'name'),
translate('metric', 'bugs', 'name'),
translate('metric', 'vulnerabilities', 'name'),
translate('metric', 'code_smells', 'name'),
translate('metric', 'coverage', 'name'),
translate('metric', 'duplicated_lines_density', 'short_name')
];
].filter(Boolean);

return (
<thead>

+ 2
- 2
server/sonar-web/src/main/js/apps/code/components/Search.js Zobrazit soubor

@@ -132,8 +132,8 @@ export default class Search extends React.PureComponent {
const { component, onError } = this.props;
this.setState({ loading: true });

const isView = component.qualifier === 'VW' || component.qualifier === 'SVW';
const qualifiers = isView ? 'SVW,TRK' : 'BRC,UTS,FIL';
const isPortfolio = ['VW', 'SVW', 'APP'].includes(component.qualifier);
const qualifiers = isPortfolio ? 'SVW,TRK' : 'BRC,UTS,FIL';

getTree(component.key, { q: query, s: 'qualifier,name', qualifiers })
.then(r => {

+ 15
- 15
server/sonar-web/src/main/js/apps/code/utils.js Zobrazit soubor

@@ -39,7 +39,7 @@ const METRICS = [
'alert_status'
];

const VIEW_METRICS = [
const PORTFOLIO_METRICS = [
'releasability_rating',
'alert_status',
'reliability_rating',
@@ -111,22 +111,22 @@ function storeChildrenBreadcrumbs(parentComponentKey, children) {
}
}

function getMetrics(isView) {
return isView ? VIEW_METRICS : METRICS;
function getMetrics(isPortfolio) {
return isPortfolio ? PORTFOLIO_METRICS : METRICS;
}

/**
* @param {string} componentKey
* @param {boolean} isView
* @param {boolean} isPortfolio
* @returns {Promise}
*/
function retrieveComponentBase(componentKey, isView) {
function retrieveComponentBase(componentKey, isPortfolio) {
const existing = getComponentFromBucket(componentKey);
if (existing) {
return Promise.resolve(existing);
}

const metrics = getMetrics(isView);
const metrics = getMetrics(isPortfolio);

return getComponent(componentKey, metrics).then(component => {
addComponent(component);
@@ -136,10 +136,10 @@ function retrieveComponentBase(componentKey, isView) {

/**
* @param {string} componentKey
* @param {boolean} isView
* @param {boolean} isPortfolio
* @returns {Promise}
*/
export function retrieveComponentChildren(componentKey, isView) {
export function retrieveComponentChildren(componentKey, isPortfolio) {
const existing = getComponentChildren(componentKey);
if (existing) {
return Promise.resolve({
@@ -149,7 +149,7 @@ export function retrieveComponentChildren(componentKey, isView) {
});
}

const metrics = getMetrics(isView);
const metrics = getMetrics(isPortfolio);

return getChildren(componentKey, metrics, { ps: PAGE_SIZE, s: 'qualifier,name' })
.then(prepareChildren)
@@ -176,13 +176,13 @@ function retrieveComponentBreadcrumbs(componentKey) {

/**
* @param {string} componentKey
* @param {boolean} isView
* @param {boolean} isPortfolio
* @returns {Promise}
*/
export function retrieveComponent(componentKey, isView) {
export function retrieveComponent(componentKey, isPortfolio) {
return Promise.all([
retrieveComponentBase(componentKey, isView),
retrieveComponentChildren(componentKey, isView),
retrieveComponentBase(componentKey, isPortfolio),
retrieveComponentChildren(componentKey, isPortfolio),
retrieveComponentBreadcrumbs(componentKey)
]).then(r => {
return {
@@ -195,8 +195,8 @@ export function retrieveComponent(componentKey, isView) {
});
}

export function loadMoreChildren(componentKey, page, isView) {
const metrics = getMetrics(isView);
export function loadMoreChildren(componentKey, page, isPortfolio) {
const metrics = getMetrics(isPortfolio);

return getChildren(componentKey, metrics, { ps: PAGE_SIZE, p: page })
.then(prepareChildren)

+ 10
- 2
server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.js Zobrazit soubor

@@ -21,9 +21,17 @@ import React from 'react';
import moment from 'moment';
import Tooltip from '../../../components/controls/Tooltip';
import { getPeriodLabel, getPeriodDate } from '../../../helpers/periods';
import { translateWithParameters } from '../../../helpers/l10n';
import { translate, translateWithParameters } from '../../../helpers/l10n';

export default function LeakPeriodLegend({ component, period }) {
if (component.qualifier === 'APP') {
return (
<div className="measures-domains-leak-header">
{translate('issues.leak_period')}
</div>
);
}

export default function LeakPeriodLegend({ period }) {
const label = (
<div className="measures-domains-leak-header">
{translateWithParameters('overview.leak_period_x', getPeriodLabel(period))}

+ 1
- 1
server/sonar-web/src/main/js/apps/component-measures/details/MeasureDetailsHeader.js Zobrazit soubor

@@ -56,7 +56,7 @@ export default function MeasureDetailsHeader({

{isDiff &&
<div className="pull-right">
<LeakPeriodLegend period={leakPeriod} />
<LeakPeriodLegend component={component} period={leakPeriod} />
</div>}

<TooltipsContainer options={{ html: false }}>

+ 1
- 1
server/sonar-web/src/main/js/apps/component-measures/home/Home.js Zobrazit soubor

@@ -70,7 +70,7 @@ export default class Home extends React.PureComponent {
</ul>
</nav>

{leakPeriod != null && <LeakPeriodLegend period={leakPeriod} />}
{leakPeriod != null && <LeakPeriodLegend component={component} period={leakPeriod} />}
</header>

<main id="component-measures-home-main">

+ 20
- 7
server/sonar-web/src/main/js/apps/component-measures/home/actions.js Zobrazit soubor

@@ -19,7 +19,6 @@
*/
import { startFetching, stopFetching } from '../store/statusActions';
import { getMeasuresAndMeta } from '../../../api/measures';
import { getLeakPeriod } from '../../../helpers/periods';
import { getLeakValue } from '../utils';
import { getMeasuresAppComponent, getMeasuresAppAllMetrics } from '../../../store/rootReducer';

@@ -30,10 +29,20 @@ export function receiveMeasures(measures, periods) {
}

function banQualityGate(component, measures) {
if (['VW', 'SVW'].includes(component.qualifier)) {
return measures;
let newMeasures = [...measures];

if (!['VW', 'SVW', 'APP'].includes(component.qualifier)) {
newMeasures = newMeasures.filter(measure => measure.metric !== 'alert_status');
}

if (component.qualifier === 'APP') {
newMeasures = newMeasures.filter(
measure =>
measure.metric !== 'releasability_rating' && measure.metric !== 'releasability_effort'
);
}
return measures.filter(measure => measure.metric !== 'alert_status');

return newMeasures;
}

export function fetchMeasures() {
@@ -50,7 +59,6 @@ export function fetchMeasures() {
.map(metric => metric.key);

getMeasuresAndMeta(component.key, metricKeys, { additionalFields: 'periods' }).then(r => {
const leakPeriod = getLeakPeriod(r.periods);
const measures = banQualityGate(component, r.component.measures)
.map(measure => {
const metric = metrics.find(metric => metric.key === measure.metric);
@@ -59,11 +67,16 @@ export function fetchMeasures() {
})
.filter(measure => {
const hasValue = measure.value != null;
const hasLeakValue = !!leakPeriod && measure.leak != null;
const hasLeakValue = measure.leak != null;
return hasValue || hasLeakValue;
});

dispatch(receiveMeasures(measures, r.periods));
const newBugs = measures.find(measure => measure.metric.key === 'new_bugs');

const applicationPeriods = newBugs ? [{ index: 1 }] : [];
const periods = component.qualifier === 'APP' ? applicationPeriods : r.periods;

dispatch(receiveMeasures(measures, periods));
dispatch(stopFetching());
});
};

+ 1
- 1
server/sonar-web/src/main/js/apps/issues/sidebar/ProjectFacet.js Zobrazit soubor

@@ -70,7 +70,7 @@ export default class ProjectFacet extends React.PureComponent {

handleSearch = (query: string) => {
const { component, organization } = this.props;
if (component != null && ['VW', 'SVW'].includes(component.qualifier)) {
if (component != null && ['VW', 'SVW', 'APP'].includes(component.qualifier)) {
return getTree(component.key, { ps: 50, q: query, qualifiers: 'TRK' }).then(response =>
response.components.map(component => ({
label: component.name,

+ 16
- 9
server/sonar-web/src/main/js/apps/overview/components/App.js Zobrazit soubor

@@ -19,7 +19,6 @@
*/
// @flow
import React from 'react';
import { withRouter } from 'react-router';
import OverviewApp from './OverviewApp';
import EmptyOverview from './EmptyOverview';
import SourceViewer from '../../../components/SourceViewer/SourceViewer';
@@ -35,20 +34,32 @@ type Props = {
router: Object
};

class App extends React.PureComponent {
export default class App extends React.PureComponent {
props: Props;
state: Object;

static contextTypes = {
router: React.PropTypes.object
};

componentDidMount() {
if (['VW', 'SVW'].includes(this.props.component.qualifier)) {
this.props.router.replace({
if (this.isPortfolio()) {
this.context.router.replace({
pathname: '/portfolio',
query: { id: this.props.component.key }
});
}
}

isPortfolio() {
return this.props.component.qualifier === 'VW' || this.props.component.qualifier === 'SVW';
}

render() {
if (this.isPortfolio()) {
return null;
}

const { component } = this.props;

if (['FIL', 'UTS'].includes(component.qualifier)) {
@@ -63,10 +74,6 @@ class App extends React.PureComponent {
return <EmptyOverview component={component} />;
}

return <OverviewApp {...this.props} leakPeriodIndex="1" />;
return <OverviewApp component={component} />;
}
}

export default withRouter(App);

export const UnconnectedApp = App;

+ 93
- 0
server/sonar-web/src/main/js/apps/overview/components/ApplicationLeakPeriodLegend.js Zobrazit soubor

@@ -0,0 +1,93 @@
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import Tooltip from '../../../components/controls/Tooltip';
import FormattedDate from '../../../components/ui/FormattedDate';
import { getApplicationLeak } from '../../../api/application';
import { translate } from '../../../helpers/l10n';

type Props = {
component: { key: string }
};

type State = {
leaks: ?Array<{ date: string, project: string, projectName: string }>
};

export default class ApplicationLeakPeriodLegend extends React.Component {
mounted: boolean;
props: Props;
state: State = {
leaks: null
};

componentDidMount() {
this.mounted = true;
}

componentWillReceiveProps(nextProps: Props) {
if (nextProps.component.key !== this.props.component.key) {
this.setState({ leaks: null });
}
}

componentWillUnmount() {
this.mounted = false;
}

fetchLeaks = (visible: boolean) => {
if (visible && this.state.leaks == null) {
getApplicationLeak(this.props.component.key).then(
leaks => {
if (this.mounted) {
this.setState({ leaks });
}
},
() => {
if (this.mounted) {
this.setState({ leaks: [] });
}
}
);
}
};

renderOverlay = () =>
this.state.leaks != null
? <ul className="text-left">
{this.state.leaks.map(leak =>
<li key={leak.project}>
{leak.projectName}: <FormattedDate date={leak.date} format="LL" />
</li>
)}
</ul>
: <i className="spinner spinner-margin" />;

render() {
return (
<Tooltip onVisibleChange={this.fetchLeaks} overlay={this.renderOverlay()}>
<div className="overview-legend overview-legend-spaced-line">
{translate('issues.leak_period')}
</div>
</Tooltip>
);
}
}

+ 9
- 2
server/sonar-web/src/main/js/apps/overview/components/OverviewApp.js Zobrazit soubor

@@ -22,6 +22,7 @@ import React from 'react';
import { uniq } from 'lodash';
import moment from 'moment';
import QualityGate from '../qualityGate/QualityGate';
import ApplicationQualityGate from '../qualityGate/ApplicationQualityGate';
import BugsAndVulnerabilities from '../main/BugsAndVulnerabilities';
import CodeSmells from '../main/CodeSmells';
import Coverage from '../main/Coverage';
@@ -122,6 +123,9 @@ export default class OverviewApp extends React.PureComponent {
}, throwGlobalError);
}

getApplicationLeakPeriod = () =>
this.state.measures.find(measure => measure.metric.key === 'new_bugs') ? { index: 1 } : null;

renderLoading() {
return (
<div className="text-center">
@@ -138,14 +142,17 @@ export default class OverviewApp extends React.PureComponent {
return this.renderLoading();
}

const leakPeriod = getLeakPeriod(periods);
const leakPeriod =
component.qualifier === 'APP' ? this.getApplicationLeakPeriod() : getLeakPeriod(periods);
const domainProps = { component, measures, leakPeriod, history, historyStartDate };

return (
<div className="page page-limited">
<div className="overview page-with-sidebar">
<div className="overview-main page-main">
<QualityGate component={component} measures={measures} />
{component.qualifier === 'APP'
? <ApplicationQualityGate component={component} />
: <QualityGate component={component} measures={measures} />}

<div className="overview-domains-list">
<BugsAndVulnerabilities {...domainProps} />

+ 3
- 9
server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.js Zobrazit soubor

@@ -19,24 +19,18 @@
*/
import React from 'react';
import { shallow } from 'enzyme';
import { UnconnectedApp } from '../App';
import App from '../App';
import OverviewApp from '../OverviewApp';
import EmptyOverview from '../EmptyOverview';

it('should render OverviewApp', () => {
const component = { id: 'id', analysisDate: '2016-01-01' };
const output = shallow(<UnconnectedApp component={component} />);
const output = shallow(<App component={component} />);
expect(output.type()).toBe(OverviewApp);
});

it('should render EmptyOverview', () => {
const component = { id: 'id' };
const output = shallow(<UnconnectedApp component={component} />);
const output = shallow(<App component={component} />);
expect(output.type()).toBe(EmptyOverview);
});

it('should pass leakPeriodIndex', () => {
const component = { id: 'id', analysisDate: '2016-01-01' };
const output = shallow(<UnconnectedApp component={component} />);
expect(output.prop('leakPeriodIndex')).toBe('1');
});

+ 35
- 0
server/sonar-web/src/main/js/apps/overview/components/__tests__/ApplicationLeakPeriodLegend-test.js Zobrazit soubor

@@ -0,0 +1,35 @@
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import { shallow } from 'enzyme';
import ApplicationLeakPeriodLegend from '../ApplicationLeakPeriodLegend';

it('renders', () => {
const wrapper = shallow(<ApplicationLeakPeriodLegend component={{ key: 'foo' }} />);
expect(wrapper).toMatchSnapshot();
wrapper.setState({
leaks: [
{ date: '2017-01-01T11:39:03+0100', project: 'foo', projectName: 'Foo' },
{ date: '2017-02-01T11:39:03+0100', project: 'bar', projectName: 'Bar' }
]
});
expect(wrapper).toMatchSnapshot();
});

+ 54
- 0
server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/ApplicationLeakPeriodLegend-test.js.snap Zobrazit soubor

@@ -0,0 +1,54 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders 1`] = `
<Tooltip
onVisibleChange={[Function]}
overlay={
<i
className="spinner spinner-margin"
/>
}
placement="bottom"
>
<div
className="overview-legend overview-legend-spaced-line"
>
issues.leak_period
</div>
</Tooltip>
`;

exports[`renders 2`] = `
<Tooltip
onVisibleChange={[Function]}
overlay={
<ul
className="text-left"
>
<li>
Foo
:
<FormattedDate
date="2017-01-01T11:39:03+0100"
format="LL"
/>
</li>
<li>
Bar
:
<FormattedDate
date="2017-02-01T11:39:03+0100"
format="LL"
/>
</li>
</ul>
}
placement="bottom"
>
<div
className="overview-legend overview-legend-spaced-line"
>
issues.leak_period
</div>
</Tooltip>
`;

+ 5
- 2
server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.js Zobrazit soubor

@@ -21,6 +21,7 @@ import React from 'react';
import { Link } from 'react-router';
import enhance from './enhance';
import LeakPeriodLegend from '../components/LeakPeriodLegend';
import ApplicationLeakPeriodLegend from '../components/ApplicationLeakPeriodLegend';
import { getMetricName } from '../helpers/metrics';
import { translate } from '../../../helpers/l10n';
import BugIcon from '../../../components/icons-components/BugIcon';
@@ -54,7 +55,7 @@ class BugsAndVulnerabilities extends React.PureComponent {
}

renderLeak() {
const { leakPeriod } = this.props;
const { component, leakPeriod } = this.props;

if (leakPeriod == null) {
return null;
@@ -62,7 +63,9 @@ class BugsAndVulnerabilities extends React.PureComponent {

return (
<div className="overview-domain-leak">
<LeakPeriodLegend period={leakPeriod} />
{component.qualifier === 'APP'
? <ApplicationLeakPeriodLegend component={component} />
: <LeakPeriodLegend period={leakPeriod} />}

<div className="overview-domain-measures">
<div className="overview-domain-measure">

+ 5
- 0
server/sonar-web/src/main/js/apps/overview/main/enhance.js Zobrazit soubor

@@ -118,6 +118,7 @@ export default function enhance(ComposedComponent) {
</div>
);
};

renderRating = metricKey => {
const { component, measures } = this.props;
const measure = measures.find(measure => measure.metric.key === metricKey);
@@ -139,6 +140,7 @@ export default function enhance(ComposedComponent) {
</Tooltip>
);
};

renderIssues = (metric, type) => {
const { measures, component } = this.props;
const measure = measures.find(measure => measure.metric.key === metric);
@@ -160,6 +162,7 @@ export default function enhance(ComposedComponent) {
</Tooltip>
);
};

renderHistoryLink = metricKey => {
const linkClass =
'button button-small button-compact spacer-left overview-domain-measure-history-link';
@@ -171,6 +174,7 @@ export default function enhance(ComposedComponent) {
</Link>
);
};

renderTimeline = (metricKey, range, children) => {
if (!this.props.history) {
return null;
@@ -190,6 +194,7 @@ export default function enhance(ComposedComponent) {
</div>
);
};

render() {
return (
<ComposedComponent

+ 6
- 6
server/sonar-web/src/main/js/apps/overview/meta/Meta.js Zobrazit soubor

@@ -34,15 +34,14 @@ const Meta = ({ component, history, measures, areThereCustomOrganizations, route
const { qualifier, description, qualityProfiles, qualityGate } = component;

const isProject = qualifier === 'TRK';
const isView = qualifier === 'VW' || qualifier === 'SVW';
const isDeveloper = qualifier === 'DEV';
const isApplication = qualifier === 'APP';

const hasDescription = !!description;
const hasQualityProfiles = Array.isArray(qualityProfiles) && qualityProfiles.length > 0;
const hasQualityGate = !!qualityGate;

const shouldShowQualityProfiles = !isView && !isDeveloper && hasQualityProfiles;
const shouldShowQualityGate = !isView && !isDeveloper && hasQualityGate;
const shouldShowQualityProfiles = isProject && hasQualityProfiles;
const shouldShowQualityGate = isProject && hasQualityGate;
const hasOrganization = component.organization != null && areThereCustomOrganizations;

return (
@@ -56,7 +55,8 @@ const Meta = ({ component, history, measures, areThereCustomOrganizations, route

{isProject && <MetaTags component={component} />}

{isProject && <AnalysesList project={component.key} history={history} router={router} />}
{(isProject || isApplication) &&
<AnalysesList project={component.key} history={history} router={router} />}

{shouldShowQualityGate &&
<MetaQualityGate
@@ -71,7 +71,7 @@ const Meta = ({ component, history, measures, areThereCustomOrganizations, route
profiles={qualityProfiles}
/>}

<MetaLinks component={component} />
{isProject && <MetaLinks component={component} />}

<MetaKey component={component} />


+ 53
- 18
server/sonar-web/src/main/js/apps/overview/meta/MetaSize.js Zobrazit soubor

@@ -19,11 +19,13 @@
*/
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { DrilldownLink } from '../../../components/shared/drilldown-link';
import LanguageDistribution from '../../../components/charts/LanguageDistribution';
import SizeRating from '../../../components/ui/SizeRating';
import { formatMeasure } from '../../../helpers/measures';
import { getMetricName } from '../helpers/metrics';
import SizeRating from '../../../components/ui/SizeRating';
import { translate } from '../../../helpers/l10n';

export default class MetaSize extends React.PureComponent {
static propTypes = {
@@ -31,32 +33,65 @@ export default class MetaSize extends React.PureComponent {
measures: PropTypes.array.isRequired
};

render() {
const ncloc = this.props.measures.find(measure => measure.metric.key === 'ncloc');
renderLoC = ncloc =>
<div
id="overview-ncloc"
className={classNames('overview-meta-size-ncloc', {
'is-half-width': this.props.component.qualifier === 'APP'
})}>
<span className="spacer-right">
<SizeRating value={ncloc.value} />
</span>
<DrilldownLink component={this.props.component.key} metric="ncloc">
{formatMeasure(ncloc.value, 'SHORT_INT')}
</DrilldownLink>
<div className="overview-domain-measure-label text-muted">
{getMetricName('ncloc')}
</div>
</div>;

renderLoCDistribution = () => {
const languageDistribution = this.props.measures.find(
measure => measure.metric.key === 'ncloc_language_distribution'
);

if (ncloc == null || languageDistribution == null) {
return null;
}
return languageDistribution
? <div id="overview-language-distribution" className="overview-meta-size-lang-dist">
<LanguageDistribution distribution={languageDistribution.value} />
</div>
: null;
};

return (
<div id="overview-size" className="overview-meta-card">
<div id="overview-ncloc" className="overview-meta-size-ncloc">
<span className="spacer-right">
<SizeRating value={ncloc.value} />
</span>
<DrilldownLink component={this.props.component.key} metric="ncloc">
{formatMeasure(ncloc.value, 'SHORT_INT')}
renderProjects = () => {
const projects = this.props.measures.find(measure => measure.metric.key === 'projects');

return projects
? <div
id="overview-projects"
className="overview-meta-size-ncloc is-half-width bordered-left">
<DrilldownLink component={this.props.component.key} metric="projects">
{formatMeasure(projects.value, 'SHORT_INT')}
</DrilldownLink>
<div className="overview-domain-measure-label text-muted">
{getMetricName('ncloc')}
{translate('metric.projects.name')}
</div>
</div>
<div id="overview-language-distribution" className="overview-meta-size-lang-dist">
<LanguageDistribution distribution={languageDistribution.value} />
</div>
: null;
};

render() {
const ncloc = this.props.measures.find(measure => measure.metric.key === 'ncloc');

if (ncloc == null) {
return null;
}

return (
<div id="overview-size" className="overview-meta-card">
{this.renderLoC(ncloc)}
{this.props.component.qualifier === 'APP'
? this.renderProjects()
: this.renderLoCDistribution()}
</div>
);
}

+ 108
- 0
server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGate.js Zobrazit soubor

@@ -0,0 +1,108 @@
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import { keyBy } from 'lodash';
import ApplicationQualityGateProject from './ApplicationQualityGateProject';
import Level from '../../../components/ui/Level';
import { getApplicationQualityGate } from '../../../api/quality-gates';
import { translate } from '../../../helpers/l10n';

type Props = {
component: { key: string }
};

type State = {
loading: boolean,
metrics?: { [string]: Object },
projects?: Array<{
conditions: Array<Object>,
key: string,
name: string,
status: string
}>,
status?: string
};

export default class ApplicationQualityGate extends React.PureComponent {
mounted: boolean;
props: Props;
state: State = {
loading: true
};

componentDidMount() {
this.mounted = true;
this.fetchDetails();
}

componentDidUpdate(prevProps: Props) {
if (prevProps.component.key !== this.props.component.key) {
this.fetchDetails();
}
}

componentWillUnmount() {
this.mounted = false;
}

fetchDetails = () => {
this.setState({ loading: true });
getApplicationQualityGate(this.props.component.key).then(({ status, projects, metrics }) => {
if (this.mounted) {
this.setState({
loading: false,
metrics: keyBy(metrics, 'key'),
status,
projects
});
}
});
};

render() {
const { metrics, status, projects } = this.state;

return (
<div className="overview-quality-gate" id="overview-quality-gate">
<h2 className="overview-title">
{translate('overview.quality_gate')}
{this.state.loading && <i className="spinner spacer-left" />}
{status != null && <Level level={status} />}
</h2>

{projects != null &&
<div
id="overview-quality-gate-conditions-list"
className="overview-quality-gate-conditions-list clearfix">
{projects
.filter(project => project.status !== 'OK')
.map(project =>
<ApplicationQualityGateProject
key={project.key}
metrics={metrics}
project={project}
/>
)}
</div>}
</div>
);
}
}

+ 15
- 0
server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGateProject.css Zobrazit soubor

@@ -0,0 +1,15 @@
.application-quality-gate-project {
padding: 10px;
}

.overview-quality-gate-condition:hover .application-quality-gate-project {
padding: 9px;
}

.application-quality-gate-project-conditions {
margin-top: 4px;
}

.application-quality-gate-project-conditions > li {
margin-top: 4px;
}

+ 103
- 0
server/sonar-web/src/main/js/apps/overview/qualityGate/ApplicationQualityGateProject.js Zobrazit soubor

@@ -0,0 +1,103 @@
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import { Link } from 'react-router';
import classNames from 'classnames';
import { getLocalizedMetricName, translate } from '../../../helpers/l10n';
import { formatMeasure, isDiffMetric } from '../../../helpers/measures';
import { getProjectUrl } from '../../../helpers/urls';
import './ApplicationQualityGateProject.css';

type Condition = {
comparator: string,
errorThreshold?: string,
metricKey: string,
onLeak: boolean,
status: string,
value: string,
warningThreshold?: string
};

type Props = {
metrics: {
[string]: {
key: string,
name: string,
type: string
}
},
project: {
conditions: Array<Condition>,
key: string,
name: string,
status: string
}
};

export default class ApplicationQualityGateProject extends React.PureComponent {
props: Props;

renderCondition = (condition: Condition) => {
const metric = this.props.metrics[condition.metricKey];
const metricName = getLocalizedMetricName(metric);
const threshold = condition.errorThreshold || condition.warningThreshold;
const isDiff = isDiffMetric(condition.metricKey);

return (
<li key={condition.metricKey}>
<span className="text-limited">
<strong>{formatMeasure(condition.value, metric.type)}</strong> {metricName}
{!isDiff && condition.onLeak && ' ' + translate('quality_gates.conditions.leak')}
</span>
<span
className={classNames('pull-right', 'big-spacer-left', {
'text-danger': condition.status === 'ERROR',
'text-warning': condition.status === 'WARN'
})}>
{translate('quality_gates.operator', condition.comparator, 'short')}{' '}
{formatMeasure(threshold, metric.type)}
</span>
</li>
);
};

render() {
const { project } = this.props;

return (
<Link
className={classNames(
'overview-quality-gate-condition',
'overview-quality-gate-condition-' + project.status.toLowerCase()
)}
to={getProjectUrl(project.key)}>
<div className="application-quality-gate-project">
<h4>
{project.name}
</h4>
<ul className="application-quality-gate-project-conditions">
{project.conditions.filter(c => c.status !== 'OK').map(this.renderCondition)}
</ul>
</div>
</Link>
);
}
}

+ 39
- 0
server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/ApplicationQualityGate-test.js Zobrazit soubor

@@ -0,0 +1,39 @@
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import { shallow } from 'enzyme';
import ApplicationQualityGate from '../ApplicationQualityGate';

it('renders', () => {
const wrapper = shallow(<ApplicationQualityGate component={{ key: 'foo' }} />);
expect(wrapper).toMatchSnapshot();
wrapper.setState({
loading: false,
metrics: {},
status: 'ERROR',
projects: [
{ conditions: [], key: 'project1', name: 'project1', status: 'ERROR' },
{ conditions: [], key: 'project2', name: 'project2', status: 'OK' },
{ conditions: [], key: 'project3', name: 'project3', status: 'WARN' }
]
});
expect(wrapper).toMatchSnapshot();
});

+ 73
- 0
server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/ApplicationQualityGateProject-test.js Zobrazit soubor

@@ -0,0 +1,73 @@
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import { shallow } from 'enzyme';
import ApplicationQualityGateProject from '../ApplicationQualityGateProject';

const metrics = {
bugs: { key: 'bugs', name: 'Bugs', type: 'INT' },
new_coverage: { key: 'new_coverage', name: 'Coverage on New Code', type: 'PERCENT' },
skipped_tests: { key: 'skipped_tests', name: 'Skipped Tests', type: 'INT' }
};

it('renders', () => {
const project = {
key: 'foo',
name: 'Foo',
status: 'ERROR',
conditions: [
{
status: 'ERROR',
metricKey: 'new_coverage',
comparator: 'LT',
onLeak: true,
errorThreshold: '85',
value: '82.50562381034781'
},
{
status: 'WARN',
metricKey: 'bugs',
comparator: 'GT',
onLeak: false,
warningThreshold: '0',
value: '17'
},
{
status: 'ERROR',
metricKey: 'bugs',
comparator: 'GT',
onLeak: true,
warningThreshold: '0',
value: '3'
},
{
status: 'OK',
metricKey: 'skipped_tests',
comparator: 'GT',
onLeak: false,
warningThreshold: '0',
value: '0'
}
]
};
const wrapper = shallow(<ApplicationQualityGateProject metrics={metrics} project={project} />);
expect(wrapper).toMatchSnapshot();
});

+ 62
- 0
server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGate-test.js.snap Zobrazit soubor

@@ -0,0 +1,62 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders 1`] = `
<div
className="overview-quality-gate"
id="overview-quality-gate"
>
<h2
className="overview-title"
>
overview.quality_gate
<i
className="spinner spacer-left"
/>
</h2>
</div>
`;

exports[`renders 2`] = `
<div
className="overview-quality-gate"
id="overview-quality-gate"
>
<h2
className="overview-title"
>
overview.quality_gate
<Level
level="ERROR"
muted={false}
small={false}
/>
</h2>
<div
className="overview-quality-gate-conditions-list clearfix"
id="overview-quality-gate-conditions-list"
>
<ApplicationQualityGateProject
metrics={Object {}}
project={
Object {
"conditions": Array [],
"key": "project1",
"name": "project1",
"status": "ERROR",
}
}
/>
<ApplicationQualityGateProject
metrics={Object {}}
project={
Object {
"conditions": Array [],
"key": "project3",
"name": "project3",
"status": "WARN",
}
}
/>
</div>
</div>
`;

+ 84
- 0
server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGateProject-test.js.snap Zobrazit soubor

@@ -0,0 +1,84 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders 1`] = `
<Link
className="overview-quality-gate-condition overview-quality-gate-condition-error"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"id": "foo",
},
}
}
>
<div
className="application-quality-gate-project"
>
<h4>
Foo
</h4>
<ul
className="application-quality-gate-project-conditions"
>
<li>
<span
className="text-limited"
>
<strong>
82.5%
</strong>
Coverage on New Code
</span>
<span
className="pull-right big-spacer-left text-danger"
>
quality_gates.operator.LT.short
85.0%
</span>
</li>
<li>
<span
className="text-limited"
>
<strong>
17
</strong>
Bugs
</span>
<span
className="pull-right big-spacer-left text-warning"
>
quality_gates.operator.GT.short
0
</span>
</li>
<li>
<span
className="text-limited"
>
<strong>
3
</strong>
Bugs
quality_gates.conditions.leak
</span>
<span
className="pull-right big-spacer-left text-danger"
>
quality_gates.operator.GT.short
0
</span>
</li>
</ul>
</div>
</Link>
`;

+ 5
- 0
server/sonar-web/src/main/js/apps/overview/styles.css Zobrazit soubor

@@ -339,6 +339,11 @@
text-align: center;
}

.overview-meta-size-ncloc.is-half-width {
width: 50%;
box-sizing: border-box;
}

.overview-meta-size-ncloc a {
line-height: 24px;
font-size: 18px;

+ 1
- 0
server/sonar-web/src/main/js/apps/overview/utils.js Zobrazit soubor

@@ -58,6 +58,7 @@ export const METRICS = [
// size
'ncloc',
'ncloc_language_distribution',
'projects',
'new_lines'
];


+ 2
- 1
server/sonar-web/src/main/js/apps/permission-templates/components/AppContainer.js Zobrazit soubor

@@ -23,7 +23,8 @@ import { getAppState } from '../../../store/rootReducer';
import { getRootQualifiers } from '../../../store/appState/duck';

const mapStateToProps = state => ({
topQualifiers: getRootQualifiers(getAppState(state))
// treat applications as portfolios
topQualifiers: getRootQualifiers(getAppState(state)).filter(q => q !== 'APP')
});

export default connect(mapStateToProps)(App);

+ 16
- 14
server/sonar-web/src/main/js/apps/permissions/project/components/App.js Zobrazit soubor

@@ -349,20 +349,22 @@ export default class App extends React.PureComponent {
/>
<PageError />
{this.props.component.qualifier === 'TRK' &&
<VisibilitySelector
canTurnToPrivate={canTurnToPrivate}
className="big-spacer-top big-spacer-bottom"
onChange={this.handleVisibilityChange}
visibility={this.props.component.visibility}
/>}
{!canTurnToPrivate &&
<UpgradeOrganizationBox organization={this.props.component.organization} />}
{this.state.disclaimer &&
<PublicProjectDisclaimer
component={this.props.component}
onClose={this.closeDisclaimer}
onConfirm={this.turnProjectToPublic}
/>}
<div>
<VisibilitySelector
canTurnToPrivate={canTurnToPrivate}
className="big-spacer-top big-spacer-bottom"
onChange={this.handleVisibilityChange}
visibility={this.props.component.visibility}
/>
{!canTurnToPrivate &&
<UpgradeOrganizationBox organization={this.props.component.organization} />}
{this.state.disclaimer &&
<PublicProjectDisclaimer
component={this.props.component}
onClose={this.closeDisclaimer}
onConfirm={this.turnProjectToPublic}
/>}
</div>}
<AllHoldersList
component={this.props.component}
filter={this.state.filter}

+ 1
- 1
server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.js Zobrazit soubor

@@ -55,7 +55,7 @@ export default class PageHeader extends React.PureComponent {
const canApplyPermissionTemplate =
configuration != null && configuration.canApplyPermissionTemplate;

const description = ['VW', 'SVW'].includes(component.qualifier)
const description = ['VW', 'SVW', 'APP'].includes(component.qualifier)
? translate('roles.page.description_portfolio')
: translate('roles.page.description2');


+ 1
- 0
server/sonar-web/src/main/js/apps/permissions/project/constants.js Zobrazit soubor

@@ -27,5 +27,6 @@ export const PERMISSIONS_ORDER_BY_QUALIFIER = {
TRK: PERMISSIONS_ORDER_FOR_PROJECT,
VW: PERMISSIONS_ORDER_FOR_VIEW,
SVW: PERMISSIONS_ORDER_FOR_VIEW,
APP: PERMISSIONS_ORDER_FOR_VIEW,
DEV: PERMISSIONS_ORDER_FOR_DEV
};

+ 5
- 2
server/sonar-web/src/main/js/apps/project-admin/deletion/Header.js Zobrazit soubor

@@ -22,9 +22,12 @@ import React from 'react';
import { translate } from '../../../helpers/l10n';

export default function Header(props: { component: { qualifier: string } }) {
const description = ['VW', 'SVW'].includes(props.component.qualifier)
const { qualifier } = props.component;
const description = ['VW', 'SVW'].includes(qualifier)
? translate('portfolio_deletion.page.description')
: translate('project_deletion.page.description');
: qualifier === 'APP'
? translate('application_deletion.page.description')
: translate('project_deletion.page.description');

return (
<header className="page-header">

+ 1
- 1
server/sonar-web/src/main/js/apps/projects-admin/constants.js Zobrazit soubor

@@ -19,7 +19,7 @@
*/
export const PAGE_SIZE = 50;

export const QUALIFIERS_ORDER = ['TRK', 'VW', 'DEV'];
export const QUALIFIERS_ORDER = ['TRK', 'VW', 'APP', 'DEV'];

export const TYPE = {
ALL: 'ALL',

+ 2
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties Zobrazit soubor

@@ -439,6 +439,7 @@ qualifiers.update.DEV=Update Developer
qualifiers.update.APP=Update Application

qualifier.description.VW=Potentially multi-level, management-oriented overview aggregation.
qualifier.description.SVW=Potentially multi-level, management-oriented overview aggregation.
qualifier.description.APP=Single-level aggregation with a technical focus and a project-like homepage.

#------------------------------------------------------------------------------
@@ -571,6 +572,7 @@ update_key.page.description=Edit the keys of a project and/or its modules. Key c
deletion.page=Deletion
project_deletion.page.description=Delete this project from SonarQube. The operation cannot be undone.
portfolio_deletion.page.description=Delete this portfolio from SonarQube. Component projects and Local Reference Portfolios will not be deleted, but component Standard Portfolios will be deleted. This operation cannot be undone.
application_deletion.page.description=Delete this application from SonarQube. Application projects will not be deleted. This operation cannot be undone.
provisioning.page=Provisioning
provisioning.page.description=Use this page to initialize projects if you would like to configure them before the first analysis. Once a project is provisioned, you have access to perform all project configurations on it.


Načítá se…
Zrušit
Uložit