aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/sonar-web/src/main/js/app/components/search/Search.js80
-rw-r--r--server/sonar-web/src/main/js/app/components/search/SearchResult.js43
-rw-r--r--server/sonar-web/src/main/js/app/components/search/__tests__/SearchResult-test.js16
-rw-r--r--server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.js.snap8
-rw-r--r--server/sonar-web/src/main/less/components/navbar.less1
-rw-r--r--server/sonar-web/yarn.lock16
6 files changed, 119 insertions, 45 deletions
diff --git a/server/sonar-web/src/main/js/app/components/search/Search.js b/server/sonar-web/src/main/js/app/components/search/Search.js
index a138b2a1b35..adfd90a8b83 100644
--- a/server/sonar-web/src/main/js/app/components/search/Search.js
+++ b/server/sonar-web/src/main/js/app/components/search/Search.js
@@ -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>
);
diff --git a/server/sonar-web/src/main/js/app/components/search/SearchResult.js b/server/sonar-web/src/main/js/app/components/search/SearchResult.js
index 2765fa3f0f6..252c82fc441 100644
--- a/server/sonar-web/src/main/js/app/components/search/SearchResult.js
+++ b/server/sonar-web/src/main/js/app/components/search/SearchResult.js
@@ -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}
diff --git a/server/sonar-web/src/main/js/app/components/search/__tests__/SearchResult-test.js b/server/sonar-web/src/main/js/app/components/search/__tests__/SearchResult-test.js
index ef4aa10dafb..70d38fd2ad5 100644
--- a/server/sonar-web/src/main/js/app/components/search/__tests__/SearchResult-test.js
+++ b/server/sonar-web/src/main/js/app/components/search/__tests__/SearchResult-test.js
@@ -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);
+});
diff --git a/server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.js.snap b/server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.js.snap
index ce33642ae2c..f09e0116d75 100644
--- a/server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.js.snap
+++ b/server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.js.snap
@@ -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"
diff --git a/server/sonar-web/src/main/less/components/navbar.less b/server/sonar-web/src/main/less/components/navbar.less
index 6e3878b0c82..fd7ac938e48 100644
--- a/server/sonar-web/src/main/less/components/navbar.less
+++ b/server/sonar-web/src/main/less/components/navbar.less
@@ -209,6 +209,7 @@
}
.navbar-search-shortcut-hint {
+ line-height: 16px;
margin-top: 5px;
padding: 5px 10px;
border-top: 1px solid #e6e6e6;
diff --git a/server/sonar-web/yarn.lock b/server/sonar-web/yarn.lock
index 3936a65ab63..f424d809212 100644
--- a/server/sonar-web/yarn.lock
+++ b/server/sonar-web/yarn.lock
@@ -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"