aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhilippe Perrin <philippe.perrin@sonarsource.com>2019-10-24 11:56:14 +0200
committerSonarTech <sonartech@sonarsource.com>2019-10-30 20:21:08 +0100
commitf8c9b9c8b3e7040907e0ded89511f86b827449ad (patch)
tree38faadc44bfd8d3b0da82fcd1ea7fe154f224afe
parent0ed5e825f6694b5f33d4234e9228ed7c6b9a236e (diff)
downloadsonarqube-f8c9b9c8b3e7040907e0ded89511f86b827449ad.tar.gz
sonarqube-f8c9b9c8b3e7040907e0ded89511f86b827449ad.zip
SONAR-12597 Rework Quality profile Web Api calls so they don't use deprecated parameters anymore
-rw-r--r--server/sonar-web/src/main/js/api/quality-profiles.ts115
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityProfiles/App.tsx47
-rw-r--r--server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/App-test.tsx116
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx88
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangelogContainer-test.tsx77
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/__snapshots__/ChangelogContainer-test.tsx.snap88
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/DeleteProfileForm.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/ExtendProfileForm.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/DeleteProfileForm-test.tsx56
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ExtendProfileForm-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileActions-test.tsx21
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/DeleteProfileForm-test.tsx.snap48
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/ProfileActions-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeParentForm.tsx20
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeProjectsForm.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileExporters.tsx18
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ChangeParentForm-test.tsx62
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ChangeProjectsForm-test.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileExporters-test.tsx41
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileInheritance-test.tsx71
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ChangeParentForm-test.tsx.snap88
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileExporters-test.tsx.snap29
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileInheritance-test.tsx.snap153
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/CreateProfileForm-test.tsx71
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/CreateProfileForm-test.tsx.snap111
-rw-r--r--server/sonar-web/src/main/js/helpers/testMocks.ts24
30 files changed, 1168 insertions, 217 deletions
diff --git a/server/sonar-web/src/main/js/api/quality-profiles.ts b/server/sonar-web/src/main/js/api/quality-profiles.ts
index e9f79660f51..6b6cefc529b 100644
--- a/server/sonar-web/src/main/js/api/quality-profiles.ts
+++ b/server/sonar-web/src/main/js/api/quality-profiles.ts
@@ -21,6 +21,7 @@ import { map } from 'lodash';
import { csvEscape } from 'sonar-ui-common/helpers/csv';
import { getJSON, post, postJSON, RequestData } from 'sonar-ui-common/helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';
+import { Exporter, ProfileChangelogEvent } from '../apps/quality-profiles/types';
export interface ProfileActions {
associateProjects?: boolean;
@@ -73,11 +74,14 @@ export function searchQualityProfiles(
return getJSON('/api/qualityprofiles/search', parameters).catch(throwGlobalError);
}
-export function getQualityProfile(data: {
+export function getQualityProfile({
+ compareToSonarWay,
+ profile: { key }
+}: {
compareToSonarWay?: boolean;
- profile: string;
+ profile: Profile;
}): Promise<any> {
- return getJSON('/api/qualityprofiles/show', data);
+ return getJSON('/api/qualityprofiles/show', { compareToSonarWay, key });
}
export function createQualityProfile(data: RequestData): Promise<any> {
@@ -101,18 +105,28 @@ export function getProfileProjects(
return getJSON('/api/qualityprofiles/projects', data).catch(throwGlobalError);
}
-export function getProfileInheritance(
- profileKey: string
-): Promise<{
+export function getProfileInheritance({
+ language,
+ name: qualityProfile,
+ organization
+}: Profile): Promise<{
ancestors: T.ProfileInheritanceDetails[];
children: T.ProfileInheritanceDetails[];
profile: T.ProfileInheritanceDetails;
}> {
- return getJSON('/api/qualityprofiles/inheritance', { profileKey }).catch(throwGlobalError);
+ return getJSON('/api/qualityprofiles/inheritance', {
+ language,
+ qualityProfile,
+ organization
+ }).catch(throwGlobalError);
}
-export function setDefaultProfile(profileKey: string) {
- return post('/api/qualityprofiles/set_default', { profileKey });
+export function setDefaultProfile({ language, name: qualityProfile, organization }: Profile) {
+ return post('/api/qualityprofiles/set_default', {
+ language,
+ qualityProfile,
+ organization
+ });
}
export function renameProfile(key: string, name: string) {
@@ -123,17 +137,45 @@ export function copyProfile(fromKey: string, toName: string): Promise<any> {
return postJSON('/api/qualityprofiles/copy', { fromKey, toName }).catch(throwGlobalError);
}
-export function deleteProfile(profileKey: string) {
- return post('/api/qualityprofiles/delete', { profileKey }).catch(throwGlobalError);
+export function deleteProfile({ language, name: qualityProfile, organization }: Profile) {
+ return post('/api/qualityprofiles/delete', { language, qualityProfile, organization }).catch(
+ throwGlobalError
+ );
}
-export function changeProfileParent(profileKey: string, parentKey: string) {
+export function changeProfileParent(
+ { language, name: qualityProfile, organization }: Profile,
+ parentProfile?: Profile
+) {
return post('/api/qualityprofiles/change_parent', {
- profileKey,
- parentKey
+ language,
+ qualityProfile,
+ organization,
+ parentQualityProfile: parentProfile ? parentProfile.name : undefined
}).catch(throwGlobalError);
}
+export function getQualityProfileBackupUrl({
+ language,
+ name: qualityProfile,
+ organization
+}: Profile) {
+ const queryParams = Object.entries({ language, qualityProfile, organization })
+ .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
+ .join('&');
+ return `/api/qualityprofiles/backup?${queryParams}`;
+}
+
+export function getQualityProfileExporterUrl(
+ { key: exporterKey }: Exporter,
+ { language, name: qualityProfile, organization }: Profile
+) {
+ const queryParams = Object.entries({ exporterKey, language, qualityProfile, organization })
+ .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
+ .join('&');
+ return `/api/qualityprofiles/export?${queryParams}`;
+}
+
export function getImporters(): Promise<
Array<{ key: string; languages: Array<string>; name: string }>
> {
@@ -144,8 +186,25 @@ export function getExporters(): Promise<any> {
return getJSON('/api/qualityprofiles/exporters').then(r => r.exporters);
}
-export function getProfileChangelog(data: RequestData): Promise<any> {
- return getJSON('/api/qualityprofiles/changelog', data);
+export function getProfileChangelog(
+ since: any,
+ to: any,
+ { language, name: qualityProfile, organization }: Profile,
+ page?: number
+): Promise<{
+ events: ProfileChangelogEvent[];
+ p: number;
+ ps: number;
+ total: number;
+}> {
+ return getJSON('/api/qualityprofiles/changelog', {
+ since,
+ to,
+ language,
+ qualityProfile,
+ organization,
+ p: page
+ });
}
export interface CompareResponse {
@@ -165,12 +224,28 @@ export function compareProfiles(leftKey: string, rightKey: string): Promise<Comp
return getJSON('/api/qualityprofiles/compare', { leftKey, rightKey });
}
-export function associateProject(key: string, project: string) {
- return post('/api/qualityprofiles/add_project', { key, project }).catch(throwGlobalError);
+export function associateProject(
+ { language, name: qualityProfile, organization }: Profile,
+ project: string
+) {
+ return post('/api/qualityprofiles/add_project', {
+ language,
+ qualityProfile,
+ organization,
+ project
+ }).catch(throwGlobalError);
}
-export function dissociateProject(key: string, project: string) {
- return post('/api/qualityprofiles/remove_project', { key, project }).catch(throwGlobalError);
+export function dissociateProject(
+ { language, name: qualityProfile, organization }: Profile,
+ project: string
+) {
+ return post('/api/qualityprofiles/remove_project', {
+ language,
+ qualityProfile,
+ organization,
+ project
+ }).catch(throwGlobalError);
}
export interface SearchUsersGroupsParameters {
diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/App.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/App.tsx
index 52b84a72ebf..5800b274ddb 100644
--- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/App.tsx
+++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/App.tsx
@@ -88,26 +88,37 @@ export default class QualityProfiles extends React.PureComponent<Props, State> {
handleChangeProfile = (oldKey: string, newKey: string) => {
const { component } = this.props;
const { allProfiles, profiles } = this.state;
+ const oldProfile = allProfiles && allProfiles.find(profile => profile.key === oldKey);
const newProfile = allProfiles && allProfiles.find(profile => profile.key === newKey);
- const request =
- newProfile && newProfile.isDefault
- ? dissociateProject(oldKey, component.key)
- : associateProject(newKey, component.key);
-
- return request.then(() => {
- if (this.mounted && profiles && newProfile) {
- // remove old profile, add new one
- const nextProfiles = [...profiles.filter(profile => profile.key !== oldKey), newProfile];
- this.setState({ profiles: nextProfiles });
-
- addGlobalSuccessMessage(
- translateWithParameters(
- 'project_quality_profile.successfully_updated',
- newProfile.languageName
- )
- );
+
+ let request;
+
+ if (newProfile) {
+ if (newProfile.isDefault && oldProfile) {
+ request = dissociateProject(oldProfile, component.key);
+ } else {
+ request = associateProject(newProfile, component.key);
}
- });
+ }
+
+ if (request) {
+ return request.then(() => {
+ if (this.mounted && profiles && newProfile) {
+ // remove old profile, add new one
+ const nextProfiles = [...profiles.filter(profile => profile.key !== oldKey), newProfile];
+ this.setState({ profiles: nextProfiles });
+
+ addGlobalSuccessMessage(
+ translateWithParameters(
+ 'project_quality_profile.successfully_updated',
+ newProfile.languageName
+ )
+ );
+ }
+ });
+ } else {
+ return Promise.resolve();
+ }
};
render() {
diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/App-test.tsx
index faa3780cb55..81b97c3d481 100644
--- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/App-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/App-test.tsx
@@ -17,11 +17,25 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-/* eslint-disable import/first */
+
+import { shallow } from 'enzyme';
+import * as React from 'react';
+import {
+ associateProject,
+ dissociateProject,
+ searchQualityProfiles
+} from '../../../api/quality-profiles';
+import handleRequiredAuthorization from '../../../app/utils/handleRequiredAuthorization';
+import { mockComponent, mockQualityProfile } from '../../../helpers/testMocks';
+import App from '../App';
+import Table from '../Table';
+
+beforeEach(() => jest.clearAllMocks());
+
jest.mock('../../../api/quality-profiles', () => ({
- associateProject: jest.fn(() => Promise.resolve()),
- dissociateProject: jest.fn(() => Promise.resolve()),
- searchQualityProfiles: jest.fn(() => Promise.resolve())
+ associateProject: jest.fn().mockResolvedValue({}),
+ dissociateProject: jest.fn().mockResolvedValue({}),
+ searchQualityProfiles: jest.fn().mockResolvedValue({})
}));
jest.mock('../../../app/utils/addGlobalSuccessMessage', () => ({
@@ -32,85 +46,47 @@ jest.mock('../../../app/utils/handleRequiredAuthorization', () => ({
default: jest.fn()
}));
-import { shallow } from 'enzyme';
-import * as React from 'react';
-import App from '../App';
-
-const associateProject = require('../../../api/quality-profiles').associateProject as jest.Mock<
- any
->;
-
-const dissociateProject = require('../../../api/quality-profiles').dissociateProject as jest.Mock<
- any
->;
-
-const searchQualityProfiles = require('../../../api/quality-profiles')
- .searchQualityProfiles as jest.Mock<any>;
-
-const addGlobalSuccessMessage = require('../../../app/utils/addGlobalSuccessMessage')
- .default as jest.Mock<any>;
-
-const handleRequiredAuthorization = require('../../../app/utils/handleRequiredAuthorization')
- .default as jest.Mock<any>;
-
-const component = {
- analysisDate: '',
- breadcrumbs: [],
- configuration: { showQualityProfiles: true },
- key: 'foo',
- name: 'foo',
- organization: 'org',
- qualifier: 'TRK',
- version: '0.0.1'
-};
+const component = mockComponent({ configuration: { showQualityProfiles: true } });
it('checks permissions', () => {
- handleRequiredAuthorization.mockClear();
- shallow(<App component={{ ...component, configuration: undefined }} />);
+ shallowRender({ component: { ...component, configuration: undefined } });
expect(handleRequiredAuthorization).toBeCalled();
});
it('fetches profiles', () => {
- searchQualityProfiles.mockClear();
- shallow(<App component={component} />);
- expect(searchQualityProfiles.mock.calls).toHaveLength(2);
- expect(searchQualityProfiles).toBeCalledWith({ organization: 'org' });
- expect(searchQualityProfiles).toBeCalledWith({ organization: 'org', project: 'foo' });
+ shallowRender();
+ expect(searchQualityProfiles).toHaveBeenCalledTimes(2);
+ expect(searchQualityProfiles).toBeCalledWith({ organization: component.organization });
+ expect(searchQualityProfiles).toBeCalledWith({
+ organization: component.organization,
+ project: component.key
+ });
});
it('changes profile', () => {
- associateProject.mockClear();
- dissociateProject.mockClear();
- addGlobalSuccessMessage.mockClear();
- const wrapper = shallow(<App component={component} />);
+ const wrapper = shallowRender();
- const fooJava = randomProfile('foo-java', 'java');
- const fooJs = randomProfile('foo-js', 'js');
- const allProfiles = [
- fooJava,
- randomProfile('bar-java', 'java'),
- randomProfile('baz-java', 'java', true),
- fooJs
- ];
+ const fooJava = mockQualityProfile({ key: 'foo-java', language: 'java' });
+ const fooJs = mockQualityProfile({ key: 'foo-js', language: 'js' });
+ const bar = mockQualityProfile({ key: 'bar-java', language: 'java' });
+ const baz = mockQualityProfile({ key: 'baz-java', language: 'java', isDefault: true });
+ const allProfiles = [fooJava, bar, baz, fooJs];
const profiles = [fooJava, fooJs];
wrapper.setState({ allProfiles, loading: false, profiles });
- wrapper.find('Table').prop<Function>('onChangeProfile')('foo-java', 'bar-java');
- expect(associateProject).toBeCalledWith('bar-java', 'foo');
-
- wrapper.find('Table').prop<Function>('onChangeProfile')('foo-java', 'baz-java');
- expect(dissociateProject).toBeCalledWith('foo-java', 'foo');
+ wrapper
+ .find(Table)
+ .props()
+ .onChangeProfile(fooJava.key, bar.key);
+ expect(associateProject).toBeCalledWith(bar, component.key);
+
+ wrapper
+ .find(Table)
+ .props()
+ .onChangeProfile(fooJava.key, baz.key);
+ expect(dissociateProject).toBeCalledWith(fooJava, component.key);
});
-function randomProfile(key: string, language: string, isDefault = false) {
- return {
- activeRuleCount: 17,
- activeDeprecatedRuleCount: 0,
- isDefault,
- key,
- name: key,
- language,
- languageName: language,
- organization: 'org'
- };
+function shallowRender(props: Partial<App['props']> = {}) {
+ return shallow<App>(<App component={component} {...props} />);
}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx
index aa92d3750f9..533796d856f 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx
@@ -18,17 +18,18 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { withRouter, WithRouterProps } from 'react-router';
+import { WithRouterProps } from 'react-router';
import { parseDate, toShortNotSoISOString } from 'sonar-ui-common/helpers/dates';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { getProfileChangelog } from '../../../api/quality-profiles';
+import { withRouter } from '../../../components/hoc/withRouter';
import { Profile, ProfileChangelogEvent } from '../types';
import { getProfileChangelogPath } from '../utils';
import Changelog from './Changelog';
import ChangelogEmpty from './ChangelogEmpty';
import ChangelogSearch from './ChangelogSearch';
-interface Props extends WithRouterProps {
+interface Props extends Pick<WithRouterProps, 'router' | 'location'> {
organization: string | null;
profile: Profile;
}
@@ -40,7 +41,7 @@ interface State {
total?: number;
}
-class ChangelogContainer extends React.PureComponent<Props, State> {
+export class ChangelogContainer extends React.PureComponent<Props, State> {
mounted = false;
state: State = { loading: true };
@@ -59,27 +60,31 @@ class ChangelogContainer extends React.PureComponent<Props, State> {
this.mounted = false;
}
- loadChangelog() {
- this.setState({ loading: true });
- const { query } = this.props.location;
- const data: any = { profileKey: this.props.profile.key };
- if (query.since) {
- data.since = query.since;
- }
- if (query.to) {
- data.to = query.to;
+ stopLoading() {
+ if (this.mounted) {
+ this.setState({ loading: false });
}
+ }
- getProfileChangelog(data).then((r: any) => {
- if (this.mounted) {
- this.setState({
- events: r.events,
- total: r.total,
- page: r.p,
- loading: false
- });
- }
- });
+ loadChangelog() {
+ this.setState({ loading: true });
+ const {
+ location: { query },
+ profile
+ } = this.props;
+
+ getProfileChangelog(query.since, query.to, profile)
+ .then((r: any) => {
+ if (this.mounted) {
+ this.setState({
+ events: r.events,
+ total: r.total,
+ page: r.p,
+ loading: false
+ });
+ }
+ })
+ .catch(this.stopLoading);
}
loadMore(event: React.SyntheticEvent<HTMLElement>) {
@@ -88,28 +93,23 @@ class ChangelogContainer extends React.PureComponent<Props, State> {
if (this.state.page != null) {
this.setState({ loading: true });
- const { query } = this.props.location;
- const data: any = {
- profileKey: this.props.profile.key,
- p: this.state.page + 1
- };
- if (query.since) {
- data.since = query.since;
- }
- if (query.to) {
- data.to = query.to;
- }
-
- getProfileChangelog(data).then((r: any) => {
- if (this.mounted && this.state.events) {
- this.setState({
- events: [...this.state.events, ...r.events],
- total: r.total,
- page: r.p,
- loading: false
- });
- }
- });
+ const {
+ location: { query },
+ profile
+ } = this.props;
+
+ getProfileChangelog(query.since, query.to, profile, this.state.page + 1)
+ .then((r: any) => {
+ if (this.mounted && this.state.events) {
+ this.setState({
+ events: [...this.state.events, ...r.events],
+ total: r.total,
+ page: r.p,
+ loading: false
+ });
+ }
+ })
+ .catch(this.stopLoading);
}
}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangelogContainer-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangelogContainer-test.tsx
new file mode 100644
index 00000000000..b892b56b4f8
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangelogContainer-test.tsx
@@ -0,0 +1,77 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { mockEvent, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
+import { getProfileChangelog } from '../../../../api/quality-profiles';
+import { mockLocation, mockQualityProfile, mockRouter } from '../../../../helpers/testMocks';
+import { ChangelogContainer } from '../ChangelogContainer';
+
+beforeEach(() => jest.clearAllMocks());
+
+jest.mock('../../../../api/quality-profiles', () => {
+ const { mockQualityProfileChangelogEvent } = require.requireActual(
+ '../../../../helpers/testMocks'
+ );
+ return {
+ getProfileChangelog: jest.fn().mockResolvedValue({
+ events: [
+ mockQualityProfileChangelogEvent(),
+ mockQualityProfileChangelogEvent(),
+ mockQualityProfileChangelogEvent()
+ ],
+ total: 6,
+ p: 1
+ })
+ };
+});
+
+it('should render correctly without events', async () => {
+ (getProfileChangelog as jest.Mock).mockResolvedValueOnce({ events: [] });
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should render correctly', async () => {
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should load more properly', async () => {
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+
+ wrapper.instance().loadMore(mockEvent());
+ expect(getProfileChangelog).toHaveBeenLastCalledWith(undefined, undefined, expect.anything(), 2);
+});
+
+function shallowRender() {
+ return shallow<ChangelogContainer>(
+ <ChangelogContainer
+ location={mockLocation()}
+ organization="TEST"
+ profile={mockQualityProfile()}
+ router={mockRouter()}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/__snapshots__/ChangelogContainer-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/__snapshots__/ChangelogContainer-test.tsx.snap
new file mode 100644
index 00000000000..788ec9aa5f6
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/__snapshots__/ChangelogContainer-test.tsx.snap
@@ -0,0 +1,88 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+ className="boxed-group boxed-group-inner js-profile-changelog"
+>
+ <header
+ className="spacer-bottom"
+ >
+ <ChangelogSearch
+ dateRange={
+ Object {
+ "from": undefined,
+ "to": undefined,
+ }
+ }
+ onDateRangeChange={[Function]}
+ onReset={[Function]}
+ />
+ </header>
+ <Changelog
+ events={
+ Array [
+ Object {
+ "action": "ACTIVATED",
+ "date": "2019-04-23T02:12:32+0100",
+ "params": Object {
+ "severity": "MAJOR",
+ },
+ "ruleKey": "rule-key",
+ "ruleName": "rule-name",
+ },
+ Object {
+ "action": "ACTIVATED",
+ "date": "2019-04-23T02:12:32+0100",
+ "params": Object {
+ "severity": "MAJOR",
+ },
+ "ruleKey": "rule-key",
+ "ruleName": "rule-name",
+ },
+ Object {
+ "action": "ACTIVATED",
+ "date": "2019-04-23T02:12:32+0100",
+ "params": Object {
+ "severity": "MAJOR",
+ },
+ "ruleKey": "rule-key",
+ "ruleName": "rule-name",
+ },
+ ]
+ }
+ organization="TEST"
+ />
+ <footer
+ className="text-center spacer-top small"
+ >
+ <a
+ href="#"
+ onClick={[Function]}
+ >
+ show_more
+ </a>
+ </footer>
+</div>
+`;
+
+exports[`should render correctly without events 1`] = `
+<div
+ className="boxed-group boxed-group-inner js-profile-changelog"
+>
+ <header
+ className="spacer-bottom"
+ >
+ <ChangelogSearch
+ dateRange={
+ Object {
+ "from": undefined,
+ "to": undefined,
+ }
+ }
+ onDateRangeChange={[Function]}
+ onReset={[Function]}
+ />
+ </header>
+ <ChangelogEmpty />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/DeleteProfileForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/DeleteProfileForm.tsx
index 2aaf88c3f84..72adade778f 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/components/DeleteProfileForm.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/DeleteProfileForm.tsx
@@ -51,7 +51,7 @@ export default class DeleteProfileForm extends React.PureComponent<Props, State>
handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
event.preventDefault();
this.setState({ loading: true });
- deleteProfile(this.props.profile.key).then(this.props.onDelete, () => {
+ deleteProfile(this.props.profile).then(this.props.onDelete, () => {
if (this.mounted) {
this.setState({ loading: false });
}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ExtendProfileForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/ExtendProfileForm.tsx
index 97d211b14c5..3fc46664230 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/components/ExtendProfileForm.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/ExtendProfileForm.tsx
@@ -77,7 +77,7 @@ export default class ExtendProfileForm extends React.PureComponent<Props, State>
try {
const { profile: newProfile } = await createQualityProfile(data);
- await changeProfileParent(newProfile.key, parentProfile.key);
+ await changeProfileParent(newProfile, parentProfile);
this.props.onExtend(newProfile.name);
} finally {
if (this.mounted) {
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx
index 29ba03a6321..f040f57773c 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx
@@ -23,7 +23,7 @@ import ActionsDropdown, {
ActionsDropdownItem
} from 'sonar-ui-common/components/controls/ActionsDropdown';
import { translate } from 'sonar-ui-common/helpers/l10n';
-import { setDefaultProfile } from '../../../api/quality-profiles';
+import { getQualityProfileBackupUrl, setDefaultProfile } from '../../../api/quality-profiles';
import { Router, withRouter } from '../../../components/hoc/withRouter';
import { getRulesUrl } from '../../../helpers/urls';
import { Profile } from '../types';
@@ -119,7 +119,7 @@ export class ProfileActions extends React.PureComponent<Props, State> {
};
handleSetDefaultClick = () => {
- setDefaultProfile(this.props.profile.key).then(this.props.updateProfiles, () => {});
+ setDefaultProfile(this.props.profile).then(this.props.updateProfiles, () => {});
};
navigateToNewProfile = (name: string) => {
@@ -137,11 +137,7 @@ export class ProfileActions extends React.PureComponent<Props, State> {
const { profile } = this.props;
const { actions = {} } = profile;
- // FIXME use org, name and lang
- const backupUrl =
- (window as any).baseUrl +
- '/api/qualityprofiles/backup?profileKey=' +
- encodeURIComponent(profile.key);
+ const backupUrl = `${(window as any).baseUrl}${getQualityProfileBackupUrl(profile)}`;
const activateMoreUrl = getRulesUrl(
{
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/DeleteProfileForm-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/DeleteProfileForm-test.tsx
new file mode 100644
index 00000000000..88fb1084c4c
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/DeleteProfileForm-test.tsx
@@ -0,0 +1,56 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
+import { deleteProfile } from '../../../../api/quality-profiles';
+import { mockEvent, mockQualityProfile } from '../../../../helpers/testMocks';
+import DeleteProfileForm from '../DeleteProfileForm';
+
+beforeEach(() => jest.clearAllMocks());
+
+jest.mock('../../../../api/quality-profiles', () => ({
+ deleteProfile: jest.fn().mockResolvedValue({})
+}));
+
+it('should render correctly', () => {
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should handle form submit correctly', async () => {
+ const wrapper = shallowRender();
+ wrapper.instance().handleFormSubmit(mockEvent());
+ await waitAndUpdate(wrapper);
+
+ expect(deleteProfile).toHaveBeenCalled();
+});
+
+function shallowRender(props: Partial<DeleteProfileForm['props']> = {}) {
+ return shallow<DeleteProfileForm>(
+ <DeleteProfileForm
+ onClose={jest.fn()}
+ onDelete={jest.fn()}
+ profile={mockQualityProfile()}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ExtendProfileForm-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ExtendProfileForm-test.tsx
index 868e03c1b7b..2f2e055cd45 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ExtendProfileForm-test.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ExtendProfileForm-test.tsx
@@ -53,7 +53,7 @@ it('should correctly create a new profile and extend the existing one', async ()
data.append('name', name);
data.append('organization', organization);
expect(createQualityProfile).toHaveBeenCalledWith(data);
- expect(changeProfileParent).toHaveBeenCalledWith('new-profile', profile.key);
+ expect(changeProfileParent).toHaveBeenCalledWith({ key: 'new-profile' }, profile);
});
function shallowRender(props: Partial<ExtendProfileForm['props']> = {}) {
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileActions-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileActions-test.tsx
index 0af06aa6a84..60b6345c821 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileActions-test.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileActions-test.tsx
@@ -20,9 +20,17 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { click, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
+import { setDefaultProfile } from '../../../../api/quality-profiles';
import { mockQualityProfile, mockRouter } from '../../../../helpers/testMocks';
import { ProfileActions } from '../ProfileActions';
+beforeEach(() => jest.clearAllMocks());
+
+jest.mock('../../../../api/quality-profiles', () => ({
+ ...jest.requireActual('../../../../api/quality-profiles'),
+ setDefaultProfile: jest.fn().mockResolvedValue({})
+}));
+
const PROFILE = mockQualityProfile({
activeRuleCount: 68,
activeDeprecatedRuleCount: 0,
@@ -105,9 +113,20 @@ it('should extend profile', async () => {
expect(wrapper.find('ExtendProfileForm').exists()).toBe(false);
});
+it('should delete profile properly', async () => {
+ const updateProfiles = jest.fn();
+
+ const wrapper = shallowRender({ updateProfiles });
+ wrapper.instance().handleSetDefaultClick();
+ await waitAndUpdate(wrapper);
+
+ expect(setDefaultProfile).toHaveBeenCalledWith(PROFILE);
+ expect(updateProfiles).toHaveBeenCalled();
+});
+
function shallowRender(props: Partial<ProfileActions['props']> = {}) {
const router = mockRouter();
- return shallow(
+ return shallow<ProfileActions>(
<ProfileActions
organization="org"
profile={PROFILE}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/DeleteProfileForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/DeleteProfileForm-test.tsx.snap
new file mode 100644
index 00000000000..41855ab772c
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/DeleteProfileForm-test.tsx.snap
@@ -0,0 +1,48 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Modal
+ contentLabel="quality_profiles.delete_confirm_title"
+ onRequestClose={[MockFunction]}
+>
+ <form
+ id="delete-profile-form"
+ onSubmit={[Function]}
+ >
+ <div
+ className="modal-head"
+ >
+ <h2>
+ quality_profiles.delete_confirm_title
+ </h2>
+ </div>
+ <div
+ className="modal-body"
+ >
+ <div
+ className="js-modal-messages"
+ />
+ <p>
+ quality_profiles.are_you_sure_want_delete_profile_x.name.JavaScript
+ </p>
+ </div>
+ <div
+ className="modal-foot"
+ >
+ <SubmitButton
+ className="button-red"
+ disabled={false}
+ id="delete-profile-submit"
+ >
+ delete
+ </SubmitButton>
+ <ResetButtonLink
+ id="delete-profile-cancel"
+ onClick={[MockFunction]}
+ >
+ cancel
+ </ResetButtonLink>
+ </div>
+ </form>
+</Modal>
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/ProfileActions-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/ProfileActions-test.tsx.snap
index b7c5aff79f9..d630932363d 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/ProfileActions-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/ProfileActions-test.tsx.snap
@@ -22,7 +22,7 @@ exports[`renders with all permissions 1`] = `
</ActionsDropdownItem>
<ActionsDropdownItem
download="key.xml"
- to="/api/qualityprofiles/backup?profileKey=key"
+ to="/api/qualityprofiles/backup?language=js&qualityProfile=name&organization=org"
>
<span
data-test="quality-profiles__backup"
@@ -103,7 +103,7 @@ exports[`renders with no permissions 1`] = `
<ActionsDropdown>
<ActionsDropdownItem
download="key.xml"
- to="/api/qualityprofiles/backup?profileKey=key"
+ to="/api/qualityprofiles/backup?language=js&qualityProfile=name&organization=org"
>
<span
data-test="quality-profiles__backup"
@@ -154,7 +154,7 @@ exports[`renders with permission to edit only 1`] = `
</ActionsDropdownItem>
<ActionsDropdownItem
download="key.xml"
- to="/api/qualityprofiles/backup?profileKey=key"
+ to="/api/qualityprofiles/backup?language=js&qualityProfile=name&organization=org"
>
<span
data-test="quality-profiles__backup"
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeParentForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeParentForm.tsx
index 41aa3af09f8..bb82c400ad3 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeParentForm.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeParentForm.tsx
@@ -60,18 +60,16 @@ export default class ChangeParentForm extends React.PureComponent<Props, State>
handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
event.preventDefault();
- const parent = this.state.selected;
+ const parent = this.props.profiles.find(p => p.key === this.state.selected);
- if (parent != null) {
- this.setState({ loading: true });
- changeProfileParent(this.props.profile.key, parent)
- .then(this.props.onChange)
- .catch(() => {
- if (this.mounted) {
- this.setState({ loading: false });
- }
- });
- }
+ this.setState({ loading: true });
+ changeProfileParent(this.props.profile, parent)
+ .then(this.props.onChange)
+ .catch(() => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ });
};
render() {
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeProjectsForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeProjectsForm.tsx
index 559a4bb9f6c..de25da3fda0 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeProjectsForm.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeProjectsForm.tsx
@@ -101,7 +101,7 @@ export default class ChangeProjectsForm extends React.PureComponent<Props, State
});
handleSelect = (key: string) =>
- associateProject(this.props.profile.key, key).then(() => {
+ associateProject(this.props.profile, key).then(() => {
if (this.mounted) {
this.setState((state: State) => ({
needToReload: true,
@@ -111,7 +111,7 @@ export default class ChangeProjectsForm extends React.PureComponent<Props, State
});
handleUnselect = (key: string) =>
- dissociateProject(this.props.profile.key, key).then(() => {
+ dissociateProject(this.props.profile, key).then(() => {
if (this.mounted) {
this.setState((state: State) => ({
needToReload: true,
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileExporters.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileExporters.tsx
index f0908d704af..4f6481d8a03 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileExporters.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileExporters.tsx
@@ -17,9 +17,9 @@
* 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 * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n';
+import { getQualityProfileExporterUrl } from '../../../api/quality-profiles';
import { Exporter, Profile } from '../types';
interface Props {
@@ -30,18 +30,8 @@ interface Props {
export default class ProfileExporters extends React.PureComponent<Props> {
getExportUrl(exporter: Exporter) {
- const { organization, profile } = this.props;
-
- const path = '/api/qualityprofiles/export';
- const parameters = {
- exporterKey: exporter.key,
- language: profile.language,
- qualityProfile: profile.name
- };
- if (organization) {
- Object.assign(parameters, { organization });
- }
- return (window as any).baseUrl + path + '?' + stringify(parameters);
+ const { profile } = this.props;
+ return `${(window as any).baseUrl}${getQualityProfileExporterUrl(exporter, profile)}`;
}
render() {
@@ -62,7 +52,7 @@ export default class ProfileExporters extends React.PureComponent<Props> {
className={index > 0 ? 'spacer-top' : undefined}
data-key={exporter.key}
key={exporter.key}>
- <a href={this.getExportUrl(exporter)} target="_blank">
+ <a href={this.getExportUrl(exporter)} rel="noopener noreferrer" target="_blank">
{exporter.name}
</a>
</li>
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.tsx
index 98c4879c012..77fc90669f2 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.tsx
@@ -65,7 +65,7 @@ export default class ProfileInheritance extends React.PureComponent<Props, State
}
loadData() {
- getProfileInheritance(this.props.profile.key).then(
+ getProfileInheritance(this.props.profile).then(
r => {
if (this.mounted) {
const { ancestors, children } = r;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx
index 0e6c50c1482..da067067b1d 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx
@@ -84,7 +84,7 @@ export default class ProfileRules extends React.PureComponent<Props, State> {
}
return getQualityProfile({
compareToSonarWay: true,
- profile: this.props.profile.key
+ profile: this.props.profile
});
}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ChangeParentForm-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ChangeParentForm-test.tsx
new file mode 100644
index 00000000000..4d2ca1fcfb8
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ChangeParentForm-test.tsx
@@ -0,0 +1,62 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { mockEvent, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
+import { mockQualityProfile } from '../../../../helpers/testMocks';
+import ChangeParentForm from '../ChangeParentForm';
+
+beforeEach(() => jest.clearAllMocks());
+
+jest.mock('../../../../api/quality-profiles', () => ({
+ changeProfileParent: jest.fn().mockResolvedValue({})
+}));
+
+it('should render correctly', () => {
+ expect(shallowRender()).toMatchSnapshot();
+});
+
+it("should handle form' submit correcty", async () => {
+ const onChange = jest.fn();
+
+ const wrapper = shallowRender({ onChange });
+ wrapper.instance().handleFormSubmit(mockEvent());
+ await waitAndUpdate(wrapper);
+
+ expect(onChange).toHaveBeenCalled();
+});
+
+function shallowRender(props?: Partial<ChangeParentForm['props']>) {
+ return shallow<ChangeParentForm>(
+ <ChangeParentForm
+ onChange={jest.fn()}
+ onClose={jest.fn()}
+ profile={mockQualityProfile()}
+ profiles={[
+ mockQualityProfile(),
+ mockQualityProfile(),
+ mockQualityProfile(),
+ mockQualityProfile()
+ ]}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ChangeProjectsForm-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ChangeProjectsForm-test.tsx
index b8928ecf7d9..fb10d9571d8 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ChangeProjectsForm-test.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ChangeProjectsForm-test.tsx
@@ -87,7 +87,7 @@ it('should handle selection properly', async () => {
wrapper.instance().handleSelect('toto');
await waitAndUpdate(wrapper);
- expect(associateProject).toHaveBeenCalledWith(profile.key, 'toto');
+ expect(associateProject).toHaveBeenCalledWith(profile, 'toto');
expect(wrapper.state().needToReload).toBe(true);
});
@@ -96,7 +96,7 @@ it('should handle deselection properly', async () => {
wrapper.instance().handleUnselect('tata');
await waitAndUpdate(wrapper);
- expect(dissociateProject).toHaveBeenCalledWith(profile.key, 'tata');
+ expect(dissociateProject).toHaveBeenCalledWith(profile, 'tata');
expect(wrapper.state().needToReload).toBe(true);
});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileExporters-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileExporters-test.tsx
new file mode 100644
index 00000000000..d4d415a8a57
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileExporters-test.tsx
@@ -0,0 +1,41 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { mockQualityProfile, mockQualityProfileExporter } from '../../../../helpers/testMocks';
+import ProfileExporters from '../ProfileExporters';
+
+it('should render correctly', () => {
+ const wrapper = shallowRender();
+ expect(wrapper).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<ProfileExporters['props']> = {}) {
+ const profile = mockQualityProfile();
+ return shallow<ProfileExporters>(
+ <ProfileExporters
+ exporters={[mockQualityProfileExporter({ languages: [profile.language] })]}
+ organization="test-org"
+ profile={profile}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileInheritance-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileInheritance-test.tsx
new file mode 100644
index 00000000000..f532c6ee1d5
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileInheritance-test.tsx
@@ -0,0 +1,71 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
+import { mockQualityProfile, mockQualityProfileInheritance } from '../../../../helpers/testMocks';
+import ProfileInheritance from '../ProfileInheritance';
+
+beforeEach(() => jest.clearAllMocks());
+
+jest.mock('../../../../api/quality-profiles', () => ({
+ getProfileInheritance: jest.fn().mockResolvedValue({
+ children: [mockQualityProfileInheritance()],
+ ancestors: [mockQualityProfileInheritance()]
+ })
+}));
+
+it('should render correctly', async () => {
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should render modal correctly', async () => {
+ const wrapper = shallowRender();
+ wrapper.instance().handleChangeParentClick();
+ await waitAndUpdate(wrapper);
+
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should handle parent change correctly', async () => {
+ const updateProfiles = jest.fn().mockResolvedValueOnce({});
+
+ const wrapper = shallowRender({ updateProfiles });
+ wrapper.instance().handleParentChange();
+ await waitAndUpdate(wrapper);
+
+ expect(updateProfiles).toHaveBeenCalled();
+});
+
+function shallowRender(props: Partial<ProfileInheritance['props']> = {}) {
+ return shallow<ProfileInheritance>(
+ <ProfileInheritance
+ organization={null}
+ profile={mockQualityProfile()}
+ profiles={[mockQualityProfile()]}
+ updateProfiles={jest.fn()}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ChangeParentForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ChangeParentForm-test.tsx.snap
new file mode 100644
index 00000000000..81929a3bd56
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ChangeParentForm-test.tsx.snap
@@ -0,0 +1,88 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Modal
+ contentLabel="quality_profiles.change_parent"
+ onRequestClose={[MockFunction]}
+ size="small"
+>
+ <form
+ id="change-profile-parent-form"
+ onSubmit={[Function]}
+ >
+ <div
+ className="modal-head"
+ >
+ <h2>
+ quality_profiles.change_parent
+ </h2>
+ </div>
+ <div
+ className="modal-body"
+ >
+ <div
+ className="modal-field"
+ >
+ <label
+ htmlFor="change-profile-parent"
+ >
+ quality_profiles.parent
+
+ <em
+ className="mandatory"
+ >
+ *
+ </em>
+ </label>
+ <Select
+ clearable={false}
+ id="change-profile-parent"
+ name="parentKey"
+ onChange={[Function]}
+ options={
+ Array [
+ Object {
+ "label": "none",
+ "value": "",
+ },
+ Object {
+ "label": "name",
+ "value": "key",
+ },
+ Object {
+ "label": "name",
+ "value": "key",
+ },
+ Object {
+ "label": "name",
+ "value": "key",
+ },
+ Object {
+ "label": "name",
+ "value": "key",
+ },
+ ]
+ }
+ value=""
+ />
+ </div>
+ </div>
+ <div
+ className="modal-foot"
+ >
+ <SubmitButton
+ disabled={true}
+ id="change-profile-parent-submit"
+ >
+ change_verb
+ </SubmitButton>
+ <ResetButtonLink
+ id="change-profile-parent-cancel"
+ onClick={[MockFunction]}
+ >
+ cancel
+ </ResetButtonLink>
+ </div>
+ </form>
+</Modal>
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileExporters-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileExporters-test.tsx.snap
new file mode 100644
index 00000000000..5f17cbf43d9
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileExporters-test.tsx.snap
@@ -0,0 +1,29 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+ className="boxed-group quality-profile-exporters"
+>
+ <h2>
+ quality_profiles.exporters
+ </h2>
+ <div
+ className="boxed-group-inner"
+ >
+ <ul>
+ <li
+ data-key="exporter-key"
+ key="exporter-key"
+ >
+ <a
+ href="/api/qualityprofiles/export?exporterKey=exporter-key&language=js&qualityProfile=name&organization=foo"
+ rel="noopener noreferrer"
+ target="_blank"
+ >
+ exporter-name
+ </a>
+ </li>
+ </ul>
+ </div>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileInheritance-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileInheritance-test.tsx.snap
new file mode 100644
index 00000000000..f3e83c49c72
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileInheritance-test.tsx.snap
@@ -0,0 +1,153 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+ className="boxed-group quality-profile-inheritance"
+>
+ <header
+ className="boxed-group-header"
+ >
+ <h2>
+ quality_profiles.profile_inheritance
+ </h2>
+ </header>
+ <div
+ className="boxed-group-inner"
+ >
+ <table
+ className="data zebra"
+ >
+ <tbody>
+ <ProfileInheritanceBox
+ depth={0}
+ key="foo"
+ language="js"
+ organization={null}
+ profile={
+ Object {
+ "activeRuleCount": 4,
+ "isBuiltIn": false,
+ "key": "foo",
+ "name": "Foo",
+ "overridingRuleCount": 0,
+ }
+ }
+ type="ancestor"
+ />
+ <ProfileInheritanceBox
+ depth={2}
+ key="foo"
+ language="js"
+ organization={null}
+ profile={
+ Object {
+ "activeRuleCount": 4,
+ "isBuiltIn": false,
+ "key": "foo",
+ "name": "Foo",
+ "overridingRuleCount": 0,
+ }
+ }
+ type="child"
+ />
+ </tbody>
+ </table>
+ </div>
+</div>
+`;
+
+exports[`should render modal correctly 1`] = `
+<div
+ className="boxed-group quality-profile-inheritance"
+>
+ <header
+ className="boxed-group-header"
+ >
+ <h2>
+ quality_profiles.profile_inheritance
+ </h2>
+ </header>
+ <div
+ className="boxed-group-inner"
+ >
+ <table
+ className="data zebra"
+ >
+ <tbody>
+ <ProfileInheritanceBox
+ depth={0}
+ key="foo"
+ language="js"
+ organization={null}
+ profile={
+ Object {
+ "activeRuleCount": 4,
+ "isBuiltIn": false,
+ "key": "foo",
+ "name": "Foo",
+ "overridingRuleCount": 0,
+ }
+ }
+ type="ancestor"
+ />
+ <ProfileInheritanceBox
+ depth={2}
+ key="foo"
+ language="js"
+ organization={null}
+ profile={
+ Object {
+ "activeRuleCount": 4,
+ "isBuiltIn": false,
+ "key": "foo",
+ "name": "Foo",
+ "overridingRuleCount": 0,
+ }
+ }
+ type="child"
+ />
+ </tbody>
+ </table>
+ </div>
+ <ChangeParentForm
+ onChange={[Function]}
+ onClose={[Function]}
+ profile={
+ Object {
+ "activeDeprecatedRuleCount": 2,
+ "activeRuleCount": 10,
+ "childrenCount": 0,
+ "depth": 1,
+ "isBuiltIn": false,
+ "isDefault": false,
+ "isInherited": false,
+ "key": "key",
+ "language": "js",
+ "languageName": "JavaScript",
+ "name": "name",
+ "organization": "foo",
+ "projectCount": 3,
+ }
+ }
+ profiles={
+ Array [
+ Object {
+ "activeDeprecatedRuleCount": 2,
+ "activeRuleCount": 10,
+ "childrenCount": 0,
+ "depth": 1,
+ "isBuiltIn": false,
+ "isDefault": false,
+ "isInherited": false,
+ "key": "key",
+ "language": "js",
+ "languageName": "JavaScript",
+ "name": "name",
+ "organization": "foo",
+ "projectCount": 3,
+ },
+ ]
+ }
+ />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx
index 249064bc116..ae864c25d78 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx
@@ -99,9 +99,12 @@ export default class CreateProfileForm extends React.PureComponent<Props, State>
try {
const { profile } = await createQualityProfile(data);
- if (this.state.parent) {
- await changeProfileParent(profile.key, this.state.parent);
+
+ const parentProfile = this.props.profiles.find(p => p.key === this.state.parent);
+ if (parentProfile) {
+ await changeProfileParent(profile, parentProfile);
}
+
this.props.onCreate(profile);
} finally {
if (this.mounted) {
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/CreateProfileForm-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/CreateProfileForm-test.tsx
index d36546a0126..74fe138dae4 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/CreateProfileForm-test.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/CreateProfileForm-test.tsx
@@ -17,27 +17,68 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockEvent, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
+import { changeProfileParent, createQualityProfile } from '../../../../api/quality-profiles';
import { mockQualityProfile } from '../../../../helpers/testMocks';
import CreateProfileForm from '../CreateProfileForm';
+beforeEach(() => jest.clearAllMocks());
+
jest.mock('../../../../api/quality-profiles', () => ({
- changeProfileParent: jest.fn(),
- createQualityProfile: jest.fn(),
- getImporters: jest.fn().mockResolvedValue([])
+ changeProfileParent: jest.fn().mockResolvedValue({}),
+ createQualityProfile: jest.fn().mockResolvedValue({}),
+ getImporters: jest.fn().mockResolvedValue([
+ {
+ key: 'key_importer',
+ languages: ['lang1_importer', 'lang2_importer', 'kr'],
+ name: 'name_importer'
+ }
+ ])
}));
-it('should render correctly', () => {
- expect(
- shallow(
- <CreateProfileForm
- languages={[{ key: 'kr', name: 'Hangeul' }]}
- onClose={jest.fn()}
- onCreate={jest.fn()}
- organization="org"
- profiles={[mockQualityProfile()]}
- />
- )
- ).toMatchSnapshot();
+it('should render correctly', async () => {
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should handle form submit correctly', async () => {
+ const onCreate = jest.fn();
+
+ const wrapper = shallowRender({ onCreate });
+ wrapper.instance().handleParentChange({ value: 'key' });
+ wrapper.instance().handleFormSubmit(mockEvent({ currentTarget: undefined }));
+ await waitAndUpdate(wrapper);
+
+ expect(createQualityProfile).toHaveBeenCalled();
+ expect(changeProfileParent).toHaveBeenCalled();
+ expect(onCreate).toHaveBeenCalled();
});
+
+it('should handle form submit without parent correctly', async () => {
+ const onCreate = jest.fn();
+
+ const wrapper = shallowRender({ onCreate });
+ wrapper.instance().handleFormSubmit(mockEvent({ currentTarget: undefined }));
+ await waitAndUpdate(wrapper);
+
+ expect(createQualityProfile).toHaveBeenCalled();
+ expect(changeProfileParent).not.toHaveBeenCalled();
+ expect(onCreate).toHaveBeenCalled();
+});
+
+function shallowRender(props?: Partial<CreateProfileForm['props']>) {
+ return shallow<CreateProfileForm>(
+ <CreateProfileForm
+ languages={[{ key: 'kr', name: 'Hangeul' }]}
+ onClose={jest.fn()}
+ onCreate={jest.fn()}
+ organization="org"
+ profiles={[mockQualityProfile()]}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/CreateProfileForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/CreateProfileForm-test.tsx.snap
index e4c75d36c7e..6efd6aef32a 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/CreateProfileForm-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/CreateProfileForm-test.tsx.snap
@@ -20,13 +20,120 @@ exports[`should render correctly 1`] = `
<div
className="modal-body"
>
- <i
- className="spinner"
+ <div
+ className="modal-field"
+ >
+ <label
+ htmlFor="create-profile-name"
+ >
+ name
+ <em
+ className="mandatory"
+ >
+ *
+ </em>
+ </label>
+ <input
+ autoFocus={true}
+ id="create-profile-name"
+ maxLength={100}
+ name="name"
+ onChange={[Function]}
+ required={true}
+ size={50}
+ type="text"
+ value=""
+ />
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label
+ htmlFor="create-profile-language"
+ >
+ language
+ <em
+ className="mandatory"
+ >
+ *
+ </em>
+ </label>
+ <Select
+ clearable={false}
+ id="create-profile-language"
+ name="language"
+ onChange={[Function]}
+ options={
+ Array [
+ Object {
+ "label": "Hangeul",
+ "value": "kr",
+ },
+ ]
+ }
+ value="kr"
+ />
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label
+ htmlFor="create-profile-parent"
+ >
+ quality_profiles.parent
+ </label>
+ <Select
+ clearable={true}
+ id="create-profile-parent"
+ name="parentKey"
+ onChange={[Function]}
+ options={
+ Array [
+ Object {
+ "label": "none",
+ "value": "",
+ },
+ ]
+ }
+ value=""
+ />
+ </div>
+ <div
+ className="modal-field spacer-bottom js-importer"
+ data-key="key_importer"
+ key="key_importer"
+ >
+ <label
+ htmlFor="create-profile-form-backup-key_importer"
+ >
+ name_importer
+ </label>
+ <input
+ id="create-profile-form-backup-key_importer"
+ name="backup_key_importer"
+ type="file"
+ />
+ <p
+ className="note"
+ >
+ quality_profiles.optional_configuration_file
+ </p>
+ </div>
+ <input
+ name="hello-ie11"
+ type="hidden"
+ value=""
/>
</div>
<div
className="modal-foot"
>
+ <SubmitButton
+ disabled={false}
+ id="create-profile-submit"
+ >
+ create
+ </SubmitButton>
<ResetButtonLink
id="create-profile-cancel"
onClick={[MockFunction]}
diff --git a/server/sonar-web/src/main/js/helpers/testMocks.ts b/server/sonar-web/src/main/js/helpers/testMocks.ts
index c667b17d2d1..e6b2119daf1 100644
--- a/server/sonar-web/src/main/js/helpers/testMocks.ts
+++ b/server/sonar-web/src/main/js/helpers/testMocks.ts
@@ -22,7 +22,7 @@ import { Location } from 'history';
import { InjectedRouter } from 'react-router';
import { createStore, Store } from 'redux';
import { DocumentationEntry } from '../apps/documentation/utils';
-import { Profile } from '../apps/quality-profiles/types';
+import { Exporter, Profile } from '../apps/quality-profiles/types';
export function mockAlmApplication(overrides: Partial<T.AlmApplication> = {}): T.AlmApplication {
return {
@@ -583,6 +583,28 @@ export function mockQualityProfileInheritance(
};
}
+export function mockQualityProfileChangelogEvent(eventOverride?: any) {
+ return {
+ action: 'ACTIVATED',
+ date: '2019-04-23T02:12:32+0100',
+ params: {
+ severity: 'MAJOR'
+ },
+ ruleKey: 'rule-key',
+ ruleName: 'rule-name',
+ ...eventOverride
+ };
+}
+
+export function mockQualityProfileExporter(override?: Partial<Exporter>): Exporter {
+ return {
+ key: 'exporter-key',
+ name: 'exporter-name',
+ languages: ['first-lang', 'second-lang'],
+ ...override
+ };
+}
+
export function mockQualityGateProjectStatus(
overrides: Partial<T.QualityGateProjectStatus> = {}
): T.QualityGateProjectStatus {