Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/src/main/kotlin/edu/wgu/osmt/RoutePaths.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ object RoutePaths {
const val EXPORT_SKILLS_XLSX = "$EXPORT_SKILLS/xlsx"
const val SEARCH_SIMILAR_SKILLS = "$SEARCH_SKILLS/similarity"
const val SEARCH_SIMILARITIES = "$SEARCH_SKILLS/similarities"
const val SEARCH_SIMILARITIES_RESULTS = "${SEARCH_SIMILARITIES}/results"
const val SEARCH_COLLECTIONS = "$SEARCH_PATH/collections"

//skills
Expand Down
25 changes: 19 additions & 6 deletions api/src/main/kotlin/edu/wgu/osmt/elasticsearch/SearchController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -333,25 +333,38 @@ class SearchController @Autowired constructor(
searchSimilarSkills(apiSimilaritySearch).body?.map { ApiSkillSummaryV2.fromLatest(it) }
)
}

@PostMapping(path = [
"${RoutePaths.API}${RoutePaths.API_V2}${RoutePaths.SEARCH_SIMILARITIES}",
"${RoutePaths.API}${RoutePaths.API_V3}${RoutePaths.SEARCH_SIMILARITIES}",
"${RoutePaths.API}${RoutePaths.UNVERSIONED}${RoutePaths.SEARCH_SIMILARITIES}"
],
produces = [MediaType.APPLICATION_JSON_VALUE])
@ResponseBody
fun similarSkillWarnings(
@RequestBody(required = true) similarities: Array<ApiSimilaritySearch>
): HttpEntity<List<Boolean>> {
fun similarSkillWarnings(@RequestBody(required = true) similarities: Array<ApiSimilaritySearch>): HttpEntity<List<Boolean>> {
val arrayLimit = 100
if (similarities.count() > arrayLimit) {
throw GeneralApiException("Request contained more than $arrayLimit objects", HttpStatus.BAD_REQUEST)
}
val hits = similarities.map { richSkillEsRepo.findSimilar(it).count() > 0 }

return ResponseEntity.status(200).body(hits)
}

@PostMapping(
path = [
"${RoutePaths.API}${RoutePaths.API_V3}${RoutePaths.SEARCH_SIMILARITIES_RESULTS}",
],
produces = [MediaType.APPLICATION_JSON_VALUE])
@ResponseBody
fun similarSkillResults(@RequestBody(required = true) similarities: Array<ApiSimilaritySearch>): HttpEntity<List<List<ApiSkillSummary>>> {
val arrayLimit = 100
if (similarities.count() > arrayLimit){
throw GeneralApiException("Request contained more than $arrayLimit objects", HttpStatus.BAD_REQUEST)
}
return ResponseEntity.status(200).body(
similarities.map{ richSkillEsRepo.findSimilar(it).map { summary -> ApiSkillSummary.fromDoc(summary.content) }.toList() }
)
}
}

class PaginatedLinks(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,23 @@ import edu.wgu.osmt.HasElasticsearchReset
import edu.wgu.osmt.SpringTest
import edu.wgu.osmt.api.model.ApiAdvancedSearch
import edu.wgu.osmt.api.model.ApiSearch
import edu.wgu.osmt.api.model.ApiSimilaritySearch
import edu.wgu.osmt.api.model.ApiSkillUpdate
import edu.wgu.osmt.collection.CollectionEsRepo
import edu.wgu.osmt.db.PublishStatus
import edu.wgu.osmt.jobcode.JobCodeEsRepo
import edu.wgu.osmt.keyword.KeywordEsRepo
import edu.wgu.osmt.mockdata.MockData
import edu.wgu.osmt.richskill.RichSkillDoc
import edu.wgu.osmt.richskill.RichSkillEsRepo
import edu.wgu.osmt.richskill.RichSkillRepository
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.util.UriComponentsBuilder
Expand All @@ -25,7 +32,8 @@ internal class SearchControllerTest @Autowired constructor(
override val richSkillEsRepo: RichSkillEsRepo,
override val collectionEsRepo: CollectionEsRepo,
override val keywordEsRepo: KeywordEsRepo,
override val jobCodeEsRepo: JobCodeEsRepo
override val jobCodeEsRepo: JobCodeEsRepo,
val richSkillRepository: RichSkillRepository
): SpringTest(), BaseDockerizedTest, HasDatabaseReset, HasElasticsearchReset {

@Autowired
Expand Down Expand Up @@ -113,4 +121,33 @@ internal class SearchControllerTest @Autowired constructor(
// Assert
assertThat(result.body?.map { it.name }).contains(listOfKeywords[0].value)
}
}

@Test
fun similarSkillWarningsShouldFindSimilarities() {
val skillUpdates = listOf(ApiSkillUpdate(
"Access and Security Levels Standardization",
"Standardize levels of access and security to maintain information security.",
PublishStatus.Draft
))
richSkillRepository.createFromApi(
skillUpdates,
"admin",
"admin@wgu.edu"
)
val response = searchController.similarSkillResults(
arrayOf(ApiSimilaritySearch("Standardize levels of access and security to maintain information security."))
)
assertThat((response as ResponseEntity).statusCode).isEqualTo(HttpStatus.OK)
assertThat(response.body?.first()?.get(0)?.skillStatement).isEqualTo(skillUpdates[0].skillStatement)
assertThat(response.body?.first()?.size).isEqualTo(1)
}

@Test
fun similarSkillWarningsShouldNotFindSimilarities() {
val response = searchController.similarSkillResults(
arrayOf(ApiSimilaritySearch("Access an application programming interface (API) with a programming language to change data for a task."))
)
assertThat((response as ResponseEntity).statusCode).isEqualTo(HttpStatus.OK)
assertThat(response.body?.first()?.size).isEqualTo(0)
}
}
26 changes: 26 additions & 0 deletions docs/int/openapi-v3.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,32 @@ paths:
items:
type: boolean

/api/v3/search/skills/similarities/results:
post:
summary: Check skill multiple statements for similar statements on existing skills returning similarities.
description: Returns an array of all similar skills for each sent statement.
tags:
- Search
requestBody:
required: true
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Similarity'
responses:
'200':
description: OK
content:
application/json:
schema:
type: array
items:
type: array
items:
$ref: '#/components/schemas/SkillSummary'

/api/v3/search/skills/similarity:
post:
summary: Check a skills statement for similar statements on existing skills
Expand Down
2 changes: 1 addition & 1 deletion test/api-test/api/v3/search/skills/similarities/post.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@
true,
true,
true
]
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
let skillStatements = [
{
"statement": "Validate authorization and authentication runbooks and troubleshoots procedures (SOPs), and troubleshoots advanced authentication and authorization issues."
},
{
"statement": "Create group and system authentication access."
},
{
"statement": "Design network connections between a core network and an internet service provider."
},
{
"statement": "Monitor network routers for performance issues."
},
{
"statement": "Monitor network routers for security issues."
},
{
"statement": "Configure network routers for traffic between a core network and an internet service provider."
},
{
"statement": "Bake pies to throw at passersby."
},
{
"statement": "Configure forest and domain trusts for Active Directory."
},
{
"statement": "Manage project feature progress with agile Kanban boards."
},
{
"statement": "Manage project feature progress with agile scrum boards."
},
{
"statement": "Communicate blocking issues during the development cycle."
},
{
"statement": "Accomplish agile project goals through collaboration with internal partners."
},
{
"statement": "Paint edge trim without painters tape."
},
{
"statement": "Maintain a cloud computing environment in Amazon Web Services (AWS)."
},
{
"statement": "Access an application programming interface (API) with a programming language to change data for a task."
},
{
"statement": "Engineer systems that leverage multi-platform application programming interface (API)."
},
{
"statement": "Configure file backups to a cloud solution."
},
{
"statement": "Identify which backed-up files must be restored."
},
{
"statement": "Identify disaster recovery solutions for backing up and restoring computer systems."
},
{
"statement": "Determine the hardware needed for the backup of a device's data."
},
{
"statement": "Determine which files must be backed up from one device to another."
},
{
"statement": "Perform manual backups of data from a device."
},
{
"statement": "Implement Border Gateway Protocol solutions."
},
{
"statement": "Implement continuity plans during the time of a disaster."
}
];

let body = {
mode: 'raw',
raw: JSON.stringify(skillStatements),
options: {
raw: {
language: 'json'
}
}
};

pm.request.body.update(body);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// let expectedData is injected from <response-type>.json

pm.test("Check similarities results", function () {
let responseData = pm.response.json();

pm.expect(responseData).to.have.deep.equal(expectedData);
});
Loading