diff options
author | Stas Vilchik <vilchiks@gmail.com> | 2015-12-22 10:46:16 +0100 |
---|---|---|
committer | Stas Vilchik <vilchiks@gmail.com> | 2015-12-22 10:46:23 +0100 |
commit | cb5a1d7fe4859d95a5ebeea7a4aa717fcf03f3a8 (patch) | |
tree | 9d6cf03e38aca013558ddd42b1da804377d6408a /server/sonar-web/src/main/js | |
parent | bfa9e24fadd011c331e3af791ac189075a73b8e3 (diff) | |
download | sonarqube-cb5a1d7fe4859d95a5ebeea7a4aa717fcf03f3a8.tar.gz sonarqube-cb5a1d7fe4859d95a5ebeea7a4aa717fcf03f3a8.zip |
SONAR-7149 Add an ability to search for sub-components
Diffstat (limited to 'server/sonar-web/src/main/js')
6 files changed, 132 insertions, 21 deletions
diff --git a/server/sonar-web/src/main/js/api/components.js b/server/sonar-web/src/main/js/api/components.js index 24f8c446a6f..5d78f0b3477 100644 --- a/server/sonar-web/src/main/js/api/components.js +++ b/server/sonar-web/src/main/js/api/components.js @@ -49,3 +49,9 @@ export function getComponent (componentKey, metrics = []) { const data = { resource: componentKey, metrics: metrics.join(',') }; return getJSON(url, data).then(r => r[0]); } + +export function getTree(baseComponentKey, options = {}) { + const url = baseUrl + '/api/components/tree'; + const data = Object.assign({}, options, { baseComponentKey }); + return getJSON(url, data); +} 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 dcf7e47d554..a68df6429b7 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 @@ -1,7 +1,7 @@ import _ from 'underscore'; import { pushPath } from 'redux-simple-router'; -import { getChildren, getComponent } from '../../../api/components'; +import { getChildren, getComponent, getTree } from '../../../api/components'; import { getComponentNavigation } from '../../../api/nav'; @@ -22,6 +22,8 @@ const METRICS_WITH_COVERAGE = [ export const INIT = 'INIT'; export const BROWSE = 'BROWSE'; +export const SEARCH = 'SEARCH'; +export const UPDATE_QUERY = 'UPDATE_QUERY'; export const START_FETCHING = 'START_FETCHING'; export const STOP_FETCHING = 'STOP_FETCHING'; @@ -43,6 +45,20 @@ export function browseAction (component, children = [], breadcrumbs = []) { }; } +export function searchAction (components) { + return { + type: SEARCH, + components + }; +} + +export function updateQueryAction (query) { + return { + type: UPDATE_QUERY, + query + }; +} + export function startFetching () { return { type: START_FETCHING }; } @@ -83,6 +99,14 @@ function retrieveComponent (componentKey, bucket) { ]); } +let requestTree = (query, baseComponent, dispatch) => { + dispatch(startFetching()); + return getTree(baseComponent.key, { q: query, s: 'qualifier,name' }) + .then(r => dispatch(searchAction(r.components))) + .then(() => dispatch(stopFetching())); +}; +requestTree = _.debounce(requestTree, 250); + export function initComponent (componentKey, breadcrumbs) { return dispatch => { dispatch(startFetching()); @@ -104,3 +128,16 @@ export function browse (componentKey) { .then(() => dispatch(stopFetching())); }; } + +export function search (query, baseComponent) { + return dispatch => { + dispatch(updateQueryAction(query)); + if (query) { + requestTree(query, baseComponent, dispatch); + } else { + dispatch(searchAction(null)); + } + }; +} + + diff --git a/server/sonar-web/src/main/js/apps/code/components/Code.js b/server/sonar-web/src/main/js/apps/code/components/Code.js index 7f364cf3939..6b38da6d007 100644 --- a/server/sonar-web/src/main/js/apps/code/components/Code.js +++ b/server/sonar-web/src/main/js/apps/code/components/Code.js @@ -5,6 +5,7 @@ import { connect } from 'react-redux'; import Components from './Components'; import Breadcrumbs from './Breadcrumbs'; import SourceViewer from './SourceViewer'; +import Search from './Search'; import { initComponent, browse } from '../actions'; import { TooltipsContainer } from '../../../components/mixins/tooltips-mixin'; @@ -33,10 +34,11 @@ class Code extends Component { } render () { - const { fetching, baseComponent, components, breadcrumbs, sourceViewer, coverageMetric } = this.props; + const { fetching, baseComponent, components, breadcrumbs, sourceViewer, coverageMetric, searchResults } = this.props; const shouldShowBreadcrumbs = Array.isArray(breadcrumbs) && breadcrumbs.length > 1; - const shouldShowComponents = !sourceViewer && components; - const shouldShowSourceViewer = sourceViewer; + const shouldShowSearchResults = !!searchResults; + const shouldShowSourceViewer = !!sourceViewer; + const shouldShowComponents = !shouldShowSearchResults && !shouldShowSourceViewer && components; const componentsClassName = classNames('spacer-top', { 'new-loading': fetching }); @@ -52,13 +54,23 @@ class Code extends Component { <i className="spinner"/> </div> - {shouldShowBreadcrumbs && ( - <Breadcrumbs - breadcrumbs={breadcrumbs} - onBrowse={this.handleBrowse.bind(this)}/> - )} + <Search component={this.props.component}/> </header> + {shouldShowBreadcrumbs && ( + <Breadcrumbs + breadcrumbs={breadcrumbs} + onBrowse={this.handleBrowse.bind(this)}/> + )} + + {shouldShowSearchResults && ( + <div className={componentsClassName}> + <Components + components={searchResults} + onBrowse={this.handleBrowse.bind(this)}/> + </div> + )} + {shouldShowComponents && ( <div className={componentsClassName}> <Components diff --git a/server/sonar-web/src/main/js/apps/code/components/Components.js b/server/sonar-web/src/main/js/apps/code/components/Components.js index d2dffb67869..a1efd179300 100644 --- a/server/sonar-web/src/main/js/apps/code/components/Components.js +++ b/server/sonar-web/src/main/js/apps/code/components/Components.js @@ -17,20 +17,22 @@ const Components = ({ baseComponent, components, coverageMetric, onBrowse }) => <th className="thin nowrap text-right">{window.t('metric.duplicated_lines_density.short_name')}</th> </tr> </thead> - <tbody> - <Component - key={baseComponent.uuid} - component={baseComponent} - coverageMetric={coverageMetric}/> - <tr className="blank"> - <td colSpan="7"> </td> - </tr> - </tbody> + {baseComponent && ( + <tbody> + <Component + key={baseComponent.key} + component={baseComponent} + coverageMetric={coverageMetric}/> + <tr className="blank"> + <td colSpan="7"> </td> + </tr> + </tbody> + )} <tbody> {components.length ? ( components.map(component => ( <Component - key={component.uuid} + key={component.key} component={component} coverageMetric={coverageMetric} onBrowse={onBrowse}/> 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 new file mode 100644 index 00000000000..2956952c43e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/code/components/Search.js @@ -0,0 +1,48 @@ +import _ from 'underscore'; +import React, { Component } from 'react'; +import { connect } from 'react-redux'; + +import { search } from '../actions'; + + +class Search extends Component { + componentDidMount () { + this.refs.input.focus(); + } + + handleSearch (e) { + e.preventDefault(); + const { dispatch, component } = this.props; + const query = this.refs.input.value; + dispatch(search(query, component)); + } + + render () { + const { query } = this.props; + + return ( + <form + onSubmit={this.handleSearch.bind(this)} + className="search-box code-search-box"> + <button className="search-box-submit button-clean"> + <i className="icon-search"></i> + </button> + <input + ref="input" + onChange={this.handleSearch.bind(this)} + value={query} + className="search-box-input" + type="search" + name="q" + placeholder="Search" + maxLength="100" + autoComplete="off"/> + </form> + ); + } +} + + +export default connect(state => { + return { query: state.current.searchQuery }; +})(Search); diff --git a/server/sonar-web/src/main/js/apps/code/reducers/index.js b/server/sonar-web/src/main/js/apps/code/reducers/index.js index 08b74a6710e..c177efd55fc 100644 --- a/server/sonar-web/src/main/js/apps/code/reducers/index.js +++ b/server/sonar-web/src/main/js/apps/code/reducers/index.js @@ -1,6 +1,6 @@ import _ from 'underscore'; -import { INIT, BROWSE, START_FETCHING, STOP_FETCHING } from '../actions'; +import { INIT, BROWSE, SEARCH, UPDATE_QUERY, START_FETCHING, STOP_FETCHING } from '../actions'; function hasSourceCode (component) { @@ -34,6 +34,8 @@ export const initialState = { components: null, breadcrumbs: null, sourceViewer: null, + searchResults: null, + searchQuery: '', coverageMetric: null, baseBreadcrumbs: [] }; @@ -53,7 +55,11 @@ export function current (state = initialState, action) { const breadcrumbs = action.breadcrumbs.slice(baseBreadcrumbsLength); const sourceViewer = hasSourceCode(action.component) ? action.component : null; - return { ...state, baseComponent, components, breadcrumbs, sourceViewer }; + return { ...state, baseComponent, components, breadcrumbs, sourceViewer, searchResults: null, searchQuery: '' }; + case SEARCH: + return { ...state, searchResults: action.components }; + case UPDATE_QUERY: + return { ...state, searchQuery: action.query }; case START_FETCHING: return { ...state, fetching: true }; case STOP_FETCHING: |