Skip to content

Commit

Permalink
Crud admin screens named references (#421)
Browse files Browse the repository at this point in the history
* Add create named reference

* Edit metadata

* Improve create button

- Improve create metadata position and size in ui.

* Show name of edited metadata

* Load related skills

* Search related skills

* Load information in public view

* Fix broken ui tests

* Update API tests

- Add test to KeywordController.

- Update open api v3.

* Reindex related RSDs after update keyword

* Add test create-named-reference
  • Loading branch information
manuel-delvillar authored Jul 25, 2023
1 parent 28c6523 commit 63926d9
Show file tree
Hide file tree
Showing 42 changed files with 2,044 additions and 476 deletions.
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 @@ -43,6 +43,7 @@ object RoutePaths {
const val KEYWORD_DETAIL = "$KEYWORD_PATH/{id}"
const val KEYWORD_UPDATE = "$KEYWORD_DETAIL/update"
const val KEYWORD_REMOVE = "$KEYWORD_DETAIL/remove"
const val KEYWORD_SKILLS = "${KEYWORD_DETAIL}/skills"

//collections
private const val COLLECTIONS_PATH = "/collections"
Expand Down
145 changes: 143 additions & 2 deletions api/src/main/kotlin/edu/wgu/osmt/keyword/KeywordController.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
package edu.wgu.osmt.keyword

import edu.wgu.osmt.PaginationDefaults
import edu.wgu.osmt.RoutePaths
import edu.wgu.osmt.api.model.ApiFilteredSearch
import edu.wgu.osmt.api.model.ApiKeyword
import edu.wgu.osmt.api.model.ApiKeywordUpdate
import edu.wgu.osmt.api.model.ApiSearch
import edu.wgu.osmt.api.model.KeywordSortEnum
import edu.wgu.osmt.api.model.SkillSortEnum
import edu.wgu.osmt.api.model.SortOrder
import edu.wgu.osmt.config.AppConfig
import edu.wgu.osmt.db.PublishStatus
import edu.wgu.osmt.elasticsearch.OffsetPageable
import edu.wgu.osmt.elasticsearch.PaginatedLinks
import edu.wgu.osmt.richskill.RichSkillDoc
import edu.wgu.osmt.richskill.RichSkillEsRepo
import edu.wgu.osmt.security.OAuthHelper
import edu.wgu.osmt.task.RemoveItemTask
import edu.wgu.osmt.task.Task
import edu.wgu.osmt.task.TaskMessageService
Expand Down Expand Up @@ -41,6 +49,7 @@ class KeywordController @Autowired constructor(
val richSkillEsRepo: RichSkillEsRepo,
val taskMessageService: TaskMessageService,
val appConfig: AppConfig,
val oAuthHelper: OAuthHelper,
) {

@GetMapping(
Expand Down Expand Up @@ -112,7 +121,10 @@ class KeywordController @Autowired constructor(
return ResponseEntity
.status(HttpStatus.OK)
.body(keywordRepository.updateFromApi(
id ,apiKeywordUpdate)
id,
apiKeywordUpdate,
oAuthHelper.readableUserName(user)
)
?.let {
ApiKeyword(it.toModel(), it.skills.count())
}
Expand All @@ -126,7 +138,10 @@ class KeywordController @Autowired constructor(
@AuthenticationPrincipal user: Jwt?
): HttpEntity<TaskResult> {

val task = RemoveItemTask(identifier = id.toString())
val task = RemoveItemTask(
identifier = id.toString(),
apiResultPath = "${RoutePaths.API}${RoutePaths.API_V3}${RoutePaths.TASK_DETAIL_BATCH}"
)
taskMessageService.enqueueJob(TaskMessageService.removeKeyword, task)
return Task.processingResponse(task)
}
Expand All @@ -138,4 +153,130 @@ class KeywordController @Autowired constructor(
return found
}

@PostMapping(
path = ["${RoutePaths.API}${RoutePaths.API_V3}${RoutePaths.KEYWORD_SKILLS}"],
produces = [MediaType.APPLICATION_JSON_VALUE]
)
@ResponseBody
@PreAuthorize("isAuthenticated()")
fun searchKeywordSkills (
uriComponentsBuilder: UriComponentsBuilder,
@PathVariable id: Long,
@RequestParam(required = false, defaultValue = PaginationDefaults.size.toString()) size: Int,
@RequestParam(required = false, defaultValue = "0") from: Int,
@RequestParam(
required = false,
defaultValue = PublishStatus.DEFAULT_API_PUBLISH_STATUS_SET
) status: Array<String>,
@RequestParam(required = false) sort: String? = null,
@RequestBody(required = false) apiSearch: ApiSearch? = null,
@AuthenticationPrincipal user: Jwt? = null
): HttpEntity<List<RichSkillDoc>> {
val sortEnum = sort?.let{ SkillSortEnum.forApiValue(it)}

val keyword = keywordRepository.findById(id) ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)

return searchRelatedSkills(
uriComponentsBuilder = uriComponentsBuilder,
keyword = keyword,
size = size,
from = from,
statusFilters = status,
sort = sortEnum ?: SkillSortEnum.defaultSort,
apiSearch = apiSearch ?: ApiSearch(),
user = user
)
}

private fun searchRelatedSkills (
uriComponentsBuilder: UriComponentsBuilder,
keyword: KeywordDao,
size: Int,
from: Int,
statusFilters: Array<String>,
sort: SkillSortEnum,
apiSearch: ApiSearch,
user: Jwt?
): HttpEntity<List<RichSkillDoc>> {

val pageable = OffsetPageable(offset = from, limit = size, sort = sort.sort)
val statuses = statusFilters.mapNotNull { PublishStatus.forApiValue(it) }.toMutableSet()

if (user == null) {
statuses.remove(PublishStatus.Deleted)
statuses.remove(PublishStatus.Draft)
}

var filterAlignments = apiSearch.filtered?.alignments
var filterAuthors = apiSearch.filtered?.authors
var filterCategories = apiSearch.filtered?.categories
var filterCertifications = apiSearch.filtered?.certifications
var filterEmployers = apiSearch.filtered?.employers
var filterKeywords = apiSearch.filtered?.keywords
var filterStandards = apiSearch.filtered?.standards

keyword.value?.let {
when(keyword.type) {
KeywordTypeEnum.Alignment -> {
filterAlignments = filterAlignments?.plus(listOf(it)) ?: listOf(it)
}
KeywordTypeEnum.Author -> {
filterAuthors = filterAuthors?.plus(listOf(it)) ?: listOf(it)
}
KeywordTypeEnum.Category -> {
filterCategories = filterCategories?.plus(listOf(it)) ?: listOf(it)
}
KeywordTypeEnum.Certification -> {
filterCertifications = filterCertifications?.plus(listOf(it)) ?: listOf(it)
}
KeywordTypeEnum.Employer -> {
filterEmployers = filterEmployers?.plus(listOf(it)) ?: listOf(it)
}
KeywordTypeEnum.Keyword -> {
filterKeywords = filterKeywords?.plus(listOf(it)) ?: listOf(it)
}
KeywordTypeEnum.Standard -> {
filterStandards = filterStandards?.plus(listOf(it)) ?: listOf(it)
}
}
}

val search = ApiSearch (
query = apiSearch.query,
advanced = apiSearch.advanced,
uuids = apiSearch.uuids,
filtered = ApiFilteredSearch(
alignments = filterAlignments,
authors = filterAuthors,
categories = filterCategories,
certifications = filterCertifications,
jobCodes = apiSearch.filtered?.jobCodes,
keywords = filterKeywords,
standards = filterStandards,
)
)

val countByApiSearch = richSkillEsRepo.countByApiSearch(search, statuses, pageable)
val searchHits = richSkillEsRepo.byApiSearch(search, statuses, pageable)

val responseHeaders = HttpHeaders()
responseHeaders.add("X-Total-Count", countByApiSearch.toString())

uriComponentsBuilder
.path(RoutePaths.SEARCH_SKILLS)
.queryParam(RoutePaths.QueryParams.FROM, from)
.queryParam(RoutePaths.QueryParams.SIZE, size)
.queryParam(RoutePaths.QueryParams.SORT, sort)
.queryParam(RoutePaths.QueryParams.STATUS, statusFilters.joinToString(",").lowercase())

PaginatedLinks(
pageable,
searchHits.totalHits.toInt(),
uriComponentsBuilder
).addToHeaders(responseHeaders)

return ResponseEntity.status(200).headers(responseHeaders)
.body(searchHits.map { it.content }.toList())
}

}
31 changes: 25 additions & 6 deletions api/src/main/kotlin/edu/wgu/osmt/keyword/KeywordRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import edu.wgu.osmt.api.model.ApiBatchResult
import edu.wgu.osmt.api.model.ApiKeywordUpdate
import edu.wgu.osmt.config.AppConfig
import edu.wgu.osmt.richskill.RichSkillKeywordRepository
import edu.wgu.osmt.richskill.RichSkillKeywords
import edu.wgu.osmt.richskill.RichSkillRepository
import edu.wgu.osmt.richskill.RsdUpdateObject
import org.jetbrains.exposed.sql.SizedIterable
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.and
Expand All @@ -17,8 +20,11 @@ import java.time.LocalDateTime
import java.time.ZoneOffset

interface KeywordRepository {

val table: KeywordTable
val richSkillKeywords: RichSkillKeywords
val dao: KeywordDao.Companion
val richSkillRepository: RichSkillRepository

fun findAll(): SizedIterable<KeywordDao>
fun findById(id: Long): KeywordDao?
Expand All @@ -44,7 +50,7 @@ interface KeywordRepository {
framework: String? = null
): KeywordDao?

fun updateFromApi(existingKeywordId: Long, apiKeywordUpdate: ApiKeywordUpdate): KeywordDao?
fun updateFromApi(existingKeywordId: Long, apiKeywordUpdate: ApiKeywordUpdate, username: String): KeywordDao?

fun createFromApi(apiKeywordUpdate: ApiKeywordUpdate): KeywordDao?

Expand All @@ -58,11 +64,13 @@ interface KeywordRepository {
class KeywordRepositoryImpl @Autowired constructor(
val appConfig: AppConfig,
val keywordEsRepo: KeywordEsRepo,
val richSkillKeywordRepository: RichSkillKeywordRepository
val richSkillKeywordRepository: RichSkillKeywordRepository,
override val richSkillRepository: RichSkillRepository
) : KeywordRepository {

override val dao = KeywordDao.Companion
override val table = KeywordTable
override val richSkillKeywords = RichSkillKeywords

override fun findAll() = dao.all()

Expand Down Expand Up @@ -101,7 +109,7 @@ class KeywordRepositoryImpl @Autowired constructor(
}.also { keywordEsRepo.save(it.toModel()) } else null
}

override fun updateFromApi(existingKeywordId: Long, apiKeywordUpdate: ApiKeywordUpdate): KeywordDao? {
override fun updateFromApi(existingKeywordId: Long, apiKeywordUpdate: ApiKeywordUpdate, username: String): KeywordDao? {
val found = dao.findById(existingKeywordId)
if (found!=null) {
transaction {
Expand All @@ -111,10 +119,21 @@ class KeywordRepositoryImpl @Autowired constructor(
found.framework = apiKeywordUpdate.framework
found.type = apiKeywordUpdate.type
keywordEsRepo.save(found.toModel())
}.also { return found }
} else {
return null

// update rich skill after values changes in keyword and reindex
richSkillKeywords.select { richSkillKeywords.keywordId eq found.id }.forEach { it ->
val richSkillId = it[richSkillKeywords.richSkillId]
// richSkillRepository.findById(richSkillId.value)?.keywords?.forEach { it2 -> println(it2.value) }
richSkillRepository.update(
RsdUpdateObject(
id = richSkillId.value
),
username
)
}
}
}
return found
}

override fun createFromApi(apiKeywordUpdate: ApiKeywordUpdate): KeywordDao? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class KeywordTaskProcessor {
deadLetterQueue = TaskMessageService.deadLetters,
concurrency = "1"
)
fun removeJobCode(task: RemoveItemTask) {
fun removeKeyword(task: RemoveItemTask) {
logger.info("Started processing to remove keyword task id: ${task.identifier}")

val batchResult = keywordRepository.remove(task.identifier.toLong())
Expand All @@ -41,4 +41,4 @@ class KeywordTaskProcessor {

logger.info("Task ${task.uuid} completed")
}
}
}
3 changes: 1 addition & 2 deletions api/src/main/kotlin/edu/wgu/osmt/task/Task.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,8 @@ import java.util.*
JsonSubTypes.Type(value = ExportSkillsToXlsxTask::class, name = "ExportSkillsToXlsxTask"),
JsonSubTypes.Type(value = RemoveCollectionSkillsTask::class, name = "RemoveCollectionSkillsTask"),
JsonSubTypes.Type(value = RemoveJobCodeTask::class, name = "RemoveJobCodeTask"),
JsonSubTypes.Type(value = RemoveCollectionSkillsTask::class, name = "RemoveCollectionSkillsTask"),
JsonSubTypes.Type(value = ExportSkillsToCsvTaskV2::class, name = "ExportSkillsToCsvTaskV2"),
JsonSubTypes.Type(value = RemoveItemTask::class, name = "RemoveCollectionSkillsTask"),
JsonSubTypes.Type(value = RemoveItemTask::class, name = "RemoveItemTask"),
JsonSubTypes.Type(value = RemoveJobCodeTask::class, name = "RemoveJobCodeTask")
)

Expand Down
Loading

0 comments on commit 63926d9

Please sign in to comment.