Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ class CollectionController @Autowired constructor(
fun getSkillsForCollectionCsv(
@PathVariable uuid: String
): HttpEntity<TaskResult> {
if (collectionRepository.findByUUID(uuid)!!.publishStatus() == PublishStatus.Draft && !oAuthHelper.hasRole(appConfig.roleAdmin)) {
throw ResponseStatusException(HttpStatus.UNAUTHORIZED)
}
val task = CsvTask(collectionUuid = uuid)
taskMessageService.enqueueJob(TaskMessageService.skillsForCollectionCsv, task)
return Task.processingResponse(task)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package edu.wgu.osmt.collection

import com.github.sonus21.rqueue.annotation.RqueueListener
import edu.wgu.osmt.config.AppConfig
import edu.wgu.osmt.db.PublishStatus
import edu.wgu.osmt.richskill.RichSkillAndCollections
import edu.wgu.osmt.richskill.RichSkillCsvExport
import edu.wgu.osmt.richskill.RichSkillDescriptorDao
Expand Down Expand Up @@ -46,6 +47,7 @@ class CsvTaskProcessor {

val csv = collectionRepository.findByUUID(csvTask.collectionUuid)
?.skills
?.filter { PublishStatus.Archived != it.publishStatus() }
?.with(RichSkillDescriptorDao::collections)
?.map { RichSkillAndCollections.fromDao(it) }
?.let { RichSkillCsvExport(appConfig).toCsv(it) }
Expand Down
33 changes: 31 additions & 2 deletions ui/src/app/collection/detail/manage-collection.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import { of } from "rxjs"
import {
createMockCollection,
createMockPaginatedSkills,
createMockSkillSummary
createMockSkillSummary,
csvContent
} from "../../../../test/resource/mock-data"
import {
AuthServiceStub,
Expand All @@ -30,6 +31,7 @@ import { ApiCollection } from "../ApiCollection"
import { CollectionService } from "../service/collection.service"
import { ManageCollectionComponent } from "./manage-collection.component"
import {AuthService} from "../../auth/auth-service";
import * as FileSaver from "file-saver"


@Component({
Expand Down Expand Up @@ -276,7 +278,7 @@ describe("ManageCollectionComponent", () => {

// Assert
expect(actions).toBeTruthy()
expect(actions.length).toEqual(4)
expect(actions.length).toEqual(5)

let action = actions[0]
expect(action.label).toEqual("Add RSDs to This Collection")
Expand Down Expand Up @@ -490,4 +492,31 @@ describe("ManageCollectionComponent", () => {
expect(component.showingMultipleConfirm).toBeFalsy()
expect(component.apiSearch).toBeFalsy()
})

it("generateCsv should call getCsv and loader", () => {
const spyCollectionService = spyOn(component["collectionService"], "requestCollectionSkillsCsv").and.callThrough()
const spyLoaderSubject = spyOn(component["toastService"].loaderSubject, "next")
component.generateCsv("My collection")
expect(spyCollectionService).toHaveBeenCalled()
expect(spyLoaderSubject).toHaveBeenCalledWith(true)
})


it("getCsv should call getCsvTaskResultsIfComplete", () => {
const collection = {
uuid: "fc0a65a6-facd-4f9d-b590-cfecbfe706ad",
name: "My Collection"
}
const spyCollectionService = spyOn(component["collectionService"], "getCsvTaskResultsIfComplete").and.returnValue(of(csvContent))
const spySaveCsv = spyOn(component, "saveCsv")
component.getCsv(collection.uuid, collection.name)
expect(spyCollectionService).toHaveBeenCalledWith(collection.uuid)
expect(spySaveCsv).toHaveBeenCalledWith(csvContent.body, collection.name)
})

it("saveCSV should call FileSaver", () => {
const spySaveAS = spyOn(FileSaver, "saveAs")
component.saveCsv(csvContent.body, "My Collection")
expect(spySaveAS).toHaveBeenCalled()
})
})
51 changes: 49 additions & 2 deletions ui/src/app/collection/detail/manage-collection.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Component, OnInit, ViewChild} from "@angular/core"
import {Component, Inject, LOCALE_ID, OnInit, ViewChild} from "@angular/core"
import {ApiCollection, ApiCollectionUpdate} from "../ApiCollection"
import {ApiSearch, ApiSkillListUpdate} from "../../richskill/service/rich-skill-search.service"
import {ActivatedRoute, Router} from "@angular/router"
Expand All @@ -11,11 +11,15 @@ import {SvgHelper, SvgIcon} from "../../core/SvgHelper"
import {TableActionDefinition} from "../../table/skills-library-table/has-action-definitions"
import {determineFilters, PublishStatus} from "../../PublishStatus"
import {ApiSkillSummary} from "../../richskill/ApiSkillSummary"
import {Observable, Subject} from "rxjs"
import {Observable, of, Subject, throwError} from "rxjs"
import {TableActionBarComponent} from "../../table/skills-library-table/table-action-bar.component"
import {Title} from "@angular/platform-browser";
import {AuthService} from "../../auth/auth-service";
import {ButtonAction} from "../../auth/auth-roles";
import {formatDate} from "@angular/common"
import * as FileSaver from "file-saver"
import {ITaskResult} from "../../task/ApiTaskResult"
import {delay, retryWhen, switchMap} from "rxjs/operators"

@Component({
selector: "app-manage-collection",
Expand All @@ -30,6 +34,7 @@ export class ManageCollectionComponent extends SkillsListComponent implements On

editIcon = SvgHelper.path(SvgIcon.EDIT)
publishIcon = SvgHelper.path(SvgIcon.PUBLISH)
downloadIcon = SvgHelper.path(SvgIcon.DOWNLOAD)
archiveIcon = SvgHelper.path(SvgIcon.ARCHIVE)
unarchiveIcon = SvgHelper.path(SvgIcon.UNARCHIVE)
addIcon = SvgHelper.path(SvgIcon.ADD)
Expand Down Expand Up @@ -60,6 +65,7 @@ export class ManageCollectionComponent extends SkillsListComponent implements On
protected route: ActivatedRoute,
protected titleService: Title,
protected authService: AuthService,
@Inject(LOCALE_ID) protected locale: string
) {
super(router, richSkillService, toastService, authService)
}
Expand Down Expand Up @@ -136,6 +142,38 @@ export class ManageCollectionComponent extends SkillsListComponent implements On
return false
}

generateCsv(collectionName: string): void {
this.collectionService.requestCollectionSkillsCsv(this.uuidParam ?? "")
.subscribe((taskStarted: ITaskResult) => {
this.toastService.loaderSubject.next(true)
this.getCsv(taskStarted.uuid ?? "", collectionName)
})
}

getCsv(uuid: string, collectionName: string): void {
this.collectionService.getCsvTaskResultsIfComplete(uuid)
.pipe(
retryWhen(errors => errors.pipe(
switchMap((error) => {
if (error.status === 404) {
return of(error.status)
}
return throwError(error)
}),
delay(1000),
)))
.subscribe(response => {
this.saveCsv(response.body, collectionName)
})
}

saveCsv(body: string, collectionName: string): void {
const blob = new Blob([body], {type: "text/csv;charset=utf-8;"})
const date = formatDate(new Date(), "yyyy-MM-dd", this.locale)
FileSaver.saveAs(blob, `RSD Skills - ${collectionName} - ${date}.csv`)
this.toastService.loaderSubject.next(false)
}

actionDefinitions(): TableActionDefinition[] {
const actions = [
new TableActionDefinition({
Expand Down Expand Up @@ -187,6 +225,15 @@ export class ManageCollectionComponent extends SkillsListComponent implements On
})
)
}

if ((this.collection?.status === PublishStatus.Draft || this.collection?.status === PublishStatus.Published) && this.authService.isEnabledByRoles(ButtonAction.LibraryExport)) {
Comment thread
manuel-delvillar marked this conversation as resolved.
actions.push(new TableActionDefinition({
label: "Download CSV",
icon: this.downloadIcon,
callback: () => this.generateCsv(this.collection?.name ?? ""),
visible: () => true
}))
}
return actions
}

Expand Down
2 changes: 2 additions & 0 deletions ui/test/resource/mock-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,3 +292,5 @@ export const mockTaskResultForExportSearch: ApiTaskResult = {
id: "/api/results/batch/77574cd6-933b-4ee0-a106-afadb7a3a292"
}

export const csvContent = {body: "value1,value2,value3"}