aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWouter Admiraal <wouter.admiraal@sonarsource.com>2019-08-19 15:50:40 +0200
committerSonarTech <sonartech@sonarsource.com>2019-08-27 20:21:04 +0200
commit6e6c533c1299ec31a1c0923e259e135912220c17 (patch)
tree24178979acdd881d91e17f9feddef1663ef23517
parentde346d035e4f8bbafa6cc7edc43f8f865947038e (diff)
downloadsonarqube-6e6c533c1299ec31a1c0923e259e135912220c17.tar.gz
sonarqube-6e6c533c1299ec31a1c0923e259e135912220c17.zip
SONAR-12403 Fix Issue filtering using File facet
-rw-r--r--server/sonar-web/src/main/js/api/components.ts21
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/FileFacet.tsx67
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/FileFacet-test.tsx75
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/FileFacet-test.tsx.snap55
5 files changed, 190 insertions, 32 deletions
diff --git a/server/sonar-web/src/main/js/api/components.ts b/server/sonar-web/src/main/js/api/components.ts
index 66d352d7221..ea2f2381150 100644
--- a/server/sonar-web/src/main/js/api/components.ts
+++ b/server/sonar-web/src/main/js/api/components.ts
@@ -127,7 +127,7 @@ export function getComponent(
}
export interface TreeComponent extends T.LightComponent {
- id: string;
+ id?: string;
name: string;
path?: string;
refId?: string;
@@ -136,21 +136,30 @@ export interface TreeComponent extends T.LightComponent {
visibility: T.Visibility;
}
-export function getTree(data: {
+export interface TreeComponentWithPath extends TreeComponent {
+ path: string;
+}
+
+type GetTreeParams = {
asc?: boolean;
- branch?: string;
component: string;
p?: number;
ps?: number;
- pullRequest?: string;
q?: string;
- qualifiers?: string;
s?: string;
strategy?: 'all' | 'leaves' | 'children';
-}): Promise<{ baseComponent: TreeComponent; components: TreeComponent[]; paging: T.Paging }> {
+} & T.BranchParameters;
+
+export function getTree<T = TreeComponent>(
+ data: GetTreeParams & { qualifiers?: string }
+): Promise<{ baseComponent: TreeComponent; components: T[]; paging: T.Paging }> {
return getJSON('/api/components/tree', data).catch(throwGlobalError);
}
+export function getFiles(data: GetTreeParams) {
+ return getTree<TreeComponentWithPath>({ ...data, qualifiers: 'FIL' });
+}
+
export function getComponentData(data: { component: string } & T.BranchParameters): Promise<any> {
return getJSON('/api/components/show', data);
}
diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/FileFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/FileFacet.tsx
index 6b3a11ba383..c2abf30cbce 100644
--- a/server/sonar-web/src/main/js/apps/issues/sidebar/FileFacet.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/sidebar/FileFacet.tsx
@@ -23,56 +23,75 @@ import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { collapsePath } from 'sonar-ui-common/helpers/path';
import { highlightTerm } from 'sonar-ui-common/helpers/search';
-import { getTree, TreeComponent } from '../../../api/components';
+import { isDefined } from 'sonar-ui-common/helpers/types';
+import { getFiles, TreeComponentWithPath } from '../../../api/components';
import ListStyleFacet from '../../../components/facet/ListStyleFacet';
import { Facet, Query, ReferencedComponent } from '../utils';
interface Props {
componentKey: string;
fetching: boolean;
- files: string[];
+ fileUuids: string[];
loadSearchResultCount: (property: string, changes: Partial<Query>) => Promise<Facet>;
onChange: (changes: Partial<Query>) => void;
onToggle: (property: string) => void;
open: boolean;
query: Query;
referencedComponents: T.Dict<ReferencedComponent>;
- stats: T.Dict<number> | undefined;
+ stats: Facet | undefined;
}
export default class FileFacet extends React.PureComponent<Props> {
- getFile = (file: string) => {
+ getFilePath = (fileUuid: string) => {
const { referencedComponents } = this.props;
- return referencedComponents[file]
- ? collapsePath(referencedComponents[file].path || '', 15)
- : file;
+ return referencedComponents[fileUuid]
+ ? collapsePath(referencedComponents[fileUuid].path || '', 15)
+ : fileUuid;
};
- getFacetItemText = (file: string) => {
+ getReferencedComponent = (key: string) => {
const { referencedComponents } = this.props;
- return referencedComponents[file] ? referencedComponents[file].path || '' : file;
+ const fileUuid = Object.keys(referencedComponents).find(uuid => {
+ return referencedComponents[uuid].key === key;
+ });
+ return fileUuid ? referencedComponents[fileUuid] : undefined;
};
- getSearchResultKey = (file: TreeComponent) => {
- return file.id;
+ getFacetItemText = (fileUuid: string) => {
+ const { referencedComponents } = this.props;
+ return referencedComponents[fileUuid] ? referencedComponents[fileUuid].path || '' : fileUuid;
+ };
+
+ getSearchResultKey = (file: TreeComponentWithPath) => {
+ const component = this.getReferencedComponent(file.key);
+ return component ? component.uuid : file.key;
};
- getSearchResultText = (file: TreeComponent) => {
- return file.path || file.name;
+ getSearchResultText = (file: TreeComponentWithPath) => {
+ return file.path;
};
handleSearch = (query: string, page: number) => {
- return getTree({
+ return getFiles({
component: this.props.componentKey,
q: query,
- qualifiers: 'FIL',
p: page,
ps: 30
- }).then(({ components, paging }) => ({ paging, results: components }));
+ }).then(({ components, paging }) => ({
+ paging,
+ results: components.filter(file => file.path !== undefined)
+ }));
};
- loadSearchResultCount = (files: TreeComponent[]) => {
- return this.props.loadSearchResultCount('files', { files: files.map(file => file.id) });
+ loadSearchResultCount = (files: TreeComponentWithPath[]) => {
+ return this.props.loadSearchResultCount('files', {
+ files: files
+ .map(file => {
+ const component = this.getReferencedComponent(file.key);
+ return component && component.uuid;
+ })
+ .filter(isDefined)
+ });
};
renderFile = (file: React.ReactNode) => (
@@ -82,18 +101,18 @@ export default class FileFacet extends React.PureComponent<Props> {
</>
);
- renderFacetItem = (file: string) => {
- const name = this.getFile(file);
+ renderFacetItem = (fileUuid: string) => {
+ const name = this.getFilePath(fileUuid);
return this.renderFile(name);
};
- renderSearchResult = (file: TreeComponent, term: string) => {
- return this.renderFile(highlightTerm(collapsePath(file.path || file.name, 15), term));
+ renderSearchResult = (file: TreeComponentWithPath, term: string) => {
+ return this.renderFile(highlightTerm(collapsePath(file.path, 15), term));
};
render() {
return (
- <ListStyleFacet<TreeComponent>
+ <ListStyleFacet<TreeComponentWithPath>
facetHeader={translate('issues.facet.files')}
fetching={this.props.fetching}
getFacetItemText={this.getFacetItemText}
@@ -111,7 +130,7 @@ export default class FileFacet extends React.PureComponent<Props> {
renderSearchResult={this.renderSearchResult}
searchPlaceholder={translate('search.search_for_files')}
stats={this.props.stats}
- values={this.props.files}
+ values={this.props.fileUuids}
/>
);
}
diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx
index ba3e4c6c88c..cb882874c7d 100644
--- a/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx
@@ -36,7 +36,7 @@ import TypeFacet from './TypeFacet';
export interface Props {
component: T.Component | undefined;
- facets: T.Dict<Facet>;
+ facets: T.Dict<Facet | undefined>;
hideAuthorFacet?: boolean;
loadSearchResultCount: (property: string, changes: Partial<Query>) => Promise<Facet>;
loadingFacets: T.Dict<boolean>;
@@ -79,7 +79,7 @@ export default class Sidebar extends React.PureComponent<Props> {
)}
<FileFacet
fetching={loadingFacets.files === true}
- files={query.files}
+ fileUuids={query.files}
open={!!openFacets.files}
referencedComponents={this.props.referencedComponentsById}
stats={facets.files}
diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/FileFacet-test.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/FileFacet-test.tsx
new file mode 100644
index 00000000000..51533428433
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/FileFacet-test.tsx
@@ -0,0 +1,75 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { TreeComponentWithPath } from '../../../../api/components';
+import { Query, ReferencedComponent } from '../../utils';
+import FileFacet from '../FileFacet';
+
+it('should render correctly', () => {
+ const wrapper = shallowRender();
+ const instance = wrapper.instance();
+ expect(wrapper).toMatchSnapshot();
+ expect(
+ instance.renderSearchResult({ path: 'foo/bar.js' } as TreeComponentWithPath, 'foo')
+ ).toMatchSnapshot();
+ expect(instance.renderFacetItem('fooUuid')).toMatchSnapshot();
+});
+
+describe("ListStyleFacet's callback props", () => {
+ const wrapper = shallowRender();
+ const instance = wrapper.instance();
+
+ test('#getSearchResultText()', () => {
+ expect(instance.getSearchResultText({ path: 'foo/bar.js' } as TreeComponentWithPath)).toBe(
+ 'foo/bar.js'
+ );
+ });
+
+ test('#getSearchResultKey()', () => {
+ expect(instance.getSearchResultKey({ key: 'foo' } as TreeComponentWithPath)).toBe('fooUuid');
+ expect(instance.getSearchResultKey({ key: 'bar' } as TreeComponentWithPath)).toBe('bar');
+ });
+
+ test('#getFacetItemText()', () => {
+ expect(instance.getFacetItemText('fooUuid')).toBe('foo/bar.js');
+ expect(instance.getFacetItemText('bar')).toBe('bar');
+ });
+});
+
+function shallowRender(props: Partial<FileFacet['props']> = {}) {
+ return shallow<FileFacet>(
+ <FileFacet
+ componentKey="foo"
+ fetching={false}
+ fileUuids={['foo', 'bar']}
+ loadSearchResultCount={jest.fn()}
+ onChange={jest.fn()}
+ onToggle={jest.fn()}
+ open={false}
+ query={{} as Query}
+ referencedComponents={{
+ fooUuid: { key: 'foo', uuid: 'fooUuid', path: 'foo/bar.js' } as ReferencedComponent
+ }}
+ stats={undefined}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/FileFacet-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/FileFacet-test.tsx.snap
new file mode 100644
index 00000000000..e0c34a44074
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/FileFacet-test.tsx.snap
@@ -0,0 +1,55 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<ListStyleFacet
+ facetHeader="issues.facet.files"
+ fetching={false}
+ getFacetItemText={[Function]}
+ getSearchResultKey={[Function]}
+ getSearchResultText={[Function]}
+ loadSearchResultCount={[Function]}
+ maxInitialItems={15}
+ maxItems={100}
+ minSearchLength={3}
+ onChange={[MockFunction]}
+ onSearch={[Function]}
+ onToggle={[MockFunction]}
+ open={false}
+ property="files"
+ query={Object {}}
+ renderFacetItem={[Function]}
+ renderSearchResult={[Function]}
+ searchPlaceholder="search.search_for_files"
+ values={
+ Array [
+ "foo",
+ "bar",
+ ]
+ }
+/>
+`;
+
+exports[`should render correctly 2`] = `
+<React.Fragment>
+ <QualifierIcon
+ className="little-spacer-right"
+ qualifier="FIL"
+ />
+ <React.Fragment>
+ <mark>
+ foo
+ </mark>
+ /bar.js
+ </React.Fragment>
+</React.Fragment>
+`;
+
+exports[`should render correctly 3`] = `
+<React.Fragment>
+ <QualifierIcon
+ className="little-spacer-right"
+ qualifier="FIL"
+ />
+ foo/bar.js
+</React.Fragment>
+`;