diff --git a/api/src/main/kotlin/edu/wgu/osmt/RoutePaths.kt b/api/src/main/kotlin/edu/wgu/osmt/RoutePaths.kt index 7c6e046ef..4bc7c9f75 100644 --- a/api/src/main/kotlin/edu/wgu/osmt/RoutePaths.kt +++ b/api/src/main/kotlin/edu/wgu/osmt/RoutePaths.kt @@ -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 diff --git a/api/src/main/kotlin/edu/wgu/osmt/elasticsearch/SearchController.kt b/api/src/main/kotlin/edu/wgu/osmt/elasticsearch/SearchController.kt index 35af47bfc..4b4632213 100644 --- a/api/src/main/kotlin/edu/wgu/osmt/elasticsearch/SearchController.kt +++ b/api/src/main/kotlin/edu/wgu/osmt/elasticsearch/SearchController.kt @@ -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 - ): HttpEntity> { + fun similarSkillWarnings(@RequestBody(required = true) similarities: Array): HttpEntity> { 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): HttpEntity>> { + 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( diff --git a/api/src/test/kotlin/edu/wgu/osmt/elasticsearch/SearchControllerTest.kt b/api/src/test/kotlin/edu/wgu/osmt/elasticsearch/SearchControllerTest.kt index 41b8be691..33324712b 100644 --- a/api/src/test/kotlin/edu/wgu/osmt/elasticsearch/SearchControllerTest.kt +++ b/api/src/test/kotlin/edu/wgu/osmt/elasticsearch/SearchControllerTest.kt @@ -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 @@ -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 @@ -113,4 +121,33 @@ internal class SearchControllerTest @Autowired constructor( // Assert assertThat(result.body?.map { it.name }).contains(listOfKeywords[0].value) } -} \ No newline at end of file + + @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) + } +} diff --git a/docs/int/openapi-v3.yaml b/docs/int/openapi-v3.yaml index 869d1c956..b6461f779 100644 --- a/docs/int/openapi-v3.yaml +++ b/docs/int/openapi-v3.yaml @@ -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 diff --git a/test/api-test/api/v3/search/skills/similarities/post.json b/test/api-test/api/v3/search/skills/similarities/post.json index 5ed149498..8a82397c3 100644 --- a/test/api-test/api/v3/search/skills/similarities/post.json +++ b/test/api-test/api/v3/search/skills/similarities/post.json @@ -23,4 +23,4 @@ true, true, true -] \ No newline at end of file +] diff --git a/test/api-test/api/v3/search/skills/similarities/results/post-pre-request.js b/test/api-test/api/v3/search/skills/similarities/results/post-pre-request.js new file mode 100644 index 000000000..cdae10a96 --- /dev/null +++ b/test/api-test/api/v3/search/skills/similarities/results/post-pre-request.js @@ -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); \ No newline at end of file diff --git a/test/api-test/api/v3/search/skills/similarities/results/post.js b/test/api-test/api/v3/search/skills/similarities/results/post.js new file mode 100644 index 000000000..dd1919401 --- /dev/null +++ b/test/api-test/api/v3/search/skills/similarities/results/post.js @@ -0,0 +1,7 @@ +// let expectedData is injected from .json + +pm.test("Check similarities results", function () { + let responseData = pm.response.json(); + + pm.expect(responseData).to.have.deep.equal(expectedData); +}); \ No newline at end of file diff --git a/test/api-test/api/v3/search/skills/similarities/results/post.json b/test/api-test/api/v3/search/skills/similarities/results/post.json new file mode 100644 index 000000000..ef610a171 --- /dev/null +++ b/test/api-test/api/v3/search/skills/similarities/results/post.json @@ -0,0 +1,1611 @@ +[ + [ + { + "id": "http://localhost:8080/api/skills/f6538cc1-8fdf-4f11-b373-781571b386bf", + "uuid": "f6538cc1-8fdf-4f11-b373-781571b386bf", + "status": "published", + "publishDate": "2023-03-30T13:02:07", + "archiveDate": null, + "skillName": "Validation and Troubleshooting", + "skillStatement": "Validate authorization and authentication runbooks and troubleshoots procedures (SOPs), and troubleshoots advanced authentication and authorization issues.", + "categories": [ + "Authentications" + ], + "keywords": [ + "Authentications", + "Authentication", + "SafeNet", + "Authorization (Computing)", + "WGUSID: 1619.1" + ], + "occupations": [ + { + "code": "11-0000", + "targetNodeName": "Management Occupations", + "frameworkName": "bls" + }, + { + "code": "11-3000", + "targetNodeName": "Operations Specialties Managers", + "frameworkName": "bls" + }, + { + "code": "11-3020", + "targetNodeName": "Computer and Information Systems Managers", + "frameworkName": "bls" + }, + { + "code": "11-3021", + "targetNodeName": "Computer and Information Systems Managers", + "frameworkName": "bls" + }, + { + "code": "15-0000", + "targetNodeName": "Computer and Mathematical Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1200", + "targetNodeName": "Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1210", + "targetNodeName": "Computer and Information Analysts", + "frameworkName": "bls" + }, + { + "code": "15-1211", + "targetNodeName": "Computer Systems Analysts", + "frameworkName": "bls" + }, + { + "code": "15-1230", + "targetNodeName": "Computer Support Specialists", + "frameworkName": "bls" + }, + { + "code": "15-1231", + "targetNodeName": "Computer Network Support Specialists", + "frameworkName": "bls" + }, + { + "code": "15-1232", + "targetNodeName": "Computer User Support Specialists", + "frameworkName": "bls" + }, + { + "code": "15-1240", + "targetNodeName": "Database and Network Administrators and Architects", + "frameworkName": "bls" + }, + { + "code": "15-1241", + "targetNodeName": "Computer Network Architects", + "frameworkName": "bls" + }, + { + "code": "15-1244", + "targetNodeName": "Network and Computer Systems Administrators", + "frameworkName": "bls" + }, + { + "code": "15-1245" + } + ] + } + ], + [ + { + "id": "http://localhost:8080/api/skills/aabb1eee-48b7-4d46-b4e0-fb584285627d", + "uuid": "aabb1eee-48b7-4d46-b4e0-fb584285627d", + "status": "published", + "publishDate": "2023-03-30T13:02:07", + "archiveDate": null, + "skillName": "Authentication Access Creation", + "skillStatement": "Create group and system authentication access.", + "categories": [ + "Authentications" + ], + "keywords": [ + "Authentications", + "Authentication", + "SafeNet", + "Authorization (Computing)", + "WGUSID: 1619" + ], + "occupations": [ + { + "code": "11-0000", + "targetNodeName": "Management Occupations", + "frameworkName": "bls" + }, + { + "code": "11-3000", + "targetNodeName": "Operations Specialties Managers", + "frameworkName": "bls" + }, + { + "code": "11-3020", + "targetNodeName": "Computer and Information Systems Managers", + "frameworkName": "bls" + }, + { + "code": "11-3021", + "targetNodeName": "Computer and Information Systems Managers", + "frameworkName": "bls" + }, + { + "code": "15-0000", + "targetNodeName": "Computer and Mathematical Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1200", + "targetNodeName": "Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1210", + "targetNodeName": "Computer and Information Analysts", + "frameworkName": "bls" + }, + { + "code": "15-1211", + "targetNodeName": "Computer Systems Analysts", + "frameworkName": "bls" + }, + { + "code": "15-1230", + "targetNodeName": "Computer Support Specialists", + "frameworkName": "bls" + }, + { + "code": "15-1231", + "targetNodeName": "Computer Network Support Specialists", + "frameworkName": "bls" + }, + { + "code": "15-1232", + "targetNodeName": "Computer User Support Specialists", + "frameworkName": "bls" + }, + { + "code": "15-1240", + "targetNodeName": "Database and Network Administrators and Architects", + "frameworkName": "bls" + }, + { + "code": "15-1241", + "targetNodeName": "Computer Network Architects", + "frameworkName": "bls" + }, + { + "code": "15-1244", + "targetNodeName": "Network and Computer Systems Administrators", + "frameworkName": "bls" + }, + { + "code": "15-1245" + } + ] + } + ], + [ + { + "id": "http://localhost:8080/api/skills/cad699ef-64b9-40ee-81db-b5de69a90558", + "uuid": "cad699ef-64b9-40ee-81db-b5de69a90558", + "status": "published", + "publishDate": "2023-03-30T13:02:05", + "archiveDate": null, + "skillName": "Network Connection Design", + "skillStatement": "Design network connections between a core network and an internet service provider.", + "categories": [ + "Access Network" + ], + "keywords": [ + "design", + "Access Network", + "WGUSID: 10407" + ], + "occupations": [ + { + "code": "15-0000", + "targetNodeName": "Computer and Mathematical Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1200", + "targetNodeName": "Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1230", + "targetNodeName": "Computer Support Specialists", + "frameworkName": "bls" + }, + { + "code": "15-1231", + "targetNodeName": "Computer Network Support Specialists", + "frameworkName": "bls" + }, + { + "code": "15-1240", + "targetNodeName": "Database and Network Administrators and Architects", + "frameworkName": "bls" + }, + { + "code": "15-1241", + "targetNodeName": "Computer Network Architects", + "frameworkName": "bls" + }, + { + "code": "15-1244", + "targetNodeName": "Network and Computer Systems Administrators", + "frameworkName": "bls" + } + ] + } + ], + [ + { + "id": "http://localhost:8080/api/skills/3ceba681-28fd-468f-91f1-223aa7a0a96a", + "uuid": "3ceba681-28fd-468f-91f1-223aa7a0a96a", + "status": "published", + "publishDate": "2023-03-30T13:02:06", + "archiveDate": null, + "skillName": "Network Router Performance Monitoring", + "skillStatement": "Monitor network routers for performance issues.", + "categories": [ + "Access Network" + ], + "keywords": [ + "Performance", + "monitor", + "Access Network", + "WGUSID: 10411" + ], + "occupations": [ + { + "code": "15-0000", + "targetNodeName": "Computer and Mathematical Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1200", + "targetNodeName": "Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1230", + "targetNodeName": "Computer Support Specialists", + "frameworkName": "bls" + }, + { + "code": "15-1231", + "targetNodeName": "Computer Network Support Specialists", + "frameworkName": "bls" + }, + { + "code": "15-1240", + "targetNodeName": "Database and Network Administrators and Architects", + "frameworkName": "bls" + }, + { + "code": "15-1241", + "targetNodeName": "Computer Network Architects", + "frameworkName": "bls" + }, + { + "code": "15-1244", + "targetNodeName": "Network and Computer Systems Administrators", + "frameworkName": "bls" + } + ] + } + ], + [ + { + "id": "http://localhost:8080/api/skills/d53bdeb9-f7fc-449a-baaf-b5e284e29c58", + "uuid": "d53bdeb9-f7fc-449a-baaf-b5e284e29c58", + "status": "published", + "publishDate": "2023-03-30T13:02:06", + "archiveDate": null, + "skillName": "Network Router Security Monitoring", + "skillStatement": "Monitor network routers for security issues.", + "categories": [ + "Access Network" + ], + "keywords": [ + "Access Network", + "Routers", + "WGUSID: 10410" + ], + "occupations": [ + { + "code": "15-0000", + "targetNodeName": "Computer and Mathematical Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1200", + "targetNodeName": "Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1230", + "targetNodeName": "Computer Support Specialists", + "frameworkName": "bls" + }, + { + "code": "15-1231", + "targetNodeName": "Computer Network Support Specialists", + "frameworkName": "bls" + }, + { + "code": "15-1240", + "targetNodeName": "Database and Network Administrators and Architects", + "frameworkName": "bls" + }, + { + "code": "15-1241", + "targetNodeName": "Computer Network Architects", + "frameworkName": "bls" + }, + { + "code": "15-1244", + "targetNodeName": "Network and Computer Systems Administrators", + "frameworkName": "bls" + } + ] + } + ], + [ + { + "id": "http://localhost:8080/api/skills/398f0acd-d6a9-49af-9b7f-48a2f9b1e800", + "uuid": "398f0acd-d6a9-49af-9b7f-48a2f9b1e800", + "status": "published", + "publishDate": "2023-03-30T13:02:06", + "archiveDate": null, + "skillName": "Router Network Traffic Configuration", + "skillStatement": "Configure network routers for traffic between a core network and an internet service provider.", + "categories": [ + "Access Network" + ], + "keywords": [ + "Access Network", + "WGUSID: 10408" + ], + "occupations": [ + { + "code": "15-0000", + "targetNodeName": "Computer and Mathematical Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1200", + "targetNodeName": "Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1230", + "targetNodeName": "Computer Support Specialists", + "frameworkName": "bls" + }, + { + "code": "15-1231", + "targetNodeName": "Computer Network Support Specialists", + "frameworkName": "bls" + }, + { + "code": "15-1240", + "targetNodeName": "Database and Network Administrators and Architects", + "frameworkName": "bls" + }, + { + "code": "15-1241", + "targetNodeName": "Computer Network Architects", + "frameworkName": "bls" + }, + { + "code": "15-1244", + "targetNodeName": "Network and Computer Systems Administrators", + "frameworkName": "bls" + } + ] + }, + { + "id": "http://localhost:8080/api/skills/419aeab5-1468-434e-b60a-dd4828503fa3", + "uuid": "419aeab5-1468-434e-b60a-dd4828503fa3", + "status": "published", + "publishDate": "2023-03-30T13:01:48", + "archiveDate": null, + "skillName": "Network Routers Security Configuration", + "skillStatement": "Configure security on network routers for traffic between a core network and an internet service provider.", + "categories": [ + "Access Network" + ], + "keywords": [ + "Access Network", + "Routers", + "WGUSID: 10409" + ], + "occupations": [ + { + "code": "15-0000", + "targetNodeName": "Computer and Mathematical Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1200", + "targetNodeName": "Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1230", + "targetNodeName": "Computer Support Specialists", + "frameworkName": "bls" + }, + { + "code": "15-1231", + "targetNodeName": "Computer Network Support Specialists", + "frameworkName": "bls" + }, + { + "code": "15-1240", + "targetNodeName": "Database and Network Administrators and Architects", + "frameworkName": "bls" + }, + { + "code": "15-1241", + "targetNodeName": "Computer Network Architects", + "frameworkName": "bls" + }, + { + "code": "15-1244", + "targetNodeName": "Network and Computer Systems Administrators", + "frameworkName": "bls" + } + ] + } + ], + [], + [ + { + "id": "http://localhost:8080/api/skills/c0544c43-1b6a-4593-aa85-9b31db2dd2bf", + "uuid": "c0544c43-1b6a-4593-aa85-9b31db2dd2bf", + "status": "published", + "publishDate": "2023-03-30T13:02:06", + "archiveDate": null, + "skillName": "Active Directory Schema Extensions Implementation", + "skillStatement": "Configure forest and domain trusts for Active Directory.", + "categories": [ + "Active Directory" + ], + "keywords": [ + "Active Directory", + "Schema", + "WGUSID: 10416" + ], + "occupations": [ + { + "code": "15-0000", + "targetNodeName": "Computer and Mathematical Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1200", + "targetNodeName": "Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1240", + "targetNodeName": "Database and Network Administrators and Architects", + "frameworkName": "bls" + }, + { + "code": "15-1241", + "targetNodeName": "Computer Network Architects", + "frameworkName": "bls" + }, + { + "code": "15-1244", + "targetNodeName": "Network and Computer Systems Administrators", + "frameworkName": "bls" + } + ] + } + ], + [ + { + "id": "http://localhost:8080/api/skills/3449eae9-f52f-47cc-9ece-a16035659680", + "uuid": "3449eae9-f52f-47cc-9ece-a16035659680", + "status": "published", + "publishDate": "2023-03-30T13:02:08", + "archiveDate": null, + "skillName": "Agile Kanban Board Management", + "skillStatement": "Manage project feature progress with agile Kanban boards.", + "categories": [ + "Agile Methodology" + ], + "keywords": [ + "Agile Methodology", + "Kanban Board", + "WGUSID: 10428" + ], + "occupations": [ + { + "code": "15-0000", + "targetNodeName": "Computer and Mathematical Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1200", + "targetNodeName": "Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1240", + "targetNodeName": "Database and Network Administrators and Architects", + "frameworkName": "bls" + }, + { + "code": "15-1244", + "targetNodeName": "Network and Computer Systems Administrators", + "frameworkName": "bls" + }, + { + "code": "15-1250", + "targetNodeName": "Software and Web Developers, Programmers, and Testers", + "frameworkName": "bls" + }, + { + "code": "15-1251", + "targetNodeName": "Computer Programmers", + "frameworkName": "bls" + }, + { + "code": "15-1290", + "targetNodeName": "Miscellaneous Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1256" + }, + { + "code": "15-1257" + }, + { + "code": "15-1245" + }, + { + "code": "15-1259" + } + ] + } + ], + [ + { + "id": "http://localhost:8080/api/skills/5069cebe-102e-40ca-a2a8-7e06cfd0214b", + "uuid": "5069cebe-102e-40ca-a2a8-7e06cfd0214b", + "status": "published", + "publishDate": "2023-03-30T13:02:06", + "archiveDate": null, + "skillName": "Agile Scrum Board Management", + "skillStatement": "Manage project feature progress with agile scrum boards.", + "categories": [ + "Agile Methodology" + ], + "keywords": [ + "Agile Methodology", + "SCRUM", + "Features", + "WGUSID: 10427" + ], + "occupations": [ + { + "code": "15-0000", + "targetNodeName": "Computer and Mathematical Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1200", + "targetNodeName": "Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1240", + "targetNodeName": "Database and Network Administrators and Architects", + "frameworkName": "bls" + }, + { + "code": "15-1244", + "targetNodeName": "Network and Computer Systems Administrators", + "frameworkName": "bls" + }, + { + "code": "15-1250", + "targetNodeName": "Software and Web Developers, Programmers, and Testers", + "frameworkName": "bls" + }, + { + "code": "15-1290", + "targetNodeName": "Miscellaneous Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1256" + }, + { + "code": "15-1257" + }, + { + "code": "15-1245" + }, + { + "code": "15-1259" + } + ] + } + ], + [ + { + "id": "http://localhost:8080/api/skills/2355dad8-d1ef-47f8-a663-899473ad2ce2", + "uuid": "2355dad8-d1ef-47f8-a663-899473ad2ce2", + "status": "published", + "publishDate": "2023-03-30T13:02:06", + "archiveDate": null, + "skillName": "Agile Solutions Development", + "skillStatement": "Communicate blocking issues during the development cycle.", + "categories": [ + "Agile Methodology" + ], + "keywords": [ + "specifications", + "Agile Methodology", + "WGUSID: 10423" + ], + "occupations": [ + { + "code": "15-0000", + "targetNodeName": "Computer and Mathematical Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1200", + "targetNodeName": "Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1210", + "targetNodeName": "Computer and Information Analysts", + "frameworkName": "bls" + }, + { + "code": "15-1211", + "targetNodeName": "Computer Systems Analysts", + "frameworkName": "bls" + }, + { + "code": "15-1240", + "targetNodeName": "Database and Network Administrators and Architects", + "frameworkName": "bls" + }, + { + "code": "15-1244", + "targetNodeName": "Network and Computer Systems Administrators", + "frameworkName": "bls" + }, + { + "code": "15-1250", + "targetNodeName": "Software and Web Developers, Programmers, and Testers", + "frameworkName": "bls" + }, + { + "code": "15-1251", + "targetNodeName": "Computer Programmers", + "frameworkName": "bls" + }, + { + "code": "15-1290", + "targetNodeName": "Miscellaneous Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1256" + }, + { + "code": "15-1259" + } + ] + } + ], + [ + { + "id": "http://localhost:8080/api/skills/c3c19663-2c8c-4183-9120-6f35c25154bf", + "uuid": "c3c19663-2c8c-4183-9120-6f35c25154bf", + "status": "published", + "publishDate": "2023-03-30T13:02:06", + "archiveDate": null, + "skillName": "Internal Partners Agile Project Goal Accomplishment", + "skillStatement": "Accomplish agile project goals through collaboration with internal partners.", + "categories": [ + "Agile Methodology" + ], + "keywords": [ + "Agile Methodology", + "WGUSID: 10425" + ], + "occupations": [ + { + "code": "15-0000", + "targetNodeName": "Computer and Mathematical Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1200", + "targetNodeName": "Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1210", + "targetNodeName": "Computer and Information Analysts", + "frameworkName": "bls" + }, + { + "code": "15-1211", + "targetNodeName": "Computer Systems Analysts", + "frameworkName": "bls" + }, + { + "code": "15-1240", + "targetNodeName": "Database and Network Administrators and Architects", + "frameworkName": "bls" + }, + { + "code": "15-1244", + "targetNodeName": "Network and Computer Systems Administrators", + "frameworkName": "bls" + }, + { + "code": "15-1250", + "targetNodeName": "Software and Web Developers, Programmers, and Testers", + "frameworkName": "bls" + }, + { + "code": "15-1251", + "targetNodeName": "Computer Programmers", + "frameworkName": "bls" + }, + { + "code": "15-1290", + "targetNodeName": "Miscellaneous Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1256" + }, + { + "code": "15-1257" + }, + { + "code": "15-1245" + }, + { + "code": "15-1259" + } + ] + } + ], + [], + [ + { + "id": "http://localhost:8080/api/skills/a0ae4134-6f69-43cc-b536-7c09ab45fdf5", + "uuid": "a0ae4134-6f69-43cc-b536-7c09ab45fdf5", + "status": "published", + "publishDate": "2023-03-30T13:02:06", + "archiveDate": null, + "skillName": "Cloud Computing Environment Maintenance", + "skillStatement": "Maintain a cloud computing environment in Amazon Web Services (AWS).", + "categories": [ + "Amazon Web Services" + ], + "keywords": [ + "Amazon Web Services", + "Cloud Computing", + "WGUSID: 10442" + ], + "occupations": [ + { + "code": "15-0000", + "targetNodeName": "Computer and Mathematical Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1200", + "targetNodeName": "Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1240", + "targetNodeName": "Database and Network Administrators and Architects", + "frameworkName": "bls" + }, + { + "code": "15-1244", + "targetNodeName": "Network and Computer Systems Administrators", + "frameworkName": "bls" + } + ] + } + ], + [ + { + "id": "http://localhost:8080/api/skills/2444c3b2-37b9-4f4d-92a0-6260ad0a33dd", + "uuid": "2444c3b2-37b9-4f4d-92a0-6260ad0a33dd", + "status": "published", + "publishDate": "2023-03-30T13:02:06", + "archiveDate": null, + "skillName": "Access Application Programming Interface (API) to Change Data", + "skillStatement": "Access an application programming interface (API) with a programming language to change data for a task.", + "categories": [ + "Software Development", + "Application Programming Interface (API)" + ], + "keywords": [ + "Application Programming Interface (API)", + "WGUSID: 10460" + ], + "occupations": [ + { + "code": "15-0000", + "targetNodeName": "Computer and Mathematical Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1200", + "targetNodeName": "Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1240", + "targetNodeName": "Database and Network Administrators and Architects", + "frameworkName": "bls" + }, + { + "code": "15-1244", + "targetNodeName": "Network and Computer Systems Administrators", + "frameworkName": "bls" + } + ] + } + ], + [ + { + "id": "http://localhost:8080/api/skills/a23fcc22-12cc-429b-81fc-c4e913d98c9e", + "uuid": "a23fcc22-12cc-429b-81fc-c4e913d98c9e", + "status": "published", + "publishDate": "2023-03-30T13:02:06", + "archiveDate": null, + "skillName": "Systems Engineering", + "skillStatement": "Engineer systems that leverage multi-platform application programming interface (API).", + "categories": [ + "Application Programming Interface (API)" + ], + "keywords": [ + "Visual Studio", + "Application Programming Interface (API)", + "Eclipse", + "Visual Studio Code", + "WGUSID: 592" + ], + "occupations": [ + { + "code": "15-0000", + "targetNodeName": "Computer and Mathematical Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1200", + "targetNodeName": "Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1220", + "targetNodeName": "Computer and Information Research Scientists", + "frameworkName": "bls" + }, + { + "code": "15-1221", + "targetNodeName": "Computer and Information Research Scientists", + "frameworkName": "bls" + }, + { + "code": "15-1240", + "targetNodeName": "Database and Network Administrators and Architects", + "frameworkName": "bls" + }, + { + "code": "15-1244", + "targetNodeName": "Network and Computer Systems Administrators", + "frameworkName": "bls" + }, + { + "code": "15-1250", + "targetNodeName": "Software and Web Developers, Programmers, and Testers", + "frameworkName": "bls" + }, + { + "code": "15-1251", + "targetNodeName": "Computer Programmers", + "frameworkName": "bls" + }, + { + "code": "15-1290", + "targetNodeName": "Miscellaneous Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1256" + }, + { + "code": "15-1257" + }, + { + "code": "15-1245" + }, + { + "code": "15-1259" + } + ] + } + ], + [ + { + "id": "http://localhost:8080/api/skills/4c98ac7d-a27b-4c75-aa53-2f273bb2f269", + "uuid": "4c98ac7d-a27b-4c75-aa53-2f273bb2f269", + "status": "published", + "publishDate": "2023-03-30T13:02:06", + "archiveDate": null, + "skillName": "Backup File Configuration", + "skillStatement": "Configure file backups to a cloud solution.", + "categories": [ + "Backup And Restore" + ], + "keywords": [ + "Backup And Restore", + "WGUSID: 10462" + ], + "occupations": [ + { + "code": "15-0000", + "targetNodeName": "Computer and Mathematical Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1200", + "targetNodeName": "Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1240", + "targetNodeName": "Database and Network Administrators and Architects", + "frameworkName": "bls" + }, + { + "code": "15-1241", + "targetNodeName": "Computer Network Architects", + "frameworkName": "bls" + }, + { + "code": "15-1244", + "targetNodeName": "Network and Computer Systems Administrators", + "frameworkName": "bls" + }, + { + "code": "15-1250", + "targetNodeName": "Software and Web Developers, Programmers, and Testers", + "frameworkName": "bls" + }, + { + "code": "15-1290", + "targetNodeName": "Miscellaneous Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1256" + }, + { + "code": "15-1259" + } + ] + } + ], + [ + { + "id": "http://localhost:8080/api/skills/9f49f23a-8301-4b61-a0df-cbdec28b6b25", + "uuid": "9f49f23a-8301-4b61-a0df-cbdec28b6b25", + "status": "published", + "publishDate": "2023-03-30T13:02:06", + "archiveDate": null, + "skillName": "Backup File Restoration Identification", + "skillStatement": "Identify which backed-up files must be restored.", + "categories": [ + "Backup And Restore" + ], + "keywords": [ + "Backup And Restore", + "WGUSID: 10461" + ], + "occupations": [ + { + "code": "15-0000", + "targetNodeName": "Computer and Mathematical Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1200", + "targetNodeName": "Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1240", + "targetNodeName": "Database and Network Administrators and Architects", + "frameworkName": "bls" + }, + { + "code": "15-1241", + "targetNodeName": "Computer Network Architects", + "frameworkName": "bls" + }, + { + "code": "15-1244", + "targetNodeName": "Network and Computer Systems Administrators", + "frameworkName": "bls" + }, + { + "code": "15-1250", + "targetNodeName": "Software and Web Developers, Programmers, and Testers", + "frameworkName": "bls" + }, + { + "code": "15-1290", + "targetNodeName": "Miscellaneous Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1256" + }, + { + "code": "15-1245" + }, + { + "code": "15-1259" + } + ] + } + ], + [ + { + "id": "http://localhost:8080/api/skills/def5e07c-4117-4b47-a720-e428d6020df5", + "uuid": "def5e07c-4117-4b47-a720-e428d6020df5", + "status": "published", + "publishDate": "2023-03-30T13:02:06", + "archiveDate": null, + "skillName": "Identify Disaster Recovery Solutions", + "skillStatement": "Identify disaster recovery solutions for backing up and restoring computer systems.", + "categories": [ + "Backup And Restore" + ], + "keywords": [ + "Backup And Restore", + "WGUSID: 10463" + ], + "occupations": [ + { + "code": "15-0000", + "targetNodeName": "Computer and Mathematical Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1200", + "targetNodeName": "Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1210", + "targetNodeName": "Computer and Information Analysts", + "frameworkName": "bls" + }, + { + "code": "15-1211", + "targetNodeName": "Computer Systems Analysts", + "frameworkName": "bls" + }, + { + "code": "15-1240", + "targetNodeName": "Database and Network Administrators and Architects", + "frameworkName": "bls" + }, + { + "code": "15-1241", + "targetNodeName": "Computer Network Architects", + "frameworkName": "bls" + }, + { + "code": "15-1244", + "targetNodeName": "Network and Computer Systems Administrators", + "frameworkName": "bls" + }, + { + "code": "15-1250", + "targetNodeName": "Software and Web Developers, Programmers, and Testers", + "frameworkName": "bls" + }, + { + "code": "15-1290", + "targetNodeName": "Miscellaneous Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1256" + }, + { + "code": "15-1259" + } + ] + } + ], + [ + { + "id": "http://localhost:8080/api/skills/40bf4278-6ead-48ca-87d6-ac799e43a332", + "uuid": "40bf4278-6ead-48ca-87d6-ac799e43a332", + "status": "published", + "publishDate": "2023-03-30T13:02:06", + "archiveDate": null, + "skillName": "Backup Hardware Determination", + "skillStatement": "Determine the hardware needed for the backup of a device's data.", + "categories": [ + "Backup Devices" + ], + "keywords": [ + "Backup Devices", + "WGUSID: 10468" + ], + "occupations": [ + { + "code": "15-0000", + "targetNodeName": "Computer and Mathematical Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1200", + "targetNodeName": "Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1240", + "targetNodeName": "Database and Network Administrators and Architects", + "frameworkName": "bls" + }, + { + "code": "15-1244", + "targetNodeName": "Network and Computer Systems Administrators", + "frameworkName": "bls" + }, + { + "code": "15-1250", + "targetNodeName": "Software and Web Developers, Programmers, and Testers", + "frameworkName": "bls" + }, + { + "code": "15-1290", + "targetNodeName": "Miscellaneous Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1245" + }, + { + "code": "15-1259" + } + ] + } + ], + [ + { + "id": "http://localhost:8080/api/skills/e42429fb-edc8-4ff5-ab5a-583ab05effac", + "uuid": "e42429fb-edc8-4ff5-ab5a-583ab05effac", + "status": "published", + "publishDate": "2023-03-30T13:02:06", + "archiveDate": null, + "skillName": "Determine Files Needing Backup", + "skillStatement": "Determine which files must be backed up from one device to another.", + "categories": [ + "Backup Devices" + ], + "keywords": [ + "Backup Devices", + "WGUSID: 10466" + ], + "occupations": [ + { + "code": "15-0000", + "targetNodeName": "Computer and Mathematical Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1200", + "targetNodeName": "Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1240", + "targetNodeName": "Database and Network Administrators and Architects", + "frameworkName": "bls" + }, + { + "code": "15-1244", + "targetNodeName": "Network and Computer Systems Administrators", + "frameworkName": "bls" + }, + { + "code": "15-1250", + "targetNodeName": "Software and Web Developers, Programmers, and Testers", + "frameworkName": "bls" + }, + { + "code": "15-1290", + "targetNodeName": "Miscellaneous Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1245" + }, + { + "code": "15-1259" + } + ] + } + ], + [ + { + "id": "http://localhost:8080/api/skills/8501c68e-3615-420f-8735-35e3d33ad7fe", + "uuid": "8501c68e-3615-420f-8735-35e3d33ad7fe", + "status": "published", + "publishDate": "2023-03-30T13:02:07", + "archiveDate": null, + "skillName": "Perform Manual Backup", + "skillStatement": "Perform manual backups of data from a device.", + "categories": [ + "Backup Devices" + ], + "keywords": [ + "Backup Devices", + "WGUSID: 10470" + ], + "occupations": [ + { + "code": "15-0000", + "targetNodeName": "Computer and Mathematical Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1200", + "targetNodeName": "Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1240", + "targetNodeName": "Database and Network Administrators and Architects", + "frameworkName": "bls" + }, + { + "code": "15-1244", + "targetNodeName": "Network and Computer Systems Administrators", + "frameworkName": "bls" + }, + { + "code": "15-1250", + "targetNodeName": "Software and Web Developers, Programmers, and Testers", + "frameworkName": "bls" + }, + { + "code": "15-1290", + "targetNodeName": "Miscellaneous Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1259" + } + ] + } + ], + [ + { + "id": "http://localhost:8080/api/skills/957a2791-5eaf-4005-9fee-46d1c7c4727e", + "uuid": "957a2791-5eaf-4005-9fee-46d1c7c4727e", + "status": "published", + "publishDate": "2023-03-30T13:02:07", + "archiveDate": null, + "skillName": "Border Gateway Protocol Solution Implementation", + "skillStatement": "Implement Border Gateway Protocol solutions.", + "categories": [ + "Border Gateway Protocol" + ], + "keywords": [ + "Border Gateway Protocol", + "WGUSID: 10488" + ], + "occupations": [ + { + "code": "15-0000", + "targetNodeName": "Computer and Mathematical Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1200", + "targetNodeName": "Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1240", + "targetNodeName": "Database and Network Administrators and Architects", + "frameworkName": "bls" + }, + { + "code": "15-1241", + "targetNodeName": "Computer Network Architects", + "frameworkName": "bls" + }, + { + "code": "15-1244", + "targetNodeName": "Network and Computer Systems Administrators", + "frameworkName": "bls" + } + ] + } + ], + [ + { + "id": "http://localhost:8080/api/skills/611e3797-3597-4dde-afc9-0f1e7d575baa", + "uuid": "611e3797-3597-4dde-afc9-0f1e7d575baa", + "status": "published", + "publishDate": "2023-03-30T13:02:08", + "archiveDate": null, + "skillName": "Implementation", + "skillStatement": "Implement continuity plans during the time of a disaster.", + "categories": [ + "Business Continuity Planning" + ], + "keywords": [ + "Business Continuity Planning", + "WGUSID: 4367" + ], + "occupations": [ + { + "code": "11-0000", + "targetNodeName": "Management Occupations", + "frameworkName": "bls" + }, + { + "code": "11-3000", + "targetNodeName": "Operations Specialties Managers", + "frameworkName": "bls" + }, + { + "code": "11-3020", + "targetNodeName": "Computer and Information Systems Managers", + "frameworkName": "bls" + }, + { + "code": "11-3021", + "targetNodeName": "Computer and Information Systems Managers", + "frameworkName": "bls" + }, + { + "code": "15-0000", + "targetNodeName": "Computer and Mathematical Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1200", + "targetNodeName": "Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1210", + "targetNodeName": "Computer and Information Analysts", + "frameworkName": "bls" + }, + { + "code": "15-1211", + "targetNodeName": "Computer Systems Analysts", + "frameworkName": "bls" + }, + { + "code": "15-1230", + "targetNodeName": "Computer Support Specialists", + "frameworkName": "bls" + }, + { + "code": "15-1231", + "targetNodeName": "Computer Network Support Specialists", + "frameworkName": "bls" + }, + { + "code": "15-1232", + "targetNodeName": "Computer User Support Specialists", + "frameworkName": "bls" + }, + { + "code": "15-1240", + "targetNodeName": "Database and Network Administrators and Architects", + "frameworkName": "bls" + }, + { + "code": "15-1241", + "targetNodeName": "Computer Network Architects", + "frameworkName": "bls" + }, + { + "code": "15-1244", + "targetNodeName": "Network and Computer Systems Administrators", + "frameworkName": "bls" + }, + { + "code": "15-1245" + } + ] + }, + { + "id": "http://localhost:8080/api/skills/a299006d-25d7-46cc-8382-acd7c69fc540", + "uuid": "a299006d-25d7-46cc-8382-acd7c69fc540", + "status": "published", + "publishDate": "2023-03-30T13:01:46", + "archiveDate": null, + "skillName": "Implementation", + "skillStatement": "Implement a continuity plan during the time of a disaster.", + "categories": [ + "Business Continuity Planning" + ], + "keywords": [ + "Business Continuity Planning" + ], + "occupations": [ + { + "code": "11-0000", + "targetNodeName": "Management Occupations", + "frameworkName": "bls" + }, + { + "code": "11-3000", + "targetNodeName": "Operations Specialties Managers", + "frameworkName": "bls" + }, + { + "code": "11-3020", + "targetNodeName": "Computer and Information Systems Managers", + "frameworkName": "bls" + }, + { + "code": "11-3021", + "targetNodeName": "Computer and Information Systems Managers", + "frameworkName": "bls" + }, + { + "code": "15-0000", + "targetNodeName": "Computer and Mathematical Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1200", + "targetNodeName": "Computer Occupations", + "frameworkName": "bls" + }, + { + "code": "15-1210", + "targetNodeName": "Computer and Information Analysts", + "frameworkName": "bls" + }, + { + "code": "15-1211", + "targetNodeName": "Computer Systems Analysts", + "frameworkName": "bls" + }, + { + "code": "15-1230", + "targetNodeName": "Computer Support Specialists", + "frameworkName": "bls" + }, + { + "code": "15-1231", + "targetNodeName": "Computer Network Support Specialists", + "frameworkName": "bls" + }, + { + "code": "15-1232", + "targetNodeName": "Computer User Support Specialists", + "frameworkName": "bls" + }, + { + "code": "15-1240", + "targetNodeName": "Database and Network Administrators and Architects", + "frameworkName": "bls" + }, + { + "code": "15-1241", + "targetNodeName": "Computer Network Architects", + "frameworkName": "bls" + }, + { + "code": "15-1244", + "targetNodeName": "Network and Computer Systems Administrators", + "frameworkName": "bls" + }, + { + "code": "11-3021.00", + "targetNodeName": "Computer and Information Systems Managers", + "frameworkName": "o*net" + }, + { + "code": "15-1000" + }, + { + "code": "15-1120" + }, + { + "code": "15-1245" + }, + { + "code": "15-1140" + }, + { + "code": "15-1150" + }, + { + "code": "15-1121" + }, + { + "code": "15-1141" + }, + { + "code": "15-1142" + }, + { + "code": "15-1143" + }, + { + "code": "15-1151" + }, + { + "code": "15-1152" + }, + { + "code": "15-1121.00" + }, + { + "code": "15-1121.01" + }, + { + "code": "15-1141.00" + }, + { + "code": "15-1142.00" + }, + { + "code": "15-1143.00" + }, + { + "code": "15-1143.01" + }, + { + "code": "15-1151.00" + }, + { + "code": "15-1152.00" + } + ] + } + ] +] \ No newline at end of file diff --git a/ui/src/app/app.module.ts b/ui/src/app/app.module.ts index 1f06ae959..a70f67182 100644 --- a/ui/src/app/app.module.ts +++ b/ui/src/app/app.module.ts @@ -87,8 +87,6 @@ import {BatchImportComponent} from "./richskill/import/batch-import.component" import {FieldMappingSelectComponent, FieldMappingTableComponent} from "./richskill/import/field-mapping-table.component" import { ImportPreviewTableComponent, - InlineErrorComponent, - InlineHeadingComponent, NamedReferenceComponent } from "./richskill/import/import-preview-table.component" import {AuditLogComponent} from "./richskill/detail/audit-log.component" @@ -111,6 +109,8 @@ import { ConvertToCollectionComponent } from "./my-workspace/convert-to-collecti import { SizePaginationComponent } from "./table/skills-library-table/size-pagination/size-pagination.component" import {OsmtTableModule} from "./table/osmt-table.module" import { getBaseApi } from "./api-versions" +import { InlineHeadingComponent } from './richskill/import/inline-heading/inline-heading.component' +import { InlineErrorComponent } from "./richskill/import/inline-error/inline-error.component" export function initializeApp( appConfig: AppConfig, @@ -219,7 +219,6 @@ export function initializeApp( FieldMappingTableComponent, FieldMappingSelectComponent, ImportPreviewTableComponent, - InlineHeadingComponent, NamedReferenceComponent, InlineErrorComponent, AuditLogComponent, @@ -230,6 +229,7 @@ export function initializeApp( CollectionPipe, ConvertToCollectionComponent, SizePaginationComponent, + InlineHeadingComponent, ], imports: [ NgIdleKeepaliveModule.forRoot(), diff --git a/ui/src/app/core/accordian.component.html b/ui/src/app/core/accordian.component.html index 6c53e5f5d..9c1c11452 100644 --- a/ui/src/app/core/accordian.component.html +++ b/ui/src/app/core/accordian.component.html @@ -14,7 +14,7 @@ {{triggerLabel}} -
+
diff --git a/ui/src/app/richskill/form/checker.component.html b/ui/src/app/richskill/form/checker.component.html index 767699010..dd337e9db 100644 --- a/ui/src/app/richskill/form/checker.component.html +++ b/ui/src/app/richskill/form/checker.component.html @@ -22,7 +22,10 @@ -

{{similarSingleMessage}}

+

{{similarSingleMessage}}

+ + +
@@ -32,6 +35,8 @@ {{skill.skillName}} + +
@@ -46,6 +51,8 @@ {{skill.skillName}} + +
@@ -56,3 +63,13 @@ + +
+

+ Statement: {{skill.skillStatement}} +

+

+ Categories: {{skill.categories.join(",")}} +

+
+
diff --git a/ui/src/app/richskill/form/checker.component.ts b/ui/src/app/richskill/form/checker.component.ts index 37dfa3749..387766d5d 100644 --- a/ui/src/app/richskill/form/checker.component.ts +++ b/ui/src/app/richskill/form/checker.component.ts @@ -1,10 +1,10 @@ import {Component, Input, OnInit} from "@angular/core"; -import {Observable} from "rxjs"; import {ApiSkillSummary} from "../ApiSkillSummary"; @Component({ selector: "app-checker", - templateUrl: "./checker.component.html" + templateUrl: "./checker.component.html", + styleUrls: ["./checker.scss"] }) export class CheckerComponent implements OnInit { @@ -14,9 +14,11 @@ export class CheckerComponent implements OnInit { @Input() similarSingleMessage = "Skill statement is very similar to one already in the library." @Input() affirmativeMessage = "Skill statement OK." @Input() similarSkills?: ApiSkillSummary[] - @Input() searching?: boolean + @Input() searching? = false @Input() showAccordion = true + @Input() showExtraInformation = false + @Input() showInTable = false @@ -31,10 +33,10 @@ export class CheckerComponent implements OnInit { return this.searching ?? false } get isAffirmative(): boolean { - return this.searching === false && this.similarSkills?.length === 0 + return !this.searching && this.similarSkills?.length === 0 } get hasMatches(): boolean { - return this.searching === false && (this.similarSkills?.length ?? 0) > 0 + return !this.searching && (this.similarSkills?.length ?? 0) > 0 } get hasSingleMatch(): boolean { return this.similarSkills?.length === 1 diff --git a/ui/src/app/richskill/form/checker.scss b/ui/src/app/richskill/form/checker.scss new file mode 100644 index 000000000..9c921f9af --- /dev/null +++ b/ui/src/app/richskill/form/checker.scss @@ -0,0 +1,3 @@ +.text-small { + font-size: 12px; +} diff --git a/ui/src/app/richskill/import/batch-import.component.html b/ui/src/app/richskill/import/batch-import.component.html index 3da8be90f..22336dd73 100644 --- a/ui/src/app/richskill/import/batch-import.component.html +++ b/ui/src/app/richskill/import/batch-import.component.html @@ -150,13 +150,6 @@

-

{{similarSkillCount}} RSDs contain skill statements that are very similar to those already in the library.

@@ -165,7 +158,7 @@

+ @@ -184,7 +177,8 @@

+ + diff --git a/ui/src/app/richskill/import/batch-import.component.ts b/ui/src/app/richskill/import/batch-import.component.ts index a43b3b1e5..d66018b23 100644 --- a/ui/src/app/richskill/import/batch-import.component.ts +++ b/ui/src/app/richskill/import/batch-import.component.ts @@ -17,6 +17,7 @@ import {forkJoin, Observable} from "rxjs" import {SvgHelper, SvgIcon} from "../../core/SvgHelper" import {Title} from "@angular/platform-browser"; import {AppConfig} from "../../app.config" +import { ApiSkillSummary } from "../ApiSkillSummary" export enum ImportStep { @@ -68,11 +69,13 @@ export class AuditedImportSkill { skill: ApiSkillUpdate missing: string[] similar: boolean + similarities: any[] = [] - constructor(skill: ApiSkillUpdate, missing: string[], similar: boolean = false) { + constructor(skill: ApiSkillUpdate, missing: string[], similar: boolean = false, similarities: any[]) { this.skill = skill this.missing = missing this.similar = similar + this.similarities = similarities; } get nameMissing(): boolean { @@ -128,7 +131,7 @@ export class BatchImportComponent extends QuickLinksHelper implements OnInit { alignmentCount: number = 3 searchingSimilarity?: boolean - similarSkills?: boolean[] + similarSkills?: any[] importSimilarSkills = false docIcon = SvgHelper.path(SvgIcon.DOC) @@ -477,7 +480,7 @@ export class BatchImportComponent extends QuickLinksHelper implements OnInit { this.handleMappingChanged(mappings) } - private batchSimilarity(statements: string[]): Observable { + private batchSimilarity(statements: string[]): Observable> { const chunk = (arr: string[], size: number) => { return Array.from({ length: Math.ceil(arr.length / size) }, (v, i) => arr.slice(i * size, i * size + size) @@ -485,10 +488,10 @@ export class BatchImportComponent extends QuickLinksHelper implements OnInit { } const chunks: string[][] = chunk(statements, 100) - const observables: Observable[] = chunks.map(it => this.richSkillService.similaritiesCheck(it)) + const observables: Observable>[] = chunks.map(it => this.richSkillService.similaritiesResults(it)) return new Observable(observer => { forkJoin(observables).subscribe(it => { - const allResponses: boolean[] = it.flat() + const allResponses: Array = it.flat() observer.next(allResponses) observer.complete() }, err => observer.error(err)) @@ -503,13 +506,12 @@ export class BatchImportComponent extends QuickLinksHelper implements OnInit { this.batchSimilarity(statements).subscribe(results => { this.searchingSimilarity = false this.similarSkills = results - this.auditedSkills = this.previewSkills?.map((skill, idx) => { const required = ["skillName", "skillStatement"] // @ts-ignore const missing = required.filter(it => skill[it] === undefined) - const similar = results[idx] ?? false - return new AuditedImportSkill(skill, missing, similar) + const similar = (results[idx]?.length ?? 0) > 0 + return new AuditedImportSkill(skill, missing, similar, results[idx]) }) }) } diff --git a/ui/src/app/richskill/import/import-preview-table.component.html b/ui/src/app/richskill/import/import-preview-table.component.html index c925e8311..66349d738 100644 --- a/ui/src/app/richskill/import/import-preview-table.component.html +++ b/ui/src/app/richskill/import/import-preview-table.component.html @@ -39,7 +39,7 @@
{{review.skill.categories?.add?.join("; ") ?? ""}}
- +

Categories:

{{review.skill.categories?.add?.join("; ") ?? ""}}

@@ -48,17 +48,35 @@ -

{{review.skill.skillName}}

- +

+ {{review.skill.skillName}} +

+ + - + -

{{review.skill.skillStatement}}

- +

+ {{review.skill.skillStatement}} +

+ + - {{review.skill.authors.add?.join("; ")}} + + {{review.skill.authors.add?.join("; ")}} + - {{review.skill.keywords.add?.join("; ")}} + + {{review.skill.keywords.add?.join("; ")}} + + + + diff --git a/ui/src/app/richskill/import/import-preview-table.component.ts b/ui/src/app/richskill/import/import-preview-table.component.ts index 278632c63..b980cb894 100644 --- a/ui/src/app/richskill/import/import-preview-table.component.ts +++ b/ui/src/app/richskill/import/import-preview-table.component.ts @@ -1,4 +1,4 @@ -import {Component, Input, OnInit} from "@angular/core"; +import { Component, Input } from "@angular/core"; import {INamedReference} from "../ApiSkill"; import {AuditedImportSkill} from "./batch-import.component"; @@ -7,28 +7,10 @@ import {AuditedImportSkill} from "./batch-import.component"; selector: "app-import-preview-table", templateUrl: "./import-preview-table.component.html" }) -export class ImportPreviewTableComponent implements OnInit { - - @Input() skills: AuditedImportSkill[] = [] - - ngOnInit(): void { - } - -} - - - -@Component({ - selector: "app-inline-heading", - template: ` -
-

{{heading}}

-

-
- ` -}) -export class InlineHeadingComponent { - @Input() heading: string = "Heading:" +export class ImportPreviewTableComponent { + @Input() skills?: AuditedImportSkill[] = [] + @Input() showChecker = false + showExtraInformation = true } @Component({ @@ -52,20 +34,3 @@ export class NamedReferenceComponent { return this.ref?.id === undefined && this.ref?.name !== undefined } } - - -@Component({ - selector: "app-inline-error", - template: ` -

- - - - {{message}} -

` -}) -export class InlineErrorComponent { - @Input() message: string = "" -} diff --git a/ui/src/app/richskill/import/inline-error/inline-error.component.html b/ui/src/app/richskill/import/inline-error/inline-error.component.html new file mode 100644 index 000000000..14a57979c --- /dev/null +++ b/ui/src/app/richskill/import/inline-error/inline-error.component.html @@ -0,0 +1,8 @@ +

+ + + + {{message}} +

diff --git a/ui/src/app/richskill/import/inline-error/inline-error.component.spec.ts b/ui/src/app/richskill/import/inline-error/inline-error.component.spec.ts new file mode 100644 index 000000000..b139d1ce7 --- /dev/null +++ b/ui/src/app/richskill/import/inline-error/inline-error.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InlineErrorComponent } from './inline-error.component'; + +describe('InlineErrorComponent', () => { + let component: InlineErrorComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [InlineErrorComponent] + }); + fixture = TestBed.createComponent(InlineErrorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ui/src/app/richskill/import/inline-error/inline-error.component.ts b/ui/src/app/richskill/import/inline-error/inline-error.component.ts new file mode 100644 index 000000000..98251a8b8 --- /dev/null +++ b/ui/src/app/richskill/import/inline-error/inline-error.component.ts @@ -0,0 +1,9 @@ +import { Component, Input } from "@angular/core" + +@Component({ + selector: 'app-inline-error', + templateUrl: './inline-error.component.html' +}) +export class InlineErrorComponent { + @Input() message = "" +} diff --git a/ui/src/app/richskill/import/inline-heading/inline-heading.component.html b/ui/src/app/richskill/import/inline-heading/inline-heading.component.html new file mode 100644 index 000000000..98a9f1998 --- /dev/null +++ b/ui/src/app/richskill/import/inline-heading/inline-heading.component.html @@ -0,0 +1,4 @@ +
+

{{heading}}

+

+
diff --git a/ui/src/app/richskill/import/inline-heading/inline-heading.component.spec.ts b/ui/src/app/richskill/import/inline-heading/inline-heading.component.spec.ts new file mode 100644 index 000000000..ee43f1f66 --- /dev/null +++ b/ui/src/app/richskill/import/inline-heading/inline-heading.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InlineHeadingComponent } from './inline-heading.component'; + +describe('InlineheadingComponent', () => { + let component: InlineHeadingComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [InlineHeadingComponent] + }); + fixture = TestBed.createComponent(InlineHeadingComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ui/src/app/richskill/import/inline-heading/inline-heading.component.ts b/ui/src/app/richskill/import/inline-heading/inline-heading.component.ts new file mode 100644 index 000000000..f9cd487a3 --- /dev/null +++ b/ui/src/app/richskill/import/inline-heading/inline-heading.component.ts @@ -0,0 +1,9 @@ +import { Component, Input } from "@angular/core" + +@Component({ + selector: 'app-inline-heading', + templateUrl: './inline-heading.component.html' +}) +export class InlineHeadingComponent { + @Input() heading = "Heading:" +} diff --git a/ui/src/app/richskill/list/skill-list-row.component.html b/ui/src/app/richskill/list/skill-list-row.component.html index 24efc43ba..f4deefc52 100644 --- a/ui/src/app/richskill/list/skill-list-row.component.html +++ b/ui/src/app/richskill/list/skill-list-row.component.html @@ -4,7 +4,7 @@
{{getFormattedCategories()}}
- +
Categories:

{{getFormattedCategories()}}

diff --git a/ui/src/app/richskill/service/rich-skill.service.spec.ts b/ui/src/app/richskill/service/rich-skill.service.spec.ts index 1afd14ba3..029c95223 100644 --- a/ui/src/app/richskill/service/rich-skill.service.spec.ts +++ b/ui/src/app/richskill/service/rich-skill.service.spec.ts @@ -459,16 +459,41 @@ describe("RichSkillService", () => { RouterData.commands = [] AuthServiceData.isDown = false const statements = ["my statement"] - const path = getBaseApi() + "/search/skills/similarities" - const testData: boolean[] = [true] + const path = getBaseApi() + "/search/skills/similarities/results" + const testData: Array = [[createMockSkillSummary()]] // Act // noinspection LocalVariableNamingConventionJS - const result$ = testService.similaritiesCheck(statements) + const result$ = testService.similaritiesResults(statements) // Assert result$ - .subscribe((data: boolean[]) => { + .subscribe((data: Array) => { + expect(data).toEqual(testData) + expect(RouterData.commands).toEqual([ ]) // No errors + expect(AuthServiceData.isDown).toEqual(false) + }) + + const req = httpTestingController.expectOne(AppConfig.settings.baseApiUrl + path) + expect(req.request.method).toEqual("POST") + req.flush(testData) + }) + + it("similaritiesResults should return", () => { + // Arrange + RouterData.commands = [] + AuthServiceData.isDown = false + const statements = ["my statement"] + const path = getBaseApi() + "/search/skills/similarities/results" + const testData: Array = [[createMockSkillSummary()]] + + // Act + // noinspection LocalVariableNamingConventionJS + const result$ = testService.similaritiesResults(statements) + + // Assert + result$ + .subscribe((data: Array) => { expect(data).toEqual(testData) expect(RouterData.commands).toEqual([ ]) // No errors expect(AuthServiceData.isDown).toEqual(false) diff --git a/ui/src/app/richskill/service/rich-skill.service.ts b/ui/src/app/richskill/service/rich-skill.service.ts index 795d0fce9..555e8a073 100644 --- a/ui/src/app/richskill/service/rich-skill.service.ts +++ b/ui/src/app/richskill/service/rich-skill.service.ts @@ -334,6 +334,16 @@ export class RichSkillService extends AbstractService { })) } + similaritiesResults(statements: string[]): Observable> { + return this.post<[ApiSkillSummary[]]>({ + path: "search/skills/similarities/results", + body: statements.map(statement => ({statement})) + }).pipe(share()) + .pipe(map(({body, headers}) => { + return body || [] + })) + } + similaritiesCheck(statements: string[]): Observable { return this.post({ path: "search/skills/similarities", diff --git a/ui/test/resource/mock-stubs.ts b/ui/test/resource/mock-stubs.ts index c86c2ec8a..01d2193cc 100644 --- a/ui/test/resource/mock-stubs.ts +++ b/ui/test/resource/mock-stubs.ts @@ -430,6 +430,10 @@ export class RichSkillServiceStub { return of(new ApiSkill(createMockSkill(now, now, PublishStatus.Draft))) } + similaritiesResults(statements: string[]): Observable> { + return of([[]]); + } + // noinspection JSUnusedGlobalSymbols,JSUnusedLocalSymbols similarityCheck(statement: string): Observable { const isoDate = new Date().toISOString()