]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8870 Search by WS description
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Tue, 2 May 2017 14:32:17 +0000 (16:32 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Fri, 5 May 2017 13:05:12 +0000 (15:05 +0200)
server/sonar-web/src/main/js/apps/web-api/components/Domain.js
server/sonar-web/src/main/js/apps/web-api/components/Menu.js
server/sonar-web/src/main/js/apps/web-api/components/Search.js
server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.js
server/sonar-web/src/main/js/apps/web-api/components/__tests__/Domain-test.js
server/sonar-web/src/main/js/apps/web-api/components/__tests__/__snapshots__/Domain-test.js.snap
server/sonar-web/src/main/js/apps/web-api/utils.js

index 6e2ac633fcf939f337e53e238f7e9bb79780263b..45513d9f9e726e5ec6a31caed3b2478ca19d839d 100644 (file)
@@ -22,7 +22,7 @@ import React from 'react';
 import Action from './Action';
 import DeprecatedBadge from './DeprecatedBadge';
 import InternalBadge from './InternalBadge';
-import { getActionKey } from '../utils';
+import { getActionKey, actionsFilter } from '../utils';
 import type { Domain as DomainType } from '../../../api/web-api';
 
 type Props = {
@@ -37,10 +37,9 @@ export default class Domain extends React.PureComponent {
 
   render() {
     const { domain, showInternal, showDeprecated, searchQuery } = this.props;
-    const filteredActions = domain.actions
-      .filter(action => showInternal || !action.internal)
-      .filter(action => showDeprecated || !action.deprecatedSince)
-      .filter(action => getActionKey(domain.path, action.key).indexOf(searchQuery) !== -1);
+    const filteredActions = domain.actions.filter(action =>
+      actionsFilter(showDeprecated, showInternal, searchQuery, domain, action)
+    );
 
     return (
       <div className="web-api-domain">
index f7cd574fd41fd986e386f0e69b69844eeecb8b9f..566533c79c7b00936936d56f6d7c44a308f96e49 100644 (file)
@@ -24,7 +24,7 @@ import classNames from 'classnames';
 import DeprecatedBadge from './DeprecatedBadge';
 import InternalBadge from './InternalBadge';
 import { TooltipsContainer } from '../../../components/mixins/tooltips-mixin';
-import { getActionKey, isDomainPathActive } from '../utils';
+import { isDomainPathActive, actionsFilter } from '../utils';
 import type { Domain as DomainType } from '../../../api/web-api';
 
 type Props = {
@@ -42,10 +42,9 @@ export default class Menu extends React.PureComponent {
     const { domains, showInternal, showDeprecated, searchQuery, splat } = this.props;
     const filteredDomains = (domains || [])
       .map(domain => {
-        const filteredActions = domain.actions
-          .filter(action => showInternal || !action.internal)
-          .filter(action => showDeprecated || !action.deprecatedSince)
-          .filter(action => getActionKey(domain.path, action.key).indexOf(searchQuery) !== -1);
+        const filteredActions = domain.actions.filter(action =>
+          actionsFilter(showDeprecated, showInternal, searchQuery, domain, action)
+        );
         return { ...domain, filteredActions };
       })
       .filter(domain => domain.filteredActions.length);
index c43163ed951db247f96fd395ba288bb6e26e6481..f5971512023f848c047762575ba9084927d712ea 100644 (file)
@@ -44,19 +44,19 @@ export default class Search extends React.PureComponent {
   constructor(props: Props) {
     super(props);
     this.state = { query: '' };
-    this.actuallySearch = debounce(this.actuallySearch.bind(this), 250);
+    this.actuallySearch = debounce(this.actuallySearch, 250);
   }
 
-  handleSearch(e: SyntheticInputEvent) {
+  handleSearch = (e: SyntheticInputEvent) => {
     const { value } = e.target;
     this.setState({ query: value });
     this.actuallySearch();
-  }
+  };
 
-  actuallySearch() {
+  actuallySearch = () => {
     const { onSearch } = this.props;
     onSearch(this.state.query);
-  }
+  };
 
   render() {
     const { showInternal, showDeprecated, onToggleInternal, onToggleDeprecated } = this.props;
@@ -70,14 +70,17 @@ export default class Search extends React.PureComponent {
             type="search"
             value={this.state.query}
             placeholder={translate('search_verb')}
-            onChange={this.handleSearch.bind(this)}
+            onChange={this.handleSearch}
           />
         </div>
 
         <TooltipsContainer>
           <div className="big-spacer-top">
-            <Checkbox checked={showInternal} onCheck={onToggleInternal} />
-            {' '}
+            <Checkbox
+              checked={showInternal}
+              className="little-spacer-right"
+              onCheck={onToggleInternal}
+            />
             <span
               style={{ cursor: 'pointer' }}
               title={translate('api_documentation.internal_tooltip')}
@@ -97,8 +100,11 @@ export default class Search extends React.PureComponent {
 
         <TooltipsContainer>
           <div className="spacer-top">
-            <Checkbox checked={showDeprecated} onCheck={onToggleDeprecated} />
-            {' '}
+            <Checkbox
+              checked={showDeprecated}
+              className="little-spacer-right"
+              onCheck={onToggleDeprecated}
+            />
             <span
               style={{ cursor: 'pointer' }}
               title={translate('api_documentation.deprecation_tooltip')}
index cda4b87b5e2c5292871123d13ebdf2b3aac9819f..a97c27b9d7a2ba0fb7fcebba726f8f43e49a740e 100644 (file)
@@ -47,7 +47,6 @@ export default class WebApiApp extends React.PureComponent {
 
   componentDidMount() {
     this.mounted = true;
-    this.scrollToAction = this.scrollToAction.bind(this);
     this.fetchList();
     const footer = document.getElementById('footer');
     if (footer) {
@@ -76,10 +75,10 @@ export default class WebApiApp extends React.PureComponent {
     });
   }
 
-  scrollToAction() {
+  scrollToAction = () => {
     const splat = this.props.params.splat || '';
     this.scrollToElement(splat);
-  }
+  };
 
   scrollToElement(id: string) {
     const element = document.getElementById(id);
@@ -113,11 +112,11 @@ export default class WebApiApp extends React.PureComponent {
     }
   }
 
-  handleSearch(searchQuery: string) {
+  handleSearch = (searchQuery: string) => {
     this.setState({ searchQuery });
-  }
+  };
 
-  handleToggleInternal() {
+  handleToggleInternal = () => {
     const splat = this.props.params.splat || '';
     const { router } = this.context;
     const { domains } = this.state;
@@ -129,11 +128,11 @@ export default class WebApiApp extends React.PureComponent {
     }
 
     this.setState({ showInternal });
-  }
+  };
 
-  handleToggleDeprecated() {
+  handleToggleDeprecated = () => {
     this.setState(state => ({ showDeprecated: !state.showDeprecated }));
-  }
+  };
 
   render() {
     const splat = this.props.params.splat || '';
@@ -153,9 +152,9 @@ export default class WebApiApp extends React.PureComponent {
           <Search
             showDeprecated={showDeprecated}
             showInternal={showInternal}
-            onSearch={this.handleSearch.bind(this)}
-            onToggleInternal={this.handleToggleInternal.bind(this)}
-            onToggleDeprecated={this.handleToggleDeprecated.bind(this)}
+            onSearch={this.handleSearch}
+            onToggleInternal={this.handleToggleInternal}
+            onToggleDeprecated={this.handleToggleDeprecated}
           />
 
           <Menu
index 968d9037864cb45ffea6470156561797249d7b08..5608b8c5987847ddef31b43d210a66551a1c38e8 100644 (file)
@@ -22,12 +22,7 @@ import { shallow } from 'enzyme';
 import Domain from '../Domain';
 
 it('should render deprecated actions', () => {
-  const actions = [
-    {
-      key: 'foo',
-      deprecatedSince: '5.0'
-    }
-  ];
+  const actions = [{ key: 'foo', deprecatedSince: '5.0' }];
   const domain = { actions, path: 'api' };
   expect(
     shallow(<Domain domain={domain} searchQuery="" showDeprecated={true} />)
@@ -35,14 +30,35 @@ it('should render deprecated actions', () => {
 });
 
 it('should not render deprecated actions', () => {
-  const actions = [
-    {
-      key: 'foo',
-      deprecatedSince: '5.0'
-    }
-  ];
+  const actions = [{ key: 'foo', deprecatedSince: '5.0' }];
   const domain = { actions, path: 'api' };
   expect(
     shallow(<Domain domain={domain} searchQuery="" showDeprecated={false} />)
   ).toMatchSnapshot();
 });
+
+it('should render internal actions', () => {
+  const actions = [{ key: 'foo', internal: true }];
+  const domain = { actions, path: 'api' };
+  expect(shallow(<Domain domain={domain} searchQuery="" showInternal={true} />)).toMatchSnapshot();
+});
+
+it('should not render internal actions', () => {
+  const actions = [{ key: 'foo', internal: true }];
+  const domain = { actions, path: 'api' };
+  expect(shallow(<Domain domain={domain} searchQuery="" showInternal={false} />)).toMatchSnapshot();
+});
+
+it('should render only actions matching the query', () => {
+  const actions = [{ key: 'foo' }, { key: 'bar' }];
+  const domain = { actions, path: 'api' };
+  expect(shallow(<Domain domain={domain} searchQuery="Foo" />)).toMatchSnapshot();
+});
+
+it('should also render actions with a description matching the query', () => {
+  const actions = [{ key: 'foo', description: 'foobar' }, { key: 'bar' }, { key: 'baz' }];
+  const domain = { actions, path: 'api' };
+  expect(
+    shallow(<Domain domain={domain} searchQuery="bar" showDeprecated={false} />)
+  ).toMatchSnapshot();
+});
index 22a9c18b1cbdbf7c197366674aabf8b21060bd79..8a1c7fd8843ec833ca275cadf91f45350aed8df6 100644 (file)
@@ -1,5 +1,76 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`should also render actions with a description matching the query 1`] = `
+<div
+  className="web-api-domain"
+>
+  <header
+    className="web-api-domain-header"
+  >
+    <h2
+      className="web-api-domain-title"
+    >
+      api
+    </h2>
+  </header>
+  <div
+    className="web-api-domain-actions"
+  >
+    <Action
+      action={
+        Object {
+          "description": "foobar",
+          "key": "foo",
+        }
+      }
+      domain={
+        Object {
+          "actions": Array [
+            Object {
+              "description": "foobar",
+              "key": "foo",
+            },
+            Object {
+              "key": "bar",
+            },
+            Object {
+              "key": "baz",
+            },
+          ],
+          "path": "api",
+        }
+      }
+      showDeprecated={false}
+    />
+    <Action
+      action={
+        Object {
+          "key": "bar",
+        }
+      }
+      domain={
+        Object {
+          "actions": Array [
+            Object {
+              "description": "foobar",
+              "key": "foo",
+            },
+            Object {
+              "key": "bar",
+            },
+            Object {
+              "key": "baz",
+            },
+          ],
+          "path": "api",
+        }
+      }
+      showDeprecated={false}
+    />
+  </div>
+</div>
+`;
+
 exports[`should not render deprecated actions 1`] = `
 <div
   className="web-api-domain"
@@ -19,6 +90,25 @@ exports[`should not render deprecated actions 1`] = `
 </div>
 `;
 
+exports[`should not render internal actions 1`] = `
+<div
+  className="web-api-domain"
+>
+  <header
+    className="web-api-domain-header"
+  >
+    <h2
+      className="web-api-domain-title"
+    >
+      api
+    </h2>
+  </header>
+  <div
+    className="web-api-domain-actions"
+  />
+</div>
+`;
+
 exports[`should render deprecated actions 1`] = `
 <div
   className="web-api-domain"
@@ -58,3 +148,83 @@ exports[`should render deprecated actions 1`] = `
   </div>
 </div>
 `;
+
+exports[`should render internal actions 1`] = `
+<div
+  className="web-api-domain"
+>
+  <header
+    className="web-api-domain-header"
+  >
+    <h2
+      className="web-api-domain-title"
+    >
+      api
+    </h2>
+  </header>
+  <div
+    className="web-api-domain-actions"
+  >
+    <Action
+      action={
+        Object {
+          "internal": true,
+          "key": "foo",
+        }
+      }
+      domain={
+        Object {
+          "actions": Array [
+            Object {
+              "internal": true,
+              "key": "foo",
+            },
+          ],
+          "path": "api",
+        }
+      }
+      showInternal={true}
+    />
+  </div>
+</div>
+`;
+
+exports[`should render only actions matching the query 1`] = `
+<div
+  className="web-api-domain"
+>
+  <header
+    className="web-api-domain-header"
+  >
+    <h2
+      className="web-api-domain-title"
+    >
+      api
+    </h2>
+  </header>
+  <div
+    className="web-api-domain-actions"
+  >
+    <Action
+      action={
+        Object {
+          "key": "foo",
+        }
+      }
+      domain={
+        Object {
+          "actions": Array [
+            Object {
+              "key": "foo",
+            },
+            Object {
+              "key": "bar",
+            },
+          ],
+          "path": "api",
+        }
+      }
+    />
+  </div>
+</div>
+`;
index d5af23b6740dcfd4779bea6597380be62027fcd7..bd6efd344671169b8a2b813304aa15db46cb92bd 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-export function getActionKey(domain, action) {
-  return domain + '/' + action;
+//@flow
+import type { Domain, Action } from '../../api/web-api';
+
+export function actionsFilter(
+  showDeprecated: boolean,
+  showInternal: boolean,
+  searchQuery: string,
+  domain: Domain,
+  action: Action
+) {
+  const lowSearchQuery = searchQuery.toLowerCase();
+  return (
+    (showInternal || !action.internal) &&
+    (showDeprecated || !action.deprecatedSince) &&
+    (getActionKey(domain.path, action.key).includes(lowSearchQuery) ||
+      (action.description || '').toLowerCase().includes(lowSearchQuery))
+  );
+}
+
+export function getActionKey(domainPath: string, actionKey: string) {
+  return domainPath + '/' + actionKey;
 }
 
-export const isDomainPathActive = (path, splat) => {
+export const isDomainPathActive = (path: string, splat: string) => {
   const pathTokens = path.split('/');
   const splatTokens = splat.split('/');