Browse Source

SONAR-12403 Fix Issue filtering using File facet

tags/8.0
Wouter Admiraal 4 years ago
parent
commit
6e6c533c12

+ 15
- 6
server/sonar-web/src/main/js/api/components.ts View File

@@ -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);
}

+ 43
- 24
server/sonar-web/src/main/js/apps/issues/sidebar/FileFacet.tsx View File

@@ -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}
/>
);
}

+ 2
- 2
server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx View File

@@ -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}

+ 75
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/FileFacet-test.tsx View File

@@ -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}
/>
);
}

+ 55
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/FileFacet-test.tsx.snap View File

@@ -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>
`;

Loading…
Cancel
Save