Skip to content

Commit

Permalink
✨ Add column renaming support for saved csv content
Browse files Browse the repository at this point in the history
  • Loading branch information
camemre49 committed Aug 12, 2024
1 parent 7546924 commit b43fb69
Show file tree
Hide file tree
Showing 14 changed files with 116 additions and 49 deletions.
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

0 comments on commit b43fb69

Please sign in to comment.