This commit is just for discussion. It demonstrates the issue mentioned in last week's call where it seems that only the first SearchResultSet in the returned list is considered.
This commit is just for discussion. It demonstrates the issue mentioned in last week's call where it seems that only the first SearchResultSet in the returned list is considered.

/*
 * Copyright 2014 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.netflix.spinnaker.oort.controllers

import com.netflix.spinnaker.oort.search.SearchProvider
import com.netflix.spinnaker.oort.search.SearchResultSet
import groovy.transform.CompileStatic
import org.apache.log4j.Logger
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@CompileStatic
@RestController
class SearchController {

  protected static final Logger log = Logger.getLogger(this)

  @Autowired
  List<SearchProvider> searchProviders

  /**
   * Simple search endpoint that delegates to {@link SearchProvider}s.
   * @param query the phrase to query
   * @param type (optional) a filter, used to only return results of that type. If no value is supplied, all types will be returned
   * @param platform a filter, used to only return results from providers whose platform value matches this
   * @param pageNumber the page number, starting with 1
   * @param pageSize the maximum number of results to return per page
   * @param filters (optional) a map of key-value pairs to further filter the keys
   * @return a list {@link SearchResultSet)s
   */
  @RequestMapping(value = '/search')
  List<SearchResultSet> search(SearchQueryCommand q) {

    log.info("Fetching search results for ${q.q}, platform: ${q.platform}, type: ${q.type}, pageSize: ${q.pageSize}, pageNumber: ${q.page}, filter: ${q.filter}")

    def providers = q.platform ?
      searchProviders.findAll { it.platform == q.platform } :
      searchProviders

    // Commenting this out and using the routine that follows allows the ui to display search results from multiple providers.
//    providers.collect {
//      if (q.type && !q.type.isEmpty()) {
//        it.search(q.q, q.type, q.page, q.pageSize, q.filter)
//      } else {
//        it.search(q.q, q.page, q.pageSize, q.filter)
//      }
//    }

    // TODO: Shouldn't the pageSize and pageNumber parameters be applied to the aggregated results, and not to each provider individually?
    def result = new SearchResultSet(totalMatches: 0,
      pageNumber: q.page,
      pageSize: q.pageSize,
      platform: "aws",
      query: q.q)

    for (def provider : providers) {
      def searchResults

      if (q.type && !q.type.isEmpty()) {
        searchResults = provider.search(q.q, q.type, q.page, q.pageSize, q.filter)
      } else {
        searchResults = provider.search(q.q, q.page, q.pageSize, q.filter)
      }

      // Aggregate the platform names here somehow?
//      result.platform = searchResults.platform
      result.totalMatches += searchResults.totalMatches
      result.results.addAll(searchResults.results)
    }

    // Based on the return type, it seems the caller (Deck) should be doing the aggregating. But when using the original routine,
    // I only see (in the ui) the results from the first SearchResultSet in the returned list.
    [result]
  }

  static class SearchQueryCommand {
    /**
     * the phrase to query
     */
    String q

    /**
     * (optional) a filter, used to only return results of that type. If no value is supplied, all types will be returned
     */
    List<String> type

    /**
     * a filter, used to only return results from providers whose platform value matches this
     */
    String platform = ''

    /**
     * the page number, starting with 1
     */
    Integer page = 1

    /**
     * the maximum number of results to return per page
     */
    Integer pageSize = 10

    /**
     * (optional) a map of ad-hoc key-value pairs to further filter the keys,
     * based on the map provided by {@link com.netflix.spinnaker.oort.data.aws.Keys#parse(java.lang.String)}
     * potential matches must fully intersect the filter map entries
     */
    Map<String, String> filter
  }

}

/*
 * Copyright 2014 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.netflix.spinnaker.oort.controllers

import com.netflix.spinnaker.oort.search.SearchProvider
import com.netflix.spinnaker.oort.search.SearchResultSet
import groovy.transform.CompileStatic
import org.apache.log4j.Logger
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@CompileStatic
@RestController
class SearchController {

  protected static final Logger log = Logger.getLogger(this)

  @Autowired
  List<SearchProvider> searchProviders

  /**
   * Simple search endpoint that delegates to {@link SearchProvider}s.
   * @param query the phrase to query
   * @param type (optional) a filter, used to only return results of that type. If no value is supplied, all types will be returned
   * @param platform a filter, used to only return results from providers whose platform value matches this
   * @param pageNumber the page number, starting with 1
   * @param pageSize the maximum number of results to return per page
   * @param filters (optional) a map of key-value pairs to further filter the keys
   * @return a list {@link SearchResultSet)s
   */
  @RequestMapping(value = '/search')
  List<SearchResultSet> search(SearchQueryCommand q) {

    log.info("Fetching search results for ${q.q}, platform: ${q.platform}, type: ${q.type}, pageSize: ${q.pageSize}, pageNumber: ${q.page}, filter: ${q.filter}")

    def providers = q.platform ?
      searchProviders.findAll { it.platform == q.platform } :
      searchProviders

    providers.collect {
      if (q.type && !q.type.isEmpty()) {
        it.search(q.q, q.type, q.page, q.pageSize, q.filter)
      } else {
        it.search(q.q, q.page, q.pageSize, q.filter)
      }
    }
  }

  static class SearchQueryCommand {
    /**
     * the phrase to query
     */
    String q

    /**
     * (optional) a filter, used to only return results of that type. If no value is supplied, all types will be returned
     */
    List<String> type

    /**
     * a filter, used to only return results from providers whose platform value matches this
     */
    String platform = ''

    /**
     * the page number, starting with 1
     */
    Integer page = 1

    /**
     * the maximum number of results to return per page
     */
    Integer pageSize = 10

    /**
     * (optional) a map of ad-hoc key-value pairs to further filter the keys,
     * based on the map provided by {@link com.netflix.spinnaker.oort.data.aws.Keys#parse(java.lang.String)}
     * potential matches must fully intersect the filter map entries
     */
    Map<String, String> filter
  }

}
