From: Grégoire Aubert Date: Wed, 10 Jan 2018 16:51:19 +0000 (+0100) Subject: SONAR-10264 Create the project badges page for SonarCloud X-Git-Tag: 7.5~1786 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=e375cacde20c7e799ac80a5ad20817587dcb9235;p=sonarqube.git SONAR-10264 Create the project badges page for SonarCloud - SONAR-10268 Create the badges page with the Scanned on SonarCloud badge - SONAR-10272 Allow user to choose the color of the badge - SONAR-10268 Add the "Scanned on SonarCloud" svg files - SONAR-10264 SONAR-10271 Add standard measure badges - SONAR-10264 Allow user to choose a metric for the standard badge --- diff --git a/server/sonar-web/src/main/js/api/web-api.ts b/server/sonar-web/src/main/js/api/web-api.ts index 7004d4dc8ff..dab190049a0 100644 --- a/server/sonar-web/src/main/js/api/web-api.ts +++ b/server/sonar-web/src/main/js/api/web-api.ts @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { getJSON } from '../helpers/request'; +import throwGlobalError from '../app/utils/throwGlobalError'; export interface Changelog { description: string; @@ -65,16 +66,20 @@ export interface Example { format: string; } -export function fetchWebApi(showInternal: boolean = true): Promise> { - return getJSON('/api/webservices/list', { include_internals: showInternal }).then(r => - r.webServices.map((domain: any) => { - const deprecated = !domain.actions.find((action: any) => !action.deprecatedSince); - const internal = !domain.actions.find((action: any) => !action.internal); - return { ...domain, deprecated, internal }; - }) - ); +export function fetchWebApi(showInternal = true): Promise { + return getJSON('/api/webservices/list', { include_internals: showInternal }) + .then(r => + r.webServices.map((domain: any) => { + const deprecated = !domain.actions.find((action: any) => !action.deprecatedSince); + const internal = !domain.actions.find((action: any) => !action.internal); + return { ...domain, deprecated, internal }; + }) + ) + .catch(throwGlobalError); } export function fetchResponseExample(domain: string, action: string): Promise { - return getJSON('/api/webservices/response_example', { controller: domain, action }); + return getJSON('/api/webservices/response_example', { controller: domain, action }).catch( + throwGlobalError + ); } diff --git a/server/sonar-web/src/main/js/app/styles/init/misc.css b/server/sonar-web/src/main/js/app/styles/init/misc.css index 8fc69d8ad5e..aa27be83099 100644 --- a/server/sonar-web/src/main/js/app/styles/init/misc.css +++ b/server/sonar-web/src/main/js/app/styles/init/misc.css @@ -85,6 +85,10 @@ th.nowrap { margin-top: 16px; } +.huge-spacer-bottom { + margin-bottom: 40px; +} + .huge-spacer-top { margin-top: 40px; } diff --git a/server/sonar-web/src/main/js/app/theme.js b/server/sonar-web/src/main/js/app/theme.js index 9847d87119a..b5ebfb7b796 100644 --- a/server/sonar-web/src/main/js/app/theme.js +++ b/server/sonar-web/src/main/js/app/theme.js @@ -38,6 +38,7 @@ module.exports = { gray71: '#b4b4b4', gray67: '#aaa', gray60: '#999', + gray40: '#404040', barBackgroundColor: '#f3f3f3', barBorderColor: '#e6e6e6', @@ -48,6 +49,8 @@ module.exports = { leakColor: '#fbf3d5', leakBorderColor: '#eae3c7', + snippetFontColor: '#f0f0f0', + // sizes gridSize: `${grid}px`, diff --git a/server/sonar-web/src/main/js/apps/overview/badges/BadgeButton.tsx b/server/sonar-web/src/main/js/apps/overview/badges/BadgeButton.tsx new file mode 100644 index 00000000000..a43aa00e075 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/badges/BadgeButton.tsx @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import * as classNames from 'classnames'; +import { BadgeType } from './utils'; +import { translate } from '../../../helpers/l10n'; + +interface Props { + onClick: (type: BadgeType) => void; + selected: boolean; + type: BadgeType; + url: string; +} + +export default class BadgeButton extends React.PureComponent { + handleClick = () => this.props.onClick(this.props.type); + + render() { + const { selected, type, url } = this.props; + const width = type !== BadgeType.measure ? '128px' : undefined; + return ( + + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/overview/badges/BadgeParams.tsx b/server/sonar-web/src/main/js/apps/overview/badges/BadgeParams.tsx new file mode 100644 index 00000000000..e0b1ca1d754 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/badges/BadgeParams.tsx @@ -0,0 +1,156 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { connect } from 'react-redux'; +import Select from '../../../components/controls/Select'; +import { fetchWebApi } from '../../../api/web-api'; +import { Metric } from '../../../app/types'; +import { BadgeColors, BadgeType, BadgeOptions } from './utils'; +import { getLocalizedMetricName, translate } from '../../../helpers/l10n'; +import { fetchMetrics } from '../../../store/rootActions'; +import { getMetrics } from '../../../store/rootReducer'; + +interface StateToProps { + metrics: { [key: string]: Metric }; +} + +interface DispatchToProps { + fetchMetrics: () => void; +} + +interface OwnProps { + className?: string; + options: BadgeOptions; + type: BadgeType; + updateOptions: (options: Partial) => void; +} + +type Props = StateToProps & DispatchToProps & OwnProps; + +interface State { + badgeMetrics: string[]; +} + +export class BadgeParams extends React.PureComponent { + mounted: boolean; + state: State = { badgeMetrics: [] }; + + componentDidMount() { + this.mounted = true; + this.props.fetchMetrics(); + this.fetchBadgeMetrics(); + } + + componentWillUnmount() { + this.mounted = false; + } + + fetchBadgeMetrics() { + fetchWebApi(false).then( + webservices => { + if (this.mounted) { + const domain = webservices.find(domain => domain.path === 'api/project_badges'); + const ws = domain && domain.actions.find(ws => ws.key === 'measure'); + const param = ws && ws.params && ws.params.find(param => param.key === 'metric'); + if (param && param.possibleValues) { + this.setState({ badgeMetrics: param.possibleValues }); + } + } + }, + () => {} + ); + } + + getColorOptions = () => + ['white', 'black', 'orange'].map(color => ({ + label: translate('overview.badges.options.colors', color), + value: color + })); + + getMetricOptions = () => { + const { metrics } = this.props; + return this.state.badgeMetrics.map(key => { + const metric = metrics[key]; + return { + value: key, + label: metric && getLocalizedMetricName(metric) + }; + }); + }; + + handleColorChange = ({ value }: { value: BadgeColors }) => + this.props.updateOptions({ color: value }); + + handleMetricChange = ({ value }: { value: string }) => + this.props.updateOptions({ metric: value }); + + render() { + const { className, options, type } = this.props; + switch (type) { + case BadgeType.marketing: + return ( +
+ + +
+ ); + default: + return null; + } + } +} + +const mapDispatchToProps: DispatchToProps = { fetchMetrics }; + +const mapStateToProps = (state: any): StateToProps => ({ + metrics: getMetrics(state) +}); + +export default connect( + mapStateToProps, + mapDispatchToProps +)(BadgeParams); diff --git a/server/sonar-web/src/main/js/apps/overview/badges/BadgeSnippet.tsx b/server/sonar-web/src/main/js/apps/overview/badges/BadgeSnippet.tsx new file mode 100644 index 00000000000..5deaf71cbbd --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/badges/BadgeSnippet.tsx @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import ClipboardButton from '../../../components/controls/ClipboardButton'; + +interface Props { + snippet: string; +} + +export default function BadgeSnippet({ snippet }: Props) { + return ( +
+
{snippet}
+ +
+ ); +} diff --git a/server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx b/server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx new file mode 100644 index 00000000000..4eea5f43a12 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx @@ -0,0 +1,107 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import Modal from '../../../components/controls/Modal'; +import BadgeButton from './BadgeButton'; +import BadgeSnippet from './BadgeSnippet'; +import BadgeParams from './BadgeParams'; +import { getBadgeUrl, BadgeType, BadgeOptions } from './utils'; +import { translate } from '../../../helpers/l10n'; +import './styles.css'; + +interface Props { + branch: string; + project: string; +} + +interface State { + open: boolean; + selectedType: BadgeType; + badgeOptions: BadgeOptions; +} + +export default class BadgesModal extends React.PureComponent { + state: State = { + open: false, + selectedType: BadgeType.measure, + badgeOptions: { color: 'white', metric: 'alert_status' } + }; + + handleClose = () => this.setState({ open: false }); + + handleOpen = () => this.setState({ open: true }); + + handleSelectBadge = (selectedType: BadgeType) => this.setState({ selectedType }); + + handleUpdateOptions = (options: Partial) => + this.setState(state => ({ + badgeOptions: { ...state.badgeOptions, ...options } + })); + + handleCancelClick = () => this.handleClose(); + + render() { + const { branch, project } = this.props; + const { selectedType, badgeOptions } = this.state; + const header = translate('overview.badges.title'); + const fullBadgeOptions = { branch, project, ...badgeOptions }; + return ( + <> + + {this.state.open && ( + +
+

{header}

+
+
+

{translate('overview.badges.description')}

+
+ {[BadgeType.measure, BadgeType.qualityGate, BadgeType.marketing].map(type => ( + + ))} +
+

+ {translate('overview.badges', selectedType, 'description')} +

+ + +
+
+ +
+
+ )} + + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgeButton-test.tsx b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgeButton-test.tsx new file mode 100644 index 00000000000..3f958698649 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgeButton-test.tsx @@ -0,0 +1,49 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import BadgeButton from '../BadgeButton'; +import { BadgeType } from '../utils'; +import { click } from '../../../../helpers/testUtils'; + +it('should display correctly', () => { + expect(getWrapper()).toMatchSnapshot(); + expect(getWrapper({ selected: true })).toMatchSnapshot(); + expect(getWrapper({ type: BadgeType.measure })).toMatchSnapshot(); +}); + +it('should return the badge type on click', () => { + const onClick = jest.fn(); + const wrapper = getWrapper({ onClick }); + click(wrapper.find('button')); + expect(onClick).toHaveBeenCalledWith(BadgeType.marketing); +}); + +function getWrapper(props = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgeParams-test.tsx b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgeParams-test.tsx new file mode 100644 index 00000000000..5108e572be4 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgeParams-test.tsx @@ -0,0 +1,73 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import { BadgeParams } from '../BadgeParams'; +import { BadgeType } from '../utils'; +import { Metric } from '../../../../app/types'; + +jest.mock('../../../../api/web-api', () => ({ + fetchWebApi: () => + Promise.resolve([ + { + path: 'api/project_badges', + actions: [ + { + key: 'measure', + params: [{ key: 'metric', possibleValues: ['alert_status', 'coverage'] }] + } + ] + } + ]) +})); + +const METRICS = { + alert_status: { key: 'alert_status', name: 'Quality Gate' } as Metric, + coverage: { key: 'coverage', name: 'Coverage' } as Metric +}; + +it('should display marketing badge params', () => { + const updateOptions = jest.fn(); + const wrapper = getWrapper({ updateOptions }); + expect(wrapper).toMatchSnapshot(); + (wrapper.instance() as BadgeParams).handleColorChange({ value: 'black' }); + expect(updateOptions).toHaveBeenCalledWith({ color: 'black' }); +}); + +it('should display measure badge params', () => { + const updateOptions = jest.fn(); + const wrapper = getWrapper({ updateOptions, type: BadgeType.measure }); + expect(wrapper).toMatchSnapshot(); + (wrapper.instance() as BadgeParams).handleColorChange({ value: 'black' }); + expect(updateOptions).toHaveBeenCalledWith({ color: 'black' }); +}); + +function getWrapper(props = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx new file mode 100644 index 00000000000..3eee7bf7074 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import BadgesModal from '../BadgesModal'; +import { click } from '../../../../helpers/testUtils'; + +jest.mock('../../../../helpers/urls', () => ({ + getHostUrl: () => 'host' +})); + +it('should display the modal after click', () => { + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + click(wrapper.find('button')); + expect(wrapper.find('Modal')).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgeButton-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgeButton-test.tsx.snap new file mode 100644 index 00000000000..90b4d8e5e37 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgeButton-test.tsx.snap @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should display correctly 1`] = ` + +`; + +exports[`should display correctly 2`] = ` + +`; + +exports[`should display correctly 3`] = ` + +`; diff --git a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgeParams-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgeParams-test.tsx.snap new file mode 100644 index 00000000000..58bb0b507a6 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgeParams-test.tsx.snap @@ -0,0 +1,56 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should display marketing badge params 1`] = ` +
+ + +
+`; diff --git a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgesModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgesModal-test.tsx.snap new file mode 100644 index 00000000000..c6fd05219b0 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgesModal-test.tsx.snap @@ -0,0 +1,89 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should display the modal after click 1`] = ` + + + +`; + +exports[`should display the modal after click 2`] = ` + +
+

+ overview.badges.title +

+
+
+

+ overview.badges.description +

+
+ + + +
+

+ overview.badges.measure.description +

+ + +
+
+ +
+
+`; diff --git a/server/sonar-web/src/main/js/apps/overview/badges/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/utils-test.ts new file mode 100644 index 00000000000..59d643811b9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/badges/__tests__/utils-test.ts @@ -0,0 +1,58 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { getBadgeUrl, BadgeOptions, BadgeType } from '../utils'; + +jest.mock('../../../../helpers/urls', () => ({ + getHostUrl: () => 'host' +})); + +const options: BadgeOptions = { + branch: 'master', + color: 'white', + project: 'foo', + metric: 'alert_status' +}; + +describe('#getBadgeUrl', () => { + it('should generate correct marketing badge links', () => { + expect(getBadgeUrl(BadgeType.marketing, options)).toBe( + 'host/images/project_badges/sonarcloud-white.svg' + ); + expect(getBadgeUrl(BadgeType.marketing, { ...options, color: 'orange' })).toBe( + 'host/images/project_badges/sonarcloud-orange.svg' + ); + }); + + it('should generate correct quality gate badge links', () => { + expect(getBadgeUrl(BadgeType.qualityGate, options)); + }); + + it('should generate correct measures badge links', () => { + expect(getBadgeUrl(BadgeType.measure, options)).toBe( + 'host/api/project_badges/measure?branch=master&project=foo&metric=alert_status' + ); + }); + + it('should ignore undefined parameters', () => { + expect(getBadgeUrl(BadgeType.measure, { color: 'white', metric: 'alert_status' })).toBe( + 'host/api/project_badges/measure?metric=alert_status' + ); + }); +}); diff --git a/server/sonar-web/src/main/js/apps/overview/badges/styles.css b/server/sonar-web/src/main/js/apps/overview/badges/styles.css new file mode 100644 index 00000000000..3fe0529fe4c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/badges/styles.css @@ -0,0 +1,85 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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. + */ +.badges-list { + display: flex; + justify-content: space-around; + justify-content: space-evenly; + flex-wrap: nowrap; +} + +.badge-button { + display: flex; + justify-content: center; + padding: var(--gridSize); + min-width: 146px; + height: 116px; + background-color: var(--barBackgroundColor); + border: solid 1px var(--barBorderColor); + border-radius: 3px; + transition: all 0.3s ease; +} + +.badge-button:hover, +.badge-button:focus, +.badge-button:active { + background-color: var(--barBackgroundColor); + border-color: var(--blue); + box-shadow: none; +} + +.badge-button.selected { + background-color: var(--lightBlue); + border-color: var(--darkBlue); +} + +.badge-snippet { + position: relative; + margin: var(--gridSize) 0; + background: var(--gray40); + color: var(--snippetFontColor); + border-radius: 3px; +} + +.badge-snippet pre { + padding: calc(2 * var(--gridSize)); + padding-bottom: 40px; + overflow: auto; +} + +.badge-snippet button { + position: absolute; + top: auto; + top: 40px; + right: calc(2 * var(--gridSize)); + height: var(--smallControlHeight); + line-height: 18px; + border: 1px solid #fff; + color: #fff; + font-size: 11px; + font-weight: normal; + user-select: none; +} + +.badge-snippet button:hover, +.badge-snippet button:focus, +.badge-snippet button:active { + background-color: #fff; + color: var(--gray40); +} diff --git a/server/sonar-web/src/main/js/apps/overview/badges/utils.ts b/server/sonar-web/src/main/js/apps/overview/badges/utils.ts new file mode 100644 index 00000000000..39113f88945 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/badges/utils.ts @@ -0,0 +1,56 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { stringify } from 'querystring'; +import { omitNil } from '../../../helpers/request'; +import { getHostUrl } from '../../../helpers/urls'; + +export type BadgeColors = 'white' | 'black' | 'orange'; + +export interface BadgeOptions { + branch?: string; + color?: BadgeColors; + project?: string; + metric?: string; +} + +export enum BadgeType { + measure = 'measure', + qualityGate = 'quality_gate', + marketing = 'marketing' +} + +export function getBadgeUrl( + type: BadgeType, + { branch, project, color = 'white', metric = 'alert_status' }: BadgeOptions +) { + switch (type) { + case BadgeType.marketing: + return `${getHostUrl()}/images/project_badges/sonarcloud-${color}.svg`; + case BadgeType.qualityGate: + return `${getHostUrl()}/api/project_badges/quality_gate?${stringify( + omitNil({ branch, project }) + )}`; + case BadgeType.measure: + default: + return `${getHostUrl()}/api/project_badges/measure?${stringify( + omitNil({ branch, project, metric }) + )}`; + } +} diff --git a/server/sonar-web/src/main/js/apps/overview/meta/Meta.js b/server/sonar-web/src/main/js/apps/overview/meta/Meta.js index 9b0f6afe149..0422440aa99 100644 --- a/server/sonar-web/src/main/js/apps/overview/meta/Meta.js +++ b/server/sonar-web/src/main/js/apps/overview/meta/Meta.js @@ -27,7 +27,9 @@ import MetaQualityProfiles from './MetaQualityProfiles'; import AnalysesList from '../events/AnalysesList'; import MetaSize from './MetaSize'; import MetaTags from './MetaTags'; -import { areThereCustomOrganizations } from '../../../store/rootReducer'; +import BadgesModal from '../badges/BadgesModal'; +import { areThereCustomOrganizations, getGlobalSettingValue } from '../../../store/rootReducer'; +import { Visibility } from '../../../app/types'; const Meta = ({ branch, @@ -38,9 +40,10 @@ const Meta = ({ onComponentChange, onSonarCloud }) => { - const { qualifier, description, qualityProfiles, qualityGate } = component; + const { qualifier, description, qualityProfiles, qualityGate, visibility } = component; const isProject = qualifier === 'TRK'; + const isPrivate = visibility === Visibility.Private; const hasDescription = !!description; const hasQualityProfiles = Array.isArray(qualityProfiles) && qualityProfiles.length > 0; @@ -87,6 +90,10 @@ const Meta = ({ {hasOrganization && } + + {onSonarCloud && + isProject && + !isPrivate && } ); }; diff --git a/server/sonar-web/src/main/js/apps/tutorials/onboarding/styles.css b/server/sonar-web/src/main/js/apps/tutorials/onboarding/styles.css index d5e4ba2fcb9..e12fb2dde86 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/onboarding/styles.css +++ b/server/sonar-web/src/main/js/apps/tutorials/onboarding/styles.css @@ -66,8 +66,8 @@ .onboarding-command pre { padding: 15px; border-radius: 2px; - background: #404040; - color: #f0f0f0; + background: var(--gray40); + color: var(--snippetFontColor); overflow: auto; } @@ -88,7 +88,7 @@ .onboarding-command button:focus, .onboarding-command button:active { background-color: #fff; - color: #404040; + color: var(--gray40); } .onboarding-command-oneline pre { diff --git a/server/sonar-web/src/main/js/apps/web-api/components/ResponseExample.tsx b/server/sonar-web/src/main/js/apps/web-api/components/ResponseExample.tsx index 916cabd375f..49f8cd2e379 100644 --- a/server/sonar-web/src/main/js/apps/web-api/components/ResponseExample.tsx +++ b/server/sonar-web/src/main/js/apps/web-api/components/ResponseExample.tsx @@ -55,8 +55,9 @@ export default class ResponseExample extends React.PureComponent { fetchResponseExample() { const { domain, action } = this.props; - fetchResponseExampleApi(domain.path, action.key).then(responseExample => - this.setState({ responseExample }) + fetchResponseExampleApi(domain.path, action.key).then( + responseExample => this.setState({ responseExample }), + () => {} ); } diff --git a/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx b/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx index c198d4dd341..270c821ce00 100644 --- a/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx +++ b/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx @@ -81,12 +81,15 @@ export default class WebApiApp extends React.PureComponent { } } - fetchList(cb?: () => void) { - fetchWebApi().then(domains => { - if (this.mounted) { - this.setState({ domains }, cb); - } - }); + fetchList() { + fetchWebApi().then( + domains => { + if (this.mounted) { + this.setState({ domains }); + } + }, + () => {} + ); } scrollToAction = () => { diff --git a/server/sonar-web/src/main/js/components/SelectList/styles.css b/server/sonar-web/src/main/js/components/SelectList/styles.css index 14988f866eb..89aa7d6e551 100644 --- a/server/sonar-web/src/main/js/components/SelectList/styles.css +++ b/server/sonar-web/src/main/js/components/SelectList/styles.css @@ -58,7 +58,7 @@ margin-top: -1px; padding: 5px 10px; border-top: 1px solid #e0e0e0; - color: #404040; + color: var(--gray40); transition: transform 0.3s ease; } diff --git a/server/sonar-web/src/main/js/components/controls/ClipboardButton.tsx b/server/sonar-web/src/main/js/components/controls/ClipboardButton.tsx index dc9b146d13e..021c390dd1e 100644 --- a/server/sonar-web/src/main/js/components/controls/ClipboardButton.tsx +++ b/server/sonar-web/src/main/js/components/controls/ClipboardButton.tsx @@ -59,7 +59,9 @@ export default class ClipboardButton extends React.PureComponent { componentWillUnmount() { this.mounted = false; - this.clipboard.destroy(); + if (this.clipboard) { + this.clipboard.destroy(); + } } showTooltip = () => { diff --git a/server/sonar-web/src/main/js/components/workspace/styles.css b/server/sonar-web/src/main/js/components/workspace/styles.css index 10ab9a82d7a..170a4e2c986 100644 --- a/server/sonar-web/src/main/js/components/workspace/styles.css +++ b/server/sonar-web/src/main/js/components/workspace/styles.css @@ -34,7 +34,7 @@ vertical-align: middle; margin-right: 10px; padding: 3px 10px; - background-color: #404040; + background-color: var(--gray40); color: #fff; font-size: var(--smallFontSize); font-weight: 300; @@ -63,7 +63,7 @@ height: 30px; padding: 3px 10px; box-sizing: border-box; - background-color: #404040; + background-color: var(--gray40); color: #fff; font-weight: 300; } diff --git a/server/sonar-web/src/main/webapp/images/project_badges/sonarcloud-black.svg b/server/sonar-web/src/main/webapp/images/project_badges/sonarcloud-black.svg new file mode 100644 index 00000000000..81d55ae77a3 --- /dev/null +++ b/server/sonar-web/src/main/webapp/images/project_badges/sonarcloud-black.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/server/sonar-web/src/main/webapp/images/project_badges/sonarcloud-orange.svg b/server/sonar-web/src/main/webapp/images/project_badges/sonarcloud-orange.svg new file mode 100644 index 00000000000..20ea73bcb72 --- /dev/null +++ b/server/sonar-web/src/main/webapp/images/project_badges/sonarcloud-orange.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/server/sonar-web/src/main/webapp/images/project_badges/sonarcloud-white.svg b/server/sonar-web/src/main/webapp/images/project_badges/sonarcloud-white.svg new file mode 100644 index 00000000000..f18a5507e9f --- /dev/null +++ b/server/sonar-web/src/main/webapp/images/project_badges/sonarcloud-white.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index ce5c514e171..1b429d48c9b 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -2319,6 +2319,20 @@ overview.complexity_tooltip.file={0} files have complexity around {1} overview.deprecated_profile=This quality profile uses {0} deprecated rules and should be updated. +overview.badges.get_badge=Get project badges +overview.badges.title=Badges +overview.badges.description=Show the status of your project metrics on your README or website. Pick your style: +overview.badges.metric=Metric +overview.badges.options.colors.white=White +overview.badges.options.colors.black=Black +overview.badges.options.colors.orange=Orange +overview.badges.measure.alt=Standard badge +overview.badges.measure.description=This badge dynamically displays the current status of one metric of your project. +overview.badges.marketing.alt=Scanned on SonarCloud badge +overview.badges.marketing.description=This badge lets you advertise that you're using SonarCloud for code quality. +overview.badges.quality_gate.alt=SonarCloud Quality Gate badge +overview.badges.quality_gate.description=This badge dynamically displays the current quality gate status of your project. + widget.as_calculated_on_x=As calculated on {0}