Skip to content

Commit

Permalink
Merge pull request #139 from ajordens/improved-application-searching
Browse files Browse the repository at this point in the history
Support rudimentary Application sorting (w/ pageSize)
  • Loading branch information
ajordens authored Sep 22, 2016
2 parents ea03959 + 12576e9 commit 98b15ab
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ public interface ApplicationDAO extends ItemDAO<Application> {
Collection<Application> getApplicationHistory(String name, int limit)

static class Searcher {
static Collection<Application> search(Collection<Application> searchableApplications, Map<String, String> attributes) {
static Collection<Application> search(Collection<Application> searchableApplications,
Map<String, String> attributes) {
attributes = attributes.collect { k,v -> [k.toLowerCase(), v] }.collectEntries()

if (attributes["accounts"]) {
Expand Down Expand Up @@ -65,7 +66,35 @@ public interface ApplicationDAO extends ItemDAO<Application> {
throw new NotFoundException("No Application found for search criteria $attributes")
}

return items
return items.sort { Application a, Application b ->
return score(b, attributes) - score(a, attributes)
}
}

static int score(Application application, Map<String, String> attributes) {
return attributes.collect { key, value ->
return score(application, key, value)
}?.sum() as Integer ?: 0
}

static int score(Application application, String attributeName, String attributeValue) {
if (!application.hasProperty(attributeName)) {
return 0
}

def attribute = application[attributeName].toString().toLowerCase()
def indexOf = attribute.indexOf(attributeValue.toLowerCase())

// what percentage of the value matched
def coverage = ((double) attributeValue.length() / attribute.length()) * 100

// where did the match occur, bonus points for it occurring close to the start
def boost = attribute.length() - indexOf

// scale boost based on coverage percentage
def scaledBoost = ((double) coverage / 100) * boost

return indexOf < 0 ? 0 : coverage + scaledBoost
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright 2016 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.front50.model.application

import spock.lang.Specification
import spock.lang.Unroll

class ApplicationDAOSpec extends Specification {
def "should support sorting by a single attribute"() {
given:
def applications = [
new Application(name: "YOUR APPLICATION"),
new Application(name: "APPLICATION"),
new Application(name: "MY APPLICATION"),
]

expect:
ApplicationDAO.Searcher.search(applications, ["name": "app"])*.name == [
"APPLICATION", "MY APPLICATION", "YOUR APPLICATION"
]
}

def "should support sorting by multiple attributes"() {
given:
def applications = [
new Application(name: "APPLICATION 123", description: "abcd"),
new Application(name: "APPLICATION 1", description: "bcda"),
new Application(name: "APPLICATION 12", description: "cdab"),
]

expect:
ApplicationDAO.Searcher.search(applications, ["name": "app", "description": "cd"])*.name == [
"APPLICATION 1", "APPLICATION 12", "APPLICATION 123"
]
}

@Unroll
def "should calculate correct single attribute scores"() {
given:
def application = new Application(applicationAttributes)

expect:
ApplicationDAO.Searcher.score(application, attributeName, attributeValue) == score

where:
applicationAttributes | attributeName | attributeValue || score
["name": "application"] | "name" | "application" || 111
["name": "application"] | "name" | "app" || 30
["name": "application"] | "name" | "ppl" || 29
["name": "application"] | "name" | "ion" || 28
["name": "application"] | "name" | "does_not_match" || 0
}

@Unroll
def "should calculate correct multiple attribute scores"() {
given:
def application = new Application(applicationAttributes)

expect:
ApplicationDAO.Searcher.score(application, attributes) == score

where:
applicationAttributes | attributes || score
["name": "Netflix"] | ["name": "flix"] || 59
["name": "Netflix", description: "Spinnaker"] | ["name": "flix", description: "Spin"] || 107
["name": "Netflix", description: "Spinnaker"] | ["name": "flix", description: "ker"] || 93
["name": "Netflix", description: "Spinnaker"] | ["name": "flix", owner: "netflix"] || 59
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,6 @@ public class ApplicationsController {
@Autowired(required = false)
List<ApplicationEventListener> applicationEventListeners = []

@Autowired
Registry registry

@PreAuthorize("@fiatPermissionEvaluator.storeWholePermission()")
@PostFilter("hasPermission(filterObject.name, 'APPLICATION', 'READ')")
@ApiOperation(value = "", notes = """Fetch all applications.
Expand All @@ -67,12 +64,18 @@ public class ApplicationsController {
- [email protected]
- [email protected]&name=flex""")
@RequestMapping(method = RequestMethod.GET)
Set<Application> applications(@RequestParam Map<String, String> params) {
Set<Application> applications(@RequestParam(value = "pageSize", required = false) Integer pageSize,
@RequestParam Map<String, String> params) {
params.remove("pageSize")

def applications
if (params.isEmpty()) {
return applicationDAO.all()
applications = applicationDAO.all().sort { it.name }
} else {
applications = applicationDAO.search(params)
}

return applicationDAO.search(params)
return pageSize ? applications.asList().subList(0, Math.min(pageSize, applications.size())) : applications
}

// TODO(ttomsu): Think through application creation permissions.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,24 @@ abstract class ApplicationsControllerTck extends Specification {
then:
response.andExpect status().isOk()
response.andExpect content().string(new ObjectMapper().writeValueAsString([dao.findByName("SAMPLEAPP")]))

when:
response = mockMvc.perform(
get("/v2/applications?name=sample&pageSize=9999")
)

then:
response.andExpect status().isOk()
response.andExpect content().string(new ObjectMapper().writeValueAsString([dao.findByName("SAMPLEAPP"), dao.findByName("SAMPLEAPP-2")]))

when:
response = mockMvc.perform(
get("/v2/applications?name=sample&pageSize=1")
)

then:
response.andExpect status().isOk()
response.andExpect content().string(new ObjectMapper().writeValueAsString([dao.findByName("SAMPLEAPP")]))
}

private Map toMap(Application application) {
Expand Down

0 comments on commit 98b15ab

Please sign in to comment.