Bläddra i källkod

apply search feedback (#2083)

tags/6.4-RC2
Stas Vilchik 7 år sedan
förälder
incheckning
c8727c5ee3

+ 44
- 36
server/sonar-web/src/main/js/app/components/search/Search.js Visa fil

@@ -28,6 +28,7 @@ import { sortQualifiers } from './utils';
import type { Component, More, Results } from './utils';
import RecentHistory from '../../components/RecentHistory';
import DeferredSpinner from '../../../components/common/DeferredSpinner';
import ClockIcon from '../../../components/common/ClockIcon';
import { getSuggestions } from '../../../api/components';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { scrollToElement } from '../../../helpers/scrolling';
@@ -162,30 +163,34 @@ export default class Search extends React.PureComponent {
};

search = (query: string) => {
this.setState({ loading: true });
const recentlyBrowsed = RecentHistory.get().map(component => component.key);
getSuggestions(query, recentlyBrowsed).then(response => {
// compare `this.state.query` and `query` to handle two request done almost at the same time
// in this case only the request that matches the current query should be taken
if (this.mounted && this.state.query === query) {
const results = {};
const more = {};
response.results.forEach(group => {
results[group.q] = group.items.map(item => ({ ...item, qualifier: group.q }));
more[group.q] = group.more;
});
const list = this.getPlainComponentsList(results, more);
this.setState(state => ({
loading: false,
more,
organizations: { ...state.organizations, ...keyBy(response.organizations, 'key') },
projects: { ...state.projects, ...keyBy(response.projects, 'key') },
results,
selected: list.length > 0 ? list[0] : null,
shortQuery: response.warning === 'short_input'
}));
}
});
if (query.length === 0 || query.length >= 2) {
this.setState({ loading: true });
const recentlyBrowsed = RecentHistory.get().map(component => component.key);
getSuggestions(query, recentlyBrowsed).then(response => {
// compare `this.state.query` and `query` to handle two request done almost at the same time
// in this case only the request that matches the current query should be taken
if (this.mounted && this.state.query === query) {
const results = {};
const more = {};
response.results.forEach(group => {
results[group.q] = group.items.map(item => ({ ...item, qualifier: group.q }));
more[group.q] = group.more;
});
const list = this.getPlainComponentsList(results, more);
this.setState(state => ({
loading: false,
more,
organizations: { ...state.organizations, ...keyBy(response.organizations, 'key') },
projects: { ...state.projects, ...keyBy(response.projects, 'key') },
results,
selected: list.length > 0 ? list[0] : null,
shortQuery: response.warning === 'short_input'
}));
}
});
} else {
this.setState({ loading: false });
}
};

searchMore = (qualifier: string) => {
@@ -216,9 +221,7 @@ export default class Search extends React.PureComponent {
handleQueryChange = (event: { currentTarget: HTMLInputElement }) => {
const query = event.currentTarget.value;
this.setState({ query, shortQuery: query.length === 1 });
if (query.length === 0 || query.length >= 2) {
this.search(query);
}
this.search(query);
};

selectPrevious = () => {
@@ -359,15 +362,20 @@ export default class Search extends React.PureComponent {
results={this.state.results}
selected={this.state.selected}
/>
<div
className="navbar-search-shortcut-hint"
dangerouslySetInnerHTML={{
__html: translateWithParameters(
'search.shortcut_hint',
'<span class="shortcut-button shortcut-button-small">s</span>'
)
}}
/>
<div className="navbar-search-shortcut-hint">
<div className="pull-right">
<ClockIcon className="little-spacer-right" size={12} />
{translate('recently_browsed')}
</div>
<div
dangerouslySetInnerHTML={{
__html: translateWithParameters(
'search.shortcut_hint',
'<span class="shortcut-button shortcut-button-small">s</span>'
)
}}
/>
</div>
</div>}
</li>
);

+ 42
- 1
server/sonar-web/src/main/js/app/components/search/SearchResult.js Visa fil

@@ -38,8 +38,45 @@ type Props = {|
selected: boolean
|};

type State = {
tooltipVisible: boolean
};

const TOOLTIP_DELAY = 1000;

export default class SearchResult extends React.PureComponent {
interval: ?number;
props: Props;
state: State = { tooltipVisible: false };

componentDidMount() {
if (this.props.selected) {
this.scheduleTooltip();
}
}

componentWillReceiveProps(nextProps: Props) {
if (!this.props.selected && nextProps.selected) {
this.scheduleTooltip();
} else if (this.props.selected && !nextProps.selected) {
this.unscheduleTooltip();
this.setState({ tooltipVisible: false });
}
}

componentWillUnmount() {
this.unscheduleTooltip();
}

scheduleTooltip = () => {
this.interval = setTimeout(() => this.setState({ tooltipVisible: true }), TOOLTIP_DELAY);
};

unscheduleTooltip = () => {
if (this.interval) {
clearInterval(this.interval);
}
};

handleMouseEnter = () => {
this.props.onSelect(this.props.component.key);
@@ -79,7 +116,11 @@ export default class SearchResult extends React.PureComponent {
className={this.props.selected ? 'active' : undefined}
key={component.key}
ref={node => this.props.innerRef(component.key, node)}>
<Tooltip mouseEnterDelay={1.0} overlay={component.key} placement="left">
<Tooltip
mouseEnterDelay={TOOLTIP_DELAY / 1000}
overlay={component.key}
placement="left"
visible={this.state.tooltipVisible}>
<Link
className="navbar-search-item-link"
data-key={component.key}

+ 16
- 0
server/sonar-web/src/main/js/app/components/search/__tests__/SearchResult-test.js Visa fil

@@ -39,6 +39,8 @@ function render(props?: Object) {
);
}

jest.useFakeTimers();

it('renders selected', () => {
const wrapper = render();
expect(wrapper).toMatchSnapshot();
@@ -107,3 +109,17 @@ it('renders organizations', () => {
wrapper.setProps({ appState: { organizationsEnabled: false } });
expect(wrapper).toMatchSnapshot();
});

it('shows tooltip after delay', () => {
const wrapper = render();
expect(wrapper.find('Tooltip').prop('visible')).toBe(false);

wrapper.setProps({ selected: true });
expect(wrapper.find('Tooltip').prop('visible')).toBe(false);

jest.runAllTimers();
expect(wrapper.find('Tooltip').prop('visible')).toBe(true);

wrapper.setProps({ selected: false });
expect(wrapper.find('Tooltip').prop('visible')).toBe(false);
});

+ 8
- 0
server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.js.snap Visa fil

@@ -6,6 +6,7 @@ exports[`renders favorite 1`] = `
mouseEnterDelay={1}
overlay="foo"
placement="left"
visible={false}
>
<Link
className="navbar-search-item-link"
@@ -51,6 +52,7 @@ exports[`renders match 1`] = `
mouseEnterDelay={1}
overlay="foo"
placement="left"
visible={false}
>
<Link
className="navbar-search-item-link"
@@ -95,6 +97,7 @@ exports[`renders organizations 1`] = `
mouseEnterDelay={1}
overlay="foo"
placement="left"
visible={false}
>
<Link
className="navbar-search-item-link"
@@ -144,6 +147,7 @@ exports[`renders organizations 2`] = `
mouseEnterDelay={1}
overlay="foo"
placement="left"
visible={false}
>
<Link
className="navbar-search-item-link"
@@ -188,6 +192,7 @@ exports[`renders projects 1`] = `
mouseEnterDelay={1}
overlay="qwe"
placement="left"
visible={false}
>
<Link
className="navbar-search-item-link"
@@ -237,6 +242,7 @@ exports[`renders recently browsed 1`] = `
mouseEnterDelay={1}
overlay="foo"
placement="left"
visible={false}
>
<Link
className="navbar-search-item-link"
@@ -281,6 +287,7 @@ exports[`renders selected 1`] = `
mouseEnterDelay={1}
overlay="foo"
placement="left"
visible={false}
>
<Link
className="navbar-search-item-link"
@@ -324,6 +331,7 @@ exports[`renders selected 2`] = `
mouseEnterDelay={1}
overlay="foo"
placement="left"
visible={false}
>
<Link
className="navbar-search-item-link"

+ 1
- 0
server/sonar-web/src/main/less/components/navbar.less Visa fil

@@ -209,6 +209,7 @@
}

.navbar-search-shortcut-hint {
line-height: 16px;
margin-top: 5px;
padding: 5px 10px;
border-top: 1px solid #e6e6e6;

+ 8
- 8
server/sonar-web/yarn.lock Visa fil

@@ -4716,23 +4716,23 @@ postcss-zindex@^2.0.1:
postcss "^5.0.4"
uniqs "^2.0.0"

postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.1.2:
version "5.2.8"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.8.tgz#05720c49df23c79bda51fd01daeb1e9222e94390"
postcss@^5.0.10, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.8, postcss@^5.2.17:
version "5.2.17"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.17.tgz#cf4f597b864d65c8a492b2eabe9d706c879c388b"
dependencies:
chalk "^1.1.3"
js-base64 "^2.1.9"
source-map "^0.5.6"
supports-color "^3.1.2"
supports-color "^3.2.3"

postcss@^5.2.17:
version "5.2.17"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.17.tgz#cf4f597b864d65c8a492b2eabe9d706c879c388b"
postcss@^5.0.11, postcss@^5.0.6, postcss@^5.1.2:
version "5.2.8"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.8.tgz#05720c49df23c79bda51fd01daeb1e9222e94390"
dependencies:
chalk "^1.1.3"
js-base64 "^2.1.9"
source-map "^0.5.6"
supports-color "^3.2.3"
supports-color "^3.1.2"

prelude-ls@~1.1.2:
version "1.1.2"

+ 1
- 1
sonar-core/src/main/resources/org/sonar/l10n/core.properties Visa fil

@@ -29,7 +29,6 @@ biggest=Biggest
blocker=Blocker
bold=Bold
branch=Branch
browsed_recently=Browsed Recently
build_date=Build date
build_time=Build time
calendar=Calendar
@@ -135,6 +134,7 @@ projects_management=Projects Management
quality_profile=Quality Profile
raw=Raw
recent_history=Recent History
recently_browsed=Recently Browsed
refresh=Refresh
reload=Reload
remove=Remove

Laddar…
Avbryt
Spara