Browse Source

SONAR-18328 SONAR-18332 [1099493] [1101757]

* [1099493] Status message not automatically announced
* [1101757] Visual list is not marked up as list
tags/10.0.0.68432
Wouter Admiraal 1 year ago
parent
commit
4420e38ff6

+ 3
- 2
server/sonar-web/src/main/js/apps/code/__tests__/Code-it.ts View File

@@ -120,7 +120,7 @@ it('should behave correctly when using search', async () => {

// 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');
@@ -141,7 +141,7 @@ it('should behave correctly when using search', async () => {
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 () => {
@@ -370,6 +370,7 @@ function getPageObject(user: UserEvent) {
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'),

+ 12
- 0
server/sonar-web/src/main/js/apps/code/code.css View File

@@ -29,6 +29,18 @@
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;
}

+ 34
- 31
server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx View File

@@ -48,6 +48,7 @@ import {
import Breadcrumbs from './Breadcrumbs';
import Components from './Components';
import Search from './Search';
import SearchResults from './SearchResults';
import SourceViewerWrapper from './SourceViewerWrapper';

interface Props {
@@ -72,7 +73,7 @@ interface State {
newCodeSelected: boolean;
}

export class CodeApp extends React.Component<Props, State> {
class CodeApp extends React.Component<Props, State> {
mounted = false;
state: State;

@@ -266,10 +267,9 @@ export class CodeApp extends React.Component<Props, 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,
@@ -333,7 +333,7 @@ export class CodeApp extends React.Component<Props, State> {
</span>
</div>
)}
{shouldShowBreadcrumbs && (
{showBreadcrumbs && (
<Breadcrumbs
branchLike={branchLike}
breadcrumbs={breadcrumbs}
@@ -341,41 +341,43 @@ export class CodeApp extends React.Component<Props, State> {
/>
)}

{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 && (
@@ -396,6 +398,7 @@ export class CodeApp extends React.Component<Props, State> {
);
}
}

const StyledAlert = styled(Alert)`
display: inline-flex;
margin-bottom: 15px;

+ 1
- 1
server/sonar-web/src/main/js/apps/code/components/Component.tsx View File

@@ -44,7 +44,7 @@ interface Props {
showAnalysisDate?: boolean;
}

export class Component extends React.PureComponent<Props> {
class Component extends React.PureComponent<Props> {
render() {
const {
branchLike,

+ 1
- 1
server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx View File

@@ -100,7 +100,7 @@ export default function ComponentName({
}
return (
<span
className="max-width-100 display-inline-block text-ellipsis"
className="max-width-100 text-ellipsis"
title={getTooltip(component)}
aria-label={ariaLabel}
>

+ 1
- 1
server/sonar-web/src/main/js/apps/code/components/Components.tsx View File

@@ -39,7 +39,7 @@ interface ComponentsProps {
showAnalysisDate?: boolean;
}

export function Components(props: ComponentsProps) {
function Components(props: ComponentsProps) {
const {
baseComponent,
branchLike,

+ 6
- 10
server/sonar-web/src/main/js/apps/code/components/Search.tsx View File

@@ -28,7 +28,7 @@ import { getBranchLikeQuery } from '../../../helpers/branch-like';
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 {
@@ -47,7 +47,7 @@ interface State {
loading: boolean;
}

export class Search extends React.PureComponent<Props, State> {
class Search extends React.PureComponent<Props, State> {
mounted = false;
state: State = {
query: '',
@@ -99,11 +99,7 @@ export class Search extends React.PureComponent<Props, State> {
});
}

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(',');

@@ -144,11 +140,11 @@ export class Search extends React.PureComponent<Props, State> {
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)}
@@ -172,7 +168,7 @@ export class Search extends React.PureComponent<Props, State> {
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}
/>

+ 67
- 0
server/sonar-web/src/main/js/apps/code/components/SearchResults.tsx View File

@@ -0,0 +1,67 @@
/*
* 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);

+ 1
- 1
server/sonar-web/src/main/js/apps/code/components/SourceViewerWrapper.tsx View File

@@ -32,7 +32,7 @@ export interface SourceViewerWrapperProps {
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;

+ 13
- 13
server/sonar-web/src/main/js/types/component.ts View File

@@ -62,12 +62,9 @@ export interface TreeComponentWithPath extends TreeComponent {
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
);
}

@@ -83,18 +80,21 @@ export function isProject(
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 {

Loading…
Cancel
Save