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
23 changes: 17 additions & 6 deletions api/src/main/kotlin/edu/wgu/osmt/richskill/RichSkillController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import edu.wgu.osmt.task.TaskResult
import edu.wgu.osmt.task.XlsxTask
import org.apache.commons.lang3.StringUtils
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.domain.Pageable
import org.springframework.http.HttpEntity
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
Expand Down Expand Up @@ -296,14 +297,20 @@ class RichSkillController @Autowired constructor(
@PostMapping(RoutePaths.EXPORT_SKILLS_CSV, produces = [MediaType.APPLICATION_JSON_VALUE])
@ResponseBody
fun exportCustomListCsv(
@RequestBody uuids: List<String>?,
@RequestBody apiSearch: ApiSearch,
status: Array<String>,
@AuthenticationPrincipal user: Jwt?
): HttpEntity<TaskResult> {
if (!appConfig.allowPublicSearching && user === null) {
throw GeneralApiException("Unauthorized", HttpStatus.UNAUTHORIZED)
}

val task = ExportSkillsToCsvTask(collectionUuid = "CustomList", uuids)
val publishStatuses = status.mapNotNull {
val status = PublishStatus.forApiValue(it)
if (user == null && (status == PublishStatus.Deleted || status == PublishStatus.Draft)) null else status
}.toSet()
val task = ExportSkillsToCsvTask(
collectionUuid = "CustomList", richSkillEsRepo.getUuidsFromApiSearch(apiSearch, publishStatuses, Pageable.unpaged(), user, StringUtils.EMPTY)
)
taskMessageService.enqueueJob(TaskMessageService.skillsForCustomListExportCsv, task)

return Task.processingResponse(task)
Expand All @@ -313,14 +320,18 @@ class RichSkillController @Autowired constructor(
@PostMapping(RoutePaths.EXPORT_SKILLS_XLSX, produces = [MediaType.APPLICATION_JSON_VALUE])
@ResponseBody
fun exportCustomListXlsx(
@RequestBody uuids: List<String>?,
@RequestBody apiSearch: ApiSearch,
status: Array<String>,
@AuthenticationPrincipal user: Jwt?
): HttpEntity<TaskResult> {
if (!appConfig.allowPublicSearching && user === null) {
throw GeneralApiException("Unauthorized", HttpStatus.UNAUTHORIZED)
}

val task = ExportSkillsToXlsxTask(collectionUuid = "CustomList", uuids)
val publishStatuses = status.mapNotNull {
val status = PublishStatus.forApiValue(it)
if (user == null && (status == PublishStatus.Deleted || status == PublishStatus.Draft)) null else status
}.toSet()
val task = ExportSkillsToXlsxTask(collectionUuid = "CustomList", richSkillEsRepo.getUuidsFromApiSearch(apiSearch, publishStatuses, Pageable.unpaged(), user, StringUtils.EMPTY))
taskMessageService.enqueueJob(TaskMessageService.skillsForCustomListExportXlsx, task)

return Task.processingResponse(task)
Expand Down
33 changes: 33 additions & 0 deletions api/src/main/kotlin/edu/wgu/osmt/richskill/RichSkillEsRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import edu.wgu.osmt.elasticsearch.FindsAllByPublishStatus
import edu.wgu.osmt.elasticsearch.OffsetPageable
import edu.wgu.osmt.jobcode.JobCodeQueries
import edu.wgu.osmt.nullIfEmpty
import org.apache.commons.lang3.StringUtils
import org.apache.lucene.search.join.ScoreMode
import org.elasticsearch.index.query.*
import org.elasticsearch.index.query.QueryBuilders.*
Expand All @@ -28,10 +29,18 @@ import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories
import org.springframework.security.oauth2.jwt.Jwt

const val collectionsUuid = "collections.uuid"

interface CustomRichSkillQueries : FindsAllByPublishStatus<RichSkillDoc> {
fun getUuidsFromApiSearch(
apiSearch: ApiSearch,
publishStatus: Set<PublishStatus>,
pageable: Pageable = Pageable.unpaged(),
user: Jwt?,
collectionId: String? = null
): List<String>
fun generateBoolQueriesFromApiSearch(bq: BoolQueryBuilder, advancedQuery: ApiAdvancedSearch)
fun generateBoolQueriesFromApiSearchWithFilters(bq: BoolQueryBuilder, filteredQuery: ApiFilteredSearch, publishStatus: Set<PublishStatus>)
fun richSkillPropertiesMultiMatch(query: String): BoolQueryBuilder
Expand Down Expand Up @@ -62,6 +71,30 @@ class CustomRichSkillQueriesImpl @Autowired constructor(override val elasticSear
CustomRichSkillQueries {
override val javaClass = RichSkillDoc::class.java

override fun getUuidsFromApiSearch(
Comment thread
jchavez137 marked this conversation as resolved.
apiSearch: ApiSearch,
publishStatus: Set<PublishStatus>,
pageable: Pageable,
user: Jwt?,
collectionId: String?
): List<String> {

val uuids = if (apiSearch.uuids != null)
{
apiSearch.uuids
}
else {
val searchHits = byApiSearch(
apiSearch,
publishStatus,
Pageable.unpaged(),
StringUtils.EMPTY
)
searchHits.mapNotNull { it.id }
}
return uuids
}

override fun occupationQueries(query: String): NestedQueryBuilder {
val jobCodePath = RichSkillDoc::jobCodes.name
return QueryBuilders.nestedQuery(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import org.assertj.core.api.Assertions
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.transaction.annotation.Transactional

@Transactional
Expand Down Expand Up @@ -131,4 +132,5 @@ class CollectionEsRepoTest @Autowired constructor(
assertThat(result.first().uuid).isEqualTo(richskill1.uuid)
assertThat(result.size).isEqualTo(1)
}

}
18 changes: 18 additions & 0 deletions api/src/test/kotlin/edu/wgu/osmt/richskill/RichSkillEsRepoTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import edu.wgu.osmt.collection.CollectionDoc
import edu.wgu.osmt.collection.CollectionEsRepo
import edu.wgu.osmt.io.csv.BatchImportRichSkill
import edu.wgu.osmt.db.ListFieldUpdate
import edu.wgu.osmt.db.PublishStatus
import edu.wgu.osmt.jobcode.JobCodeEsRepo
import edu.wgu.osmt.keyword.KeywordEsRepo
import edu.wgu.osmt.keyword.KeywordRepository
Expand All @@ -22,6 +23,7 @@ import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.domain.Pageable
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.transaction.annotation.Transactional
import java.util.*

Expand Down Expand Up @@ -61,6 +63,7 @@ class RichSkillEsRepoTest @Autowired constructor(
) : SpringTest(), HasDatabaseReset, HasElasticsearchReset, QuotedSearchHelpers {

val authorString = "unit-test-author"
val nullJwt : Jwt? = null

@Test
fun `Should insert a rich skill into elastic search`() {
Expand Down Expand Up @@ -1244,4 +1247,19 @@ class RichSkillEsRepoTest @Autowired constructor(
assertThat(skillResult.contains(skill2))

}

@Test
fun `Should return an array of uuids without using query`() {
val uuids: List<String> = listOf("24234-abcff-342")
val uuidsFromApiSearch = richSkillEsRepo.getUuidsFromApiSearch(ApiSearch(uuids = uuids), arrayOf(PublishStatus.Published).toSet(), Pageable.unpaged(), nullJwt)
assertThat(uuidsFromApiSearch.size).isGreaterThan(0)
}

@Test
fun `Should return an array of uuids using query`() {
val richSkill = TestObjectHelpers.randomRichSkillDoc().copy(name = "RSD to export by query", collections = listOf(), jobCodes = listOf(), publishStatus = PublishStatus.Published)
richSkillEsRepo.save(richSkill)
val uuids = richSkillEsRepo.getUuidsFromApiSearch(ApiSearch(query = "export"), arrayOf(PublishStatus.Draft,PublishStatus.Published).toSet(), Pageable.unpaged(), nullJwt)
assertThat(uuids.size).isGreaterThan(0)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import edu.wgu.osmt.collection.CollectionEsRepo
import edu.wgu.osmt.collection.CollectionRepository
import edu.wgu.osmt.collection.CollectionUpdateObject
import edu.wgu.osmt.db.ListFieldUpdate
import edu.wgu.osmt.db.NullableFieldUpdate
import edu.wgu.osmt.db.PublishStatus
import edu.wgu.osmt.jobcode.JobCode
import edu.wgu.osmt.jobcode.JobCodeEsRepo
Expand Down
58 changes: 58 additions & 0 deletions docs/int/osmt-v2.x-openapi3.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -993,6 +993,64 @@ paths:
items:
$ref: '#/components/schemas/NamedReference'

/api/export/skills/csv:
post:
tags:
- Export selected skills
summary: Create a task result to generate a CSV of selected skills
parameters:
- in: query
name: status
schema:
default:
- Draft
- Published
type: array
items:
$ref: '#/components/schemas/PublishStatus'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Search'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/TaskResult'

/api/export/skills/xlsx:
post:
tags:
- Export selected skills
summary: Create a task result to generate a XLSX of selected skills
parameters:
- in: query
name: status
schema:
default:
- Draft
- Published
type: array
items:
$ref: '#/components/schemas/PublishStatus'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Search'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/TaskResult'

components:
securitySchemes:
bearerAuth:
Expand Down
4 changes: 2 additions & 2 deletions ui/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
],
"styles": [
"node_modules/@concentricsky/wgu-design-system-patternlibrary/dist/css/screen.css",
"src/styles.css"
"src/styles.scss"
],
"scripts": []
},
Expand Down Expand Up @@ -132,7 +132,7 @@
"src/assets"
],
"styles": [
"src/styles.css"
"src/styles.scss"
],
"scripts": [],
"codeCoverage": true
Expand Down
4 changes: 3 additions & 1 deletion ui/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ import { ExportRsdComponent } from "./export/export-rsd.component"
import { OsmtFormModule } from "./form/osmt-form.module"
import { ConvertToCollectionComponent } from "./my-workspace/convert-to-collection/convert-to-collection.component"
import { SizePaginationComponent } from "./table/skills-library-table/size-pagination/size-pagination.component"
import {OsmtTableModule} from "./table/osmt-table.module"

export function initializeApp(
appConfig: AppConfig,
Expand Down Expand Up @@ -219,7 +220,8 @@ export function initializeApp(
SharedModule,
OsmtCoreModule,
OsmtFormModule,
FormsModule
FormsModule,
OsmtTableModule
],
providers: [
EnvironmentService,
Expand Down
28 changes: 10 additions & 18 deletions ui/src/app/collection/collection-table.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,24 +52,16 @@
</td>

<td class="m-tableHeader-x-actions" aria-hidden="true">
<div class="m-tableLabel" *ngIf="selectAllEnabled">
<label class="m-tableLabel-x-text" for="checkbox">Select ({{getSelectAllCount()}})</label>

<div class="m-tableLabel-x-control">
<div class="m-checkbox">
<input type="checkbox" id="checkbox" name="checkbox" (change)="handleSelectAll($event)">
<div class="m-checkbox-x-icon">
<svg class="t-icon" aria-hidden="true">
<use [attr.xlink:href]="checkIcon"></use>
</svg>
</div>
</div>
</div>
<nav class="m-quickLinks" aria-labelledby="save-quicklinks">
<h3 class="t-visuallyHidden" id="save-quicklinks">Quick Links</h3>
<a class="t-visuallyHidden" (click)="focusActionBar.emit()">Actions menu.</a>
</nav>
</div>
<app-select-all
[selectAllEnabled]="selectAllEnabled"
(valueChange)="handleSelectAll($event)"
[totalCount]="getSelectAllCount()"
[totalPageCount]="items.length">
</app-select-all>
<!--nav class="m-quickLinks" aria-labelledby="save-quicklinks">
<h3 class="t-visuallyHidden" id="save-quicklinks">Quick Links</h3>
<a class="t-visuallyHidden" (click)="focusActionBar.emit()">Ations menu.</a>
</nav-->
</td>

</tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ export class ManageCollectionComponent extends SkillsListComponent implements On
getApiSearch(skill?: ApiSkillSummary): ApiSearch | undefined {
if (this.selectAllChecked) {
return this.searchQuery
? new ApiSearch({query: this.searchQuery})
? new ApiSearch({query: this.searchQuery, filtered: {}})
: new ApiSearch({uuids: this.collection?.skills.map((i: any) => i.uuid)})
} else {
return super.getApiSearch(skill)
Expand Down
7 changes: 5 additions & 2 deletions ui/src/app/export/export-rsd-component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"
import { ExportRsdComponent } from "./export-rsd.component"
import { RichSkillService } from "../richskill/service/rich-skill.service"
import { RichSkillServiceStub } from "../../../test/resource/mock-stubs"
import {PublishStatus} from "../PublishStatus"
import {ApiSearch} from "../richskill/service/rich-skill-search.service"

describe("ExportRsdComponent", () => {
let component: ExportRsdComponent
let fixture: ComponentFixture<ExportRsdComponent>
let richSkillService: RichSkillService
const statuses = new Set<PublishStatus>([PublishStatus.Published, PublishStatus.Draft])

beforeEach(async () => {
TestBed.configureTestingModule({
Expand Down Expand Up @@ -60,7 +63,7 @@ describe("ExportRsdComponent", () => {
const entityName = ["RSD name"]
const spyLoader = spyOn(component["toastService"], "showBlockingLoader")
const spyService = spyOn(richSkillService, "exportSearchCsv").and.callThrough()
component.exportSearchCsv(uuids, entityName)
component.exportSearchCsv(new ApiSearch({uuids}), entityName, statuses)
expect(spyLoader)
expect(spyService).toHaveBeenCalled()
})
Expand All @@ -70,7 +73,7 @@ describe("ExportRsdComponent", () => {
const entityName = ["RSD name"]
const spyLoader = spyOn(component["toastService"], "showBlockingLoader")
const spyService = spyOn(richSkillService, "exportSearchXlsx").and.callThrough()
component.exportSearchXlsx(uuids, entityName)
component.exportSearchXlsx(new ApiSearch({uuids}), entityName, statuses)
expect(spyLoader)
expect(spyService).toHaveBeenCalled()
})
Expand Down
Loading