Bladeren bron

SONAR-9067 Display multiple flows in the issues list (#1969)

tags/6.4-RC1
Stas Vilchik 7 jaren geleden
bovenliggende
commit
c547138087
16 gewijzigde bestanden met toevoegingen van 176 en 47 verwijderingen
  1. 11
    4
      server/sonar-web/src/main/js/apps/issues/actions.js
  2. 23
    4
      server/sonar-web/src/main/js/apps/issues/components/App.js
  3. 5
    2
      server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.js
  4. 4
    0
      server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssue.js
  5. 11
    2
      server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.js
  6. 5
    2
      server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocationBadge.js
  7. 53
    8
      server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocations.js
  8. 9
    4
      server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocationsNavigator.js
  9. 4
    1
      server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocationsNavigatorLocation.js
  10. 4
    0
      server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesList.js
  11. 1
    0
      server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssue-test.js.snap
  12. 1
    0
      server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocationBadge-test.js.snap
  13. 17
    8
      server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocations-test.js.snap
  14. 16
    0
      server/sonar-web/src/main/js/apps/issues/styles.css
  15. 5
    11
      server/sonar-web/src/main/js/components/common/LocationIndex.js
  16. 7
    1
      server/sonar-web/src/main/js/helpers/issues.js

+ 11
- 4
server/sonar-web/src/main/js/apps/issues/actions.js Bestand weergeven

@@ -22,6 +22,8 @@ import type { State } from './components/App';

export const enableLocationsNavigator = (state: State) => ({
locationsNavigator: true,
selectedFlowIndex: state.selectedFlowIndex ||
(state.openIssue && state.openIssue.flows.length > 0 ? 0 : null),
selectedLocationIndex: state.selectedLocationIndex || 0
});

@@ -47,12 +49,13 @@ export const selectLocation = (nextIndex: ?number) => (state: State) => {
};

export const selectNextLocation = (state: State) => {
const { selectedLocationIndex: index, openIssue } = state;
const { selectedFlowIndex, selectedLocationIndex: index, openIssue } = state;
if (openIssue) {
const locations = selectedFlowIndex != null
? openIssue.flows[selectedFlowIndex]
: openIssue.secondaryLocations;
return {
selectedLocationIndex: index != null && openIssue.secondaryLocations.length > index + 1
? index + 1
: index
selectedLocationIndex: index != null && locations.length > index + 1 ? index + 1 : index
};
}
};
@@ -63,3 +66,7 @@ export const selectPreviousLocation = (state: State) => {
return { selectedLocationIndex: index != null && index > 0 ? index - 1 : index };
}
};

export const selectFlow = (nextIndex: ?number) => () => {
return { selectedFlowIndex: nextIndex, selectedLocationIndex: 0 };
};

+ 23
- 4
server/sonar-web/src/main/js/apps/issues/components/App.js Bestand weergeven

@@ -89,6 +89,7 @@ export type State = {
referencedRules: { [string]: { name: string } },
referencedUsers: { [string]: ReferencedUser },
selected?: string,
selectedFlowIndex: ?number,
selectedLocationIndex: ?number
};

@@ -117,6 +118,7 @@ export default class App extends React.PureComponent {
referencedRules: {},
referencedUsers: {},
selected: getOpen(props.location.query),
selectedFlowIndex: null,
selectedLocationIndex: null
};
}
@@ -137,11 +139,15 @@ export default class App extends React.PureComponent {
const openIssue = this.getOpenIssue(nextProps, this.state.issues);

if (openIssue != null && openIssue.key !== this.state.selected) {
this.setState({ selected: openIssue.key, selectedLocationIndex: null });
this.setState({
selected: openIssue.key,
selectedFlowIndex: null,
selectedLocationIndex: null
});
}

if (openIssue == null) {
this.setState({ selectedLocationIndex: null });
this.setState({ selectedFlowIndex: null, selectedLocationIndex: null });
}

this.setState({
@@ -252,7 +258,11 @@ export default class App extends React.PureComponent {
if (this.state.openIssue) {
this.openIssue(issues[selectedIndex + 1].key);
} else {
this.setState({ selected: issues[selectedIndex + 1].key, selectedLocationIndex: null });
this.setState({
selected: issues[selectedIndex + 1].key,
selectedFlowIndex: null,
selectedLocationIndex: null
});
}
}
};
@@ -264,7 +274,11 @@ export default class App extends React.PureComponent {
if (this.state.openIssue) {
this.openIssue(issues[selectedIndex - 1].key);
} else {
this.setState({ selected: issues[selectedIndex - 1].key, selectedLocationIndex: null });
this.setState({
selected: issues[selectedIndex - 1].key,
selectedFlowIndex: null,
selectedLocationIndex: null
});
}
}
};
@@ -372,6 +386,7 @@ export default class App extends React.PureComponent {
selected: issues.length > 0
? openIssue != null ? openIssue.key : issues[0].key
: undefined,
selectedFlowIndex: null,
selectedLocationIndex: null
});
}
@@ -560,6 +575,7 @@ export default class App extends React.PureComponent {
selectLocation = (index: ?number) => this.setState(actions.selectLocation(index));
selectNextLocation = () => this.setState(actions.selectNextLocation);
selectPreviousLocation = () => this.setState(actions.selectPreviousLocation);
selectFlow = (index: ?number) => this.setState(actions.selectFlow(index));

renderBulkChange(openIssue: ?Issue) {
const { component, currentUser } = this.props;
@@ -649,9 +665,11 @@ export default class App extends React.PureComponent {
/>
<ConciseIssuesList
issues={issues}
onFlowSelect={this.selectFlow}
onIssueSelect={this.openIssue}
onLocationSelect={this.selectLocation}
selected={this.state.selected}
selectedFlowIndex={this.state.selectedFlowIndex}
selectedLocationIndex={this.state.selectedLocationIndex}
/>
{paging != null &&
@@ -755,6 +773,7 @@ export default class App extends React.PureComponent {
onIssueChange={this.handleIssueChange}
onIssueSelect={this.openIssue}
onLocationSelect={this.selectLocation}
selectedFlowIndex={this.state.selectedFlowIndex}
selectedLocationIndex={
this.state.locationsNavigator ? this.state.selectedLocationIndex : null
}

+ 5
- 2
server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.js Bestand weergeven

@@ -29,6 +29,7 @@ type Props = {|
onIssueSelect: string => void,
onLocationSelect: number => void,
openIssue: Issue,
selectedFlowIndex: ?number,
selectedLocationIndex: ?number
|};

@@ -58,9 +59,11 @@ export default class IssuesSourceViewer extends React.PureComponent {
};

render() {
const { openIssue, selectedLocationIndex } = this.props;
const { openIssue, selectedFlowIndex, selectedLocationIndex } = this.props;

const locations = openIssue.secondaryLocations;
const locations = selectedFlowIndex != null
? openIssue.flows[selectedFlowIndex]
: openIssue.flows.length > 0 ? openIssue.flows[0] : openIssue.secondaryLocations;

const locationMessage = locations != null &&
selectedLocationIndex != null &&

+ 4
- 0
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssue.js Bestand weergeven

@@ -25,11 +25,13 @@ import type { Issue } from '../../../components/issue/types';

type Props = {|
issue: Issue,
onFlowSelect: number => void,
onLocationSelect: number => void,
onSelect: string => void,
previousIssue: ?Issue,
scroll: HTMLElement => void,
selected: boolean,
selectedFlowIndex: ?number,
selectedLocationIndex: ?number
|};

@@ -47,9 +49,11 @@ export default class ConciseIssue extends React.PureComponent {
<ConciseIssueBox
issue={issue}
onClick={this.props.onSelect}
onFlowSelect={this.props.onFlowSelect}
onLocationSelect={this.props.onLocationSelect}
scroll={this.props.scroll}
selected={selected}
selectedFlowIndex={selected ? this.props.selectedFlowIndex : null}
selectedLocationIndex={selected ? this.props.selectedLocationIndex : null}
/>
</div>

+ 11
- 2
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.js Bestand weergeven

@@ -29,9 +29,11 @@ import type { Issue } from '../../../components/issue/types';
type Props = {|
issue: Issue,
onClick: string => void,
onFlowSelect: number => void,
onLocationSelect: number => void,
scroll: HTMLElement => void,
selected: boolean,
selectedFlowIndex: ?number,
selectedLocationIndex: ?number
|};

@@ -66,20 +68,27 @@ export default class ConciseIssueBox extends React.PureComponent {
: { onClick: this.handleClick, role: 'listitem', tabIndex: 0 };

return (
<div className={classNames('concise-issue-box', { selected })} {...clickAttributes}>
<div
className={classNames('concise-issue-box', 'clearfix', { selected })}
{...clickAttributes}>
<div className="concise-issue-box-message" ref={node => (this.node = node)}>
{issue.message}
</div>
<div className="concise-issue-box-attributes">
<TypeHelper type={issue.type} />
<SeverityHelper className="big-spacer-left" severity={issue.severity} />
<ConciseIssueLocations issue={issue} />
<ConciseIssueLocations
issue={issue}
onFlowSelect={this.props.onFlowSelect}
selectedFlowIndex={this.props.selectedFlowIndex}
/>
</div>
{selected &&
<ConciseIssueLocationsNavigator
issue={issue}
onLocationSelect={this.props.onLocationSelect}
scroll={this.props.scroll}
selectedFlowIndex={this.props.selectedFlowIndex}
selectedLocationIndex={this.props.selectedLocationIndex}
/>}
</div>

+ 5
- 2
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocationBadge.js Bestand weergeven

@@ -25,17 +25,20 @@ import { translateWithParameters } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures';

type Props = {|
count: number
count: number,
onClick?: () => void,
selected?: boolean
|};

export default function ConciseIssueLocationBadge(props: Props) {
return (
<Tooltip
mouseEnterDelay={0.5}
overlay={translateWithParameters(
'issue.this_issue_involves_x_code_locations',
formatMeasure(props.count)
)}>
<LocationIndex>
<LocationIndex onClick={props.onClick} selected={props.selected}>
{'+'}{props.count}
</LocationIndex>
</Tooltip>

+ 53
- 8
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocations.js Bestand weergeven

@@ -23,22 +23,67 @@ import ConciseIssueLocationBadge from './ConciseIssueLocationBadge';
import type { Issue } from '../../../components/issue/types';

type Props = {|
issue: Issue
issue: Issue,
onFlowSelect: number => void,
selectedFlowIndex: ?number
|};

type State = {
collapsed: boolean
};

const LIMIT = 3;

export default class ConciseIssueLocations extends React.PureComponent {
props: Props;
state: State = { collapsed: true };

handleExpandClick = (event: Event) => {
event.preventDefault();
this.setState({ collapsed: false });
};

renderExpandButton() {
return (
<a className="little-spacer-left link-no-underline" href="#" onClick={this.handleExpandClick}>
...
</a>
);
}

render() {
const { secondaryLocations, flows } = this.props.issue;

return (
<div className="pull-right">
{secondaryLocations.length > 0 &&
<ConciseIssueLocationBadge count={secondaryLocations.length} />}
const badges = [];

{flows.map((flow, index) => <ConciseIssueLocationBadge key={index} count={flow.length} />)}
</div>
);
if (secondaryLocations.length > 0) {
badges.push(
<ConciseIssueLocationBadge
key="-1"
count={secondaryLocations.length}
selected={this.props.selectedFlowIndex == null}
/>
);
}

flows.forEach((flow, index) => {
badges.push(
<ConciseIssueLocationBadge
key={index}
count={flow.length}
onClick={() => this.props.onFlowSelect(index)}
selected={index === this.props.selectedFlowIndex}
/>
);
});

return this.state.collapsed
? <div className="concise-issue-locations pull-right">
{badges.slice(0, LIMIT)}
{badges.length > LIMIT && this.renderExpandButton()}
</div>
: <div className="concise-issue-locations spacer-top">
{badges}
</div>;
}
}

+ 9
- 4
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocationsNavigator.js Bestand weergeven

@@ -26,6 +26,7 @@ type Props = {|
issue: Issue,
onLocationSelect: number => void,
scroll: HTMLElement => void,
selectedFlowIndex: ?number,
selectedLocationIndex: ?number
|};

@@ -38,16 +39,20 @@ export default class ConciseIssueLocationsNavigator extends React.PureComponent
};

render() {
const { selectedLocationIndex } = this.props;
const { secondaryLocations } = this.props.issue;
const { selectedFlowIndex, selectedLocationIndex } = this.props;
const { flows, secondaryLocations } = this.props.issue;

if (secondaryLocations.length === 0) {
const locations = selectedFlowIndex != null
? flows[selectedFlowIndex]
: flows.length > 0 ? flows[0] : secondaryLocations;

if (locations == null || locations.length === 0) {
return null;
}

return (
<div className="spacer-top">
{secondaryLocations.map((location, index) => (
{locations.map((location, index) => (
<ConciseIssueLocationsNavigatorLocation
key={index}
index={index}

+ 4
- 1
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocationsNavigatorLocation.js Bestand weergeven

@@ -54,7 +54,10 @@ export default class ConciseIssueLocationsNavigatorLocation extends React.PureCo
render() {
return (
<div className="little-spacer-top" ref={node => (this.node = node)}>
<a className="link-no-underline" href="#" onClick={this.handleClick}>
<a
className="consice-issue-locations-navigator-location"
href="#"
onClick={this.handleClick}>
<LocationIndex selected={this.props.selected}>
{this.props.index + 1}
</LocationIndex>

+ 4
- 0
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesList.js Bestand weergeven

@@ -25,9 +25,11 @@ import type { Issue } from '../../../components/issue/types';

type Props = {|
issues: Array<Issue>,
onFlowSelect: number => void,
onIssueSelect: string => void,
onLocationSelect: number => void,
selected?: string,
selectedFlowIndex: ?number,
selectedLocationIndex: ?number
|};

@@ -48,11 +50,13 @@ export default class ConciseIssuesList extends React.PureComponent {
<ConciseIssue
key={issue.key}
issue={issue}
onFlowSelect={this.props.onFlowSelect}
onLocationSelect={this.props.onLocationSelect}
onSelect={this.props.onIssueSelect}
previousIssue={index > 0 ? this.props.issues[index - 1] : null}
scroll={this.handleScroll}
selected={issue.key === this.props.selected}
selectedFlowIndex={this.props.selectedFlowIndex}
selectedLocationIndex={this.props.selectedLocationIndex}
/>
))}

+ 1
- 0
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssue-test.js.snap Bestand weergeven

@@ -5,6 +5,7 @@ exports[`test should render 1`] = `
issue={Object {}}
onClick={[Function]}
selected={false}
selectedFlowIndex={null}
selectedLocationIndex={null} />
</div>
`;

+ 1
- 0
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocationBadge-test.js.snap Bestand weergeven

@@ -1,5 +1,6 @@
exports[`test should render 1`] = `
<Tooltip
mouseEnterDelay={0.5}
overlay="issue.this_issue_involves_x_code_locations.7"
placement="bottom">
<LocationIndex

+ 17
- 8
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocations-test.js.snap Bestand weergeven

@@ -1,27 +1,36 @@
exports[`test should render one flow 1`] = `
<div
className="pull-right">
className="concise-issue-locations pull-right">
<ConciseIssueLocationBadge
count={3} />
count={3}
onClick={[Function]}
selected={false} />
</div>
`;

exports[`test should render secondary locations 1`] = `
<div
className="pull-right">
className="concise-issue-locations pull-right">
<ConciseIssueLocationBadge
count={3} />
count={3}
selected={true} />
</div>
`;

exports[`test should render several flows 1`] = `
<div
className="pull-right">
className="concise-issue-locations pull-right">
<ConciseIssueLocationBadge
count={3} />
count={3}
onClick={[Function]}
selected={false} />
<ConciseIssueLocationBadge
count={2} />
count={2}
onClick={[Function]}
selected={false} />
<ConciseIssueLocationBadge
count={3} />
count={3}
onClick={[Function]}
selected={false} />
</div>
`;

+ 16
- 0
server/sonar-web/src/main/js/apps/issues/styles.css Bestand weergeven

@@ -113,4 +113,20 @@

.concise-issue-box:not(.selected) .location-index {
background-color: #ccc;
}

.concise-issue-locations {
margin-right: -4px;
margin-bottom: -4px;
}

.concise-issue-locations .location-index {
margin-right: 4px;
margin-bottom: 4px;
}

.consice-issue-locations-navigator-location {
display: flex;
align-items: flex-start;
border: none;
}

+ 5
- 11
server/sonar-web/src/main/js/components/common/LocationIndex.js Bestand weergeven

@@ -29,19 +29,13 @@ type Props = {
};

export default function LocationIndex(props: Props) {
const clickAttributes = props.onClick
? {
onClick: props.onClick,
role: 'button',
tabIndex: 0
}
: {};
const { children, onClick, selected, ...other } = props;
const clickAttributes = onClick ? { onClick, role: 'button', tabIndex: 0 } : {};

// put {...others} because Tooltip sets some event handlers
return (
<div
className={classNames('location-index', { selected: props.selected })}
{...clickAttributes}>
{props.children}
<div className={classNames('location-index', { selected })} {...clickAttributes} {...other}>
{children}
</div>
);
}

+ 7
- 1
server/sonar-web/src/main/js/helpers/issues.js Bestand weergeven

@@ -108,6 +108,12 @@ const ensureTextRange = (issue: RawIssue) => {
: {};
};

const reverseLocations = (locations: Array<*>) => {
const x = [...locations];
x.reverse();
return x;
};

const splitFlows = (
issue: RawIssue
// $FlowFixMe textRange is not null
@@ -121,7 +127,7 @@ const splitFlows = (

return onlySecondaryLocations
? { secondaryLocations: flatten(parsedFlows), flows: [] }
: { secondaryLocations: [], flows: parsedFlows };
: { secondaryLocations: [], flows: parsedFlows.map(reverseLocations) };
};

export const parseIssueFromResponse = (

Laden…
Annuleren
Opslaan