// Search with results that are deeper than the current level.
await ui.searchForComponent('out');
- expect(ui.childComponent(/out\.tsx/).get()).toBeInTheDocument();
+ expect(ui.searchResult(/out\.tsx/).get()).toBeInTheDocument();
// Search with no results.
await ui.searchForComponent('nonexistent');
await act(async () => {
await ui.arrowLeft();
});
- expect(await ui.childComponent(/folderA/).find()).toBeInTheDocument();
+ expect(await ui.searchResult(/folderA/).find()).toBeInTheDocument();
});
it('should correcly handle long lists of components', async () => {
const ui = {
componentName: (name: string) => byText(name),
childComponent: (name: string | RegExp) => byRole('cell', { name, exact: false }),
+ searchResult: (name: string | RegExp) => byRole('link', { name, exact: false }),
componentIsEmptyTxt: (qualifier: ComponentQualifier) =>
byText(`code_viewer.no_source_code_displayed_due_to_empty_analysis.${qualifier}`),
searchInput: byRole('searchbox'),
margin-top: -50px;
}
+.code-components .boxed-group.search-results li {
+ padding: var(--gridSize) calc(2 * var(--gridSize));
+ border-width: 0 0 0 2px;
+ border-style: solid;
+ border-color: transparent;
+}
+
+.code-components .boxed-group.search-results li.selected {
+ background-color: var(--info100);
+ border-left-color: var(--info500);
+}
+
.code-components .table-wrapper {
margin: 0 20px;
}
import Breadcrumbs from './Breadcrumbs';
import Components from './Components';
import Search from './Search';
+import SearchResults from './SearchResults';
import SourceViewerWrapper from './SourceViewerWrapper';
interface Props {
newCodeSelected: boolean;
}
-export class CodeApp extends React.Component<Props, State> {
+class CodeApp extends React.Component<Props, State> {
mounted = false;
state: State;
const hasComponents = components.length > 0 || searchResults !== undefined;
- const shouldShowBreadcrumbs = breadcrumbs.length > 1 && !showSearch;
+ const showBreadcrumbs = breadcrumbs.length > 1 && !showSearch;
- const shouldShowComponentList =
- sourceViewer === undefined && components.length > 0 && !showSearch;
+ const showComponentList = sourceViewer === undefined && components.length > 0 && !showSearch;
const componentsClassName = classNames('boxed-group', 'spacer-top', {
'new-loading': loading,
</span>
</div>
)}
- {shouldShowBreadcrumbs && (
+ {showBreadcrumbs && (
<Breadcrumbs
branchLike={branchLike}
breadcrumbs={breadcrumbs}
/>
)}
- {shouldShowComponentList && (
- <>
- <div className={componentsClassName}>
- <Components
- baseComponent={baseComponent}
- branchLike={branchLike}
- components={components}
- cycle={true}
- metrics={metrics}
- onEndOfList={this.handleLoadMore}
- onGoToParent={this.handleGoToParent}
- onHighlight={this.handleHighlight}
- onSelect={this.handleSelect}
- rootComponent={component}
- selected={highlighted}
- newCodeSelected={newCodeSelected}
- showAnalysisDate={isPortfolio}
- />
- </div>
- <ListFooter count={components.length} loadMore={this.handleLoadMore} total={total} />
- </>
- )}
-
- {showSearch && searchResults && (
- <div className={componentsClassName}>
+ <div className={componentsClassName}>
+ {showComponentList && (
<Components
+ baseComponent={baseComponent}
+ branchLike={branchLike}
+ components={components}
+ cycle={true}
+ metrics={metrics}
+ onEndOfList={this.handleLoadMore}
+ onGoToParent={this.handleGoToParent}
+ onHighlight={this.handleHighlight}
+ onSelect={this.handleSelect}
+ rootComponent={component}
+ selected={highlighted}
+ newCodeSelected={newCodeSelected}
+ showAnalysisDate={isPortfolio}
+ />
+ )}
+
+ {showSearch && (
+ <SearchResults
branchLike={this.props.branchLike}
components={searchResults}
- metrics={[]}
onHighlight={this.handleHighlight}
onSelect={this.handleSelect}
rootComponent={component}
selected={highlighted}
/>
+ )}
+
+ <div role="status" className={showSearch ? 'text-center big-padded-bottom' : undefined}>
+ {searchResults?.length === 0 && translate('no_results')}
</div>
+ </div>
+
+ {showComponentList && (
+ <ListFooter count={components.length} loadMore={this.handleLoadMore} total={total} />
)}
{sourceViewer !== undefined && !showSearch && (
);
}
}
+
const StyledAlert = styled(Alert)`
display: inline-flex;
margin-bottom: 15px;
showAnalysisDate?: boolean;
}
-export class Component extends React.PureComponent<Props> {
+class Component extends React.PureComponent<Props> {
render() {
const {
branchLike,
}
return (
<span
- className="max-width-100 display-inline-block text-ellipsis"
+ className="max-width-100 text-ellipsis"
title={getTooltip(component)}
aria-label={ariaLabel}
>
showAnalysisDate?: boolean;
}
-export function Components(props: ComponentsProps) {
+function Components(props: ComponentsProps) {
const {
baseComponent,
branchLike,
import { KeyboardKeys } from '../../../helpers/keycodes';
import { translate } from '../../../helpers/l10n';
import { BranchLike } from '../../../types/branch-like';
-import { ComponentQualifier } from '../../../types/component';
+import { ComponentQualifier, isView } from '../../../types/component';
import { ComponentMeasure } from '../../../types/types';
interface Props {
loading: boolean;
}
-export class Search extends React.PureComponent<Props, State> {
+class Search extends React.PureComponent<Props, State> {
mounted = false;
state: State = {
query: '',
});
}
- const qualifiers = [
- ComponentQualifier.Portfolio,
- ComponentQualifier.SubPortfolio,
- ComponentQualifier.Application,
- ].includes(component.qualifier as ComponentQualifier)
+ const qualifiers = isView(component.qualifier)
? [ComponentQualifier.SubPortfolio, ComponentQualifier.Project].join(',')
: [ComponentQualifier.TestFile, ComponentQualifier.File].join(',');
render() {
const { component, newCodeSelected } = this.props;
const { loading, query } = this.state;
- const isPortfolio = ['VW', 'SVW', 'APP'].includes(component.qualifier);
+ const isViewLike = isView(component.qualifier);
return (
<div className="code-search" id="code-search">
- {isPortfolio && (
+ {isViewLike && (
<span className="big-spacer-right">
<ButtonToggle
disabled={!isEmpty(query)}
onChange={this.handleQueryChange}
onKeyDown={this.handleKeyDown}
placeholder={translate(
- isPortfolio ? 'code.search_placeholder.portfolio' : 'code.search_placeholder'
+ isViewLike ? 'code.search_placeholder.portfolio' : 'code.search_placeholder'
)}
value={this.state.query}
/>
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 classNames from 'classnames';
+import { sortBy } from 'lodash';
+import * as React from 'react';
+import withKeyboardNavigation, {
+ WithKeyboardNavigationProps,
+} from '../../../components/hoc/withKeyboardNavigation';
+import { getComponentMeasureUniqueKey } from '../../../helpers/component';
+import { BranchLike } from '../../../types/branch-like';
+import { ComponentMeasure } from '../../../types/types';
+import ComponentName from './ComponentName';
+
+export interface SearchResultsProps extends WithKeyboardNavigationProps {
+ branchLike?: BranchLike;
+ rootComponent: ComponentMeasure;
+ newCodeSelected?: boolean;
+}
+
+function SearchResults(props: SearchResultsProps) {
+ const { branchLike, components, newCodeSelected, rootComponent, selected } = props;
+
+ return (
+ <ul>
+ {components &&
+ components.length > 0 &&
+ sortBy(
+ components,
+ (c) => c.qualifier,
+ (c) => c.name.toLowerCase(),
+ (c) => (c.branch ? c.branch.toLowerCase() : '')
+ ).map((component) => (
+ <li
+ className={classNames({ selected: selected?.key === component.key })}
+ key={getComponentMeasureUniqueKey(component)}
+ >
+ <ComponentName
+ branchLike={branchLike}
+ canBrowse={true}
+ component={component}
+ rootComponent={rootComponent}
+ newCodeSelected={newCodeSelected}
+ />
+ </li>
+ ))}
+ </ul>
+ );
+}
+
+export default withKeyboardNavigation(SearchResults);
onIssueChange?: (issue: Issue) => void;
}
-export function SourceViewerWrapper(props: SourceViewerWrapperProps) {
+function SourceViewerWrapper(props: SourceViewerWrapperProps) {
const { branchLike, component, componentMeasures, location } = props;
const { line } = location.query;
const finalLine = line ? Number(line) : undefined;
export function isPortfolioLike(
componentQualifier?: string | ComponentQualifier
): componentQualifier is ComponentQualifier.Portfolio | ComponentQualifier.SubPortfolio {
- return Boolean(
- componentQualifier &&
- [
- ComponentQualifier.Portfolio.toString(),
- ComponentQualifier.SubPortfolio.toString(),
- ].includes(componentQualifier)
+ return (
+ componentQualifier === ComponentQualifier.Portfolio ||
+ componentQualifier === ComponentQualifier.SubPortfolio
);
}
return componentQualifier === ComponentQualifier.Project;
}
-export function isFile(componentQualifier?: string | ComponentQualifier): boolean {
+export function isFile(
+ componentQualifier?: string | ComponentQualifier
+): componentQualifier is ComponentQualifier.File {
return [ComponentQualifier.File, ComponentQualifier.TestFile].includes(
componentQualifier as ComponentQualifier
);
}
-export function isView(componentQualifier?: string | ComponentQualifier): boolean {
- return [
- ComponentQualifier.Portfolio,
- ComponentQualifier.SubPortfolio,
- ComponentQualifier.Application,
- ].includes(componentQualifier as ComponentQualifier);
+export function isView(
+ componentQualifier?: string | ComponentQualifier
+): componentQualifier is
+ | ComponentQualifier.Application
+ | ComponentQualifier.Portfolio
+ | ComponentQualifier.SubPortfolio {
+ return isPortfolioLike(componentQualifier) || isApplication(componentQualifier);
}
export interface ComponentContextShape {