inheritance?: string;
is_template?: string;
languages?: string;
- organization: string | undefined;
q?: string;
qprofile?: string;
repositories?: string;
*/
import { getJSON, post, postJSON } from 'sonar-ui-common/helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';
+import { GetRulesAppResponse, SearchRulesResponse } from '../types/coding-rules';
-export interface GetRulesAppResponse {
- canWrite?: boolean;
- repositories: { key: string; language: string; name: string }[];
-}
-
-export function getRulesApp(organization?: string): Promise<GetRulesAppResponse> {
- return getJSON('/api/rules/app', { organization }).catch(throwGlobalError);
-}
-
-export interface SearchRulesResponse {
- actives?: T.Dict<T.RuleActivation[]>;
- facets?: { property: string; values: { count: number; val: string }[] }[];
- p: number;
- ps: number;
- rules: T.Rule[];
- total: number;
+export function getRulesApp(): Promise<GetRulesAppResponse> {
+ return getJSON('/api/rules/app').catch(throwGlobalError);
}
export function searchRules(data: {
export function getRuleDetails(parameters: {
actives?: boolean;
key: string;
- organization?: string;
}): Promise<{ actives?: T.RuleActivation[]; rule: T.RuleDetails }> {
return getJSON('/api/rules/show', parameters).catch(throwGlobalError);
}
-export function getRuleTags(parameters: {
- organization?: string;
- ps?: number;
- q: string;
-}): Promise<string[]> {
+export function getRuleTags(parameters: { ps?: number; q: string }): Promise<string[]> {
return getJSON('/api/rules/tags', parameters).then(r => r.tags, throwGlobalError);
}
custom_key: string;
markdown_description: string;
name: string;
- organization?: string;
params?: string;
prevent_reactivation?: boolean;
severity?: string;
markdown_description?: string;
markdown_note?: string;
name?: string;
- organization?: string;
params?: string;
remediation_fn_base_effort?: string;
remediation_fn_type?: string;
import FiltersHeader from '../../../components/common/FiltersHeader';
import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
import '../../../components/search-navigator.css';
-import { hasPrivateAccess } from '../../../helpers/organizations';
import { isLoggedIn } from '../../../helpers/users';
-import {
- getAppState,
- getCurrentUser,
- getLanguages,
- getMyOrganizations,
- Store
-} from '../../../store/rootReducer';
+import { getCurrentUser, getLanguages, Store } from '../../../store/rootReducer';
import { SecurityStandard } from '../../../types/security';
import {
shouldOpenSonarSourceSecurityFacet,
const MAX_SEARCH_LENGTH = 200;
const LIMIT_BEFORE_LOAD_MORE = 5;
-interface StateToProps {
- appState: T.AppState;
+interface Props extends WithRouterProps {
currentUser: T.CurrentUser;
languages: T.Languages;
- userOrganizations: T.Organization[];
-}
-
-interface OwnProps extends WithRouterProps {
- organization: T.Organization | undefined;
}
-type Props = OwnProps & StateToProps;
-
interface State {
actives?: Actives;
canWrite?: boolean;
getSearchParameters = () => ({
f: this.getFieldsToFetch().join(),
facets: this.getFacetsToFetch().join(),
- organization: this.props.organization && this.props.organization.key,
ps: PAGE_SIZE,
s: 'name',
...serializeQuery(this.state.query)
fetchInitialData = () => {
this.setState({ loading: true });
- const organization = this.props.organization && this.props.organization.key;
- Promise.all([getRulesApp(organization), this.fetchQualityProfiles()]).then(
+ Promise.all([getRulesApp(), searchQualityProfiles()]).then(
([{ canWrite, repositories }, { profiles }]) => {
this.setState({
canWrite,
}, this.stopLoading);
};
- fetchQualityProfiles = () => {
- const { currentUser, organization, userOrganizations } = this.props;
- if (hasPrivateAccess(currentUser, organization, userOrganizations)) {
- return searchQualityProfiles();
- }
- return { profiles: [] };
- };
-
getSelectedIndex = ({ selected, rules } = this.state) => {
const index = rules.findIndex(rule => rule.key === selected);
return index !== -1 ? index : undefined;
renderBulkButton = () => {
const { currentUser, languages } = this.props;
const { canWrite, paging, query, referencedProfiles } = this.state;
- const organization = this.props.organization && this.props.organization.key;
if (!isLoggedIn(currentUser) || !canWrite) {
return null;
paging && (
<BulkChange
languages={languages}
- organization={organization}
query={query}
referencedProfiles={referencedProfiles}
total={paging.total}
render() {
const { paging, rules } = this.state;
const selectedIndex = this.getSelectedIndex();
- const organization = this.props.organization && this.props.organization.key;
- const hideQualityProfiles = !hasPrivateAccess(
- this.props.currentUser,
- this.props.organization,
- this.props.userOrganizations
- );
return (
<>
/>
<FacetsList
facets={this.state.facets}
- hideProfileFacet={hideQualityProfiles}
onFacetToggle={this.handleFacetToggle}
onFilterChange={this.handleFilterChange}
openFacets={this.state.openFacets}
- organization={organization}
- organizationsEnabled={this.props.appState.organizationsEnabled}
query={this.state.query}
referencedProfiles={this.state.referencedProfiles}
referencedRepositories={this.state.referencedRepositories}
<div className="layout-page-main-inner">
{this.state.openRule ? (
<RuleDetails
- allowCustomRules={!this.props.appState.organizationsEnabled}
+ allowCustomRules={true}
canWrite={this.state.canWrite}
- hideQualityProfiles={hideQualityProfiles}
onActivate={this.handleRuleActivate}
onDeactivate={this.handleRuleDeactivate}
onDelete={this.handleRuleDelete}
onDeactivate={this.handleRuleDeactivate}
onFilterChange={this.handleFilterChange}
onOpen={this.handleRuleOpen}
- organization={organization}
rule={rule}
selected={rule.key === this.state.selected}
selectedProfile={this.getSelectedProfile()}
}
const mapStateToProps = (state: Store) => ({
- appState: getAppState(state),
currentUser: getCurrentUser(state),
- languages: getLanguages(state),
- userOrganizations: getMyOrganizations(state)
+ languages: getLanguages(state)
});
export default withRouter(connect(mapStateToProps)(App));
import { Button } from 'sonar-ui-common/components/controls/buttons';
import Dropdown from 'sonar-ui-common/components/controls/Dropdown';
import Tooltip from 'sonar-ui-common/components/controls/Tooltip';
+import { PopupPlacement } from 'sonar-ui-common/components/ui/popups';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { Profile } from '../../../api/quality-profiles';
import { Query } from '../query';
import BulkChangeModal from './BulkChangeModal';
-import { PopupPlacement } from 'sonar-ui-common/components/ui/popups';
interface Props {
languages: T.Languages;
- organization: string | undefined;
query: Query;
referencedProfiles: T.Dict<Profile>;
total: number;
action={this.state.action}
languages={this.props.languages}
onClose={this.closeModal}
- organization={this.props.organization}
profile={this.state.profile}
query={this.props.query}
referencedProfiles={this.props.referencedProfiles}
action: string;
languages: T.Languages;
onClose: () => void;
- organization: string | undefined;
profile?: Profile;
query: Query;
referencedProfiles: T.Dict<Profile>;
: this.state.selectedProfiles;
for (const profile of profiles) {
- looper = looper.then(() =>
- method({
- ...data,
- organization: this.props.organization,
- targetKey: profile
- }).then(response => this.processResponse(profile, response))
- );
+ looper = looper
+ .then(() =>
+ method({
+ ...data,
+ targetKey: profile
+ })
+ )
+ .then(response => this.processResponse(profile, response));
}
return looper;
};
import TemplateFacet from './TemplateFacet';
import TypeFacet from './TypeFacet';
-interface Props {
+export interface FacetsListProps {
facets?: Facets;
hideProfileFacet?: boolean;
onFacetToggle: (facet: string) => void;
onFilterChange: (changes: Partial<Query>) => void;
openFacets: OpenFacets;
- organization: string | undefined;
- organizationsEnabled?: boolean;
query: Query;
referencedProfiles: T.Dict<Profile>;
referencedRepositories: T.Dict<{ key: string; language: string; name: string }>;
selectedProfile?: Profile;
}
-export default function FacetsList(props: Props) {
+export default function FacetsList(props: FacetsListProps) {
const languageDisabled = !props.hideProfileFacet && props.query.profile !== undefined;
const inheritanceDisabled =
onChange={props.onFilterChange}
onToggle={props.onFacetToggle}
open={!!props.openFacets.tags}
- organization={props.organization}
stats={props.facets && props.facets.tags}
values={props.query.tags}
/>
open={!!props.openFacets.availableSince}
value={props.query.availableSince}
/>
- {!props.organizationsEnabled && (
- <TemplateFacet
- onChange={props.onFilterChange}
- onToggle={props.onFacetToggle}
- open={!!props.openFacets.template}
- value={props.query.template}
- />
- )}
+ <TemplateFacet
+ onChange={props.onFilterChange}
+ onToggle={props.onFacetToggle}
+ open={!!props.openFacets.template}
+ value={props.query.template}
+ />
{!props.hideProfileFacet && (
<>
<ProfileFacet
onDeactivate: (profile: string, rule: string) => void;
onFilterChange: (changes: Partial<Query>) => void;
onOpen: (ruleKey: string) => void;
- organization: string | undefined;
rule: T.Rule;
selected: boolean;
selectedProfile?: Profile;
if (this.props.selectedProfile) {
const data = {
key: this.props.selectedProfile.key,
- organization: this.props.organization,
rule: this.props.rule.key
};
deactivateRule(data).then(
import ListStyleFacet from '../../../components/facet/ListStyleFacet';
import { BasicProps } from './Facet';
-interface Props extends BasicProps {
- organization: string | undefined;
-}
-
-export default class TagFacet extends React.PureComponent<Props> {
+export default class TagFacet extends React.PureComponent<BasicProps> {
handleSearch = (query: string) => {
- return getRuleTags({ organization: this.props.organization, ps: 50, q: query }).then(tags => ({
+ return getRuleTags({ ps: 50, q: query }).then(tags => ({
paging: { pageIndex: 1, pageSize: tags.length, total: tags.length },
results: tags
}));
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { getRulesApp } from '../../../../api/rules';
import ScreenPositionHelper from '../../../../components/common/ScreenPositionHelper';
-import {
- mockAppState,
- mockCurrentUser,
- mockLocation,
- mockOrganization,
- mockRouter,
- mockRule
-} from '../../../../helpers/testMocks';
+import { mockCurrentUser, mockLocation, mockRouter, mockRule } from '../../../../helpers/testMocks';
import { App } from '../App';
jest.mock('../../../../components/common/ScreenPositionHelper');
});
function shallowRender(props: Partial<App['props']> = {}) {
- const organization = mockOrganization();
return shallow<App>(
<App
- appState={mockAppState()}
currentUser={mockCurrentUser({
isLoggedIn: true
})}
languages={{ js: { key: 'js', name: 'JavaScript' } }}
location={mockLocation()}
- organization={organization}
params={{}}
router={mockRouter()}
routes={[]}
- userOrganizations={[organization]}
{...props}
/>
);
return shallow<BulkChange>(
<BulkChange
languages={{ js: { key: 'js', name: 'JavaScript' } }}
- organization={undefined}
query={{ activation: false, profile: 'key' } as BulkChange['props']['query']}
referencedProfiles={{ key: profile }}
total={2}
action="activate"
languages={{ js: mockLanguage() }}
onClose={jest.fn()}
- organization={undefined}
profile={mockQualityProfile()}
query={{ languages: ['js'] } as Query}
referencedProfiles={{
import { shallow } from 'enzyme';
import * as React from 'react';
import { Query } from '../../query';
-import FacetsList from '../FacetsList';
+import FacetsList, { FacetsListProps } from '../FacetsList';
it('should render correctly', () => {
const wrapper = shallowRender();
expect(wrapper.find('ActivationSeverityFacet').length).toEqual(0);
});
-it('should correctly hide the template facet', () => {
- const wrapper = shallowRender({ organizationsEnabled: true });
- expect(wrapper.find('TemplateFacet').length).toEqual(0);
-});
-
it('should correctly enable/disable the language facet', () => {
- const wrapper = shallowRender({ query: { profile: 'foo' } });
+ const wrapper = shallowRender({ query: { profile: 'foo' } as Query });
expect(wrapper.find('Connect(LanguageFacet)').prop('disabled')).toBe(true);
wrapper.setProps({ query: {} }).update();
expect(wrapper.find('InheritanceFacet').prop('disabled')).toBe(false);
});
-function shallowRender(props = {}) {
+function shallowRender(props: Partial<FacetsListProps> = {}) {
return shallow(
<FacetsList
onFacetToggle={jest.fn()}
onFilterChange={jest.fn()}
openFacets={{}}
- organization="foo"
query={{} as Query}
referencedProfiles={{}}
referencedRepositories={{}}
onDeactivate={jest.fn()}
onFilterChange={jest.fn()}
onOpen={jest.fn()}
- organization={undefined}
rule={mockRule({ key: 'javascript:S1067' })}
selected={false}
{...props}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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 { getRuleTags } from '../../../../api/rules';
+import TagFacet from '../TagFacet';
+
+jest.mock('../../../../api/rules', () => ({
+ getRuleTags: jest.fn().mockResolvedValue([])
+}));
+
+it('should render correctly', () => {
+ expect(shallowRender()).toMatchSnapshot();
+});
+
+it('should handle search', async () => {
+ const wrapper = shallowRender();
+
+ const query = 'query';
+
+ await wrapper.instance().handleSearch(query);
+
+ expect(getRuleTags).toBeCalledWith({ ps: 50, q: query });
+});
+
+describe('ListStyleFacet Renderers', () => {
+ const instance = shallowRender().instance();
+
+ it('should include renderFacetItem', () => {
+ expect(instance.renderTag('tag')).toMatchSnapshot();
+ });
+
+ it('should include renderSearchResult', () => {
+ expect(instance.renderSearchResult('my-tag', 'tag')).toMatchSnapshot();
+ });
+});
+
+function shallowRender(props: Partial<TagFacet['props']> = {}) {
+ return shallow<TagFacet>(
+ <TagFacet
+ onChange={jest.fn()}
+ onToggle={jest.fn()}
+ open={false}
+ stats={{}}
+ values={[]}
+ {...props}
+ />
+ );
+}
},
}
}
- organization="foo"
query={
Object {
"activation": undefined,
/>
<FacetsList
facets={Object {}}
- hideProfileFacet={false}
onFacetToggle={[Function]}
onFilterChange={[Function]}
openFacets={
"types": true,
}
}
- organization="foo"
query={
Object {
"activation": undefined,
},
}
}
- organization="foo"
query={
Object {
"activation": undefined,
onDeactivate={[Function]}
onFilterChange={[Function]}
onOpen={[Function]}
- organization="foo"
rule={
Object {
"key": "javascript:S1067",
onDeactivate={[Function]}
onFilterChange={[Function]}
onOpen={[Function]}
- organization="foo"
rule={
Object {
"key": "javascript:S1067",
/>
<FacetsList
facets={Object {}}
- hideProfileFacet={false}
onFacetToggle={[Function]}
onFilterChange={[Function]}
openFacets={
"types": true,
}
}
- organization="foo"
query={
Object {
"activation": undefined,
<RuleDetails
allowCustomRules={true}
canWrite={true}
- hideQualityProfiles={false}
onActivate={[Function]}
onDeactivate={[Function]}
onDelete={[Function]}
<RuleDetails
allowCustomRules={true}
canWrite={true}
- hideQualityProfiles={false}
onActivate={[Function]}
onDeactivate={[Function]}
onDelete={[Function]}
onChange={[MockFunction]}
onToggle={[MockFunction]}
open={false}
- organization="foo"
/>
<Connect(RepositoryFacet)
onChange={[MockFunction]}
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ListStyleFacet Renderers should include renderFacetItem 1`] = `
+<React.Fragment>
+ <TagsIcon
+ className="little-spacer-right"
+ fill="#999"
+ />
+ tag
+</React.Fragment>
+`;
+
+exports[`ListStyleFacet Renderers should include renderSearchResult 1`] = `
+<React.Fragment>
+ <TagsIcon
+ className="little-spacer-right"
+ fill="#999"
+ />
+ <React.Fragment>
+ my-
+ <mark>
+ tag
+ </mark>
+ </React.Fragment>
+</React.Fragment>
+`;
+
+exports[`should render correctly 1`] = `
+<ListStyleFacet
+ facetHeader="coding_rules.facet.tags"
+ fetching={false}
+ getFacetItemText={[Function]}
+ getSearchResultKey={[Function]}
+ getSearchResultText={[Function]}
+ maxInitialItems={15}
+ maxItems={100}
+ minSearchLength={2}
+ onChange={[MockFunction]}
+ onSearch={[Function]}
+ onToggle={[MockFunction]}
+ open={false}
+ property="tags"
+ renderFacetItem={[Function]}
+ renderSearchResult={[Function]}
+ searchPlaceholder="search.search_for_tags"
+ stats={Object {}}
+ values={Array []}
+/>
+`;
{ branchLike: undefined, key: 'foo' },
{ branchLike: undefined, key: 'bar' }
];
- const rules = [{ key: 'qux', organization: 'org' }];
+ const rules = [{ key: 'qux' }];
return shallow(
<WorkspaceNav
components={components}
});
function shallowRender(props?: Partial<Props>) {
- const rule = { key: 'foo', organization: 'org' };
+ const rule = { key: 'foo' };
return shallow(
<WorkspaceNavRule onClose={jest.fn()} onOpen={jest.fn()} rule={rule} {...props} />
);
import WorkspaceRuleTitle from '../WorkspaceRuleTitle';
it('should render rule', () => {
- const rule = { key: 'foo', organization: 'org' };
+ const rule = { key: 'foo' };
expect(shallow(<WorkspaceRuleTitle rule={rule} />)).toMatchSnapshot();
});
it('should render loaded rule', () => {
- const rule = { key: 'foo', name: 'Foo', organization: 'org' };
+ const rule = { key: 'foo', name: 'Foo' };
expect(shallow(<WorkspaceRuleTitle rule={rule} />)).toMatchSnapshot();
});
});
it('should correctly mark the content as busy loading (aria)', () => {
- const rule = { key: 'foo', name: 'Foo', organization: 'org' };
+ const rule = { key: 'foo', name: 'Foo' };
const wrapper = shallowRender({ rule });
const instance = wrapper.instance();
const container = () => wrapper.find('.workspace-viewer-container');
wrapper.update();
expect(container().prop('aria-busy')).toBe(false);
- const newRule = { key: 'bar', name: 'Bar', organization: 'org' };
+ const newRule = { key: 'bar', name: 'Bar' };
wrapper.setProps({ rule: newRule }).update();
expect(container().prop('aria-busy')).toBe(true);
});
function shallowRender(props?: Partial<Props>) {
- const rule = { key: 'foo', name: 'Foo', organization: 'org' };
+ const rule = { key: 'foo', name: 'Foo' };
return shallow<WorkspaceRuleViewer>(
<WorkspaceRuleViewer
height={300}
rule={
Object {
"key": "qux",
- "organization": "org",
}
}
/>
rule={
Object {
"key": "qux",
- "organization": "org",
}
}
/>
rule={
Object {
"key": "foo",
- "organization": "org",
}
}
/>
Object {
"key": "foo",
"name": "Foo",
- "organization": "org",
}
}
/>
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.
+ */
+export interface GetRulesAppResponse {
+ canWrite?: boolean;
+ repositories: { key: string; language: string; name: string }[];
+}
+
+export interface SearchRulesResponse {
+ actives?: T.Dict<T.RuleActivation[]>;
+ facets?: { property: string; values: { count: number; val: string }[] }[];
+ p: number;
+ ps: number;
+ rules: T.Rule[];
+ total: number;
+}