* 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);
+}
<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 && (
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 {
},
}
}
- project="my-project"
- qualifier="TRK"
/>
</InfoDrawerPage>
</Fragment>
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 {
},
}
}
- project="my-project"
- qualifier="TRK"
/>
</InfoDrawerPage>
<InfoDrawerPage
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 {
},
}
}
- project="my-project"
- qualifier="TRK"
/>
</InfoDrawerPage>
</Fragment>
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 {
},
}
}
- project="my-project"
- qualifier="TRK"
/>
</InfoDrawerPage>
</Fragment>
* 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';
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;
export default class ProjectBadges extends React.PureComponent<Props, State> {
mounted = false;
state: State = {
+ isRenewing: false,
token: '',
selectedType: BadgeType.measure,
badgeOptions: { metric: MetricKey.alert_status }
}
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 });
}
};
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">
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>
);
}
*/
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', () => ({
}));
jest.mock('../../../../../../../api/project-badges', () => ({
- getProjectBadgesToken: jest.fn().mockResolvedValue('foo')
+ getProjectBadgesToken: jest.fn().mockResolvedValue('foo'),
+ renewProjectBadgesToken: jest.fn().mockResolvedValue({})
}));
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
[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}
/>
);
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>
`;
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.
#------------------------------------------------------------------------------
#
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:
+
#------------------------------------------------------------------------------
#