diff options
7 files changed, 66 insertions, 7 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/TreeAction.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/TreeAction.java index 09a7efdb496..292b77400e7 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/TreeAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/TreeAction.java @@ -73,6 +73,7 @@ import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_STR public class TreeAction implements ComponentsWsAction { private static final int MAX_SIZE = 500; + private static final int QUERY_MINIMUM_LENGTH = 3; private static final String ALL_STRATEGY = "all"; private static final String CHILDREN_STRATEGY = "children"; private static final String LEAVES_STRATEGY = "leaves"; @@ -125,10 +126,11 @@ public class TreeAction implements ComponentsWsAction { .setExampleValue(NAME_SORT + ", " + PATH_SORT); action.createParam(Param.TEXT_QUERY) - .setDescription("Limit search to: <ul>" + + .setDescription(format("Limit search to: <ul>" + "<li>component names that contain the supplied string</li>" + "<li>component keys that are exactly the same as the supplied string</li>" + - "</ul>") + "</ul>" + + "Must have at least %d characters", QUERY_MINIMUM_LENGTH)) .setExampleValue("FILE_NAM"); createQualifiersParameter(action, newQualifierParameterContext(userSession, i18n, resourceTypes)); @@ -292,6 +294,9 @@ public class TreeAction implements ComponentsWsAction { .setPage(request.mandatoryParamAsInt(Param.PAGE)) .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE)); checkRequest(treeWsRequest.getPageSize() <= MAX_SIZE, "The '%s' parameter must be less than %d", Param.PAGE_SIZE, MAX_SIZE); + String searchQuery = treeWsRequest.getQuery(); + checkRequest(searchQuery == null || searchQuery.length() >= QUERY_MINIMUM_LENGTH, + "The '%s' parameter must have at least %d characters", Param.TEXT_QUERY, QUERY_MINIMUM_LENGTH); return treeWsRequest; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java index 7f903223247..f7850a85908 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java @@ -75,6 +75,7 @@ import static org.sonarqube.ws.client.measure.MeasuresWsParameters.PARAM_STRATEG */ public class ComponentTreeAction implements MeasuresWsAction { private static final int MAX_SIZE = 500; + private static final int QUERY_MINIMUM_LENGTH = 3; static final String ALL_STRATEGY = "all"; static final String CHILDREN_STRATEGY = "children"; static final String LEAVES_STRATEGY = "leaves"; @@ -120,10 +121,11 @@ public class ComponentTreeAction implements MeasuresWsAction { .setExampleValue(NAME_SORT + ", " + PATH_SORT); action.createParam(Param.TEXT_QUERY) - .setDescription("Limit search to: <ul>" + + .setDescription(format("Limit search to: <ul>" + "<li>component names that contain the supplied string</li>" + "<li>component keys that are exactly the same as the supplied string</li>" + - "</ul>") + "</ul>" + + "Must have at least %d characters.", QUERY_MINIMUM_LENGTH)) .setExampleValue("FILE_NAM"); action.createParam(PARAM_BASE_COMPONENT_ID) @@ -243,6 +245,9 @@ public class ComponentTreeAction implements MeasuresWsAction { .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE)) .setQuery(request.param(Param.TEXT_QUERY)); checkRequest(componentTreeWsRequest.getPageSize() <= MAX_SIZE, "The '%s' parameter must be less than %d", Param.PAGE_SIZE, MAX_SIZE); + String searchQuery = componentTreeWsRequest.getQuery(); + checkRequest(searchQuery == null || searchQuery.length() >= QUERY_MINIMUM_LENGTH, + "The '%s' parameter must have at least %d characters", Param.TEXT_QUERY, QUERY_MINIMUM_LENGTH); String metricSortValue = componentTreeWsRequest.getMetricSort(); checkRequest(!componentTreeWsRequest.getMetricKeys().isEmpty(), "The '%s' parameter must contain at least one metric key", PARAM_METRIC_KEYS); checkRequest(metricSortValue == null ^ componentTreeWsRequest.getSort().contains(METRIC_SORT), diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/TreeActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/TreeActionTest.java index 015d4e35673..85c78b149af 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/TreeActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/TreeActionTest.java @@ -306,6 +306,19 @@ public class TreeActionTest { } @Test + public void fail_when_search_query_has_less_than_3_characters() { + expectedException.expect(BadRequestException.class); + expectedException.expectMessage("The 'q' parameter must have at least 3 characters"); + componentDb.insertComponent(newProjectDto("project-uuid")); + db.commit(); + + ws.newRequest() + .setParam(PARAM_BASE_COMPONENT_ID, "project-uuid") + .setParam(Param.TEXT_QUERY, "fi") + .execute(); + } + + @Test public void fail_when_sort_is_unknown() { expectedException.expect(IllegalArgumentException.class); componentDb.insertComponent(newProjectDto("project-uuid")); diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java index 7ed74774d4b..4461b351508 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java @@ -317,6 +317,20 @@ public class ComponentTreeActionTest { } @Test + public void fail_when_search_query_have_less_than_3_characters() { + componentDb.insertProjectAndSnapshot(newProjectDto("project-uuid")); + insertNclocMetric(); + insertNewViolationsMetric(); + expectedException.expect(BadRequestException.class); + expectedException.expectMessage("The 'q' parameter must have at least 3 characters"); + + call(ws.newRequest() + .setParam(PARAM_BASE_COMPONENT_ID, "project-uuid") + .setParam(PARAM_METRIC_KEYS, "ncloc, new_violations") + .setParam(Param.TEXT_QUERY, "fi")); + } + + @Test public void fail_when_insufficient_privileges() { userSession.anonymous().setGlobalPermissions(GlobalPermissions.QUALITY_PROFILE_ADMIN); componentDb.insertProjectAndSnapshot(newProjectDto("project-uuid")); diff --git a/server/sonar-web/src/main/js/apps/code/actions/index.js b/server/sonar-web/src/main/js/apps/code/actions/index.js index a89e78786f5..6bca96a7b02 100644 --- a/server/sonar-web/src/main/js/apps/code/actions/index.js +++ b/server/sonar-web/src/main/js/apps/code/actions/index.js @@ -222,7 +222,10 @@ debouncedSearch = _.debounce(debouncedSearch, 250); export function search (query, baseComponent) { return dispatch => { dispatch(updateQueryAction(query)); - debouncedSearch(query, baseComponent, dispatch); + + if (query.length > 2 || !query.length) { + debouncedSearch(query, baseComponent, dispatch); + } }; } diff --git a/server/sonar-web/src/main/js/apps/code/components/Search.js b/server/sonar-web/src/main/js/apps/code/components/Search.js index 60522b0f759..97cba288b03 100644 --- a/server/sonar-web/src/main/js/apps/code/components/Search.js +++ b/server/sonar-web/src/main/js/apps/code/components/Search.js @@ -19,8 +19,10 @@ */ import React, { Component } from 'react'; import { connect } from 'react-redux'; +import classNames from 'classnames'; import { search, selectCurrent, selectNext, selectPrev } from '../actions'; +import { translateWithParameters } from '../../../helpers/l10n'; class Search extends Component { @@ -44,7 +46,7 @@ class Search extends Component { dispatch(selectNext()); break; default: - // do nothing + // do nothing } } @@ -57,6 +59,9 @@ class Search extends Component { render () { const { query } = this.props; + const inputClassName = classNames('search-box-input', { + 'touched': query.length > 0 && query.length < 3 + }); return ( <form @@ -70,12 +75,15 @@ class Search extends Component { onKeyDown={this.handleKeyDown.bind(this)} onChange={this.handleSearch.bind(this)} value={query} - className="search-box-input" + className={inputClassName} type="search" name="q" placeholder="Search" maxLength="100" autoComplete="off"/> + <div className="note"> + {translateWithParameters('select2.tooShort', 3)} + </div> </form> ); } diff --git a/server/sonar-web/src/main/js/apps/code/styles/code.css b/server/sonar-web/src/main/js/apps/code/styles/code.css index 3577f7f341e..f5ffda5a8e0 100644 --- a/server/sonar-web/src/main/js/apps/code/styles/code.css +++ b/server/sonar-web/src/main/js/apps/code/styles/code.css @@ -49,3 +49,14 @@ float: left; padding-right: 10px; } + +.code-search-box .note { + margin-top: 4px; + margin-left: 25px; + opacity: 0; + transition: opacity 0.3s ease; +} + +.code-search-box input.touched ~ .note { + opacity: 1; +} |