Browse Source

SONAR-1330 Allow only authorized actions on the Quality Profiles page

tags/6.6-RC1
Stas Vilchik 6 years ago
parent
commit
77d3e365ac
21 changed files with 517 additions and 92 deletions
  1. 18
    2
      server/sonar-web/src/main/js/api/quality-profiles.ts
  2. 2
    2
      server/sonar-web/src/main/js/apps/projectQualityProfiles/App.tsx
  3. 1
    1
      server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/App-test.tsx
  4. 10
    13
      server/sonar-web/src/main/js/apps/quality-profiles/components/App.tsx
  5. 7
    11
      server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx
  6. 0
    2
      server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.tsx
  7. 77
    0
      server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileActions-test.tsx
  8. 0
    4
      server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileContainer-test.tsx
  9. 172
    0
      server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/ProfileActions-test.tsx.snap
  10. 5
    7
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.tsx
  11. 0
    2
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.tsx
  12. 3
    3
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.tsx
  13. 6
    5
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx
  14. 2
    2
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx
  15. 53
    0
      server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileDetails-test.tsx
  16. 8
    14
      server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.tsx
  17. 133
    0
      server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileDetails-test.tsx.snap
  18. 2
    1
      server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.tsx
  19. 3
    2
      server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx
  20. 1
    3
      server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.tsx
  21. 14
    18
      server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.tsx

+ 18
- 2
server/sonar-web/src/main/js/api/quality-profiles.ts View File

@@ -29,7 +29,18 @@ import {
import { Paging } from '../app/types';
import throwGlobalError from '../app/utils/throwGlobalError';

export interface ProfileActions {
copy?: boolean;
edit?: boolean;
setAsDefault?: boolean;
}

export interface Actions {
create?: boolean;
}

export interface Profile {
actions?: ProfileActions;
key: string;
name: string;
language: string;
@@ -56,10 +67,15 @@ export interface SearchQualityProfilesParameters {
qualityProfile?: string;
}

export interface SearchQualityProfilesResponse {
actions?: Actions;
profiles: Profile[];
}

export function searchQualityProfiles(
parameters: SearchQualityProfilesParameters
): Promise<Profile[]> {
return getJSON('/api/qualityprofiles/search', parameters).then(r => r.profiles);
): Promise<SearchQualityProfilesResponse> {
return getJSON('/api/qualityprofiles/search', parameters);
}

export function getQualityProfile(data: {

+ 2
- 2
server/sonar-web/src/main/js/apps/projectQualityProfiles/App.tsx View File

@@ -70,8 +70,8 @@ export default class QualityProfiles extends React.PureComponent<Props, State> {
const { component } = this.props;
const organization = this.props.customOrganizations ? component.organization : undefined;
Promise.all([
searchQualityProfiles({ organization }),
searchQualityProfiles({ organization, project: component.key })
searchQualityProfiles({ organization }).then(r => r.profiles),
searchQualityProfiles({ organization, project: component.key }).then(r => r.profiles)
]).then(
([allProfiles, profiles]) => {
if (this.mounted) {

+ 1
- 1
server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/App-test.tsx View File

@@ -20,7 +20,7 @@
jest.mock('../../../api/quality-profiles', () => ({
associateProject: jest.fn(() => Promise.resolve()),
dissociateProject: jest.fn(() => Promise.resolve()),
searchQualityProfiles: jest.fn()
searchQualityProfiles: jest.fn(() => Promise.resolve())
}));

jest.mock('../../../app/utils/addGlobalSuccessMessage', () => ({

+ 10
- 13
server/sonar-web/src/main/js/apps/quality-profiles/components/App.tsx View File

@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { searchQualityProfiles, getExporters } from '../../../api/quality-profiles';
import { searchQualityProfiles, getExporters, Actions } from '../../../api/quality-profiles';
import { sortProfiles } from '../utils';
import { translate } from '../../../helpers/l10n';
import OrganizationHelmet from '../../../components/common/OrganizationHelmet';
@@ -27,13 +27,13 @@ import { Exporter, Profile } from '../types';

interface Props {
children: React.ReactElement<any>;
currentUser: { permissions: { global: Array<string> } };
languages: Array<{}>;
onRequestFail: (reasong: any) => void;
organization: { name: string; canAdmin?: boolean; key: string } | null;
organization: { name: string; key: string } | null;
}

interface State {
actions?: Actions;
loading: boolean;
exporters?: Exporter[];
profiles?: Profile[];
@@ -73,10 +73,11 @@ export default class App extends React.PureComponent<Props, State> {
this.setState({ loading: true });
Promise.all([getExporters(), this.fetchProfiles()]).then(responses => {
if (this.mounted) {
const [exporters, profiles] = responses;
const [exporters, profilesResponse] = responses;
this.setState({
actions: profilesResponse.actions,
exporters,
profiles: sortProfiles(profiles),
profiles: sortProfiles(profilesResponse.profiles),
loading: false
});
}
@@ -84,9 +85,9 @@ export default class App extends React.PureComponent<Props, State> {
}

updateProfiles = () => {
return this.fetchProfiles().then((profiles: any) => {
return this.fetchProfiles().then(r => {
if (this.mounted) {
this.setState({ profiles: sortProfiles(profiles) });
this.setState({ profiles: sortProfiles(r.profiles) });
}
});
};
@@ -98,18 +99,14 @@ export default class App extends React.PureComponent<Props, State> {
const { organization } = this.props;
const finalLanguages = Object.values(this.props.languages);

const canAdmin = organization
? organization.canAdmin
: this.props.currentUser.permissions.global.includes('profileadmin');

return React.cloneElement(this.props.children, {
actions: this.state.actions || {},
profiles: this.state.profiles,
languages: finalLanguages,
exporters: this.state.exporters,
updateProfiles: this.updateProfiles,
onRequestFail: this.props.onRequestFail,
organization: organization ? organization.key : null,
canAdmin
organization: organization ? organization.key : null
});
}


+ 7
- 11
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx View File

@@ -30,7 +30,6 @@ import { getProfilePath, getProfileComparePath, getProfilesPath } from '../utils
import { Profile } from '../types';

interface Props {
canAdmin: boolean;
fromList?: boolean;
onRequestFail: (reasong: any) => void;
organization: string | null;
@@ -45,10 +44,6 @@ interface State {
}

export default class ProfileActions extends React.PureComponent<Props, State> {
static defaultProps = {
fromList: false
};

static contextTypes = {
router: PropTypes.object
};
@@ -119,7 +114,8 @@ export default class ProfileActions extends React.PureComponent<Props, State> {
};

render() {
const { profile, canAdmin } = this.props;
const { profile } = this.props;
const { actions = {} } = profile;

// FIXME use org, name and lang
const backupUrl =
@@ -137,7 +133,7 @@ export default class ProfileActions extends React.PureComponent<Props, State> {

return (
<ul className="dropdown-menu dropdown-menu-right">
{canAdmin &&
{actions.edit &&
!profile.isBuiltIn && (
<li>
<Link to={activateMoreUrl}>{translate('quality_profiles.activate_more_rules')}</Link>
@@ -157,14 +153,14 @@ export default class ProfileActions extends React.PureComponent<Props, State> {
{translate('compare')}
</Link>
</li>
{canAdmin && (
{actions.copy && (
<li>
<a id="quality-profile-copy" href="#" onClick={this.handleCopyClick}>
{translate('copy')}
</a>
</li>
)}
{canAdmin &&
{actions.edit &&
!profile.isBuiltIn && (
<li>
<a id="quality-profile-rename" href="#" onClick={this.handleRenameClick}>
@@ -172,7 +168,7 @@ export default class ProfileActions extends React.PureComponent<Props, State> {
</a>
</li>
)}
{canAdmin &&
{actions.setAsDefault &&
!profile.isDefault && (
<li>
<a id="quality-profile-set-as-default" href="#" onClick={this.handleSetDefaultClick}>
@@ -180,7 +176,7 @@ export default class ProfileActions extends React.PureComponent<Props, State> {
</a>
</li>
)}
{canAdmin &&
{actions.edit &&
!profile.isDefault &&
!profile.isBuiltIn && (
<li>

+ 0
- 2
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.tsx View File

@@ -24,7 +24,6 @@ import ProfileHeader from '../details/ProfileHeader';
import { Profile } from '../types';

interface Props {
canAdmin: boolean;
children: React.ReactElement<any>;
location: {
pathname: string;
@@ -87,7 +86,6 @@ export default class ProfileContainer extends React.PureComponent<Props> {
<div id="quality-profile">
<Helmet title={profile.name} />
<ProfileHeader
canAdmin={this.props.canAdmin}
onRequestFail={this.props.onRequestFail}
organization={organization}
profile={profile}

+ 77
- 0
server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileActions-test.tsx View File

@@ -0,0 +1,77 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 ProfileActions from '../ProfileActions';

const PROFILE = {
activeRuleCount: 68,
activeDeprecatedRuleCount: 0,
childrenCount: 0,
depth: 0,
isBuiltIn: false,
isDefault: false,
isInherited: false,
key: 'foo',
language: 'java',
languageName: 'Java',
name: 'Foo',
organization: 'org',
rulesUpdatedAt: '2017-06-28T12:58:44+0000'
};

it('renders with no permissions', () => {
expect(
shallow(
<ProfileActions
onRequestFail={jest.fn()}
organization="org"
profile={PROFILE}
updateProfiles={jest.fn()}
/>
)
).toMatchSnapshot();
});

it('renders with permission to edit', () => {
expect(
shallow(
<ProfileActions
onRequestFail={jest.fn()}
organization="org"
profile={{ ...PROFILE, actions: { edit: true } }}
updateProfiles={jest.fn()}
/>
)
).toMatchSnapshot();
});

it('renders with all permissions', () => {
expect(
shallow(
<ProfileActions
onRequestFail={jest.fn()}
organization="org"
profile={{ ...PROFILE, actions: { copy: true, edit: true, setAsDefault: true } }}
updateProfiles={jest.fn()}
/>
)
).toMatchSnapshot();
});

+ 0
- 4
server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileContainer-test.tsx View File

@@ -31,7 +31,6 @@ it('should render ProfileHeader', () => {
const updateProfiles = jest.fn();
const output = shallow(
<ProfileContainer
canAdmin={false}
location={{ pathname: '', query: { language: 'js', name: 'fake' } }}
onRequestFail={jest.fn()}
organization={null}
@@ -44,7 +43,6 @@ it('should render ProfileHeader', () => {
const header = output.find(ProfileHeader);
expect(header.length).toBe(1);
expect(header.prop('profile')).toBe(targetProfile);
expect(header.prop('canAdmin')).toBe(false);
expect(header.prop('updateProfiles')).toBe(updateProfiles);
});

@@ -55,7 +53,6 @@ it('should render ProfileNotFound', () => {
];
const output = shallow(
<ProfileContainer
canAdmin={false}
location={{ pathname: '', query: { language: 'js', name: 'random' } }}
onRequestFail={jest.fn()}
organization={null}
@@ -73,7 +70,6 @@ it('should render Helmet', () => {
const updateProfiles = jest.fn();
const output = shallow(
<ProfileContainer
canAdmin={false}
location={{ pathname: '', query: { language: 'js', name: 'First Profile' } }}
onRequestFail={jest.fn()}
organization={null}

+ 172
- 0
server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/__snapshots__/ProfileActions-test.tsx.snap View File

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

exports[`renders with all permissions 1`] = `
<ul
className="dropdown-menu dropdown-menu-right"
>
<li>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to="/organizations/org/rules#qprofile=foo|activation=false"
>
quality_profiles.activate_more_rules
</Link>
</li>
<li>
<a
href="/api/qualityprofiles/backup?profileKey=foo"
id="quality-profile-backup"
>
backup_verb
</a>
</li>
<li>
<Link
id="quality-profile-compare"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/organizations/org/quality_profiles/compare",
"query": Object {
"language": "java",
"name": "Foo",
},
}
}
>
compare
</Link>
</li>
<li>
<a
href="#"
id="quality-profile-copy"
onClick={[Function]}
>
copy
</a>
</li>
<li>
<a
href="#"
id="quality-profile-rename"
onClick={[Function]}
>
rename
</a>
</li>
<li>
<a
href="#"
id="quality-profile-set-as-default"
onClick={[Function]}
>
set_as_default
</a>
</li>
<li>
<a
href="#"
id="quality-profile-delete"
onClick={[Function]}
>
delete
</a>
</li>
</ul>
`;

exports[`renders with no permissions 1`] = `
<ul
className="dropdown-menu dropdown-menu-right"
>
<li>
<a
href="/api/qualityprofiles/backup?profileKey=foo"
id="quality-profile-backup"
>
backup_verb
</a>
</li>
<li>
<Link
id="quality-profile-compare"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/organizations/org/quality_profiles/compare",
"query": Object {
"language": "java",
"name": "Foo",
},
}
}
>
compare
</Link>
</li>
</ul>
`;

exports[`renders with permission to edit 1`] = `
<ul
className="dropdown-menu dropdown-menu-right"
>
<li>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to="/organizations/org/rules#qprofile=foo|activation=false"
>
quality_profiles.activate_more_rules
</Link>
</li>
<li>
<a
href="/api/qualityprofiles/backup?profileKey=foo"
id="quality-profile-backup"
>
backup_verb
</a>
</li>
<li>
<Link
id="quality-profile-compare"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/organizations/org/quality_profiles/compare",
"query": Object {
"language": "java",
"name": "Foo",
},
}
}
>
compare
</Link>
</li>
<li>
<a
href="#"
id="quality-profile-rename"
onClick={[Function]}
>
rename
</a>
</li>
<li>
<a
href="#"
id="quality-profile-delete"
onClick={[Function]}
>
delete
</a>
</li>
</ul>
`;

+ 5
- 7
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.tsx View File

@@ -26,7 +26,6 @@ import ProfilePermissions from './ProfilePermissions';
import { Exporter, Profile } from '../types';

interface Props {
canAdmin: boolean;
exporters: Exporter[];
onRequestFail: (reasong: any) => void;
organization: string | null;
@@ -36,18 +35,17 @@ interface Props {
}

export default function ProfileDetails(props: Props) {
const { profile } = props;
return (
<div>
<div className="quality-profile-grid">
<div className="quality-profile-grid-left">
<ProfileRules {...props} />
<ProfileExporters {...props} />
{props.canAdmin &&
!props.profile.isBuiltIn && (
<ProfilePermissions
organization={props.organization || undefined}
profile={props.profile}
/>
{profile.actions &&
profile.actions.edit &&
!profile.isBuiltIn && (
<ProfilePermissions organization={props.organization || undefined} profile={profile} />
)}
</div>
<div className="quality-profile-grid-right">

+ 0
- 2
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.tsx View File

@@ -33,7 +33,6 @@ import {
import { Profile } from '../types';

interface Props {
canAdmin: boolean;
onRequestFail: (reasong: any) => void;
profile: Profile;
organization: string | null;
@@ -113,7 +112,6 @@ export default class ProfileHeader extends React.PureComponent<Props> {
{translate('actions')} <i className="icon-dropdown" />
</button>
<ProfileActions
canAdmin={this.props.canAdmin}
onRequestFail={this.props.onRequestFail}
organization={organization}
profile={profile}

+ 3
- 3
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.tsx View File

@@ -26,7 +26,6 @@ import { getProfileInheritance } from '../../../api/quality-profiles';
import { Profile } from '../types';

interface Props {
canAdmin: boolean;
onRequestFail: (reason: any) => void;
organization: string | null;
profile: Profile;
@@ -120,8 +119,9 @@ export default class ProfileInheritance extends React.PureComponent<Props, State

return (
<div className="boxed-group quality-profile-inheritance">
{this.props.canAdmin &&
!this.props.profile.isBuiltIn && (
{profile.actions &&
profile.actions.edit &&
!profile.isBuiltIn && (
<div className="boxed-group-actions">
<button className="pull-right js-change-parent" onClick={this.handleChangeParentClick}>
{translate('quality_profiles.change_parent')}

+ 6
- 5
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx View File

@@ -26,7 +26,6 @@ import { translate } from '../../../helpers/l10n';
import { Profile } from '../types';

interface Props {
canAdmin: boolean;
organization: string | null;
profile: Profile;
updateProfiles: () => Promise<void>;
@@ -127,10 +126,12 @@ export default class ProfileProjects extends React.PureComponent<Props, State> {
}

render() {
const { profile } = this.props;
return (
<div className="boxed-group quality-profile-projects">
{this.props.canAdmin &&
!this.props.profile.isDefault && (
{profile.actions &&
profile.actions.edit &&
!profile.isDefault && (
<div className="boxed-group-actions">
<button className="js-change-projects" onClick={this.handleChangeClick}>
{translate('quality_profiles.change_projects')}
@@ -145,7 +146,7 @@ export default class ProfileProjects extends React.PureComponent<Props, State> {
<div className="boxed-group-inner">
{this.state.loading ? (
<i className="spinner" />
) : this.props.profile.isDefault ? (
) : profile.isDefault ? (
this.renderDefault()
) : (
this.renderProjects()
@@ -156,7 +157,7 @@ export default class ProfileProjects extends React.PureComponent<Props, State> {
<ChangeProjectsForm
onClose={this.closeForm}
organization={this.props.organization}
profile={this.props.profile}
profile={profile}
/>
)}
</div>

+ 2
- 2
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx View File

@@ -33,7 +33,6 @@ import { Profile } from '../types';
const TYPES = ['BUG', 'VULNERABILITY', 'CODE_SMELL'];

interface Props {
canAdmin: boolean;
organization: string | null;
profile: Profile;
}
@@ -181,7 +180,8 @@ export default class ProfileRules extends React.PureComponent<Props, State> {
</tbody>
</table>

{this.props.canAdmin &&
{profile.actions &&
profile.actions.edit &&
!profile.isBuiltIn && (
<div className="text-right big-spacer-top">
<Link to={activateMoreUrl} className="button js-activate-rules">

+ 53
- 0
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileDetails-test.tsx View File

@@ -0,0 +1,53 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 ProfileDetails from '../ProfileDetails';
import { Profile } from '../../types';

it('renders without permissions', () => {
expect(
shallow(
<ProfileDetails
exporters={[]}
onRequestFail={jest.fn()}
organization="org"
profile={{} as Profile}
profiles={[]}
updateProfiles={jest.fn()}
/>
)
).toMatchSnapshot();
});

it('renders with edit permission', () => {
expect(
shallow(
<ProfileDetails
exporters={[]}
onRequestFail={jest.fn()}
organization="org"
profile={{ actions: { edit: true } } as Profile}
profiles={[]}
updateProfiles={jest.fn()}
/>
)
).toMatchSnapshot();
});

+ 8
- 14
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.tsx View File

@@ -40,6 +40,8 @@ const PROFILE = {
rulesUpdatedAt: '2017-06-28T12:58:44+0000'
};

const EDITABLE_PROFILE = { ...PROFILE, actions: { edit: true } };

const apiResponseAll = {
total: 243,
facets: [
@@ -81,7 +83,7 @@ const apiResponseActive = {
});

it('should render the quality profiles rules with sonarway comparison', () => {
const wrapper = shallow(<ProfileRules canAdmin={false} organization="foo" profile={PROFILE} />);
const wrapper = shallow(<ProfileRules organization="foo" profile={PROFILE} />);
const instance = wrapper.instance() as any;
instance.mounted = true;
instance.loadRules();
@@ -93,16 +95,15 @@ it('should render the quality profiles rules with sonarway comparison', () => {
});

it('should show a button to activate more rules for admins', () => {
const wrapper = shallow(<ProfileRules canAdmin={true} organization="foo" profile={PROFILE} />);
const wrapper = shallow(<ProfileRules organization="foo" profile={EDITABLE_PROFILE} />);
expect(wrapper.find('.js-activate-rules')).toMatchSnapshot();
});

it('should show a deprecated rules warning message', () => {
const wrapper = shallow(
<ProfileRules
canAdmin={true}
organization="foo"
profile={{ ...PROFILE, activeDeprecatedRuleCount: 8 }}
profile={{ ...EDITABLE_PROFILE, activeDeprecatedRuleCount: 8 }}
/>
);
expect(wrapper.find('ProfileRulesDeprecatedWarning')).toMatchSnapshot();
@@ -110,14 +111,7 @@ it('should show a deprecated rules warning message', () => {

it('should not show a button to activate more rules on built in profiles', () => {
const wrapper = shallow(
<ProfileRules canAdmin={true} organization={null} profile={{ ...PROFILE, isBuiltIn: true }} />
);
expect(wrapper.find('.js-activate-rules')).toHaveLength(0);
});

it('should not show a button to activate more rules on built in profiles', () => {
const wrapper = shallow(
<ProfileRules canAdmin={true} organization={null} profile={{ ...PROFILE, isBuiltIn: true }} />
<ProfileRules organization={null} profile={{ ...EDITABLE_PROFILE, isBuiltIn: true }} />
);
expect(wrapper.find('.js-activate-rules')).toHaveLength(0);
});
@@ -125,7 +119,7 @@ it('should not show a button to activate more rules on built in profiles', () =>
it('should not show sonarway comparison for built in profiles', () => {
(apiQP as any).getQualityProfile = jest.fn(() => Promise.resolve());
const wrapper = shallow(
<ProfileRules canAdmin={true} organization={null} profile={{ ...PROFILE, isBuiltIn: true }} />
<ProfileRules organization={null} profile={{ ...PROFILE, isBuiltIn: true }} />
);
const instance = wrapper.instance() as any;
instance.mounted = true;
@@ -147,7 +141,7 @@ it('should not show sonarway comparison if there is no missing rules', () => {
}
})
);
const wrapper = shallow(<ProfileRules canAdmin={true} organization={null} profile={PROFILE} />);
const wrapper = shallow(<ProfileRules organization={null} profile={PROFILE} />);
const instance = wrapper.instance() as any;
instance.mounted = true;
instance.loadRules();

+ 133
- 0
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileDetails-test.tsx.snap View File

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

exports[`renders with edit permission 1`] = `
<div>
<div
className="quality-profile-grid"
>
<div
className="quality-profile-grid-left"
>
<ProfileRules
exporters={Array []}
onRequestFail={[Function]}
organization="org"
profile={
Object {
"actions": Object {
"edit": true,
},
}
}
profiles={Array []}
updateProfiles={[Function]}
/>
<ProfileExporters
exporters={Array []}
onRequestFail={[Function]}
organization="org"
profile={
Object {
"actions": Object {
"edit": true,
},
}
}
profiles={Array []}
updateProfiles={[Function]}
/>
<ProfilePermissions
organization="org"
profile={
Object {
"actions": Object {
"edit": true,
},
}
}
/>
</div>
<div
className="quality-profile-grid-right"
>
<ProfileInheritance
exporters={Array []}
onRequestFail={[Function]}
organization="org"
profile={
Object {
"actions": Object {
"edit": true,
},
}
}
profiles={Array []}
updateProfiles={[Function]}
/>
<ProfileProjects
exporters={Array []}
onRequestFail={[Function]}
organization="org"
profile={
Object {
"actions": Object {
"edit": true,
},
}
}
profiles={Array []}
updateProfiles={[Function]}
/>
</div>
</div>
</div>
`;

exports[`renders without permissions 1`] = `
<div>
<div
className="quality-profile-grid"
>
<div
className="quality-profile-grid-left"
>
<ProfileRules
exporters={Array []}
onRequestFail={[Function]}
organization="org"
profile={Object {}}
profiles={Array []}
updateProfiles={[Function]}
/>
<ProfileExporters
exporters={Array []}
onRequestFail={[Function]}
organization="org"
profile={Object {}}
profiles={Array []}
updateProfiles={[Function]}
/>
</div>
<div
className="quality-profile-grid-right"
>
<ProfileInheritance
exporters={Array []}
onRequestFail={[Function]}
organization="org"
profile={Object {}}
profiles={Array []}
updateProfiles={[Function]}
/>
<ProfileProjects
exporters={Array []}
onRequestFail={[Function]}
organization="org"
profile={Object {}}
profiles={Array []}
updateProfiles={[Function]}
/>
</div>
</div>
</div>
`;

+ 2
- 1
server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.tsx View File

@@ -22,9 +22,10 @@ import PageHeader from './PageHeader';
import Evolution from './Evolution';
import ProfilesList from './ProfilesList';
import { Profile } from '../types';
import { Actions } from '../../../api/quality-profiles';

interface Props {
canAdmin: boolean;
actions: Actions;
languages: Array<{ key: string; name: string }>;
location: { query: { [p: string]: string } };
onRequestFail: (reason: any) => void;

+ 3
- 2
server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx View File

@@ -24,9 +24,10 @@ import RestoreProfileForm from './RestoreProfileForm';
import { getProfilePath } from '../utils';
import { translate } from '../../../helpers/l10n';
import { Profile } from '../types';
import { Actions } from '../../../api/quality-profiles';

interface Props {
canAdmin: boolean;
actions: Actions;
languages: Array<{ key: string; name: string }>;
onRequestFail: (reason: any) => void;
organization: string | null;
@@ -80,7 +81,7 @@ export default class PageHeader extends React.PureComponent<Props, State> {
<header className="page-header">
<h1 className="page-title">{translate('quality_profiles.page')}</h1>

{this.props.canAdmin && (
{this.props.actions.create && (
<div className="page-actions button-group dropdown">
<button id="quality-profiles-create" onClick={this.handleCreateClick}>
{translate('create')}

+ 1
- 3
server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.tsx View File

@@ -25,7 +25,6 @@ import { translate, translateWithParameters } from '../../../helpers/l10n';
import { Profile } from '../types';

interface Props {
canAdmin: boolean;
languages: Array<{ key: string; name: string }>;
location: { query: { [p: string]: string } };
onRequestFail: (reason: any) => void;
@@ -38,7 +37,6 @@ export default class ProfilesList extends React.PureComponent<Props> {
renderProfiles(profiles: Profile[]) {
return profiles.map(profile => (
<ProfilesListRow
canAdmin={this.props.canAdmin}
key={profile.key}
onRequestFail={this.props.onRequestFail}
organization={this.props.organization}
@@ -67,7 +65,7 @@ export default class ProfilesList extends React.PureComponent<Props> {
<th className="text-right nowrap">{translate('quality_profiles.list.rules')}</th>
<th className="text-right nowrap">{translate('quality_profiles.list.updated')}</th>
<th className="text-right nowrap">{translate('quality_profiles.list.used')}</th>
{this.props.canAdmin && <th>&nbsp;</th>}
<th>&nbsp;</th>
</tr>
</thead>
);

+ 14
- 18
server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.tsx View File

@@ -30,7 +30,6 @@ import { Profile } from '../types';
import Tooltip from '../../../components/controls/Tooltip';

interface Props {
canAdmin: boolean;
onRequestFail: (reason: any) => void;
organization: string | null;
profile: Profile;
@@ -139,23 +138,20 @@ export default class ProfilesListRow extends React.PureComponent<Props> {
<td className="quality-profiles-table-date thin nowrap text-right">
{this.renderUsageDate()}
</td>
{this.props.canAdmin && (
<td className="quality-profiles-table-actions thin nowrap text-right">
<div className="dropdown">
<button className="dropdown-toggle" data-toggle="dropdown">
<i className="icon-dropdown" />
</button>
<ProfileActions
canAdmin={this.props.canAdmin}
fromList={true}
onRequestFail={this.props.onRequestFail}
organization={this.props.organization}
profile={this.props.profile}
updateProfiles={this.props.updateProfiles}
/>
</div>
</td>
)}
<td className="quality-profiles-table-actions thin nowrap text-right">
<div className="dropdown">
<button className="dropdown-toggle" data-toggle="dropdown">
<i className="icon-dropdown" />
</button>
<ProfileActions
fromList={true}
onRequestFail={this.props.onRequestFail}
organization={this.props.organization}
profile={this.props.profile}
updateProfiles={this.props.updateProfiles}
/>
</div>
</td>
</tr>
);
}

Loading…
Cancel
Save