Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add column renaming support for saved csv content #203

Merged
merged 1 commit into from
Aug 13, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import io.tofhir.server.common.model.{ResourceNotFound, ToFhirRestCall}
import io.tofhir.server.endpoint.CodeSystemEndpoint.SEGMENT_CODE_SYSTEMS
import io.tofhir.server.endpoint.TerminologyServiceManagerEndpoint._
import io.tofhir.common.model.Json4sSupport._
import io.tofhir.server.model.csv.CsvHeader
import io.tofhir.server.service.terminology.CodeSystemService
import io.tofhir.server.repository.terminology.codesystem.ICodeSystemRepository

Expand Down Expand Up @@ -73,26 +74,37 @@ class CodeSystemEndpoint(codeSystemRepository: ICodeSystemRepository) extends La
}

/**
* Route to update code system csv headers
* Headers are passed as a list of strings and overwrite the existing headers in the first line of the CSV file
* Compare existing columns names are new names and adjust the rows to match the new headers if necessary
* Route to update code system CSV headers.
*
* Headers are passed as a list of `CsvHeader` objects, where each `CsvHeader` contains both the `currentName` (new header name) and `previousName` (the previously saved header name).
* The existing headers in the first line of the CSV file are overwritten with the new headers provided. The rows are adjusted to match the new headers if necessary.
*
* e.g. 1:
* Existing headers in a CSV: "header1", "header2" --> New headers: "header1", "header2", "header3"
* Rows belonging to the header1 and header2 are preserved and only header3 is added with a default value for each row (<header3>)
* Existing headers in a CSV:
* - CsvHeader(currentName = "header1", previousName = "header1")
* - CsvHeader(currentName = "header2", previousName = "header2")
* New headers:
* - CsvHeader(currentName = "header1", previousName = "header1")
* - CsvHeader(currentName = "headerChanged", previousName = "header2")
* - CsvHeader(currentName = "header3", previousName = "header3")
* Rows under "header1" are preserved, rows belonging to "header2" are moved to "headerChanged", and "header3" is added with a default value for each row (`<header3>`).
*
* e.g. 2:
* Existing headers in a CSV: "header1", "header2" --> New headers: "header2", "header3"
* Rows belonging to the header1 are removed, header2 is preserved and shifted to the first column with its values
* and header3 is added as second column with a default value for each row (<header3>)
* Existing headers in a CSV:
* - CsvHeader(currentName = "header1", previousName = "header1")
* - CsvHeader(currentName = "header2", previousName = "header2")
* New headers:
* - CsvHeader(currentName = "header2", previousName = "header2")
* - CsvHeader(currentName = "header3", previousName = "header3")
* Rows under "header1" are removed, rows under "header2" are preserved and shifted to the first column, and "header3" is added as the second column with a default value for each row (`<header3>`).
*
* @param terminologyId terminology id
* @param codeSystemId code system id
* @return
*/
private def updateCodeSystemHeaderRoute(terminologyId: String, codeSystemId: String): Route = {
post {
entity(as[Seq[String]]) { headers =>
entity(as[Seq[CsvHeader]]) { headers =>
complete {
service.updateCodeSystemHeader(terminologyId, codeSystemId, headers) map { _ =>
StatusCodes.OK
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import io.tofhir.server.common.model.{ResourceNotFound, ToFhirRestCall}
import io.tofhir.server.endpoint.ConceptMapEndpoint.SEGMENT_CONCEPT_MAPS
import io.tofhir.server.endpoint.TerminologyServiceManagerEndpoint._
import io.tofhir.common.model.Json4sSupport._
import io.tofhir.server.model.csv.CsvHeader
import io.tofhir.server.service.terminology.ConceptMapService
import io.tofhir.server.repository.terminology.conceptmap.IConceptMapRepository

Expand Down Expand Up @@ -75,24 +76,36 @@ class ConceptMapEndpoint(conceptMapRepository: IConceptMapRepository) extends La
/**
* Route to update concept map csv header
*
* Headers are passed as a list of strings and overwrite the existing headers in the first line of the CSV file
* Compare existing columns names are new names and adjust the rows to match the new headers if necessary
* Headers are passed as a list of `CsvHeader` objects and overwrite the existing headers in the first line of the CSV file.
* The `CsvHeader` class represents each header with both its current name and previously saved name.
* The rows are adjusted to match the new headers if necessary.
*
* e.g. 1:
* Existing headers in a CSV: "header1", "header2" --> New headers: "header1", "header2", "header3"
* Rows belonging to the header1 and header2 are preserved and only header3 is added with a default value for each row (<header3>)
* Existing headers in a CSV:
* - CsvHeader(currentName = "header1", previousName = "header1")
* - CsvHeader(currentName = "header2", previousName = "header2")
* New headers:
* - CsvHeader(currentName = "header1", previousName = "header1")
* - CsvHeader(currentName = "headerChanged", previousName = "header2")
* - CsvHeader(currentName = "header3", previousName = "header3")
* Rows under "header1" are preserved, rows belonging to "header2" are moved to "headerChanged", and "header3" is added with a default value for each row (`<header3>`).
*
* e.g. 2:
* Existing headers in a CSV: "header1", "header2" --> New headers: "header2", "header3"
* Rows belonging to the header1 are removed, header2 is preserved and shifted to the first column with its values
* and header3 is added as second column with a default value for each row (<header3>)
* Existing headers in a CSV:
* - CsvHeader(currentName = "header1", previousName = "header1")
* - CsvHeader(currentName = "header2", previousName = "header2")
* New headers:
* - CsvHeader(currentName = "header2", previousName = "header2")
* - CsvHeader(currentName = "header3", previousName = "header3")
* Rows under "header1" are removed, rows under "header2" are preserved and shifted to the first column, and "header3" is added as the second column with a default value for each row (`<header3>`).
*
* @param terminologyId terminology id
* @param conceptMapId concept map id
* @return
*/
private def updateConceptMapHeaderRoute(terminologyId: String, conceptMapId: String): Route = {
post {
entity(as[Seq[String]]) { headers =>
entity(as[Seq[CsvHeader]]) { headers =>
complete {
service.updateConceptMapHeader(terminologyId, conceptMapId, headers) map { _ =>
StatusCodes.OK
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import io.tofhir.engine.Execution.actorSystem.dispatcher
import io.tofhir.server.common.model.ToFhirRestCall
import io.tofhir.server.endpoint.MappingContextEndpoint.{ATTACHMENT, SEGMENT_CONTENT, SEGMENT_CONTEXTS, SEGMENT_FILE, SEGMENT_HEADER}
import io.tofhir.common.model.Json4sSupport._
import io.tofhir.server.model.csv.CsvHeader
import io.tofhir.server.repository.mappingContext.IMappingContextRepository
import io.tofhir.server.service.MappingContextService

Expand Down Expand Up @@ -91,24 +92,36 @@ class MappingContextEndpoint(mappingContextRepository: IMappingContextRepository
/**
* Route to update the mapping context CSV headers
*
* Headers are passed as a list of strings and overwrite the existing headers in the first line of the CSV file
* Compare existing columns names are new names and adjust the rows to match the new headers if necessary
* Headers are passed as a list of `CsvHeader` objects and overwrite the existing headers in the first line of the CSV file.
* The `CsvHeader` class represents each header with both its current name and previously saved name.
* The rows are adjusted to match the new headers if necessary.
*
* e.g. 1:
* Existing headers in a CSV: "header1", "header2" --> New headers: "header1", "header2", "header3"
* Rows belonging to the header1 and header2 are preserved and only header3 is added with a default value for each row (<header3>)
* Existing headers in a CSV:
* - CsvHeader(currentName = "header1", previousName = "header1")
* - CsvHeader(currentName = "header2", previousName = "header2")
* New headers:
* - CsvHeader(currentName = "header1", previousName = "header1")
* - CsvHeader(currentName = "headerChanged", previousName = "header2")
* - CsvHeader(currentName = "header3", previousName = "header3")
* Rows under "header1" are preserved, rows belonging to "header2" are moved to "headerChanged", and "header3" is added with a default value for each row (`<header3>`).
*
* e.g. 2:
* Existing headers in a CSV: "header1", "header2" --> New headers: "header2", "header3"
* Rows belonging to the header1 are removed, header2 is preserved and shifted to the first column with its values
* and header3 is added as second column with a default value for each row (<header3>)
* Existing headers in a CSV:
* - CsvHeader(currentName = "header1", previousName = "header1")
* - CsvHeader(currentName = "header2", previousName = "header2")
* New headers:
* - CsvHeader(currentName = "header2", previousName = "header2")
* - CsvHeader(currentName = "header3", previousName = "header3")
* Rows under "header1" are removed, rows under "header2" are preserved and shifted to the first column, and "header3" is added as the second column with a default value for each row (`<header3>`).
*
* @param projectId project id
* @param id mapping context id
* @return
*/
private def updateMappingContextHeaderRoute(projectId: String, id: String): Route = {
post {
entity(as[Seq[String]]) { headers =>
entity(as[Seq[CsvHeader]]) { headers =>
complete {
service.updateMappingContextHeader(projectId, id, headers) map { _ =>
StatusCodes.OK
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.tofhir.server.model.csv

/**
* Represents a CSV header with a new name and a previously saved name.
* @param currentName The current name of the header.
* @param previousName The previously saved name of the header, used for column name change without data loss
* If a new header is desired to be created, saved name may be any placeholder
*/
case class CsvHeader(currentName: String, previousName: String)
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.tofhir.server.repository.mappingContext

import akka.stream.scaladsl.Source
import akka.util.ByteString
import io.tofhir.server.model.csv.CsvHeader

import scala.concurrent.Future

Expand Down Expand Up @@ -48,7 +49,7 @@ trait IMappingContextRepository {
* @param headers mapping context headers
* @return
*/
def updateMappingContextHeader(projectId: String, id: String, headers: Seq[String]): Future[Unit]
def updateMappingContextHeader(projectId: String, id: String, headers: Seq[CsvHeader]): Future[Unit]

/**
* Save the mapping context content to the repository
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import io.tofhir.engine.Execution.actorSystem.dispatcher
import io.tofhir.engine.util.FileUtils
import io.tofhir.server.common.model.{AlreadyExists, ResourceNotFound}
import io.tofhir.server.model._
import io.tofhir.server.model.csv.CsvHeader
import io.tofhir.server.repository.project.ProjectFolderRepository
import io.tofhir.server.util.CsvUtil

Expand Down Expand Up @@ -114,7 +115,7 @@ class MappingContextFolderRepository(mappingContextRepositoryFolderPath: String,
* @param headers mapping context headers
* @return
*/
def updateMappingContextHeader(projectId: String, id: String, headers: Seq[String]): Future[Unit] = {
def updateMappingContextHeader(projectId: String, id: String, headers: Seq[CsvHeader]): Future[Unit] = {
if (!mappingContextExists(projectId, id)) {
throw ResourceNotFound("Mapping context does not exists.", s"A mapping context with id $id does not exists in the mapping context repository at ${FileUtils.getPath(mappingContextRepositoryFolderPath).toAbsolutePath.toString}")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import io.tofhir.engine.util.FileUtils
import io.tofhir.server.common.model.{AlreadyExists, BadRequest, ResourceNotFound}
import io.tofhir.server.model.TerminologySystem.TerminologyCodeSystem
import io.tofhir.server.model._
import io.tofhir.server.model.csv.CsvHeader
import io.tofhir.server.repository.terminology.TerminologySystemFolderRepository.getTerminologySystemsJsonPath
import io.tofhir.server.util.{CsvUtil, FileOperations}
import org.json4s.jackson.Serialization.writePretty
Expand Down Expand Up @@ -166,7 +167,7 @@ class CodeSystemRepository(terminologySystemFolderPath: String) extends ICodeSys
* @param headers new headers to update
* @return
*/
def updateCodeSystemHeader(terminologyId: String, codeSystemId: String, headers: Seq[String]): Future[Unit] = {
def updateCodeSystemHeader(terminologyId: String, codeSystemId: String, headers: Seq[CsvHeader]): Future[Unit] = {
val codeSystem = findCodeSystemById(terminologyId, codeSystemId)
// get file and update headers
val codeSystemFile = FileUtils.getPath(terminologySystemFolderPath, terminologyId, codeSystem.id).toFile
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.tofhir.server.repository.terminology.codesystem
import akka.stream.scaladsl.Source
import akka.util.ByteString
import io.tofhir.server.model.TerminologySystem.TerminologyCodeSystem
import io.tofhir.server.model.csv.CsvHeader

import scala.concurrent.Future

Expand Down Expand Up @@ -53,7 +54,7 @@ trait ICodeSystemRepository {
* @param headers new headers to update
* @return
*/
def updateCodeSystemHeader(terminologyId: String, codeSystemId: String, headers: Seq[String]): Future[Unit]
def updateCodeSystemHeader(terminologyId: String, codeSystemId: String, headers: Seq[CsvHeader]): Future[Unit]

/**
* Retrieve and save the content of a code system csv file within a terminology
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import io.tofhir.engine.util.FileUtils
import io.tofhir.server.common.model.{AlreadyExists, BadRequest, InternalError, ResourceNotFound}
import io.tofhir.server.model.TerminologySystem.TerminologyConceptMap
import io.tofhir.server.model._
import io.tofhir.server.model.csv.CsvHeader
import io.tofhir.server.repository.terminology.TerminologySystemFolderRepository.getTerminologySystemsJsonPath
import io.tofhir.server.util.{CsvUtil, FileOperations}
import org.json4s.jackson.Serialization.writePretty
Expand Down Expand Up @@ -168,7 +169,7 @@ class ConceptMapRepository(terminologySystemFolderPath: String) extends IConcept
* @param headers new headers to update
* @return
*/
def updateConceptMapHeader(terminologyId: String, conceptMapId: String, headers: Seq[String]): Future[Unit] = {
def updateConceptMapHeader(terminologyId: String, conceptMapId: String, headers: Seq[CsvHeader]): Future[Unit] = {
val conceptMap = findConceptMapById(terminologyId, conceptMapId)
// get file and update headers
val conceptMapFile = FileUtils.getPath(terminologySystemFolderPath, terminologyId, conceptMap.id).toFile
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.tofhir.server.repository.terminology.conceptmap
import akka.stream.scaladsl.Source
import akka.util.ByteString
import io.tofhir.server.model.TerminologySystem.TerminologyConceptMap
import io.tofhir.server.model.csv.CsvHeader

import scala.concurrent.Future

Expand Down Expand Up @@ -53,7 +54,7 @@ trait IConceptMapRepository {
* @param headers new headers to update
* @return
*/
def updateConceptMapHeader(terminologyId: String, conceptMapId: String, headers: Seq[String]): Future[Unit]
def updateConceptMapHeader(terminologyId: String, conceptMapId: String, headers: Seq[CsvHeader]): Future[Unit]

/**
* Retrieve and save the content of a concept map csv file within a terminology
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.tofhir.server.service
import akka.stream.scaladsl.Source
import akka.util.ByteString
import com.typesafe.scalalogging.LazyLogging
import io.tofhir.server.model.csv.CsvHeader
import io.tofhir.server.repository.mappingContext.IMappingContextRepository

import scala.concurrent.Future
Expand Down Expand Up @@ -49,7 +50,7 @@ class MappingContextService(mappingContextRepository: IMappingContextRepository)
* @param headers mapping context headers
* @return
*/
def updateMappingContextHeader(projectId: String, id: String, headers: Seq[String]): Future[Unit] = {
def updateMappingContextHeader(projectId: String, id: String, headers: Seq[CsvHeader]): Future[Unit] = {
mappingContextRepository.updateMappingContextHeader(projectId, id, headers)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import akka.stream.scaladsl.Source
import akka.util.ByteString
import com.typesafe.scalalogging.LazyLogging
import io.tofhir.server.model.TerminologySystem.TerminologyCodeSystem
import io.tofhir.server.model.csv.CsvHeader
import io.tofhir.server.repository.terminology.codesystem.ICodeSystemRepository

import scala.concurrent.Future
Expand Down Expand Up @@ -67,7 +68,7 @@ class CodeSystemService(codeSystemRepository: ICodeSystemRepository) extends Laz
* @param headers new headers to update
* @return
*/
def updateCodeSystemHeader(terminologyId: String, codeSystemId: String, headers: Seq[String]): Future[Unit] = {
def updateCodeSystemHeader(terminologyId: String, codeSystemId: String, headers: Seq[CsvHeader]): Future[Unit] = {
codeSystemRepository.updateCodeSystemHeader(terminologyId, codeSystemId, headers)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import akka.stream.scaladsl.Source
import akka.util.ByteString
import com.typesafe.scalalogging.LazyLogging
import io.tofhir.server.model.TerminologySystem.TerminologyConceptMap
import io.tofhir.server.model.csv.CsvHeader
import io.tofhir.server.repository.terminology.conceptmap.IConceptMapRepository

import scala.concurrent.Future
Expand Down Expand Up @@ -67,7 +68,7 @@ class ConceptMapService(conceptMapRepository: IConceptMapRepository) extends Laz
* @param headers new headers to update
* @return
*/
def updateConceptMapHeader(terminologyId: String, conceptMapId: String, headers: Seq[String]): Future[Unit] = {
def updateConceptMapHeader(terminologyId: String, conceptMapId: String, headers: Seq[CsvHeader]): Future[Unit] = {
conceptMapRepository.updateConceptMapHeader(terminologyId, conceptMapId, headers)
}

Expand Down
Loading
Loading