!scripts/patches/debug_ce.sh
!scripts/patches/debug_web.sh
!scripts/patches/postgres.sh
+gherkin-features/
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon';
import { collapsePath, limitComponentName } from 'sonar-ui-common/helpers/path';
import Organization from '../../../components/shared/Organization';
import { getSelectedLocation } from '../utils';
T.Issue,
| 'component'
| 'componentLongName'
+ | 'componentQualifier'
| 'flows'
| 'organization'
| 'project'
return (
<div className="component-name text-ellipsis">
+ <QualifierIcon className="spacer-right" qualifier={issue.componentQualifier} />
+
{displayOrganization && <Organization link={false} organizationKey={issue.organization} />}
{displayProject && (
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { ComponentQualifier } from '../../../../types/component';
import ComponentBreadcrumbs from '../ComponentBreadcrumbs';
const baseIssue = {
component: 'comp',
componentLongName: 'comp-name',
+ componentQualifier: ComponentQualifier.File,
flows: [],
organization: 'org',
project: 'proj',
<div
className="component-name text-ellipsis"
>
+ <QualifierIcon
+ className="spacer-right"
+ qualifier="FIL"
+ />
<Connect(Organization)
link={false}
organizationKey="org"
<div
className="component-name text-ellipsis"
>
+ <QualifierIcon
+ className="spacer-right"
+ qualifier="FIL"
+ />
<Connect(Organization)
link={false}
organizationKey="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.
+ */
+import { without } from 'lodash';
+import * as React from 'react';
+import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon';
+import { translate } from 'sonar-ui-common/helpers/l10n';
+import FacetBox from '../../../components/facet/FacetBox';
+import FacetHeader from '../../../components/facet/FacetHeader';
+import FacetItem from '../../../components/facet/FacetItem';
+import FacetItemsList from '../../../components/facet/FacetItemsList';
+import MultipleSelectionHint from '../../../components/facet/MultipleSelectionHint';
+import { SOURCE_SCOPES } from '../../../helpers/constants';
+import { formatFacetStat, Query } from '../utils';
+
+export interface ScopeFacetProps {
+ fetching: boolean;
+ onChange: (changes: Partial<Query>) => void;
+ onToggle: (property: string) => void;
+ open: boolean;
+ scopes: string[];
+ stats: T.Dict<number> | undefined;
+}
+
+export default function ScopeFacet(props: ScopeFacetProps) {
+ const { fetching, open, scopes = [], stats = {} } = props;
+ const values = scopes.map(scope => translate('issue.scope', scope));
+
+ return (
+ <FacetBox property="scopes">
+ <FacetHeader
+ fetching={fetching}
+ name={translate('issues.facet.scopes')}
+ onClear={() => props.onChange({ scopes: [] })}
+ onClick={() => props.onToggle('scopes')}
+ open={open}
+ values={values}
+ />
+
+ {open && (
+ <>
+ <FacetItemsList>
+ {SOURCE_SCOPES.map(({ scope, qualifier }) => {
+ const active = scopes.includes(scope);
+ const stat = stats[scope];
+
+ return (
+ <FacetItem
+ active={active}
+ disabled={stat === 0 && !active}
+ key={scope}
+ name={
+ <span className="display-flex-center">
+ <QualifierIcon className="little-spacer-right" qualifier={qualifier} />{' '}
+ {translate('issue.scope', scope)}
+ </span>
+ }
+ onClick={(itemValue: string, multiple: boolean) => {
+ if (multiple) {
+ props.onChange({
+ scopes: active ? without(scopes, itemValue) : [...scopes, itemValue]
+ });
+ } else {
+ props.onChange({
+ scopes: active && scopes.length === 1 ? [] : [itemValue]
+ });
+ }
+ }}
+ stat={formatFacetStat(stat)}
+ value={scope}
+ />
+ );
+ })}
+ </FacetItemsList>
+ <MultipleSelectionHint options={Object.keys(stats).length} values={scopes.length} />
+ </>
+ )}
+ </FacetBox>
+ );
+}
import ProjectFacet from './ProjectFacet';
import ResolutionFacet from './ResolutionFacet';
import RuleFacet from './RuleFacet';
+import ScopeFacet from './ScopeFacet';
import SeverityFacet from './SeverityFacet';
import StandardFacet from './StandardFacet';
import StatusFacet from './StatusFacet';
severities={query.severities}
stats={facets.severities}
/>
+ <ScopeFacet
+ fetching={this.props.loadingFacets.scopes === true}
+ onChange={this.props.onFilterChange}
+ onToggle={this.props.onFacetToggle}
+ open={!!openFacets.scopes}
+ stats={facets.scopes}
+ scopes={query.scopes}
+ />
<ResolutionFacet
fetching={this.props.loadingFacets.resolutions === true}
onChange={this.props.onFilterChange}
--- /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, ShallowWrapper } from 'enzyme';
+import * as React from 'react';
+import FacetHeader from '../../../../components/facet/FacetHeader';
+import FacetItem from '../../../../components/facet/FacetItem';
+import { IssueScope } from '../../../../types/issues';
+import ScopeFacet, { ScopeFacetProps } from '../ScopeFacet';
+
+it('should render correctly', () => {
+ expect(shallowRender()).toMatchSnapshot('default');
+ expect(shallowRender({ open: true })).toMatchSnapshot('open');
+ expect(shallowRender({ open: true, scopes: [IssueScope.Main] })).toMatchSnapshot('active facet');
+ expect(shallowRender({ open: true, stats: { [IssueScope.Main]: 0 } })).toMatchSnapshot(
+ 'disabled facet'
+ );
+});
+
+it('should correctly handle facet header clicks', () => {
+ const onChange = jest.fn();
+ const onToggle = jest.fn();
+ const wrapper = shallowRender({ onChange, onToggle });
+
+ wrapper.find(FacetHeader).props().onClear!();
+ expect(onChange).toBeCalledWith({ scopes: [] });
+
+ wrapper.find(FacetHeader).props().onClick!();
+ expect(onToggle).toBeCalledWith('scopes');
+});
+
+it('should correctly handle facet item clicks', () => {
+ const wrapper = shallowRender({ open: true, scopes: [IssueScope.Main] });
+ const onChange = jest.fn(({ scopes }) => wrapper.setProps({ scopes }));
+ wrapper.setProps({ onChange });
+
+ clickFacetItem(wrapper, IssueScope.Test);
+ expect(onChange).toHaveBeenLastCalledWith({ scopes: [IssueScope.Test] });
+
+ clickFacetItem(wrapper, IssueScope.Test);
+ expect(onChange).toHaveBeenLastCalledWith({ scopes: [] });
+
+ clickFacetItem(wrapper, IssueScope.Test, true);
+ clickFacetItem(wrapper, IssueScope.Main, true);
+ expect(onChange).toHaveBeenLastCalledWith({
+ scopes: expect.arrayContaining([IssueScope.Main, IssueScope.Test])
+ });
+
+ clickFacetItem(wrapper, IssueScope.Test, true);
+ expect(onChange).toHaveBeenLastCalledWith({ scopes: [IssueScope.Main] });
+});
+
+function clickFacetItem(
+ wrapper: ShallowWrapper<ScopeFacetProps>,
+ scope: IssueScope,
+ multiple = false
+) {
+ return wrapper
+ .find(FacetItem)
+ .filterWhere(f => f.key() === scope)
+ .props()
+ .onClick(scope, multiple);
+}
+
+function shallowRender(props: Partial<ScopeFacetProps> = {}) {
+ return shallow<ScopeFacetProps>(
+ <ScopeFacet
+ fetching={true}
+ onChange={jest.fn()}
+ onToggle={jest.fn()}
+ open={false}
+ scopes={[]}
+ stats={{}}
+ {...props}
+ />
+ );
+}
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly: active facet 1`] = `
+<FacetBox
+ property="scopes"
+>
+ <FacetHeader
+ fetching={true}
+ name="issues.facet.scopes"
+ onClear={[Function]}
+ onClick={[Function]}
+ open={true}
+ values={
+ Array [
+ "issue.scope.MAIN",
+ ]
+ }
+ />
+ <FacetItemsList>
+ <FacetItem
+ active={true}
+ disabled={false}
+ halfWidth={false}
+ key="MAIN"
+ loading={false}
+ name={
+ <span
+ className="display-flex-center"
+ >
+ <QualifierIcon
+ className="little-spacer-right"
+ qualifier="FIL"
+ />
+
+ issue.scope.MAIN
+ </span>
+ }
+ onClick={[Function]}
+ value="MAIN"
+ />
+ <FacetItem
+ active={false}
+ disabled={false}
+ halfWidth={false}
+ key="TEST"
+ loading={false}
+ name={
+ <span
+ className="display-flex-center"
+ >
+ <QualifierIcon
+ className="little-spacer-right"
+ qualifier="UTS"
+ />
+
+ issue.scope.TEST
+ </span>
+ }
+ onClick={[Function]}
+ value="TEST"
+ />
+ </FacetItemsList>
+ <MultipleSelectionHint
+ options={0}
+ values={1}
+ />
+</FacetBox>
+`;
+
+exports[`should render correctly: default 1`] = `
+<FacetBox
+ property="scopes"
+>
+ <FacetHeader
+ fetching={true}
+ name="issues.facet.scopes"
+ onClear={[Function]}
+ onClick={[Function]}
+ open={false}
+ values={Array []}
+ />
+</FacetBox>
+`;
+
+exports[`should render correctly: disabled facet 1`] = `
+<FacetBox
+ property="scopes"
+>
+ <FacetHeader
+ fetching={true}
+ name="issues.facet.scopes"
+ onClear={[Function]}
+ onClick={[Function]}
+ open={true}
+ values={Array []}
+ />
+ <FacetItemsList>
+ <FacetItem
+ active={false}
+ disabled={true}
+ halfWidth={false}
+ key="MAIN"
+ loading={false}
+ name={
+ <span
+ className="display-flex-center"
+ >
+ <QualifierIcon
+ className="little-spacer-right"
+ qualifier="FIL"
+ />
+
+ issue.scope.MAIN
+ </span>
+ }
+ onClick={[Function]}
+ stat={0}
+ value="MAIN"
+ />
+ <FacetItem
+ active={false}
+ disabled={false}
+ halfWidth={false}
+ key="TEST"
+ loading={false}
+ name={
+ <span
+ className="display-flex-center"
+ >
+ <QualifierIcon
+ className="little-spacer-right"
+ qualifier="UTS"
+ />
+
+ issue.scope.TEST
+ </span>
+ }
+ onClick={[Function]}
+ value="TEST"
+ />
+ </FacetItemsList>
+ <MultipleSelectionHint
+ options={1}
+ values={0}
+ />
+</FacetBox>
+`;
+
+exports[`should render correctly: open 1`] = `
+<FacetBox
+ property="scopes"
+>
+ <FacetHeader
+ fetching={true}
+ name="issues.facet.scopes"
+ onClear={[Function]}
+ onClick={[Function]}
+ open={true}
+ values={Array []}
+ />
+ <FacetItemsList>
+ <FacetItem
+ active={false}
+ disabled={false}
+ halfWidth={false}
+ key="MAIN"
+ loading={false}
+ name={
+ <span
+ className="display-flex-center"
+ >
+ <QualifierIcon
+ className="little-spacer-right"
+ qualifier="FIL"
+ />
+
+ issue.scope.MAIN
+ </span>
+ }
+ onClick={[Function]}
+ value="MAIN"
+ />
+ <FacetItem
+ active={false}
+ disabled={false}
+ halfWidth={false}
+ key="TEST"
+ loading={false}
+ name={
+ <span
+ className="display-flex-center"
+ >
+ <QualifierIcon
+ className="little-spacer-right"
+ qualifier="UTS"
+ />
+
+ issue.scope.TEST
+ </span>
+ }
+ onClick={[Function]}
+ value="TEST"
+ />
+ </FacetItemsList>
+ <MultipleSelectionHint
+ options={0}
+ values={0}
+ />
+</FacetBox>
+`;
Array [
"TypeFacet",
"SeverityFacet",
+ "ScopeFacet",
"ResolutionFacet",
"StatusFacet",
"StandardFacet",
Array [
"TypeFacet",
"SeverityFacet",
+ "ScopeFacet",
"ResolutionFacet",
"StatusFacet",
"StandardFacet",
Array [
"TypeFacet",
"SeverityFacet",
+ "ScopeFacet",
"ResolutionFacet",
"StatusFacet",
"StandardFacet",
Array [
"TypeFacet",
"SeverityFacet",
+ "ScopeFacet",
"ResolutionFacet",
"StatusFacet",
"StandardFacet",
Array [
"TypeFacet",
"SeverityFacet",
+ "ScopeFacet",
"ResolutionFacet",
"StatusFacet",
"StandardFacet",
Array [
"TypeFacet",
"SeverityFacet",
+ "ScopeFacet",
"ResolutionFacet",
"StatusFacet",
"StandardFacet",
Array [
"TypeFacet",
"SeverityFacet",
+ "ScopeFacet",
"ResolutionFacet",
"StatusFacet",
"StandardFacet",
}
.issues-workspace-list-component {
- padding: 10px 10px 6px;
+ padding: 10px 0 6px;
}
.issues-workspace-list-item + .issues-workspace-list-item {
resolved: boolean;
rules: string[];
sansTop25: string[];
+ scopes: string[];
severities: string[];
sinceLeakPeriod: boolean;
sonarsourceSecurity: string[];
resolved: parseAsBoolean(query.resolved),
rules: parseAsArray(query.rules, parseAsString),
sansTop25: parseAsArray(query.sansTop25, parseAsString),
+ scopes: parseAsArray(query.scopes, parseAsString),
severities: parseAsArray(query.severities, parseAsString),
sinceLeakPeriod: parseAsBoolean(query.sinceLeakPeriod, false),
sonarsourceSecurity: parseAsArray(query.sonarsourceSecurity, parseAsString),
rules: serializeStringArray(query.rules),
s: serializeString(query.sort),
sansTop25: serializeStringArray(query.sansTop25),
+ scopes: serializeStringArray(query.scopes),
severities: serializeStringArray(query.severities),
sinceLeakPeriod: query.sinceLeakPeriod ? 'true' : undefined,
sonarsourceSecurity: serializeStringArray(query.sonarsourceSecurity),
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { colors } from '../app/theme';
-import { IssueType } from '../types/issues';
+import { ComponentQualifier } from '../types/component';
+import { IssueScope, IssueType } from '../types/issues';
export const SEVERITIES = ['BLOCKER', 'CRITICAL', 'MAJOR', 'MINOR', 'INFO'];
export const STATUSES = ['OPEN', 'REOPENED', 'CONFIRMED', 'RESOLVED', 'CLOSED'];
IssueType.CodeSmell,
IssueType.SecurityHotspot
];
+export const SOURCE_SCOPES = [
+ { scope: IssueScope.Main, qualifier: ComponentQualifier.File },
+ { scope: IssueScope.Test, qualifier: ComponentQualifier.TestFile }
+];
export const RULE_TYPES: T.RuleType[] = ['BUG', 'VULNERABILITY', 'CODE_SMELL', 'SECURITY_HOTSPOT'];
export const RULE_STATUSES = ['READY', 'BETA', 'DEPRECATED'];
Bug = 'BUG',
SecurityHotspot = 'SECURITY_HOTSPOT'
}
+
+export enum IssueScope {
+ Main = 'MAIN',
+ Test = 'TEST'
+}
issue.status.IN_REVIEW=In Review
issue.status.REVIEWED=Reviewed
+issue.scope.MAIN=Main code
+issue.scope.TEST=Test code
+
issue.resolution.FALSE-POSITIVE=False Positive
issue.resolution.FALSE-POSITIVE.description=Issues that manual review determined were False Positives. Effort from these issues is ignored.
issue.resolution.FIXED=Fixed
#------------------------------------------------------------------------------
issues.facet.types=Type
issues.facet.severities=Severity
+issues.facet.scopes=Scope
issues.facet.projects=Project
issues.facet.statuses=Status
issues.facet.hotspotStatuses=Hotspot Status