From 85d1166601eaee096d20bb1319b190599412e35f Mon Sep 17 00:00:00 2001
From: David Holub <74815577+Corpratespaz@users.noreply.github.com>
Date: Wed, 1 Mar 2023 21:23:09 +0700
Subject: [PATCH 1/5] OSMT-25: Initial package refactor and abstraction for CSV
and Excel exports
OSMT-25: Additional package refactor to nest io
OSMT-25: Further abstraction of CSV resource
OSMT-25: Added endpoints and backend logic
for Excel export
OSMT-25: Updated endpoint security conf & OpenAPI
---
api/pom.xml | 10 +++
.../edu/wgu/osmt/ImportCommandRunner.kt | 6 +-
.../main/kotlin/edu/wgu/osmt/RoutePaths.kt | 2 +
.../edu/wgu/osmt/auditlog/AuditLogUtils.kt | 2 +-
.../osmt/collection/CollectionController.kt | 13 ++++
.../edu/wgu/osmt/io/common/TabularResource.kt | 12 +++
.../edu/wgu/osmt/io/common/TabularTask.kt | 26 +++++++
.../osmt/{ => io}/csv/BatchImportRichSkill.kt | 2 +-
.../edu/wgu/osmt/{ => io}/csv/BlsImport.kt | 3 +-
.../edu/wgu/osmt/{ => io}/csv/CsvBuilder.kt | 2 +-
.../edu/wgu/osmt/{ => io}/csv/CsvImport.kt | 2 +-
.../edu/wgu/osmt/{ => io}/csv/CsvResource.kt | 22 +++---
.../csv}/CsvTaskProcessor.kt | 39 +++-------
.../csv}/ExportSkillToCsvTaskProcessor.kt | 4 +-
.../edu/wgu/osmt/{ => io}/csv/OnetImport.kt | 2 +-
.../csv}/RichSkillCsvExport.kt | 5 +-
.../wgu/osmt/io/xlsx/RichSkillXlsxExport.kt | 52 +++++++++++++
.../edu/wgu/osmt/io/xlsx/XlsxResource.kt | 74 +++++++++++++++++++
.../edu/wgu/osmt/io/xlsx/XlsxTaskProcessor.kt | 65 ++++++++++++++++
.../wgu/osmt/richskill/RichSkillController.kt | 2 +
.../edu/wgu/osmt/security/SecurityConfig.kt | 4 +
api/src/main/kotlin/edu/wgu/osmt/task/Task.kt | 11 +++
.../edu/wgu/osmt/task/TaskController.kt | 6 ++
.../edu/wgu/osmt/task/TaskMessageService.kt | 2 +
.../{ => io}/csv/BatchImportRichSkillTest.kt | 2 +-
.../wgu/osmt/{ => io}/csv/BlsImportTest.kt | 2 +-
.../wgu/osmt/{ => io}/csv/CsvResourceTest.kt | 8 +-
.../wgu/osmt/{ => io}/csv/OnetImportTest.kt | 2 +-
.../kotlin/edu/wgu/osmt/mockdata/MockData.kt | 6 +-
.../osmt/richskill/RichSkillControllerTest.kt | 4 +-
.../wgu/osmt/richskill/RichSkillEsRepoTest.kt | 2 +-
docs/int/osmt-v1.0.x-openapi3.yaml | 26 +++++++
docs/int/osmt-v2.x-openapi3.yaml | 26 +++++++
33 files changed, 382 insertions(+), 64 deletions(-)
create mode 100644 api/src/main/kotlin/edu/wgu/osmt/io/common/TabularResource.kt
create mode 100644 api/src/main/kotlin/edu/wgu/osmt/io/common/TabularTask.kt
rename api/src/main/kotlin/edu/wgu/osmt/{ => io}/csv/BatchImportRichSkill.kt (99%)
rename api/src/main/kotlin/edu/wgu/osmt/{ => io}/csv/BlsImport.kt (98%)
rename api/src/main/kotlin/edu/wgu/osmt/{ => io}/csv/CsvBuilder.kt (98%)
rename api/src/main/kotlin/edu/wgu/osmt/{ => io}/csv/CsvImport.kt (98%)
rename api/src/main/kotlin/edu/wgu/osmt/{ => io}/csv/CsvResource.kt (82%)
rename api/src/main/kotlin/edu/wgu/osmt/{collection => io/csv}/CsvTaskProcessor.kt (61%)
rename api/src/main/kotlin/edu/wgu/osmt/{richskill => io/csv}/ExportSkillToCsvTaskProcessor.kt (92%)
rename api/src/main/kotlin/edu/wgu/osmt/{ => io}/csv/OnetImport.kt (99%)
rename api/src/main/kotlin/edu/wgu/osmt/{richskill => io/csv}/RichSkillCsvExport.kt (96%)
create mode 100644 api/src/main/kotlin/edu/wgu/osmt/io/xlsx/RichSkillXlsxExport.kt
create mode 100644 api/src/main/kotlin/edu/wgu/osmt/io/xlsx/XlsxResource.kt
create mode 100644 api/src/main/kotlin/edu/wgu/osmt/io/xlsx/XlsxTaskProcessor.kt
rename api/src/test/kotlin/edu/wgu/osmt/{ => io}/csv/BatchImportRichSkillTest.kt (98%)
rename api/src/test/kotlin/edu/wgu/osmt/{ => io}/csv/BlsImportTest.kt (98%)
rename api/src/test/kotlin/edu/wgu/osmt/{ => io}/csv/CsvResourceTest.kt (96%)
rename api/src/test/kotlin/edu/wgu/osmt/{ => io}/csv/OnetImportTest.kt (98%)
diff --git a/api/pom.xml b/api/pom.xml
index 880252993..b1d60eb56 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -250,6 +250,16 @@
opencsv
5.7.1
+
+ org.apache.poi
+ poi
+ 5.2.3
+
+
+ org.apache.poi
+ poi-ooxml
+ 5.2.3
+
org.springframework.boot
spring-boot-starter-actuator
diff --git a/api/src/main/kotlin/edu/wgu/osmt/ImportCommandRunner.kt b/api/src/main/kotlin/edu/wgu/osmt/ImportCommandRunner.kt
index a09095086..ccec455bb 100644
--- a/api/src/main/kotlin/edu/wgu/osmt/ImportCommandRunner.kt
+++ b/api/src/main/kotlin/edu/wgu/osmt/ImportCommandRunner.kt
@@ -1,8 +1,8 @@
package edu.wgu.osmt
-import edu.wgu.osmt.csv.BatchImportRichSkill
-import edu.wgu.osmt.csv.BlsImport
-import edu.wgu.osmt.csv.OnetImport
+import edu.wgu.osmt.io.csv.BatchImportRichSkill
+import edu.wgu.osmt.io.csv.BlsImport
+import edu.wgu.osmt.io.csv.OnetImport
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
diff --git a/api/src/main/kotlin/edu/wgu/osmt/RoutePaths.kt b/api/src/main/kotlin/edu/wgu/osmt/RoutePaths.kt
index ae0088a78..01cee4c59 100644
--- a/api/src/main/kotlin/edu/wgu/osmt/RoutePaths.kt
+++ b/api/src/main/kotlin/edu/wgu/osmt/RoutePaths.kt
@@ -31,12 +31,14 @@ object RoutePaths {
const val COLLECTION_SKILLS = "$COLLECTION_DETAIL/skills"
const val COLLECTION_AUDIT_LOG = "$COLLECTION_DETAIL/log"
const val COLLECTION_CSV = "$COLLECTION_DETAIL/csv"
+ const val COLLECTION_XLSX = "$COLLECTION_DETAIL/xlsx"
const val COLLECTION_REMOVE = "$COLLECTION_DETAIL/remove"
const val WORKSPACE_PATH = "$API/workspace"
const val TASKS_PATH = "$API/results"
const val TASK_DETAIL_TEXT = "$TASKS_PATH/text/{uuid}"
+ const val TASK_DETAIL_MEDIA = "$TASKS_PATH/media/{uuid}"
const val TASK_DETAIL_BATCH = "$TASKS_PATH/batch/{uuid}"
const val TASK_DETAIL_SKILLS = "$TASKS_PATH/skills/{uuid}"
diff --git a/api/src/main/kotlin/edu/wgu/osmt/auditlog/AuditLogUtils.kt b/api/src/main/kotlin/edu/wgu/osmt/auditlog/AuditLogUtils.kt
index 06e1549ff..c62568f79 100644
--- a/api/src/main/kotlin/edu/wgu/osmt/auditlog/AuditLogUtils.kt
+++ b/api/src/main/kotlin/edu/wgu/osmt/auditlog/AuditLogUtils.kt
@@ -4,7 +4,7 @@ import edu.wgu.osmt.collection.CollectionRepository
import edu.wgu.osmt.collection.CollectionTable
import edu.wgu.osmt.collection.diff
import edu.wgu.osmt.config.AppConfig
-import edu.wgu.osmt.csv.BatchImportRichSkill
+import edu.wgu.osmt.io.csv.BatchImportRichSkill
import edu.wgu.osmt.richskill.RichSkillDescriptorTable
import edu.wgu.osmt.richskill.RichSkillRepository
import edu.wgu.osmt.richskill.diff
diff --git a/api/src/main/kotlin/edu/wgu/osmt/collection/CollectionController.kt b/api/src/main/kotlin/edu/wgu/osmt/collection/CollectionController.kt
index 60986a98a..512e906c4 100644
--- a/api/src/main/kotlin/edu/wgu/osmt/collection/CollectionController.kt
+++ b/api/src/main/kotlin/edu/wgu/osmt/collection/CollectionController.kt
@@ -26,6 +26,7 @@ import edu.wgu.osmt.task.Task
import edu.wgu.osmt.task.TaskMessageService
import edu.wgu.osmt.task.TaskResult
import edu.wgu.osmt.task.UpdateCollectionSkillsTask
+import edu.wgu.osmt.task.XlsxTask
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpEntity
import org.springframework.http.HttpStatus
@@ -185,6 +186,18 @@ class CollectionController @Autowired constructor(
return Task.processingResponse(task)
}
+ @GetMapping(RoutePaths.COLLECTION_XLSX, produces = [MediaType.APPLICATION_OCTET_STREAM_VALUE])
+ fun getSkillsForCollectionXlsx(
+ @PathVariable uuid: String
+ ): HttpEntity {
+ if (collectionRepository.findByUUID(uuid)!!.status == PublishStatus.Draft && !oAuthHelper.hasRole(appConfig.roleAdmin)) {
+ throw ResponseStatusException(HttpStatus.UNAUTHORIZED)
+ }
+ val task = XlsxTask(collectionUuid = uuid)
+ taskMessageService.enqueueJob(TaskMessageService.skillsForCollectionXlsx, task)
+ return Task.processingResponse(task)
+ }
+
@DeleteMapping(RoutePaths.COLLECTION_REMOVE, produces = [MediaType.APPLICATION_JSON_VALUE])
fun removeCollection(
@PathVariable uuid: String
diff --git a/api/src/main/kotlin/edu/wgu/osmt/io/common/TabularResource.kt b/api/src/main/kotlin/edu/wgu/osmt/io/common/TabularResource.kt
new file mode 100644
index 000000000..edbef5ae8
--- /dev/null
+++ b/api/src/main/kotlin/edu/wgu/osmt/io/common/TabularResource.kt
@@ -0,0 +1,12 @@
+package edu.wgu.osmt.io.common
+
+interface TabularResource, T> {
+
+ /**
+ * Defines the columns of this table in their desired order.
+ */
+ fun columnTranslations(data: List): Array
+
+}
+
+interface TabColumn
diff --git a/api/src/main/kotlin/edu/wgu/osmt/io/common/TabularTask.kt b/api/src/main/kotlin/edu/wgu/osmt/io/common/TabularTask.kt
new file mode 100644
index 000000000..0e4f33a85
--- /dev/null
+++ b/api/src/main/kotlin/edu/wgu/osmt/io/common/TabularTask.kt
@@ -0,0 +1,26 @@
+package edu.wgu.osmt.io.common
+
+import edu.wgu.osmt.collection.CollectionRepository
+import edu.wgu.osmt.config.AppConfig
+import edu.wgu.osmt.richskill.RichSkillRepository
+import edu.wgu.osmt.task.Task
+import edu.wgu.osmt.task.TaskMessageService
+import org.springframework.beans.factory.annotation.Autowired
+
+abstract class TabularTask {
+ @Autowired
+ lateinit var taskMessageService: TaskMessageService
+
+ @Autowired
+ lateinit var collectionRepository: CollectionRepository
+
+ @Autowired
+ lateinit var richSkillRepository: RichSkillRepository
+
+ @Autowired
+ lateinit var appConfig: AppConfig
+
+ abstract fun tabularSkillsInCollectionProcessor(task: T)
+
+ abstract fun tabularSkillsInFullLibraryProcessor(task: T)
+}
diff --git a/api/src/main/kotlin/edu/wgu/osmt/csv/BatchImportRichSkill.kt b/api/src/main/kotlin/edu/wgu/osmt/io/csv/BatchImportRichSkill.kt
similarity index 99%
rename from api/src/main/kotlin/edu/wgu/osmt/csv/BatchImportRichSkill.kt
rename to api/src/main/kotlin/edu/wgu/osmt/io/csv/BatchImportRichSkill.kt
index d11828605..8ed3ea5c7 100644
--- a/api/src/main/kotlin/edu/wgu/osmt/csv/BatchImportRichSkill.kt
+++ b/api/src/main/kotlin/edu/wgu/osmt/io/csv/BatchImportRichSkill.kt
@@ -1,4 +1,4 @@
-package edu.wgu.osmt.csv
+package edu.wgu.osmt.io.csv
import com.opencsv.bean.CsvBindByName
import edu.wgu.osmt.collection.CollectionDao
diff --git a/api/src/main/kotlin/edu/wgu/osmt/csv/BlsImport.kt b/api/src/main/kotlin/edu/wgu/osmt/io/csv/BlsImport.kt
similarity index 98%
rename from api/src/main/kotlin/edu/wgu/osmt/csv/BlsImport.kt
rename to api/src/main/kotlin/edu/wgu/osmt/io/csv/BlsImport.kt
index 96d432f0f..4492a1a28 100644
--- a/api/src/main/kotlin/edu/wgu/osmt/csv/BlsImport.kt
+++ b/api/src/main/kotlin/edu/wgu/osmt/io/csv/BlsImport.kt
@@ -1,4 +1,4 @@
-package edu.wgu.osmt.csv
+package edu.wgu.osmt.io.csv
import com.opencsv.bean.CsvBindByName
import edu.wgu.osmt.config.AppConfig
@@ -10,7 +10,6 @@ import edu.wgu.osmt.richskill.RichSkillRepository
import org.jetbrains.exposed.sql.transactions.transaction
import org.slf4j.Logger
import org.slf4j.LoggerFactory
-import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
/**
diff --git a/api/src/main/kotlin/edu/wgu/osmt/csv/CsvBuilder.kt b/api/src/main/kotlin/edu/wgu/osmt/io/csv/CsvBuilder.kt
similarity index 98%
rename from api/src/main/kotlin/edu/wgu/osmt/csv/CsvBuilder.kt
rename to api/src/main/kotlin/edu/wgu/osmt/io/csv/CsvBuilder.kt
index 31ab4b323..d92b4d193 100644
--- a/api/src/main/kotlin/edu/wgu/osmt/csv/CsvBuilder.kt
+++ b/api/src/main/kotlin/edu/wgu/osmt/io/csv/CsvBuilder.kt
@@ -1,4 +1,4 @@
-package edu.wgu.osmt.csv
+package edu.wgu.osmt.io.csv
/**
* DSL style entry-point to build a CsvResource. This approach should be used if a csv file is simple in structure
diff --git a/api/src/main/kotlin/edu/wgu/osmt/csv/CsvImport.kt b/api/src/main/kotlin/edu/wgu/osmt/io/csv/CsvImport.kt
similarity index 98%
rename from api/src/main/kotlin/edu/wgu/osmt/csv/CsvImport.kt
rename to api/src/main/kotlin/edu/wgu/osmt/io/csv/CsvImport.kt
index ae54b92ce..9b6deee34 100644
--- a/api/src/main/kotlin/edu/wgu/osmt/csv/CsvImport.kt
+++ b/api/src/main/kotlin/edu/wgu/osmt/io/csv/CsvImport.kt
@@ -1,4 +1,4 @@
-package edu.wgu.osmt.csv
+package edu.wgu.osmt.io.csv
import com.opencsv.bean.CsvToBeanBuilder
import edu.wgu.osmt.jobcode.JobCodeBreakout
diff --git a/api/src/main/kotlin/edu/wgu/osmt/csv/CsvResource.kt b/api/src/main/kotlin/edu/wgu/osmt/io/csv/CsvResource.kt
similarity index 82%
rename from api/src/main/kotlin/edu/wgu/osmt/csv/CsvResource.kt
rename to api/src/main/kotlin/edu/wgu/osmt/io/csv/CsvResource.kt
index 3fb5c94b3..967fe6565 100644
--- a/api/src/main/kotlin/edu/wgu/osmt/csv/CsvResource.kt
+++ b/api/src/main/kotlin/edu/wgu/osmt/io/csv/CsvResource.kt
@@ -1,15 +1,17 @@
-package edu.wgu.osmt.csv
+package edu.wgu.osmt.io.csv
import com.opencsv.CSVWriter
+import edu.wgu.osmt.io.common.TabularResource
+import edu.wgu.osmt.io.common.TabColumn
import java.io.StringWriter
import java.io.Writer
-abstract class CsvResource(val debugName: String) {
+abstract class CsvResource(val debugName: String) : TabularResource, T> {
/**
* Defines the columns of this csv in their desired order.
*/
- abstract fun columnTranslations(data: List): Array>
+ abstract override fun columnTranslations(data: List): Array>
/**
* Override if opencsv defaults are not desired
@@ -79,19 +81,19 @@ abstract class CsvResource(val debugName: String) {
data class CsvColumn(
val name: String = "",
val translate: (T) -> String
-)
+): TabColumn
/**
* Configure the global attributes of a csv export
*/
data class CsvConfig(
- val delimeter: Char = CsvConfig.delimeter,
- val quoteChar: Char = CsvConfig.quoteChar,
- val escapeChar: Char = CsvConfig.escapeChar,
- val lineEnd: String = CsvConfig.lineEnd,
- val includeHeader: Boolean = CsvConfig.includeHeader
+ val delimeter: Char = Defaults.delimeter,
+ val quoteChar: Char = Defaults.quoteChar,
+ val escapeChar: Char = Defaults.escapeChar,
+ val lineEnd: String = Defaults.lineEnd,
+ val includeHeader: Boolean = Defaults.includeHeader
) {
- companion object Defaults { // Allows the default values to be shared with it's builder
+ companion object Defaults { // Allows the default values to be shared with its builder
val delimeter: Char = CSVWriter.DEFAULT_SEPARATOR
val quoteChar: Char = CSVWriter.DEFAULT_QUOTE_CHARACTER
val escapeChar: Char = CSVWriter.DEFAULT_ESCAPE_CHARACTER
diff --git a/api/src/main/kotlin/edu/wgu/osmt/collection/CsvTaskProcessor.kt b/api/src/main/kotlin/edu/wgu/osmt/io/csv/CsvTaskProcessor.kt
similarity index 61%
rename from api/src/main/kotlin/edu/wgu/osmt/collection/CsvTaskProcessor.kt
rename to api/src/main/kotlin/edu/wgu/osmt/io/csv/CsvTaskProcessor.kt
index aed3830e0..4fc8becaa 100644
--- a/api/src/main/kotlin/edu/wgu/osmt/collection/CsvTaskProcessor.kt
+++ b/api/src/main/kotlin/edu/wgu/osmt/io/csv/CsvTaskProcessor.kt
@@ -1,19 +1,16 @@
-package edu.wgu.osmt.collection
+package edu.wgu.osmt.io.csv
import com.github.sonus21.rqueue.annotation.RqueueListener
-import edu.wgu.osmt.config.AppConfig
import edu.wgu.osmt.db.PublishStatus
+import edu.wgu.osmt.io.common.TabularTask
import edu.wgu.osmt.richskill.RichSkillAndCollections
-import edu.wgu.osmt.richskill.RichSkillCsvExport
import edu.wgu.osmt.richskill.RichSkillDescriptorDao
-import edu.wgu.osmt.richskill.RichSkillRepository
import edu.wgu.osmt.task.CsvTask
import edu.wgu.osmt.task.TaskMessageService
import edu.wgu.osmt.task.TaskStatus
import org.jetbrains.exposed.dao.with
import org.slf4j.Logger
import org.slf4j.LoggerFactory
-import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Profile
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional
@@ -21,31 +18,19 @@ import org.springframework.transaction.annotation.Transactional
@Component
@Profile("apiserver")
@Transactional
-class CsvTaskProcessor {
+class CsvTaskProcessor : TabularTask() {
val logger: Logger = LoggerFactory.getLogger(CsvTaskProcessor::class.java)
- @Autowired
- lateinit var taskMessageService: TaskMessageService
-
- @Autowired
- lateinit var collectionRepository: CollectionRepository
-
- @Autowired
- lateinit var richSkillRepository: RichSkillRepository
-
- @Autowired
- lateinit var appConfig: AppConfig
-
@RqueueListener(
value = [TaskMessageService.skillsForCollectionCsv],
deadLetterQueueListenerEnabled = "true",
deadLetterQueue = TaskMessageService.deadLetters,
concurrency = "1"
)
- fun csvSkillsInCollectionProcessor(csvTask: CsvTask) {
- logger.info("Started processing task id: ${csvTask.uuid}")
+ override fun tabularSkillsInCollectionProcessor(task: CsvTask) {
+ logger.info("Started processing task id: ${task.uuid}")
- val csv = collectionRepository.findByUUID(csvTask.collectionUuid)
+ val csv = collectionRepository.findByUUID(task.collectionUuid)
?.skills
?.filter { PublishStatus.Archived != it.publishStatus() }
?.with(RichSkillDescriptorDao::collections)
@@ -53,9 +38,9 @@ class CsvTaskProcessor {
?.let { RichSkillCsvExport(appConfig).toCsv(it) }
taskMessageService.publishResult(
- csvTask.copy(result = csv, status = TaskStatus.Ready)
+ task.copy(result = csv, status = TaskStatus.Ready)
)
- logger.info("Task ${csvTask.uuid} completed")
+ logger.info("Task ${task.uuid} completed")
}
@RqueueListener(
@@ -64,8 +49,8 @@ class CsvTaskProcessor {
deadLetterQueue = TaskMessageService.deadLetters,
concurrency = "1"
)
- fun csvSkillsInFullLibraryProcessor(csvTask: CsvTask) {
- logger.info("Started processing task for Full Library export")
+ override fun tabularSkillsInFullLibraryProcessor(task: CsvTask) {
+ logger.info("Started processing task for Full Library .csv export")
val csv = richSkillRepository.findAll()
?.with(RichSkillDescriptorDao::collections)
@@ -73,9 +58,9 @@ class CsvTaskProcessor {
?.let { RichSkillCsvExport(appConfig).toCsv(it) }
taskMessageService.publishResult(
- csvTask.copy(result = csv, status = TaskStatus.Ready)
+ task.copy(result = csv, status = TaskStatus.Ready)
)
- logger.info("Full Library export task completed")
+ logger.info("Full Library export task .csv completed")
}
}
diff --git a/api/src/main/kotlin/edu/wgu/osmt/richskill/ExportSkillToCsvTaskProcessor.kt b/api/src/main/kotlin/edu/wgu/osmt/io/csv/ExportSkillToCsvTaskProcessor.kt
similarity index 92%
rename from api/src/main/kotlin/edu/wgu/osmt/richskill/ExportSkillToCsvTaskProcessor.kt
rename to api/src/main/kotlin/edu/wgu/osmt/io/csv/ExportSkillToCsvTaskProcessor.kt
index f971f7f59..9eaea2970 100644
--- a/api/src/main/kotlin/edu/wgu/osmt/richskill/ExportSkillToCsvTaskProcessor.kt
+++ b/api/src/main/kotlin/edu/wgu/osmt/io/csv/ExportSkillToCsvTaskProcessor.kt
@@ -1,7 +1,9 @@
-package edu.wgu.osmt.richskill
+package edu.wgu.osmt.io.csv
import com.github.sonus21.rqueue.annotation.RqueueListener
import edu.wgu.osmt.config.AppConfig
+import edu.wgu.osmt.richskill.RichSkillAndCollections
+import edu.wgu.osmt.richskill.RichSkillRepository
import edu.wgu.osmt.task.ExportSkillsToCsvTask
import edu.wgu.osmt.task.TaskMessageService
import edu.wgu.osmt.task.TaskStatus
diff --git a/api/src/main/kotlin/edu/wgu/osmt/csv/OnetImport.kt b/api/src/main/kotlin/edu/wgu/osmt/io/csv/OnetImport.kt
similarity index 99%
rename from api/src/main/kotlin/edu/wgu/osmt/csv/OnetImport.kt
rename to api/src/main/kotlin/edu/wgu/osmt/io/csv/OnetImport.kt
index 9ed9260d4..cd532d2be 100644
--- a/api/src/main/kotlin/edu/wgu/osmt/csv/OnetImport.kt
+++ b/api/src/main/kotlin/edu/wgu/osmt/io/csv/OnetImport.kt
@@ -1,4 +1,4 @@
-package edu.wgu.osmt.csv
+package edu.wgu.osmt.io.csv
import com.opencsv.bean.CsvBindByName
import edu.wgu.osmt.config.AppConfig
diff --git a/api/src/main/kotlin/edu/wgu/osmt/richskill/RichSkillCsvExport.kt b/api/src/main/kotlin/edu/wgu/osmt/io/csv/RichSkillCsvExport.kt
similarity index 96%
rename from api/src/main/kotlin/edu/wgu/osmt/richskill/RichSkillCsvExport.kt
rename to api/src/main/kotlin/edu/wgu/osmt/io/csv/RichSkillCsvExport.kt
index 2fb294a45..fc66f04f7 100644
--- a/api/src/main/kotlin/edu/wgu/osmt/richskill/RichSkillCsvExport.kt
+++ b/api/src/main/kotlin/edu/wgu/osmt/io/csv/RichSkillCsvExport.kt
@@ -1,10 +1,9 @@
-package edu.wgu.osmt.richskill
+package edu.wgu.osmt.io.csv
import edu.wgu.osmt.config.AppConfig
-import edu.wgu.osmt.csv.CsvColumn
-import edu.wgu.osmt.csv.CsvResource
import edu.wgu.osmt.jobcode.JobCode
import edu.wgu.osmt.jobcode.JobCodeBreakout
+import edu.wgu.osmt.richskill.RichSkillAndCollections
class RichSkillCsvExport(
private val appConfig: AppConfig
diff --git a/api/src/main/kotlin/edu/wgu/osmt/io/xlsx/RichSkillXlsxExport.kt b/api/src/main/kotlin/edu/wgu/osmt/io/xlsx/RichSkillXlsxExport.kt
new file mode 100644
index 000000000..dcb755d3b
--- /dev/null
+++ b/api/src/main/kotlin/edu/wgu/osmt/io/xlsx/RichSkillXlsxExport.kt
@@ -0,0 +1,52 @@
+package edu.wgu.osmt.io.xlsx
+
+import edu.wgu.osmt.config.AppConfig
+import edu.wgu.osmt.jobcode.JobCode
+import edu.wgu.osmt.jobcode.JobCodeBreakout
+import edu.wgu.osmt.richskill.RichSkillAndCollections
+
+class RichSkillXlsxExport(
+ private val appConfig: AppConfig
+) : XlsxResource("RichSkillXlsxExport") {
+ private val listDelimiter = "; "
+
+ override fun columnTranslations(data: List): Array> {
+ val columns = arrayOf(
+ XlsxColumn("Canonical URL") { it.rs.canonicalUrl(appConfig.baseUrl) },
+ XlsxColumn("RSD Name") { it.rs.name },
+ XlsxColumn("Author") { it.rs.author?.value ?: "" },
+ XlsxColumn("Skill Statement") { it.rs.statement },
+ XlsxColumn("Category") { it.rs.category?.value ?: "" },
+ XlsxColumn("Keywords") { it.rs.searchingKeywords.map { keyword -> keyword.value ?: "" }.joinToString(listDelimiter) },
+ XlsxColumn("Standards") { it.rs.standards.map { keyword -> keyword.value ?: "" }.joinToString(listDelimiter) },
+ XlsxColumn("Certifications") { it.rs.certifications.map { keyword -> keyword.value ?: "" }.joinToString(listDelimiter) },
+ XlsxColumn("Occupation Major Groups") { prepareJobCodePart(it.rs.jobCodes, JobCodeBreakout::majorCode) },
+ XlsxColumn("Occupation Minor Groups") { prepareJobCodePart(it.rs.jobCodes, JobCodeBreakout::minorCode) },
+ XlsxColumn("Broad Occupations") { prepareJobCodePart(it.rs.jobCodes, JobCodeBreakout::broadCode) },
+ XlsxColumn("Detailed Occupations") { prepareJobCodePart(it.rs.jobCodes, JobCodeBreakout::detailedCode) },
+ XlsxColumn("O*Net Job Codes") { prepareJobCodePart(it.rs.jobCodes, JobCodeBreakout::jobRoleCode) },
+ XlsxColumn("Employers") { it.rs.employers.map { keyword -> keyword.value ?: "" }.joinToString(listDelimiter) }
+ )
+ val alignmentCount = data.map { s -> s.rs.alignments.size }.maxOrNull() ?: 0
+ val alignmentColumns = (0 until alignmentCount).flatMap { i ->
+ val label = if (i > 0) " ${i + 1}" else ""
+ listOf(
+ XlsxColumn("Alignment${label} Name") { it.rs.alignments.getOrNull(i)?.value ?: "" },
+ XlsxColumn("Alignment${label} URL") { it.rs.alignments.getOrNull(i)?.uri ?: "" },
+ XlsxColumn("Alignment${label} Framework") { it.rs.alignments.getOrNull(i)?.framework ?: "" }
+ )
+ }
+ return columns + alignmentColumns
+ }
+
+ private fun prepareJobCodePart(
+ codes: List,
+ partTransformation: (String) -> String?
+ ): String = codes
+ .asSequence()
+ .map { it.code }
+ .map(partTransformation)
+ .filterNotNull()
+ .distinct()
+ .joinToString(listDelimiter)
+}
diff --git a/api/src/main/kotlin/edu/wgu/osmt/io/xlsx/XlsxResource.kt b/api/src/main/kotlin/edu/wgu/osmt/io/xlsx/XlsxResource.kt
new file mode 100644
index 000000000..8aed9e0a6
--- /dev/null
+++ b/api/src/main/kotlin/edu/wgu/osmt/io/xlsx/XlsxResource.kt
@@ -0,0 +1,74 @@
+package edu.wgu.osmt.io.xlsx
+
+import com.opencsv.CSVWriter
+import edu.wgu.osmt.io.common.TabColumn
+import edu.wgu.osmt.io.common.TabularResource
+import edu.wgu.osmt.richskill.RichSkillAndCollections
+import org.apache.poi.ss.usermodel.BuiltinFormats
+import org.apache.poi.ss.usermodel.Cell
+import org.apache.poi.ss.usermodel.Row
+import org.apache.poi.xssf.usermodel.XSSFSheet
+import org.apache.poi.xssf.usermodel.XSSFWorkbook
+import java.io.ByteArrayOutputStream
+
+abstract class XlsxResource(val debugName: String) : TabularResource, T> {
+ private val workbook: XSSFWorkbook = XSSFWorkbook()
+ private var rowCount: Int = 0
+
+ /**
+ * Defines the columns of this xlsx in their desired order.
+ */
+ abstract override fun columnTranslations(data: List): Array>
+
+ /**
+ * Produce a xlsx export from the list of data using this XlsxResource's configuration and translations
+ */
+ fun toXlsx(data: List): ByteArray {
+ val sheet: XSSFSheet = this.workbook.createSheet()
+
+ writeHeaderRow(data, sheet)
+ writeRows(data, sheet)
+
+ val output: ByteArrayOutputStream = ByteArrayOutputStream()
+ workbook.write(output)
+
+ return output.toByteArray()
+ }
+
+ private fun writeHeaderRow(data: List, sheet: XSSFSheet) {
+ if (true) {
+ val headerRow = columnTranslations(data).map { column -> column.name }.toTypedArray()
+ writeRow(headerRow, sheet, true)
+ }
+ }
+
+ private fun writeRows(data: List, sheet: XSSFSheet) {
+ val rowsList: List> = data.map { datum ->
+ columnTranslations(data).map { it.translate(datum) }.toTypedArray()
+ }
+ rowsList.forEach { writeRow(it, sheet) }
+ }
+
+ private fun writeRow(rowData: Array, sheet: XSSFSheet, isHeader: Boolean=false) {
+ val row: Row = sheet.createRow(this.rowCount)
+
+ rowData.forEachIndexed { colIndex, element ->
+ if (isHeader) {
+ val format = BuiltinFormats.getBuiltinFormat("@")
+ val textStyle = this.workbook.createCellStyle()
+ textStyle.setDataFormat(format)
+ sheet.setDefaultColumnStyle(colIndex, textStyle)
+ }
+
+ val cell: Cell = row.createCell(colIndex)
+ cell.setCellValue(element)
+ }
+
+ this.rowCount++
+ }
+}
+
+data class XlsxColumn(
+ val name: String = "",
+ val translate: (T) -> String
+): TabColumn
\ No newline at end of file
diff --git a/api/src/main/kotlin/edu/wgu/osmt/io/xlsx/XlsxTaskProcessor.kt b/api/src/main/kotlin/edu/wgu/osmt/io/xlsx/XlsxTaskProcessor.kt
new file mode 100644
index 000000000..d6f6283e4
--- /dev/null
+++ b/api/src/main/kotlin/edu/wgu/osmt/io/xlsx/XlsxTaskProcessor.kt
@@ -0,0 +1,65 @@
+package edu.wgu.osmt.io.xlsx
+
+import com.github.sonus21.rqueue.annotation.RqueueListener
+import edu.wgu.osmt.db.PublishStatus
+import edu.wgu.osmt.io.common.TabularTask
+import edu.wgu.osmt.richskill.RichSkillAndCollections
+import edu.wgu.osmt.richskill.RichSkillDescriptorDao
+import edu.wgu.osmt.task.XlsxTask
+import edu.wgu.osmt.task.TaskMessageService
+import edu.wgu.osmt.task.TaskStatus
+import org.jetbrains.exposed.dao.with
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.springframework.context.annotation.Profile
+import org.springframework.stereotype.Component
+import org.springframework.transaction.annotation.Transactional
+
+@Component
+@Profile("apiserver")
+@Transactional
+class XlsxTaskProcessor : TabularTask() {
+ val logger: Logger = LoggerFactory.getLogger(XlsxTaskProcessor::class.java)
+
+ @RqueueListener(
+ value = [TaskMessageService.skillsForCollectionXlsx],
+ deadLetterQueueListenerEnabled = "true",
+ deadLetterQueue = TaskMessageService.deadLetters,
+ concurrency = "1"
+ )
+ override fun tabularSkillsInCollectionProcessor(task: XlsxTask) {
+ logger.info("Started processing task id: ${task.uuid}")
+
+ val xlsx = collectionRepository.findByUUID(task.collectionUuid)
+ ?.skills
+ ?.filter { PublishStatus.Archived != it.publishStatus() }
+ ?.with(RichSkillDescriptorDao::collections)
+ ?.map { RichSkillAndCollections.fromDao(it) }
+ ?.let { RichSkillXlsxExport(appConfig).toXlsx(it) }
+
+ taskMessageService.publishResult(
+ task.copy(result = xlsx, status = TaskStatus.Ready)
+ )
+ logger.info("Task ${task.uuid} completed")
+ }
+
+ @RqueueListener(
+ value = [TaskMessageService.skillsForFullLibraryXlsx],
+ deadLetterQueueListenerEnabled = "true",
+ deadLetterQueue = TaskMessageService.deadLetters,
+ concurrency = "1"
+ )
+ override fun tabularSkillsInFullLibraryProcessor(task: XlsxTask) {
+ logger.info("Started processing task for Full Library .xlsx export")
+
+ val xlsx = richSkillRepository.findAll()
+ ?.with(RichSkillDescriptorDao::collections)
+ ?.map { RichSkillAndCollections.fromDao(it) }
+ ?.let { RichSkillXlsxExport(appConfig).toXlsx(it) }
+
+ taskMessageService.publishResult(
+ task.copy(result = xlsx, status = TaskStatus.Ready)
+ )
+ logger.info("Full Library .xlsx export task completed")
+ }
+}
diff --git a/api/src/main/kotlin/edu/wgu/osmt/richskill/RichSkillController.kt b/api/src/main/kotlin/edu/wgu/osmt/richskill/RichSkillController.kt
index fd032c847..7d1d3c9fb 100644
--- a/api/src/main/kotlin/edu/wgu/osmt/richskill/RichSkillController.kt
+++ b/api/src/main/kotlin/edu/wgu/osmt/richskill/RichSkillController.kt
@@ -13,6 +13,7 @@ import edu.wgu.osmt.auditlog.AuditLog
import edu.wgu.osmt.auditlog.AuditLogRepository
import edu.wgu.osmt.auditlog.AuditLogSortEnum
import edu.wgu.osmt.config.AppConfig
+import edu.wgu.osmt.io.csv.RichSkillCsvExport
import edu.wgu.osmt.db.PublishStatus
import edu.wgu.osmt.elasticsearch.OffsetPageable
import edu.wgu.osmt.elasticsearch.PaginatedLinks
@@ -269,6 +270,7 @@ class RichSkillController @Autowired constructor(
return Task.processingResponse(task)
}
+ // This really appears to be never used....
@Transactional(readOnly = true)
@PostMapping(RoutePaths.EXPORT_SKILLS, produces = [MediaType.APPLICATION_JSON_VALUE])
@ResponseBody
diff --git a/api/src/main/kotlin/edu/wgu/osmt/security/SecurityConfig.kt b/api/src/main/kotlin/edu/wgu/osmt/security/SecurityConfig.kt
index 3e8cdc7c0..f56ea9b63 100644
--- a/api/src/main/kotlin/edu/wgu/osmt/security/SecurityConfig.kt
+++ b/api/src/main/kotlin/edu/wgu/osmt/security/SecurityConfig.kt
@@ -11,6 +11,7 @@ import edu.wgu.osmt.RoutePaths.COLLECTION_REMOVE
import edu.wgu.osmt.RoutePaths.COLLECTION_SKILLS
import edu.wgu.osmt.RoutePaths.COLLECTION_SKILLS_UPDATE
import edu.wgu.osmt.RoutePaths.COLLECTION_UPDATE
+import edu.wgu.osmt.RoutePaths.COLLECTION_XLSX
import edu.wgu.osmt.RoutePaths.SEARCH_COLLECTIONS
import edu.wgu.osmt.RoutePaths.SEARCH_JOBCODES_PATH
import edu.wgu.osmt.RoutePaths.SEARCH_KEYWORDS_PATH
@@ -22,6 +23,7 @@ import edu.wgu.osmt.RoutePaths.SKILL_DETAIL
import edu.wgu.osmt.RoutePaths.SKILL_PUBLISH
import edu.wgu.osmt.RoutePaths.SKILL_UPDATE
import edu.wgu.osmt.RoutePaths.TASK_DETAIL_BATCH
+import edu.wgu.osmt.RoutePaths.TASK_DETAIL_MEDIA
import edu.wgu.osmt.RoutePaths.TASK_DETAIL_SKILLS
import edu.wgu.osmt.RoutePaths.TASK_DETAIL_TEXT
import edu.wgu.osmt.RoutePaths.WORKSPACE_PATH
@@ -93,6 +95,8 @@ class SecurityConfig : WebSecurityConfigurerAdapter() {
.mvcMatchers(POST, COLLECTION_SKILLS).permitAll()
.mvcMatchers(GET, COLLECTION_CSV).permitAll()
.mvcMatchers(GET, TASK_DETAIL_TEXT).permitAll() // public csv results
+ .mvcMatchers(GET, COLLECTION_XLSX).permitAll()
+ .mvcMatchers(GET, TASK_DETAIL_MEDIA).permitAll() // public csv results
.and().exceptionHandling().authenticationEntryPoint(returnUnauthorized)
.and().oauth2Login().successHandler(redirectToFrontend)
diff --git a/api/src/main/kotlin/edu/wgu/osmt/task/Task.kt b/api/src/main/kotlin/edu/wgu/osmt/task/Task.kt
index d615b07ad..5a10510c3 100644
--- a/api/src/main/kotlin/edu/wgu/osmt/task/Task.kt
+++ b/api/src/main/kotlin/edu/wgu/osmt/task/Task.kt
@@ -21,6 +21,7 @@ import java.util.*
)
@JsonSubTypes(
JsonSubTypes.Type(value = CsvTask::class, name = "CsvTask"),
+ JsonSubTypes.Type(value = XlsxTask::class, name = "XlsxTask"),
JsonSubTypes.Type(value = ApiSearch::class, name = "ApiSearch"),
JsonSubTypes.Type(value = ApiBatchResult::class, name = "ApiBatchResult"),
JsonSubTypes.Type(value = PublishTask::class, name = "PublishTask"),
@@ -67,7 +68,17 @@ data class CsvTask(
) : Task {
override val contentType = "text/csv"
override val apiResultPath = RoutePaths.TASK_DETAIL_TEXT
+}
+data class XlsxTask(
+ val collectionUuid: String = "",
+ override val uuid: String = UUID.randomUUID().toString(),
+ override val start: Date = Date(),
+ override val result: ByteArray? = null,
+ override val status: TaskStatus = TaskStatus.Processing
+) : Task {
+ override val contentType = "application/vnd.ms-excel"
+ override val apiResultPath = RoutePaths.TASK_DETAIL_MEDIA
}
data class ExportSkillsToCsvTask(
diff --git a/api/src/main/kotlin/edu/wgu/osmt/task/TaskController.kt b/api/src/main/kotlin/edu/wgu/osmt/task/TaskController.kt
index c394e1d6e..2ee66da68 100644
--- a/api/src/main/kotlin/edu/wgu/osmt/task/TaskController.kt
+++ b/api/src/main/kotlin/edu/wgu/osmt/task/TaskController.kt
@@ -35,6 +35,12 @@ class TaskController @Autowired constructor(
return taskResult(uuid)
}
+ @GetMapping(RoutePaths.TASK_DETAIL_MEDIA)
+ @ResponseBody
+ fun mediaResult(@PathVariable uuid: String): HttpEntity<*> {
+ return taskResult(uuid)
+ }
+
@GetMapping(RoutePaths.TASK_DETAIL_BATCH)
@ResponseBody
fun batchResult(@PathVariable uuid: String): HttpEntity<*> {
diff --git a/api/src/main/kotlin/edu/wgu/osmt/task/TaskMessageService.kt b/api/src/main/kotlin/edu/wgu/osmt/task/TaskMessageService.kt
index 9174368ae..fa51fa1f0 100644
--- a/api/src/main/kotlin/edu/wgu/osmt/task/TaskMessageService.kt
+++ b/api/src/main/kotlin/edu/wgu/osmt/task/TaskMessageService.kt
@@ -43,8 +43,10 @@ class TaskMessageService {
const val publishSkills = "batch-publish-skills"
const val updateCollectionSkills = "update-collection-skills"
const val skillsForCollectionCsv = "collection-skills-csv-process"
+ const val skillsForCollectionXlsx = "collection-skills-xlsx-process"
const val removeCollectionSkills = "remove-collection"
const val skillsForFullLibraryCsv = "full-library-skills-csv-process"
+ const val skillsForFullLibraryXlsx = "full-library-skills-xlsx-process"
const val skillsForCustomListExportCsv = "custom-rsd-list-export"
}
}
diff --git a/api/src/test/kotlin/edu/wgu/osmt/csv/BatchImportRichSkillTest.kt b/api/src/test/kotlin/edu/wgu/osmt/io/csv/BatchImportRichSkillTest.kt
similarity index 98%
rename from api/src/test/kotlin/edu/wgu/osmt/csv/BatchImportRichSkillTest.kt
rename to api/src/test/kotlin/edu/wgu/osmt/io/csv/BatchImportRichSkillTest.kt
index 759301948..317cefc2f 100644
--- a/api/src/test/kotlin/edu/wgu/osmt/csv/BatchImportRichSkillTest.kt
+++ b/api/src/test/kotlin/edu/wgu/osmt/io/csv/BatchImportRichSkillTest.kt
@@ -1,4 +1,4 @@
-package edu.wgu.osmt.csv
+package edu.wgu.osmt.io.csv
import edu.wgu.osmt.BaseDockerizedTest
import edu.wgu.osmt.HasDatabaseReset
diff --git a/api/src/test/kotlin/edu/wgu/osmt/csv/BlsImportTest.kt b/api/src/test/kotlin/edu/wgu/osmt/io/csv/BlsImportTest.kt
similarity index 98%
rename from api/src/test/kotlin/edu/wgu/osmt/csv/BlsImportTest.kt
rename to api/src/test/kotlin/edu/wgu/osmt/io/csv/BlsImportTest.kt
index e654cd6d1..a5159017e 100644
--- a/api/src/test/kotlin/edu/wgu/osmt/csv/BlsImportTest.kt
+++ b/api/src/test/kotlin/edu/wgu/osmt/io/csv/BlsImportTest.kt
@@ -1,4 +1,4 @@
-package edu.wgu.osmt.csv
+package edu.wgu.osmt.io.csv
import edu.wgu.osmt.BaseDockerizedTest
import edu.wgu.osmt.HasDatabaseReset
diff --git a/api/src/test/kotlin/edu/wgu/osmt/csv/CsvResourceTest.kt b/api/src/test/kotlin/edu/wgu/osmt/io/csv/CsvResourceTest.kt
similarity index 96%
rename from api/src/test/kotlin/edu/wgu/osmt/csv/CsvResourceTest.kt
rename to api/src/test/kotlin/edu/wgu/osmt/io/csv/CsvResourceTest.kt
index 7ddb650ba..f1ffa0e1a 100644
--- a/api/src/test/kotlin/edu/wgu/osmt/csv/CsvResourceTest.kt
+++ b/api/src/test/kotlin/edu/wgu/osmt/io/csv/CsvResourceTest.kt
@@ -1,4 +1,4 @@
-package edu.wgu.osmt.csv
+package edu.wgu.osmt.io.csv
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
@@ -165,9 +165,9 @@ internal class CsvResourceTest {
}
fun getCsvTestResource(
- debugName: String = "unit-test",
- columnDefinitions: Array>,
- configuration: CsvConfig? = null
+ debugName: String = "unit-test",
+ columnDefinitions: Array>,
+ configuration: CsvConfig? = null
): CsvResource {
return object : CsvResource(debugName) {
override fun columnTranslations(d: List): Array> = columnDefinitions
diff --git a/api/src/test/kotlin/edu/wgu/osmt/csv/OnetImportTest.kt b/api/src/test/kotlin/edu/wgu/osmt/io/csv/OnetImportTest.kt
similarity index 98%
rename from api/src/test/kotlin/edu/wgu/osmt/csv/OnetImportTest.kt
rename to api/src/test/kotlin/edu/wgu/osmt/io/csv/OnetImportTest.kt
index 90c13d2c6..322b9a7f9 100644
--- a/api/src/test/kotlin/edu/wgu/osmt/csv/OnetImportTest.kt
+++ b/api/src/test/kotlin/edu/wgu/osmt/io/csv/OnetImportTest.kt
@@ -1,4 +1,4 @@
-package edu.wgu.osmt.csv
+package edu.wgu.osmt.io.csv
import edu.wgu.osmt.BaseDockerizedTest
import edu.wgu.osmt.HasDatabaseReset
diff --git a/api/src/test/kotlin/edu/wgu/osmt/mockdata/MockData.kt b/api/src/test/kotlin/edu/wgu/osmt/mockdata/MockData.kt
index 97d41f0be..95c6d6915 100644
--- a/api/src/test/kotlin/edu/wgu/osmt/mockdata/MockData.kt
+++ b/api/src/test/kotlin/edu/wgu/osmt/mockdata/MockData.kt
@@ -3,9 +3,9 @@ package edu.wgu.osmt.mockdata
import com.fasterxml.jackson.dataformat.xml.XmlMapper
import edu.wgu.osmt.collection.CollectionDoc
import edu.wgu.osmt.config.AppConfig
-import edu.wgu.osmt.csv.BlsJobCode
-import edu.wgu.osmt.csv.OnetJobCode
-import edu.wgu.osmt.csv.RichSkillRow
+import edu.wgu.osmt.io.csv.BlsJobCode
+import edu.wgu.osmt.io.csv.OnetJobCode
+import edu.wgu.osmt.io.csv.RichSkillRow
import edu.wgu.osmt.db.PublishStatus
import edu.wgu.osmt.jobcode.JobCode
import edu.wgu.osmt.keyword.Keyword
diff --git a/api/src/test/kotlin/edu/wgu/osmt/richskill/RichSkillControllerTest.kt b/api/src/test/kotlin/edu/wgu/osmt/richskill/RichSkillControllerTest.kt
index 67683e492..8aa176018 100644
--- a/api/src/test/kotlin/edu/wgu/osmt/richskill/RichSkillControllerTest.kt
+++ b/api/src/test/kotlin/edu/wgu/osmt/richskill/RichSkillControllerTest.kt
@@ -9,8 +9,8 @@ import edu.wgu.osmt.api.model.ApiFilteredSearch
import edu.wgu.osmt.api.model.ApiSearch
import edu.wgu.osmt.collection.CollectionEsRepo
import edu.wgu.osmt.config.AppConfig
-import edu.wgu.osmt.csv.BatchImportRichSkill
-import edu.wgu.osmt.csv.RichSkillRow
+import edu.wgu.osmt.io.csv.BatchImportRichSkill
+import edu.wgu.osmt.io.csv.RichSkillRow
import edu.wgu.osmt.jobcode.JobCodeEsRepo
import edu.wgu.osmt.keyword.KeywordEsRepo
import edu.wgu.osmt.mockdata.MockData
diff --git a/api/src/test/kotlin/edu/wgu/osmt/richskill/RichSkillEsRepoTest.kt b/api/src/test/kotlin/edu/wgu/osmt/richskill/RichSkillEsRepoTest.kt
index 3cccadc36..dd72c1a64 100644
--- a/api/src/test/kotlin/edu/wgu/osmt/richskill/RichSkillEsRepoTest.kt
+++ b/api/src/test/kotlin/edu/wgu/osmt/richskill/RichSkillEsRepoTest.kt
@@ -12,7 +12,7 @@ import edu.wgu.osmt.api.model.ApiSearch
import edu.wgu.osmt.api.model.ApiSimilaritySearch
import edu.wgu.osmt.collection.CollectionDoc
import edu.wgu.osmt.collection.CollectionEsRepo
-import edu.wgu.osmt.csv.BatchImportRichSkill
+import edu.wgu.osmt.io.csv.BatchImportRichSkill
import edu.wgu.osmt.db.ListFieldUpdate
import edu.wgu.osmt.jobcode.JobCodeEsRepo
import edu.wgu.osmt.keyword.KeywordEsRepo
diff --git a/docs/int/osmt-v1.0.x-openapi3.yaml b/docs/int/osmt-v1.0.x-openapi3.yaml
index d018e9302..fd8444156 100644
--- a/docs/int/osmt-v1.0.x-openapi3.yaml
+++ b/docs/int/osmt-v1.0.x-openapi3.yaml
@@ -681,6 +681,32 @@ paths:
parameters:
uuid: '$response.body#/uuid'
+ /api/collections/{uuid}/xlsx:
+ get:
+ tags:
+ - Collections
+ summary: Retrieve the skills in a collection as a XLSX (MS Excel)
+ description: Retrieve the skills that belong to the collection in XLSX (MS Excel) format
+ parameters:
+ - name: uuid
+ in: path
+ description: uuid of a collection
+ required: true
+ schema:
+ type: string
+ responses:
+ '200':
+ description: OK
+ content:
+ application/vnd.ms-excel:
+ schema:
+ $ref: '#/components/schemas/TaskResult'
+ links:
+ CollectionXlsxResults:
+ operationRef: '/api/results/media/{uuid}'
+ parameters:
+ uuid: '$response.body#/uuid'
+
/api/collections/{uuid}/updateSkills:
post:
tags:
diff --git a/docs/int/osmt-v2.x-openapi3.yaml b/docs/int/osmt-v2.x-openapi3.yaml
index 8ce85d4e2..ca577704b 100644
--- a/docs/int/osmt-v2.x-openapi3.yaml
+++ b/docs/int/osmt-v2.x-openapi3.yaml
@@ -739,6 +739,32 @@ paths:
parameters:
uuid: '$response.body#/uuid'
+ /api/collections/{uuid}/xlsx:
+ get:
+ tags:
+ - Collections
+ summary: Retrieve the skills in a collection as a XLSX (MS Excel)
+ description: Retrieve the skills that belong to the collection in XLSX (MS Excel) format
+ parameters:
+ - name: uuid
+ in: path
+ description: uuid of a collection
+ required: true
+ schema:
+ type: string
+ responses:
+ '200':
+ description: OK
+ content:
+ application/vnd.ms-excel:
+ schema:
+ $ref: '#/components/schemas/TaskResult'
+ links:
+ CollectionCsvResults:
+ operationRef: '/api/results/media/{uuid}'
+ parameters:
+ uuid: '$response.body#/uuid'
+
/api/collections/{uuid}/updateSkills:
post:
tags:
From 8d93d41f6c3ecda95bebae9e1b49f550dd6523b7 Mon Sep 17 00:00:00 2001
From: manuel-delvillar <68391066+manuel-delvillar@users.noreply.github.com>
Date: Tue, 28 Feb 2023 11:43:12 -0600
Subject: [PATCH 2/5] Add support for menu in vertical-action-bar
- Item in vertical-action-bar can display a menu if available.
Add vertical-action-bar-item.component
Update collection-public-action-bar
- Add vertical-action-bar-item.component in collection-public-action-bar.
Add download-csv
Add visible argument in constructor
Add horizontal action bar item
Fix download on public skill
Add drop up menu
Add drop up menu to export rsd
Add drop up in export selected.
Remove unused code
Remove empty constructor and ng on init.
Position droup content
---
ui/src/app/app.module.ts | 4 ++-
.../collection-public-action-bar.component.ts | 20 +++++++++++++++
...ublic-horizontal-action-bar.component.html | 13 ++--------
...-public-vertical-action-bar.component.html | 12 ++-------
.../detail/manage-collection.component.ts | 18 ++++++++++---
.../drop-up-menu/drop-up-menu.component.html | 6 +++++
.../drop-up-menu.component.spec.ts | 25 +++++++++++++++++++
.../drop-up-menu/drop-up-menu.component.ts | 14 +++++++++++
.../horizontal-action-bar-item.component.html | 19 ++++++++++++++
.../horizontal-action-bar-item.component.scss | 0
...rizontal-action-bar-item.component.spec.ts | 25 +++++++++++++++++++
.../horizontal-action-bar-item.component.ts | 14 +++++++++++
ui/src/app/core/osmt-core.module.ts | 25 +++++++++++++++++++
.../vertical-action-bar-item.component.html | 20 +++++++++++++++
.../vertical-action-bar-item.component.scss | 15 +++++++++++
...vertical-action-bar-item.component.spec.ts | 25 +++++++++++++++++++
.../vertical-action-bar-item.component.ts | 19 ++++++++++++++
.../core/vertical-action-bar.component.html | 19 +++-----------
.../my-workspace/my-workspace.component.ts | 15 +++++++++--
.../navigation/libraryexport.component.html | 19 +++++++++-----
.../navigation/libraryexport.component.scss | 4 +++
.../app/navigation/libraryexport.component.ts | 25 ++++++++++++++++++-
...skill-action-bar-horizontal.component.html | 13 ++--------
...c-skill-action-bar-vertical.component.html | 10 ++------
.../public-rich-skill-action-bar.component.ts | 21 ++++++++++++++++
.../richskill/list/skills-list.component.ts | 15 +++++++++--
.../action-bar-item.components.scss | 14 ++---------
27 files changed, 347 insertions(+), 82 deletions(-)
create mode 100644 ui/src/app/core/drop-up-menu/drop-up-menu.component.html
create mode 100644 ui/src/app/core/drop-up-menu/drop-up-menu.component.spec.ts
create mode 100644 ui/src/app/core/drop-up-menu/drop-up-menu.component.ts
create mode 100644 ui/src/app/core/horizontal-action-bar-item/horizontal-action-bar-item.component.html
create mode 100644 ui/src/app/core/horizontal-action-bar-item/horizontal-action-bar-item.component.scss
create mode 100644 ui/src/app/core/horizontal-action-bar-item/horizontal-action-bar-item.component.spec.ts
create mode 100644 ui/src/app/core/horizontal-action-bar-item/horizontal-action-bar-item.component.ts
create mode 100644 ui/src/app/core/osmt-core.module.ts
create mode 100644 ui/src/app/core/vertical-action-bar-item/vertical-action-bar-item.component.html
create mode 100644 ui/src/app/core/vertical-action-bar-item/vertical-action-bar-item.component.scss
create mode 100644 ui/src/app/core/vertical-action-bar-item/vertical-action-bar-item.component.spec.ts
create mode 100644 ui/src/app/core/vertical-action-bar-item/vertical-action-bar-item.component.ts
create mode 100644 ui/src/app/navigation/libraryexport.component.scss
diff --git a/ui/src/app/app.module.ts b/ui/src/app/app.module.ts
index 745219af9..6c398af44 100644
--- a/ui/src/app/app.module.ts
+++ b/ui/src/app/app.module.ts
@@ -94,7 +94,8 @@ import {MyWorkspaceComponent} from "./my-workspace/my-workspace.component"
import {CollectionPipe} from "./pipes"
import { ConvertToCollectionComponent } from "./my-workspace/convert-to-collection/convert-to-collection.component"
import {SharedModule} from "@shared/shared.module"
-import {OsmtFormModule} from "./form/osmt-form.module"
+import { OsmtCoreModule } from "./core/osmt-core.module"
+import { OsmtFormModule } from "./form/osmt-form.module"
import { SizePaginationComponent } from "./table/skills-library-table/size-pagination/size-pagination.component"
export function initializeApp(
@@ -212,6 +213,7 @@ export function initializeApp(
ReactiveFormsModule,
CommonModule,
SharedModule,
+ OsmtCoreModule,
OsmtFormModule,
FormsModule
],
diff --git a/ui/src/app/collection/detail/collection-public/action-bar/collection-public-action-bar.component.ts b/ui/src/app/collection/detail/collection-public/action-bar/collection-public-action-bar.component.ts
index 56286f0d4..eb9e082a4 100644
--- a/ui/src/app/collection/detail/collection-public/action-bar/collection-public-action-bar.component.ts
+++ b/ui/src/app/collection/detail/collection-public/action-bar/collection-public-action-bar.component.ts
@@ -7,6 +7,8 @@ import {formatDate} from "@angular/common"
import {ITaskResult} from "../../../../task/ApiTaskResult"
import { Observable } from "rxjs"
import * as FileSaver from "file-saver";
+import {TableActionDefinition} from "../../../../table/skills-library-table/has-action-definitions"
+
@Component({
selector: "app-collection-public-action-bar",
@@ -27,6 +29,23 @@ export class CollectionPublicActionBarComponent implements OnInit {
taskUuidInProgress: string | undefined
csvExport: string | undefined
intervalHandle: number | undefined
+ action = new TableActionDefinition({
+ label: "Download",
+ icon: this.downloadIcon,
+ menu: [
+ {
+ label: "Download as CSV",
+ visible: () => true,
+ callback: () => this.onDownloadCsv(),
+ },
+ {
+ label: "Download as XLSX",
+ visible: () => true,
+ callback: () => this.onDownloadCsv(),
+ }
+ ],
+ visible: () => true
+ })
constructor(
protected router: Router,
@@ -88,4 +107,5 @@ export class CollectionPublicActionBarComponent implements OnInit {
this.toastService.showToast("Success!", "JSON copied to clipboard")
})
}
+
}
diff --git a/ui/src/app/collection/detail/collection-public/action-bar/horizontal/collection-public-horizontal-action-bar.component.html b/ui/src/app/collection/detail/collection-public/action-bar/horizontal/collection-public-horizontal-action-bar.component.html
index 47b34a62c..e7b206b4f 100644
--- a/ui/src/app/collection/detail/collection-public/action-bar/horizontal/collection-public-horizontal-action-bar.component.html
+++ b/ui/src/app/collection/detail/collection-public/action-bar/horizontal/collection-public-horizontal-action-bar.component.html
@@ -16,17 +16,8 @@
-
+
+
-
-
-
+
+
-
+
+
-
+
+