aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMathieu Suen <mathieu.suen@sonarsource.com>2021-11-11 18:04:34 +0100
committersonartech <sonartech@sonarsource.com>2021-11-16 20:03:55 +0000
commitbe28c6d7d70b85472a1213e2384a499432de2e99 (patch)
treebaac261376d6f4338a67d2cfe630a37e9fb7ba7f
parent4a478729ea3ba1c7903760253253d38a5b8cb6de (diff)
downloadsonarqube-be28c6d7d70b85472a1213e2384a499432de2e99.tar.gz
sonarqube-be28c6d7d70b85472a1213e2384a499432de2e99.zip
SONAR-13427 Add renew token badge for admin
-rw-r--r--server/sonar-web/src/main/js/api/project-badges.ts6
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/projectInformation/ProjectInformation.tsx7
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/projectInformation/__tests__/__snapshots__/ProjectInformation-test.tsx.snap97
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/ProjectBadges.tsx79
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/ProjectBadges-test.tsx32
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/__snapshots__/ProjectBadges-test.tsx.snap13
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties5
7 files changed, 201 insertions, 38 deletions
diff --git a/server/sonar-web/src/main/js/api/project-badges.ts b/server/sonar-web/src/main/js/api/project-badges.ts
index 692b8d793c0..b8783f72f9e 100644
--- a/server/sonar-web/src/main/js/api/project-badges.ts
+++ b/server/sonar-web/src/main/js/api/project-badges.ts
@@ -18,10 +18,14 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import throwGlobalError from '../app/utils/throwGlobalError';
-import { getJSON } from '../helpers/request';
+import { getJSON, postJSON } from '../helpers/request';
export function getProjectBadgesToken(project: string) {
return getJSON('/api/project_badges/token', { project })
.then(({ token }) => token)
.catch(throwGlobalError);
}
+
+export function renewProjectBadgesToken(project: string) {
+ return postJSON('/api/project_badges/renew_token', { project }).catch(throwGlobalError);
+}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/ProjectInformation.tsx b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/ProjectInformation.tsx
index 0b2072f2d11..3df9c2b97f0 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/ProjectInformation.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/ProjectInformation.tsx
@@ -107,12 +107,7 @@ export class ProjectInformation extends React.PureComponent<Props, State> {
<InfoDrawerPage
displayed={page === ProjectInformationPages.badges}
onPageChange={this.setPage}>
- <ProjectBadges
- branchLike={branchLike}
- metrics={metrics}
- project={component.key}
- qualifier={component.qualifier}
- />
+ <ProjectBadges branchLike={branchLike} metrics={metrics} component={component} />
</InfoDrawerPage>
)}
{canConfigureNotifications && (
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/__tests__/__snapshots__/ProjectInformation-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/__tests__/__snapshots__/ProjectInformation-test.tsx.snap
index fe5bdc421ce..91b4f6e28fc 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/__tests__/__snapshots__/ProjectInformation-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/__tests__/__snapshots__/ProjectInformation-test.tsx.snap
@@ -35,6 +35,28 @@ exports[`should render correctly: default 1`] = `
onPageChange={[Function]}
>
<ProjectBadges
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
metrics={
Object {
"coverage": Object {
@@ -45,8 +67,6 @@ exports[`should render correctly: default 1`] = `
},
}
}
- project="my-project"
- qualifier="TRK"
/>
</InfoDrawerPage>
</Fragment>
@@ -87,6 +107,28 @@ exports[`should render correctly: logged in user 1`] = `
onPageChange={[Function]}
>
<ProjectBadges
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
metrics={
Object {
"coverage": Object {
@@ -97,8 +139,6 @@ exports[`should render correctly: logged in user 1`] = `
},
}
}
- project="my-project"
- qualifier="TRK"
/>
</InfoDrawerPage>
<InfoDrawerPage
@@ -182,6 +222,28 @@ exports[`should render correctly: measures loaded 1`] = `
onPageChange={[Function]}
>
<ProjectBadges
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
metrics={
Object {
"coverage": Object {
@@ -192,8 +254,6 @@ exports[`should render correctly: measures loaded 1`] = `
},
}
}
- project="my-project"
- qualifier="TRK"
/>
</InfoDrawerPage>
</Fragment>
@@ -235,6 +295,29 @@ exports[`should render correctly: private 1`] = `
onPageChange={[Function]}
>
<ProjectBadges
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ "visibility": "private",
+ }
+ }
metrics={
Object {
"coverage": Object {
@@ -245,8 +328,6 @@ exports[`should render correctly: private 1`] = `
},
}
}
- project="my-project"
- qualifier="TRK"
/>
</InfoDrawerPage>
</Fragment>
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/ProjectBadges.tsx b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/ProjectBadges.tsx
index 73bdf3fb9a1..b219a077bae 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/ProjectBadges.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/ProjectBadges.tsx
@@ -18,9 +18,14 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { getProjectBadgesToken } from '../../../../../../api/project-badges';
+import {
+ getProjectBadgesToken,
+ renewProjectBadgesToken
+} from '../../../../../../api/project-badges';
import CodeSnippet from '../../../../../../components/common/CodeSnippet';
+import { Button } from '../../../../../../components/controls/buttons';
import { Alert } from '../../../../../../components/ui/Alert';
+import DeferredSpinner from '../../../../../../components/ui/DeferredSpinner';
import { getBranchLikeQuery } from '../../../../../../helpers/branch-like';
import { translate } from '../../../../../../helpers/l10n';
import { BranchLike } from '../../../../../../types/branch-like';
@@ -33,11 +38,11 @@ import { BadgeOptions, BadgeType, getBadgeSnippet, getBadgeUrl } from './utils';
interface Props {
branchLike?: BranchLike;
metrics: T.Dict<T.Metric>;
- project: string;
- qualifier: string;
+ component: T.Component;
}
interface State {
+ isRenewing: boolean;
token: string;
selectedType: BadgeType;
badgeOptions: BadgeOptions;
@@ -46,6 +51,7 @@ interface State {
export default class ProjectBadges extends React.PureComponent<Props, State> {
mounted = false;
state: State = {
+ isRenewing: false,
token: '',
selectedType: BadgeType.measure,
badgeOptions: { metric: MetricKey.alert_status }
@@ -61,8 +67,10 @@ export default class ProjectBadges extends React.PureComponent<Props, State> {
}
async fetchToken() {
- const { project } = this.props;
- const token = await getProjectBadgesToken(project);
+ const {
+ component: { key }
+ } = this.props;
+ const token = await getProjectBadgesToken(key).catch(() => '');
if (this.mounted) {
this.setState({ token });
}
@@ -73,13 +81,36 @@ export default class ProjectBadges extends React.PureComponent<Props, State> {
};
handleUpdateOptions = (options: Partial<BadgeOptions>) => {
- this.setState(state => ({ badgeOptions: { ...state.badgeOptions, ...options } }));
+ this.setState(state => ({
+ badgeOptions: { ...state.badgeOptions, ...options }
+ }));
+ };
+
+ handleRenew = async () => {
+ const {
+ component: { key }
+ } = this.props;
+
+ this.setState({ isRenewing: true });
+ await renewProjectBadgesToken(key).catch(() => {});
+ await this.fetchToken();
+ if (this.mounted) {
+ this.setState({ isRenewing: false });
+ }
};
render() {
- const { branchLike, project, qualifier } = this.props;
- const { selectedType, badgeOptions, token } = this.state;
- const fullBadgeOptions = { project, ...badgeOptions, ...getBranchLikeQuery(branchLike) };
+ const {
+ branchLike,
+ component: { key: project, qualifier, configuration }
+ } = this.props;
+ const { isRenewing, selectedType, badgeOptions, token } = this.state;
+ const fullBadgeOptions = {
+ project,
+ ...badgeOptions,
+ ...getBranchLikeQuery(branchLike)
+ };
+ const canRenew = configuration?.showSettings;
return (
<div className="display-flex-column">
@@ -110,11 +141,31 @@ export default class ProjectBadges extends React.PureComponent<Props, State> {
type={selectedType}
updateOptions={this.handleUpdateOptions}
/>
- <Alert variant="warning">{translate('overview.badges.leak_warning')}</Alert>
- <CodeSnippet
- isOneLine={true}
- snippet={getBadgeSnippet(selectedType, fullBadgeOptions, token)}
- />
+ {isRenewing ? (
+ <div className="spacer-top spacer-bottom display-flex-row display-flex-justify-center">
+ <DeferredSpinner className="spacer-top spacer-bottom" loading={isRenewing} />
+ </div>
+ ) : (
+ <CodeSnippet
+ isOneLine={true}
+ snippet={getBadgeSnippet(selectedType, fullBadgeOptions, token)}
+ />
+ )}
+
+ <Alert variant="warning">
+ <p>
+ {translate('overview.badges.leak_warning')}{' '}
+ {canRenew && translate('overview.badges.renew.description')}
+ </p>
+ {canRenew && (
+ <Button
+ disabled={isRenewing}
+ className="spacer-top it__project-info-renew-badge"
+ onClick={this.handleRenew}>
+ {translate('overview.badges.renew')}
+ </Button>
+ )}
+ </Alert>
</div>
);
}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/ProjectBadges-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/ProjectBadges-test.tsx
index 8b0868d437c..da888216e20 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/ProjectBadges-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/ProjectBadges-test.tsx
@@ -19,11 +19,16 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { getProjectBadgesToken } from '../../../../../../../api/project-badges';
+import CodeSnippet from '../../../../../../../components/common/CodeSnippet';
import { mockBranch } from '../../../../../../../helpers/mocks/branch-like';
+import { mockComponent } from '../../../../../../../helpers/mocks/component';
import { mockMetric } from '../../../../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../../../../helpers/testUtils';
import { Location } from '../../../../../../../helpers/urls';
+import { ComponentQualifier } from '../../../../../../../types/component';
import { MetricKey } from '../../../../../../../types/metrics';
+import BadgeButton from '../BadgeButton';
import ProjectBadges from '../ProjectBadges';
jest.mock('../../../../../../../helpers/urls', () => ({
@@ -33,7 +38,8 @@ jest.mock('../../../../../../../helpers/urls', () => ({
}));
jest.mock('../../../../../../../api/project-badges', () => ({
- getProjectBadgesToken: jest.fn().mockResolvedValue('foo')
+ getProjectBadgesToken: jest.fn().mockResolvedValue('foo'),
+ renewProjectBadgesToken: jest.fn().mockResolvedValue({})
}));
it('should display correctly', async () => {
@@ -42,6 +48,27 @@ it('should display correctly', async () => {
expect(wrapper).toMatchSnapshot();
});
+it('should renew token', async () => {
+ (getProjectBadgesToken as jest.Mock).mockResolvedValueOnce('foo').mockResolvedValueOnce('bar');
+ const wrapper = shallowRender({
+ component: mockComponent({ configuration: { showSettings: true } })
+ });
+ await waitAndUpdate(wrapper);
+ wrapper.find('.it__project-info-renew-badge').simulate('click');
+
+ // it shoud be loading
+ expect(wrapper.find('.it__project-info-renew-badge').props().disabled).toBe(true);
+
+ await waitAndUpdate(wrapper);
+ const buttons = wrapper.find(BadgeButton);
+ expect(buttons.at(0).props().url).toMatch('token=bar');
+ expect(buttons.at(1).props().url).toMatch('token=bar');
+ expect(wrapper.find(CodeSnippet).props().snippet).toMatch('token=bar');
+
+ // let's check that the loading has correclty ends.
+ expect(wrapper.find('.it__project-info-renew-badge').props().disabled).toBe(false);
+});
+
function shallowRender(overrides = {}) {
return shallow(
<ProjectBadges
@@ -50,8 +77,7 @@ function shallowRender(overrides = {}) {
[MetricKey.coverage]: mockMetric({ key: MetricKey.coverage }),
[MetricKey.new_code_smells]: mockMetric({ key: MetricKey.new_code_smells })
}}
- project="foo"
- qualifier="TRK"
+ component={mockComponent({ key: 'foo', qualifier: ComponentQualifier.Project })}
{...overrides}
/>
);
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/__snapshots__/ProjectBadges-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/__snapshots__/ProjectBadges-test.tsx.snap
index 4c7f8b850db..ea5049ad896 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/__snapshots__/ProjectBadges-test.tsx.snap
+++ b/server/sonar-web/src/main/js/app/components/nav/component/projectInformation/badges/__tests__/__snapshots__/ProjectBadges-test.tsx.snap
@@ -60,14 +60,17 @@ exports[`should display correctly 1`] = `
type="measure"
updateOptions={[Function]}
/>
- <Alert
- variant="warning"
- >
- overview.badges.leak_warning
- </Alert>
<CodeSnippet
isOneLine={true}
snippet="[![alert_status](host/api/project_badges/measure?branch=branch-6.7&project=foo&metric=alert_status&token=foo)](/dashboard)"
/>
+ <Alert
+ variant="warning"
+ >
+ <p>
+ overview.badges.leak_warning
+
+ </p>
+ </Alert>
</div>
`;
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 423495025c3..4d4c5557a0c 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -2997,7 +2997,7 @@ project_dump.pending_import=Import was scheduled on {0}, waiting to be processed
project_dump.in_progress_import=Import is in progress, started {0}.
project_dump.failed_import=The last import has failed. Please try once again.
project_dump.import_form_description=A dump has been found on the file system for this project. You can import it by clicking on the button below.
-project_dump.import_form_description_disabled=Projects cannot be imported. This feature is only available starting from Enterprise Edition
+project_dump.import_form_description_disabled=Projects cannot be imported. This feature is only available starting from Enterprise Edition.
#------------------------------------------------------------------------------
#
@@ -3170,6 +3170,9 @@ overview.badges.quality_gate.description.APP=Displays the current quality gate s
overview.badges.quality_gate.description.TRK=Displays the current quality gate status of your project.
overview.badges.quality_gate.description.VW=Displays the current quality gate status of your portfolio.
overview.badges.leak_warning=Project badges can expose your security rating and other measures. Only use project badges in trusted environments.
+overview.badges.renew=Renew Token
+overview.badges.renew.description=If your project badge security token has leaked to an unsafe environment, you can renew it:
+
#------------------------------------------------------------------------------
#