export const enableLocationsNavigator = (state: State) => ({
locationsNavigator: true,
+ selectedFlowIndex: state.selectedFlowIndex ||
+ (state.openIssue && state.openIssue.flows.length > 0 ? 0 : null),
selectedLocationIndex: state.selectedLocationIndex || 0
});
};
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
};
}
};
return { selectedLocationIndex: index != null && index > 0 ? index - 1 : index };
}
};
+
+export const selectFlow = (nextIndex: ?number) => () => {
+ return { selectedFlowIndex: nextIndex, selectedLocationIndex: 0 };
+};
referencedRules: { [string]: { name: string } },
referencedUsers: { [string]: ReferencedUser },
selected?: string,
+ selectedFlowIndex: ?number,
selectedLocationIndex: ?number
};
referencedRules: {},
referencedUsers: {},
selected: getOpen(props.location.query),
+ selectedFlowIndex: null,
selectedLocationIndex: null
};
}
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({
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
+ });
}
}
};
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
+ });
}
}
};
selected: issues.length > 0
? openIssue != null ? openIssue.key : issues[0].key
: undefined,
+ selectedFlowIndex: null,
selectedLocationIndex: null
});
}
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;
/>
<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 &&
onIssueChange={this.handleIssueChange}
onIssueSelect={this.openIssue}
onLocationSelect={this.selectLocation}
+ selectedFlowIndex={this.state.selectedFlowIndex}
selectedLocationIndex={
this.state.locationsNavigator ? this.state.selectedLocationIndex : null
}
onIssueSelect: string => void,
onLocationSelect: number => void,
openIssue: Issue,
+ selectedFlowIndex: ?number,
selectedLocationIndex: ?number
|};
};
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 &&
type Props = {|
issue: Issue,
+ onFlowSelect: number => void,
onLocationSelect: number => void,
onSelect: string => void,
previousIssue: ?Issue,
scroll: HTMLElement => void,
selected: boolean,
+ selectedFlowIndex: ?number,
selectedLocationIndex: ?number
|};
<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>
type Props = {|
issue: Issue,
onClick: string => void,
+ onFlowSelect: number => void,
onLocationSelect: number => void,
scroll: HTMLElement => void,
selected: boolean,
+ selectedFlowIndex: ?number,
selectedLocationIndex: ?number
|};
: { 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>
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>
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>;
}
}
issue: Issue,
onLocationSelect: number => void,
scroll: HTMLElement => void,
+ selectedFlowIndex: ?number,
selectedLocationIndex: ?number
|};
};
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}
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>
type Props = {|
issues: Array<Issue>,
+ onFlowSelect: number => void,
onIssueSelect: string => void,
onLocationSelect: number => void,
selected?: string,
+ selectedFlowIndex: ?number,
selectedLocationIndex: ?number
|};
<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}
/>
))}
issue={Object {}}
onClick={[Function]}
selected={false}
+ selectedFlowIndex={null}
selectedLocationIndex={null} />
</div>
`;
exports[`test should render 1`] = `
<Tooltip
+ mouseEnterDelay={0.5}
overlay="issue.this_issue_involves_x_code_locations.7"
placement="bottom">
<LocationIndex
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>
`;
.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;
}
\ No newline at end of file
};
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>
);
}
: {};
};
+const reverseLocations = (locations: Array<*>) => {
+ const x = [...locations];
+ x.reverse();
+ return x;
+};
+
const splitFlows = (
issue: RawIssue
// $FlowFixMe textRange is not null
return onlySecondaryLocations
? { secondaryLocations: flatten(parsedFlows), flows: [] }
- : { secondaryLocations: [], flows: parsedFlows };
+ : { secondaryLocations: [], flows: parsedFlows.map(reverseLocations) };
};
export const parseIssueFromResponse = (