aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2019-05-06 17:12:05 +0200
committerSonarTech <sonartech@sonarsource.com>2019-05-22 20:21:13 +0200
commite861cb3f45bc28434dc59d857c38e414dba2a0bc (patch)
treed856732255bfb0fcc92ae28bc0eb7ef0a5d339af /server/sonar-web
parent7a547418b7fc4beae7a541f81a0c225b0af60631 (diff)
downloadsonarqube-e861cb3f45bc28434dc59d857c38e414dba2a0bc.tar.gz
sonarqube-e861cb3f45bc28434dc59d857c38e414dba2a0bc.zip
SONAR-12026 Add new hotspot status facet in issues page
Diffstat (limited to 'server/sonar-web')
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/ResolutionFacet.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/StatusFacet.tsx18
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/StatusFacet-test.tsx81
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/StatusFacet-test.tsx.snap163
-rw-r--r--server/sonar-web/src/main/js/components/facet/FacetItemsList.tsx10
-rw-r--r--server/sonar-web/src/main/js/components/facet/__tests__/FacetItemsList-test.tsx10
-rw-r--r--server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetItemsList-test.tsx.snap13
-rw-r--r--server/sonar-web/src/main/js/components/icons-components/StatusIcon.tsx5
-rw-r--r--server/sonar-web/src/main/js/components/search-navigator.css11
9 files changed, 304 insertions, 9 deletions
diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/ResolutionFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/ResolutionFacet.tsx
index 5e069b27e14..958245f1ad6 100644
--- a/server/sonar-web/src/main/js/apps/issues/sidebar/ResolutionFacet.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/sidebar/ResolutionFacet.tsx
@@ -38,7 +38,7 @@ interface Props {
stats: T.Dict<number> | undefined;
}
-const RESOLUTIONS = ['', 'FIXED', 'FALSE-POSITIVE', 'WONTFIX', 'REMOVED'];
+const RESOLUTIONS = ['', 'FALSE-POSITIVE', 'FIXED', 'REMOVED', 'WONTFIX'];
export default class ResolutionFacet extends React.PureComponent<Props> {
property = 'resolutions';
diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/StatusFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/StatusFacet.tsx
index 03d6864947a..ea83e7a20c2 100644
--- a/server/sonar-web/src/main/js/apps/issues/sidebar/StatusFacet.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/sidebar/StatusFacet.tsx
@@ -38,14 +38,14 @@ interface Props {
statuses: string[];
}
-const STATUSES = ['OPEN', 'RESOLVED', 'REOPENED', 'CLOSED', 'CONFIRMED'];
+const STATUSES = ['OPEN', 'CONFIRMED', 'REOPENED', 'RESOLVED'];
+const HOTSPOT_STATUSES = ['TO_REVIEW', 'REVIEWED', 'IN_REVIEW'];
+const COMMON_STATUSES = ['CLOSED'];
export default class StatusFacet extends React.PureComponent<Props> {
property = 'statuses';
- static defaultProps = {
- open: true
- };
+ static defaultProps = { open: true };
handleItemClick = (itemValue: string, multiple: boolean) => {
const { statuses } = this.props;
@@ -110,7 +110,15 @@ export default class StatusFacet extends React.PureComponent<Props> {
<DeferredSpinner loading={this.props.fetching} />
{this.props.open && (
<>
- <FacetItemsList>{STATUSES.map(this.renderItem)}</FacetItemsList>
+ <FacetItemsList title={translate('issues')}>
+ {STATUSES.map(this.renderItem)}
+ </FacetItemsList>
+ <FacetItemsList title={translate('issue.type.SECURITY_HOTSPOT.plural')}>
+ {HOTSPOT_STATUSES.map(this.renderItem)}
+ </FacetItemsList>
+ <FacetItemsList title={translate('issues.issues_and_hotspots')}>
+ {COMMON_STATUSES.map(this.renderItem)}
+ </FacetItemsList>
<MultipleSelectionHint options={Object.keys(stats).length} values={statuses.length} />
</>
)}
diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/StatusFacet-test.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/StatusFacet-test.tsx
new file mode 100644
index 00000000000..3b7b1482d35
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/StatusFacet-test.tsx
@@ -0,0 +1,81 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import StatusFacet from '../StatusFacet';
+import { click } from '../../../../helpers/testUtils';
+
+it('should render correctly', () => {
+ expect(shallowRender()).toMatchSnapshot();
+});
+
+it('should toggle status facet', () => {
+ const onToggle = jest.fn();
+ const wrapper = shallowRender({ onToggle });
+ click(wrapper.children('FacetHeader'));
+ expect(onToggle).toBeCalledWith('statuses');
+});
+
+it('should clear status facet', () => {
+ const onChange = jest.fn();
+ const wrapper = shallowRender({ onChange, statuses: ['TO_REVIEW'] });
+ wrapper.children('FacetHeader').prop<Function>('onClear')();
+ expect(onChange).toBeCalledWith({ statuses: [] });
+});
+
+it('should select a status', () => {
+ const onChange = jest.fn();
+ const wrapper = shallowRender({ onChange });
+ clickAndCheck('TO_REVIEW');
+ clickAndCheck('OPEN', true, ['OPEN', 'TO_REVIEW']);
+ clickAndCheck('CONFIRMED');
+
+ function clickAndCheck(status: string, multiple = false, expected = [status]) {
+ wrapper
+ .find(`FacetItemsList`)
+ .find(`FacetItem[value="${status}"]`)
+ .prop<Function>('onClick')(status, multiple);
+ expect(onChange).lastCalledWith({ statuses: expected });
+ wrapper.setProps({ statuses: expected });
+ }
+});
+
+function shallowRender(props: Partial<StatusFacet['props']> = {}) {
+ return shallow(
+ <StatusFacet
+ fetching={false}
+ onChange={jest.fn()}
+ onToggle={jest.fn()}
+ open={true}
+ stats={{
+ OPEN: 104,
+ CONFIRMED: 8,
+ REOPENED: 0,
+ RESOLVED: 0,
+ CLOSED: 8,
+ TO_REVIEW: 150,
+ IN_REVIEW: 7,
+ REVIEWED: 1105
+ }}
+ statuses={[]}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/StatusFacet-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/StatusFacet-test.tsx.snap
new file mode 100644
index 00000000000..9d01da1c88e
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/StatusFacet-test.tsx.snap
@@ -0,0 +1,163 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<FacetBox
+ property="statuses"
+>
+ <FacetHeader
+ name="issues.facet.statuses"
+ onClear={[Function]}
+ onClick={[Function]}
+ open={true}
+ values={Array []}
+ />
+ <DeferredSpinner
+ loading={false}
+ timeout={100}
+ />
+ <FacetItemsList
+ title="issues"
+ >
+ <FacetItem
+ active={false}
+ disabled={false}
+ halfWidth={true}
+ key="OPEN"
+ loading={false}
+ name={
+ <StatusHelper
+ status="OPEN"
+ />
+ }
+ onClick={[Function]}
+ stat="104"
+ tooltip="issue.status.OPEN"
+ value="OPEN"
+ />
+ <FacetItem
+ active={false}
+ disabled={false}
+ halfWidth={true}
+ key="CONFIRMED"
+ loading={false}
+ name={
+ <StatusHelper
+ status="CONFIRMED"
+ />
+ }
+ onClick={[Function]}
+ stat="8"
+ tooltip="issue.status.CONFIRMED"
+ value="CONFIRMED"
+ />
+ <FacetItem
+ active={false}
+ disabled={true}
+ halfWidth={true}
+ key="REOPENED"
+ loading={false}
+ name={
+ <StatusHelper
+ status="REOPENED"
+ />
+ }
+ onClick={[Function]}
+ stat={0}
+ tooltip="issue.status.REOPENED"
+ value="REOPENED"
+ />
+ <FacetItem
+ active={false}
+ disabled={true}
+ halfWidth={true}
+ key="RESOLVED"
+ loading={false}
+ name={
+ <StatusHelper
+ status="RESOLVED"
+ />
+ }
+ onClick={[Function]}
+ stat={0}
+ tooltip="issue.status.RESOLVED"
+ value="RESOLVED"
+ />
+ </FacetItemsList>
+ <FacetItemsList
+ title="issue.type.SECURITY_HOTSPOT.plural"
+ >
+ <FacetItem
+ active={false}
+ disabled={false}
+ halfWidth={true}
+ key="TO_REVIEW"
+ loading={false}
+ name={
+ <StatusHelper
+ status="TO_REVIEW"
+ />
+ }
+ onClick={[Function]}
+ stat="150"
+ tooltip="issue.status.TO_REVIEW"
+ value="TO_REVIEW"
+ />
+ <FacetItem
+ active={false}
+ disabled={false}
+ halfWidth={true}
+ key="REVIEWED"
+ loading={false}
+ name={
+ <StatusHelper
+ status="REVIEWED"
+ />
+ }
+ onClick={[Function]}
+ stat="1.1short_number_suffix.k"
+ tooltip="issue.status.REVIEWED"
+ value="REVIEWED"
+ />
+ <FacetItem
+ active={false}
+ disabled={false}
+ halfWidth={true}
+ key="IN_REVIEW"
+ loading={false}
+ name={
+ <StatusHelper
+ status="IN_REVIEW"
+ />
+ }
+ onClick={[Function]}
+ stat="7"
+ tooltip="issue.status.IN_REVIEW"
+ value="IN_REVIEW"
+ />
+ </FacetItemsList>
+ <FacetItemsList
+ title="issues.issues_and_hotspots"
+ >
+ <FacetItem
+ active={false}
+ disabled={false}
+ halfWidth={true}
+ key="CLOSED"
+ loading={false}
+ name={
+ <StatusHelper
+ status="CLOSED"
+ />
+ }
+ onClick={[Function]}
+ stat="8"
+ tooltip="issue.status.CLOSED"
+ value="CLOSED"
+ />
+ </FacetItemsList>
+ <MultipleSelectionHint
+ options={8}
+ values={0}
+ />
+</FacetBox>
+`;
diff --git a/server/sonar-web/src/main/js/components/facet/FacetItemsList.tsx b/server/sonar-web/src/main/js/components/facet/FacetItemsList.tsx
index f72a84915e8..b58c19d585b 100644
--- a/server/sonar-web/src/main/js/components/facet/FacetItemsList.tsx
+++ b/server/sonar-web/src/main/js/components/facet/FacetItemsList.tsx
@@ -21,8 +21,14 @@ import * as React from 'react';
interface Props {
children?: React.ReactNode;
+ title?: string;
}
-export default function FacetItemsList(props: Props) {
- return <div className="search-navigator-facet-list">{props.children}</div>;
+export default function FacetItemsList({ children, title }: Props) {
+ return (
+ <div className="search-navigator-facet-list">
+ {title && <div className="search-navigator-facet-list-title">{title}</div>}
+ {children}
+ </div>
+ );
}
diff --git a/server/sonar-web/src/main/js/components/facet/__tests__/FacetItemsList-test.tsx b/server/sonar-web/src/main/js/components/facet/__tests__/FacetItemsList-test.tsx
index 82ebe6d53fd..c8f21ca697b 100644
--- a/server/sonar-web/src/main/js/components/facet/__tests__/FacetItemsList-test.tsx
+++ b/server/sonar-web/src/main/js/components/facet/__tests__/FacetItemsList-test.tsx
@@ -30,3 +30,13 @@ it('should render', () => {
)
).toMatchSnapshot();
});
+
+it('should render with title', () => {
+ expect(
+ shallow(
+ <FacetItemsList title="title test">
+ <div />
+ </FacetItemsList>
+ )
+ ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetItemsList-test.tsx.snap b/server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetItemsList-test.tsx.snap
index 9962cfc364e..fe288787b43 100644
--- a/server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetItemsList-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetItemsList-test.tsx.snap
@@ -7,3 +7,16 @@ exports[`should render 1`] = `
<div />
</div>
`;
+
+exports[`should render with title 1`] = `
+<div
+ className="search-navigator-facet-list"
+>
+ <div
+ className="search-navigator-facet-list-title"
+ >
+ title test
+ </div>
+ <div />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/components/icons-components/StatusIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/StatusIcon.tsx
index a4d0e3bfcb9..74fd63f4bd1 100644
--- a/server/sonar-web/src/main/js/components/icons-components/StatusIcon.tsx
+++ b/server/sonar-web/src/main/js/components/icons-components/StatusIcon.tsx
@@ -31,7 +31,10 @@ const statusIcons: T.Dict<(props: IconProps) => React.ReactElement<any>> = {
confirmed: ConfirmedStatusIcon,
reopened: ReopenedStatusIcon,
resolved: ResolvedStatusIcon,
- closed: ClosedStatusIcon
+ closed: ClosedStatusIcon,
+ to_review: OpenStatusIcon,
+ in_review: ConfirmedStatusIcon,
+ reviewed: ResolvedStatusIcon
};
export default function StatusIcon(props: Props) {
diff --git a/server/sonar-web/src/main/js/components/search-navigator.css b/server/sonar-web/src/main/js/components/search-navigator.css
index a723606ffb5..75c8d0f5110 100644
--- a/server/sonar-web/src/main/js/components/search-navigator.css
+++ b/server/sonar-web/src/main/js/components/search-navigator.css
@@ -358,6 +358,17 @@ a.search-navigator-facet:focus,
font-size: 0;
}
+.search-navigator-facet-list-title {
+ margin: 0 var(--gridSize) calc(var(--gridSize) / 2);
+ font-size: var(--smallFontSize);
+ font-weight: bold;
+}
+
+.search-navigator-facet-list + .search-navigator-facet-list > .search-navigator-facet-list-title {
+ border-top: 1px solid var(--barBorderColor);
+ padding-top: var(--gridSize);
+}
+
.search-navigator-facet-empty {
margin: 0 0 0 0;
padding: 0 10px 10px;