@@ -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: { |
@@ -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) { |
@@ -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', () => ({ |
@@ -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 | |||
}); | |||
} | |||
@@ -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> |
@@ -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} |
@@ -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(); | |||
}); |
@@ -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} |
@@ -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> | |||
`; |
@@ -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"> |
@@ -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} |
@@ -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')} |
@@ -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> |
@@ -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"> |
@@ -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(); | |||
}); |
@@ -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(); |
@@ -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> | |||
`; |
@@ -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; |
@@ -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')} |
@@ -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> </th>} | |||
<th> </th> | |||
</tr> | |||
</thead> | |||
); |
@@ -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> | |||
); | |||
} |