From 0fc769e8e2141451da34247e7733fcf0a3396b9c Mon Sep 17 00:00:00 2001 From: Kseniya Tomskikh Date: Thu, 17 Aug 2017 16:34:06 +0700 Subject: Created SlickQueryBuilder --- .../pdsuicommon/db/SlickPostgresQueryBuilder.scala | 103 +++++++ .../driver/pdsuicommon/db/SlickQueryBuilder.scala | 341 +++++++++++++++++++++ 2 files changed, 444 insertions(+) create mode 100644 src/main/scala/xyz/driver/pdsuicommon/db/SlickPostgresQueryBuilder.scala create mode 100644 src/main/scala/xyz/driver/pdsuicommon/db/SlickQueryBuilder.scala (limited to 'src/main') diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/SlickPostgresQueryBuilder.scala b/src/main/scala/xyz/driver/pdsuicommon/db/SlickPostgresQueryBuilder.scala new file mode 100644 index 0000000..66434f0 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuicommon/db/SlickPostgresQueryBuilder.scala @@ -0,0 +1,103 @@ +package xyz.driver.pdsuicommon.db + +import java.time.{LocalDateTime, ZoneOffset} + +import slick.driver.JdbcProfile +import slick.jdbc.GetResult +import xyz.driver.core.database.SlickDal + +import scala.collection.breakOut +import scala.concurrent.ExecutionContext + +object SlickPostgresQueryBuilder { + + import xyz.driver.pdsuicommon.db.SlickQueryBuilder._ + + def apply[T](tableName: String, + lastUpdateFieldName: Option[String], + nullableFields: Set[String], + links: Set[SlickTableLink], + runner: Runner[T], + countRunner: CountRunner)(implicit sqlContext: SlickDal, + profile: JdbcProfile, + getResult: GetResult[T], + ec: ExecutionContext): SlickPostgresQueryBuilder[T] = { + val parameters = SlickPostgresQueryBuilderParameters( + tableData = TableData(tableName, lastUpdateFieldName, nullableFields), + links = links.map(x => x.foreignTableName -> x)(breakOut) + ) + new SlickPostgresQueryBuilder[T](parameters)(runner, countRunner) + } + + def apply[T](tableName: String, + lastUpdateFieldName: Option[String], + nullableFields: Set[String], + links: Set[SlickTableLink])(implicit sqlContext: SlickDal, + profile: JdbcProfile, + getResult: GetResult[T], + ec: ExecutionContext): SlickPostgresQueryBuilder[T] = { + apply(tableName, SlickQueryBuilderParameters.AllFields, lastUpdateFieldName, nullableFields, links) + } + + def apply[T](tableName: String, + fields: Set[String], + lastUpdateFieldName: Option[String], + nullableFields: Set[String], + links: Set[SlickTableLink])(implicit sqlContext: SlickDal, + profile: JdbcProfile, + getResult: GetResult[T], + ec: ExecutionContext): SlickPostgresQueryBuilder[T] = { + + val runner: Runner[T] = { parameters => + val sql = parameters.toSql(countQuery = false, fields = fields).as[T] + sqlContext.execute(sql) + } + + val countRunner: CountRunner = { parameters => + implicit val getCountResult: GetResult[(Int, Option[LocalDateTime])] = GetResult({ r => + val count = r.rs.getInt(1) + val lastUpdate = if (parameters.tableData.lastUpdateFieldName.isDefined) { + Option(r.rs.getTimestamp(2)).map(timestampToLocalDateTime) + } else None + (count, lastUpdate) + }) + val sql = parameters.toSql(countQuery = true).as[(Int, Option[LocalDateTime])] + sqlContext.execute(sql).map(_.head) + } + + apply[T]( + tableName = tableName, + lastUpdateFieldName = lastUpdateFieldName, + nullableFields = nullableFields, + links = links, + runner = runner, + countRunner = countRunner + ) + } + + def timestampToLocalDateTime(timestamp: java.sql.Timestamp): LocalDateTime = { + LocalDateTime.ofInstant(timestamp.toInstant, ZoneOffset.UTC) + } +} + +class SlickPostgresQueryBuilder[T](parameters: SlickPostgresQueryBuilderParameters)( + implicit runner: SlickQueryBuilder.Runner[T], + countRunner: SlickQueryBuilder.CountRunner) + extends SlickQueryBuilder[T](parameters) { + + def withFilter(newFilter: SearchFilterExpr): SlickQueryBuilder[T] = { + new SlickPostgresQueryBuilder[T](parameters.copy(filter = newFilter)) + } + + def withSorting(newSorting: Sorting): SlickQueryBuilder[T] = { + new SlickPostgresQueryBuilder[T](parameters.copy(sorting = newSorting)) + } + + def withPagination(newPagination: Pagination): SlickQueryBuilder[T] = { + new SlickPostgresQueryBuilder[T](parameters.copy(pagination = Some(newPagination))) + } + + def resetPagination: SlickQueryBuilder[T] = { + new SlickPostgresQueryBuilder[T](parameters.copy(pagination = None)) + } +} diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/SlickQueryBuilder.scala b/src/main/scala/xyz/driver/pdsuicommon/db/SlickQueryBuilder.scala new file mode 100644 index 0000000..e45ff87 --- /dev/null +++ b/src/main/scala/xyz/driver/pdsuicommon/db/SlickQueryBuilder.scala @@ -0,0 +1,341 @@ +package xyz.driver.pdsuicommon.db + +import java.sql.PreparedStatement +import java.time.LocalDateTime + +import slick.driver.JdbcProfile +import slick.jdbc.{PositionedParameters, SQLActionBuilder, SetParameter} +import xyz.driver.pdsuicommon.db.Sorting.{Dimension, Sequential} +import xyz.driver.pdsuicommon.db.SortingOrder.{Ascending, Descending} + +import scala.concurrent.{ExecutionContext, Future} + +object SlickQueryBuilder { + + type Runner[T] = SlickQueryBuilderParameters => Future[Seq[T]] + + type CountResult = Future[(Int, Option[LocalDateTime])] + + type CountRunner = SlickQueryBuilderParameters => CountResult + + /** + * Binder for PreparedStatement + */ + type Binder = PreparedStatement => PreparedStatement + + final case class TableData(tableName: String, + lastUpdateFieldName: Option[String] = None, + nullableFields: Set[String] = Set.empty) + + val AllFields = Set("*") + + implicit class SQLActionBuilderConcat(a: SQLActionBuilder) { + def concat(b: SQLActionBuilder): SQLActionBuilder = { + SQLActionBuilder(a.queryParts ++ b.queryParts, new SetParameter[Unit] { + def apply(p: Unit, pp: PositionedParameters): Unit = { + a.unitPConv.apply(p, pp) + b.unitPConv.apply(p, pp) + } + }) + } + } +} + +final case class SlickTableLink(keyColumnName: String, foreignTableName: String, foreignKeyColumnName: String) + +object SlickQueryBuilderParameters { + val AllFields = Set("*") +} + +sealed trait SlickQueryBuilderParameters { + import SlickQueryBuilder._ + + def tableData: SlickQueryBuilder.TableData + def links: Map[String, SlickTableLink] + def filter: SearchFilterExpr + def sorting: Sorting + def pagination: Option[Pagination] + + def findLink(tableName: String): SlickTableLink = links.get(tableName) match { + case None => throw new IllegalArgumentException(s"Cannot find a link for `$tableName`") + case Some(link) => link + } + + def toSql(countQuery: Boolean = false)(implicit profile: JdbcProfile): SQLActionBuilder = { + toSql(countQuery, QueryBuilderParameters.AllFields) + } + + def toSql(countQuery: Boolean, fields: Set[String])(implicit profile: JdbcProfile): SQLActionBuilder = { + import profile.api._ + val escapedTableName = tableData.tableName + val fieldsSql: String = if (countQuery) { + val suffix: String = tableData.lastUpdateFieldName match { + case Some(lastUpdateField) => s", max($escapedTableName.$lastUpdateField)" + case None => "" + } + "count(*)" + suffix + } else { + if (fields == SlickQueryBuilderParameters.AllFields) { + s"$escapedTableName.*" + } else { + fields + .map { field => + s"$escapedTableName.$field" + } + .mkString(", ") + } + } + val where = filterToSql(escapedTableName, filter) + val orderBy = sortingToSql(escapedTableName, sorting) + + val limitSql = limitToSql() + + val sql = sql"select #$fieldsSql from #$escapedTableName" + + val filtersTableLinks: Seq[SlickTableLink] = { + import SearchFilterExpr._ + def aux(expr: SearchFilterExpr): Seq[SlickTableLink] = expr match { + case Atom.TableName(tableName) => List(findLink(tableName)) + case Intersection(xs) => xs.flatMap(aux) + case Union(xs) => xs.flatMap(aux) + case _ => Nil + } + aux(filter) + } + + val sortingTableLinks: Seq[SlickTableLink] = Sorting.collect(sorting) { + case Dimension(Some(foreignTableName), _, _) => findLink(foreignTableName) + } + + // Combine links from sorting and filter without duplicates + val foreignTableLinks = (filtersTableLinks ++ sortingTableLinks).distinct + + foreignTableLinks.foreach { + case SlickTableLink(keyColumnName, foreignTableName, foreignKeyColumnName) => + sql = sql concat sql"""inner join #$foreignTableName + on #$escapedTableName.#$keyColumnName = #$foreignTableName.#$foreignKeyColumnName""" + } + + if (where.toString.nonEmpty) { + sql = sql concat sql"where #$where" + } + + if (orderBy.toString.nonEmpty && !countQuery) { + sql = sql concat sql"order by #$orderBy" + } + + if (limitSql.toString.nonEmpty && !countQuery) { + sql = sql concat sql"#$limitSql" + } + + sql + } + + /** + * Converts filter expression to SQL expression. + * + * @return Returns SQL string and list of values for binding in prepared statement. + */ + protected def filterToSql(escapedTableName: String, filter: SearchFilterExpr)( + implicit profile: JdbcProfile): SQLActionBuilder = { + import SearchFilterBinaryOperation._ + import SearchFilterExpr._ + import profile.api._ + + def isNull(string: AnyRef) = Option(string).isEmpty || string.toString.toLowerCase == "null" + + def escapeDimension(dimension: SearchFilterExpr.Dimension) = { + val tableName = escapedTableName + s"$tableName.$dimension.name" + } + + def filterToSqlMultiple(operands: Seq[SearchFilterExpr]) = operands.collect { + case x if !SearchFilterExpr.isEmpty(x) => filterToSql(escapedTableName, x) + } + + def multipleSqlToAction(op: String, conditions: Seq[SQLActionBuilder]): SQLActionBuilder = { + var first = true + var filterSql = sql"(" + for (condition <- conditions) { + if (first) { + filterSql = filterSql concat condition + first = false + } else { + filterSql = filterSql concat sql" #$op " concat condition + } + } + filterSql concat sql")" + } + + filter match { + case x if isEmpty(x) => + sql"" + + case AllowAll => + sql"1" + + case DenyAll => + sql"0" + + case Atom.Binary(dimension, Eq, value) if isNull(value) => + sql"#${escapeDimension(dimension)} is NULL" + + case Atom.Binary(dimension, NotEq, value) if isNull(value) => + sql"#${escapeDimension(dimension)} is not NULL" + + case Atom.Binary(dimension, NotEq, value) if tableData.nullableFields.contains(dimension.name) => + // In MySQL NULL <> Any === NULL + // So, to handle NotEq for nullable fields we need to use more complex SQL expression. + // http://dev.mysql.com/doc/refman/5.7/en/working-with-null.html + val escapedColumn = escapeDimension(dimension) + sql"(#$escapedColumn is null or #$escapedColumn != #$value)" + + case Atom.Binary(dimension, op, value) => + val operator = op match { + case Eq => sql"=" + case NotEq => sql"!=" + case Like => sql"like" + case Gt => sql">" + case GtEq => sql">=" + case Lt => sql"<" + case LtEq => sql"<=" + } + sql"#${escapeDimension(dimension)}" concat operator concat sql"#$value" + + case Atom.NAry(dimension, op, values) => + val sqlOp = op match { + case SearchFilterNAryOperation.In => sql"in" + case SearchFilterNAryOperation.NotIn => sql"not in" + } + + val formattedValues = if (values.nonEmpty) { + sql"#$values" + } else sql"NULL" + sql"#${escapeDimension(dimension)}" concat sqlOp concat formattedValues + + case Intersection(operands) => + multipleSqlToAction("and", filterToSqlMultiple(operands)) + + case Union(operands) => + multipleSqlToAction("or", filterToSqlMultiple(operands)) + } + } + + protected def limitToSql()(implicit profile: JdbcProfile): SQLActionBuilder + + /** + * @param escapedMainTableName Should be escaped + */ + protected def sortingToSql(escapedMainTableName: String, sorting: Sorting)( + implicit profile: JdbcProfile): SQLActionBuilder = { + import profile.api._ + sorting match { + case Dimension(optSortingTableName, field, order) => + val sortingTableName = optSortingTableName.getOrElse(escapedMainTableName) + val fullName = s"$sortingTableName.$field" + + sql"#$fullName #${orderToSql(order)}" + + case Sequential(xs) => + sql"#${xs.map(sortingToSql(escapedMainTableName, _)).mkString(", ")}" + } + } + + protected def orderToSql(x: SortingOrder): String = x match { + case Ascending => "asc" + case Descending => "desc" + } + + protected def binder(bindings: List[AnyRef])(bind: PreparedStatement): PreparedStatement = { + bindings.zipWithIndex.foreach { + case (binding, index) => + bind.setObject(index + 1, binding) + } + + bind + } + +} + +final case class SlickPostgresQueryBuilderParameters(tableData: SlickQueryBuilder.TableData, + links: Map[String, SlickTableLink] = Map.empty, + filter: SearchFilterExpr = SearchFilterExpr.Empty, + sorting: Sorting = Sorting.Empty, + pagination: Option[Pagination] = None) + extends SlickQueryBuilderParameters { + + def limitToSql()(implicit profile: JdbcProfile): SQLActionBuilder = { + import profile.api._ + pagination.map { pagination => + val startFrom = (pagination.pageNumber - 1) * pagination.pageSize + sql"limit #${pagination.pageSize} OFFSET #$startFrom" + } getOrElse (sql"") + } + +} + +/** + * @param links Links to another tables grouped by foreignTableName + */ +final case class SlickMysqlQueryBuilderParameters(tableData: SlickQueryBuilder.TableData, + links: Map[String, SlickTableLink] = Map.empty, + filter: SearchFilterExpr = SearchFilterExpr.Empty, + sorting: Sorting = Sorting.Empty, + pagination: Option[Pagination] = None) + extends SlickQueryBuilderParameters { + + def limitToSql()(implicit profile: JdbcProfile): SQLActionBuilder = { + import profile.api._ + pagination + .map { pagination => + val startFrom = (pagination.pageNumber - 1) * pagination.pageSize + sql"limit #$startFrom, #${pagination.pageSize}" + } + .getOrElse(sql"") + } + +} + +abstract class SlickQueryBuilder[T](val parameters: SlickQueryBuilderParameters)( + implicit runner: SlickQueryBuilder.Runner[T], + countRunner: SlickQueryBuilder.CountRunner) { + + def run()(implicit ec: ExecutionContext): Future[Seq[T]] = runner(parameters) + + def runCount()(implicit ec: ExecutionContext): SlickQueryBuilder.CountResult = countRunner(parameters) + + /** + * Runs the query and returns total found rows without considering of pagination. + */ + def runWithCount()(implicit ec: ExecutionContext): Future[(Seq[T], Int, Option[LocalDateTime])] = { + for { + all <- run + (total, lastUpdate) <- runCount + } yield (all, total, lastUpdate) + } + + def withFilter(newFilter: SearchFilterExpr): SlickQueryBuilder[T] + + def withFilter(filter: Option[SearchFilterExpr]): SlickQueryBuilder[T] = { + filter.fold(this)(withFilter) + } + + def resetFilter: SlickQueryBuilder[T] = withFilter(SearchFilterExpr.Empty) + + def withSorting(newSorting: Sorting): SlickQueryBuilder[T] + + def withSorting(sorting: Option[Sorting]): SlickQueryBuilder[T] = { + sorting.fold(this)(withSorting) + } + + def resetSorting: SlickQueryBuilder[T] = withSorting(Sorting.Empty) + + def withPagination(newPagination: Pagination): SlickQueryBuilder[T] + + def withPagination(pagination: Option[Pagination]): SlickQueryBuilder[T] = { + pagination.fold(this)(withPagination) + } + + def resetPagination: SlickQueryBuilder[T] + +} -- cgit v1.2.3 From 2c17e8696ea1e5cbe3f557dfc62b1ca9c66a1135 Mon Sep 17 00:00:00 2001 From: Kseniya Tomskikh Date: Fri, 18 Aug 2017 16:17:05 +0700 Subject: Fixed compile errors --- .../pdsuicommon/db/SlickPostgresQueryBuilder.scala | 7 +- .../driver/pdsuicommon/db/SlickQueryBuilder.scala | 76 ++++++++++++---------- 2 files changed, 48 insertions(+), 35 deletions(-) (limited to 'src/main') diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/SlickPostgresQueryBuilder.scala b/src/main/scala/xyz/driver/pdsuicommon/db/SlickPostgresQueryBuilder.scala index 66434f0..3ff1688 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/db/SlickPostgresQueryBuilder.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/db/SlickPostgresQueryBuilder.scala @@ -5,11 +5,12 @@ import java.time.{LocalDateTime, ZoneOffset} import slick.driver.JdbcProfile import slick.jdbc.GetResult import xyz.driver.core.database.SlickDal +import xyz.driver.pdsuicommon.logging._ import scala.collection.breakOut import scala.concurrent.ExecutionContext -object SlickPostgresQueryBuilder { +object SlickPostgresQueryBuilder extends PhiLogging { import xyz.driver.pdsuicommon.db.SlickQueryBuilder._ @@ -36,7 +37,7 @@ object SlickPostgresQueryBuilder { profile: JdbcProfile, getResult: GetResult[T], ec: ExecutionContext): SlickPostgresQueryBuilder[T] = { - apply(tableName, SlickQueryBuilderParameters.AllFields, lastUpdateFieldName, nullableFields, links) + apply[T](tableName, SlickQueryBuilderParameters.AllFields, lastUpdateFieldName, nullableFields, links) } def apply[T](tableName: String, @@ -50,6 +51,7 @@ object SlickPostgresQueryBuilder { val runner: Runner[T] = { parameters => val sql = parameters.toSql(countQuery = false, fields = fields).as[T] + logger.debug(phi"${Unsafe(sql)}") sqlContext.execute(sql) } @@ -62,6 +64,7 @@ object SlickPostgresQueryBuilder { (count, lastUpdate) }) val sql = parameters.toSql(countQuery = true).as[(Int, Option[LocalDateTime])] + logger.debug(phi"${Unsafe(sql)}") sqlContext.execute(sql).map(_.head) } diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/SlickQueryBuilder.scala b/src/main/scala/xyz/driver/pdsuicommon/db/SlickQueryBuilder.scala index e45ff87..2eb8b9b 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/db/SlickQueryBuilder.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/db/SlickQueryBuilder.scala @@ -110,25 +110,33 @@ sealed trait SlickQueryBuilderParameters { // Combine links from sorting and filter without duplicates val foreignTableLinks = (filtersTableLinks ++ sortingTableLinks).distinct - foreignTableLinks.foreach { - case SlickTableLink(keyColumnName, foreignTableName, foreignKeyColumnName) => - sql = sql concat sql"""inner join #$foreignTableName - on #$escapedTableName.#$keyColumnName = #$foreignTableName.#$foreignKeyColumnName""" + def fkSql(fkLinksSql: SQLActionBuilder, tableLinks: Seq[SlickTableLink]): SQLActionBuilder = { + if (tableLinks.nonEmpty) { + tableLinks.head match { + case SlickTableLink(keyColumnName, foreignTableName, foreignKeyColumnName) => + fkSql( + fkLinksSql concat sql""" inner join #$foreignTableName + on #$escapedTableName.#$keyColumnName = #$foreignTableName.#$foreignKeyColumnName""", + tableLinks.tail + ) + } + } else fkLinksSql } + val foreignTableLinksSql = fkSql(sql"", foreignTableLinks) - if (where.toString.nonEmpty) { - sql = sql concat sql"where #$where" - } + val whereSql = if (where.toString.nonEmpty) { + sql" where " concat where + } else sql"" - if (orderBy.toString.nonEmpty && !countQuery) { - sql = sql concat sql"order by #$orderBy" - } + val orderSql = if (orderBy.toString.nonEmpty && !countQuery) { + sql" order by" concat orderBy + } else sql"" - if (limitSql.toString.nonEmpty && !countQuery) { - sql = sql concat sql"#$limitSql" - } + val limSql = if (limitSql.toString.nonEmpty && !countQuery) { + limitSql + } else sql"" - sql + sql concat foreignTableLinksSql concat whereSql concat orderSql concat limSql } /** @@ -146,25 +154,25 @@ sealed trait SlickQueryBuilderParameters { def escapeDimension(dimension: SearchFilterExpr.Dimension) = { val tableName = escapedTableName - s"$tableName.$dimension.name" + s"$tableName.${dimension.name}" } def filterToSqlMultiple(operands: Seq[SearchFilterExpr]) = operands.collect { case x if !SearchFilterExpr.isEmpty(x) => filterToSql(escapedTableName, x) } - def multipleSqlToAction(op: String, conditions: Seq[SQLActionBuilder]): SQLActionBuilder = { - var first = true - var filterSql = sql"(" - for (condition <- conditions) { + def multipleSqlToAction(first: Boolean, + op: String, + conditions: Seq[SQLActionBuilder], + sql: SQLActionBuilder): SQLActionBuilder = { + if (conditions.nonEmpty) { + val condition = conditions.head if (first) { - filterSql = filterSql concat condition - first = false + multipleSqlToAction(false, op, conditions.tail, condition) } else { - filterSql = filterSql concat sql" #$op " concat condition + multipleSqlToAction(false, op, conditions.tail, sql concat sql" #${op} " concat condition) } - } - filterSql concat sql")" + } else sql } filter match { @@ -188,7 +196,7 @@ sealed trait SlickQueryBuilderParameters { // So, to handle NotEq for nullable fields we need to use more complex SQL expression. // http://dev.mysql.com/doc/refman/5.7/en/working-with-null.html val escapedColumn = escapeDimension(dimension) - sql"(#$escapedColumn is null or #$escapedColumn != #$value)" + sql"(#${escapedColumn} is null or #${escapedColumn} != ${value.toString})" case Atom.Binary(dimension, op, value) => val operator = op match { @@ -200,7 +208,7 @@ sealed trait SlickQueryBuilderParameters { case Lt => sql"<" case LtEq => sql"<=" } - sql"#${escapeDimension(dimension)}" concat operator concat sql"#$value" + sql"#${escapeDimension(dimension)}" concat operator concat sql"""${value.toString}""" case Atom.NAry(dimension, op, values) => val sqlOp = op match { @@ -209,15 +217,17 @@ sealed trait SlickQueryBuilderParameters { } val formattedValues = if (values.nonEmpty) { - sql"#$values" + sql"${values.mkString(",")}" } else sql"NULL" sql"#${escapeDimension(dimension)}" concat sqlOp concat formattedValues case Intersection(operands) => - multipleSqlToAction("and", filterToSqlMultiple(operands)) + val filter = multipleSqlToAction(true, "and", filterToSqlMultiple(operands), sql"") + sql"(" concat filter concat sql")" case Union(operands) => - multipleSqlToAction("or", filterToSqlMultiple(operands)) + val filter = multipleSqlToAction(true, "or", filterToSqlMultiple(operands), sql"") + sql"(" concat filter concat sql")" } } @@ -234,10 +244,10 @@ sealed trait SlickQueryBuilderParameters { val sortingTableName = optSortingTableName.getOrElse(escapedMainTableName) val fullName = s"$sortingTableName.$field" - sql"#$fullName #${orderToSql(order)}" + sql"#$fullName ${orderToSql(order)}" case Sequential(xs) => - sql"#${xs.map(sortingToSql(escapedMainTableName, _)).mkString(", ")}" + sql"${xs.map(sortingToSql(escapedMainTableName, _)).mkString(", ")}" } } @@ -268,7 +278,7 @@ final case class SlickPostgresQueryBuilderParameters(tableData: SlickQueryBuilde import profile.api._ pagination.map { pagination => val startFrom = (pagination.pageNumber - 1) * pagination.pageSize - sql"limit #${pagination.pageSize} OFFSET #$startFrom" + sql"limit ${pagination.pageSize} OFFSET $startFrom" } getOrElse (sql"") } @@ -289,7 +299,7 @@ final case class SlickMysqlQueryBuilderParameters(tableData: SlickQueryBuilder.T pagination .map { pagination => val startFrom = (pagination.pageNumber - 1) * pagination.pageSize - sql"limit #$startFrom, #${pagination.pageSize}" + sql"limit $startFrom, ${pagination.pageSize}" } .getOrElse(sql"") } -- cgit v1.2.3 From d5ecec043a3d70dd09bda8a79fcd188f411b47df Mon Sep 17 00:00:00 2001 From: Kseniya Tomskikh Date: Mon, 21 Aug 2017 15:53:05 +0700 Subject: Fixed builder errors --- .../pdsuicommon/db/SlickPostgresQueryBuilder.scala | 18 +++++-- .../driver/pdsuicommon/db/SlickQueryBuilder.scala | 62 ++++++++++++---------- 2 files changed, 48 insertions(+), 32 deletions(-) (limited to 'src/main') diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/SlickPostgresQueryBuilder.scala b/src/main/scala/xyz/driver/pdsuicommon/db/SlickPostgresQueryBuilder.scala index 3ff1688..f882441 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/db/SlickPostgresQueryBuilder.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/db/SlickPostgresQueryBuilder.scala @@ -14,7 +14,8 @@ object SlickPostgresQueryBuilder extends PhiLogging { import xyz.driver.pdsuicommon.db.SlickQueryBuilder._ - def apply[T](tableName: String, + def apply[T](databaseName: String, + tableName: String, lastUpdateFieldName: Option[String], nullableFields: Set[String], links: Set[SlickTableLink], @@ -24,23 +25,31 @@ object SlickPostgresQueryBuilder extends PhiLogging { getResult: GetResult[T], ec: ExecutionContext): SlickPostgresQueryBuilder[T] = { val parameters = SlickPostgresQueryBuilderParameters( + databaseName = databaseName, tableData = TableData(tableName, lastUpdateFieldName, nullableFields), links = links.map(x => x.foreignTableName -> x)(breakOut) ) new SlickPostgresQueryBuilder[T](parameters)(runner, countRunner) } - def apply[T](tableName: String, + def apply[T](databaseName: String, + tableName: String, lastUpdateFieldName: Option[String], nullableFields: Set[String], links: Set[SlickTableLink])(implicit sqlContext: SlickDal, profile: JdbcProfile, getResult: GetResult[T], ec: ExecutionContext): SlickPostgresQueryBuilder[T] = { - apply[T](tableName, SlickQueryBuilderParameters.AllFields, lastUpdateFieldName, nullableFields, links) + apply[T](databaseName, + tableName, + SlickQueryBuilderParameters.AllFields, + lastUpdateFieldName, + nullableFields, + links) } - def apply[T](tableName: String, + def apply[T](databaseName: String, + tableName: String, fields: Set[String], lastUpdateFieldName: Option[String], nullableFields: Set[String], @@ -69,6 +78,7 @@ object SlickPostgresQueryBuilder extends PhiLogging { } apply[T]( + databaseName = databaseName, tableName = tableName, lastUpdateFieldName = lastUpdateFieldName, nullableFields = nullableFields, diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/SlickQueryBuilder.scala b/src/main/scala/xyz/driver/pdsuicommon/db/SlickQueryBuilder.scala index 2eb8b9b..79cb114 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/db/SlickQueryBuilder.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/db/SlickQueryBuilder.scala @@ -50,12 +50,15 @@ object SlickQueryBuilderParameters { sealed trait SlickQueryBuilderParameters { import SlickQueryBuilder._ + def databaseName: String def tableData: SlickQueryBuilder.TableData def links: Map[String, SlickTableLink] def filter: SearchFilterExpr def sorting: Sorting def pagination: Option[Pagination] + def qs: String + def findLink(tableName: String): SlickTableLink = links.get(tableName) match { case None => throw new IllegalArgumentException(s"Cannot find a link for `$tableName`") case Some(link) => link @@ -67,20 +70,20 @@ sealed trait SlickQueryBuilderParameters { def toSql(countQuery: Boolean, fields: Set[String])(implicit profile: JdbcProfile): SQLActionBuilder = { import profile.api._ - val escapedTableName = tableData.tableName + val escapedTableName = s"""$qs$databaseName$qs.$qs${tableData.tableName}$qs""" val fieldsSql: String = if (countQuery) { val suffix: String = tableData.lastUpdateFieldName match { - case Some(lastUpdateField) => s", max($escapedTableName.$lastUpdateField)" + case Some(lastUpdateField) => s", max($escapedTableName.$qs$lastUpdateField$qs)" case None => "" } - "count(*)" + suffix + s"count(*) $suffix" } else { if (fields == SlickQueryBuilderParameters.AllFields) { s"$escapedTableName.*" } else { fields .map { field => - s"$escapedTableName.$field" + s"$escapedTableName.$qs$field$qs" } .mkString(", ") } @@ -90,7 +93,7 @@ sealed trait SlickQueryBuilderParameters { val limitSql = limitToSql() - val sql = sql"select #$fieldsSql from #$escapedTableName" + val sql = sql"""select #$fieldsSql from #$escapedTableName""" val filtersTableLinks: Seq[SlickTableLink] = { import SearchFilterExpr._ @@ -114,26 +117,25 @@ sealed trait SlickQueryBuilderParameters { if (tableLinks.nonEmpty) { tableLinks.head match { case SlickTableLink(keyColumnName, foreignTableName, foreignKeyColumnName) => - fkSql( - fkLinksSql concat sql""" inner join #$foreignTableName - on #$escapedTableName.#$keyColumnName = #$foreignTableName.#$foreignKeyColumnName""", - tableLinks.tail - ) + val escapedForeignTableName = s"$qs$databaseName$qs.$qs$foreignTableName$qs" + val join = sql""" inner join #$escapedForeignTableName + on #$escapedTableName.#$qs#$keyColumnName#$qs=#$escapedForeignTableName.#$qs#$foreignKeyColumnName#$qs""" + fkSql(fkLinksSql concat join, tableLinks.tail) } } else fkLinksSql } val foreignTableLinksSql = fkSql(sql"", foreignTableLinks) - val whereSql = if (where.toString.nonEmpty) { + val whereSql = if (where.queryParts.size > 1) { sql" where " concat where } else sql"" - val orderSql = if (orderBy.toString.nonEmpty && !countQuery) { - sql" order by" concat orderBy + val orderSql = if (orderBy.nonEmpty && !countQuery) { + sql" order by #$orderBy" } else sql"" - val limSql = if (limitSql.toString.nonEmpty && !countQuery) { - limitSql + val limSql = if (limitSql.queryParts.size > 1 && !countQuery) { + sql" " concat limitSql } else sql"" sql concat foreignTableLinksSql concat whereSql concat orderSql concat limSql @@ -153,8 +155,7 @@ sealed trait SlickQueryBuilderParameters { def isNull(string: AnyRef) = Option(string).isEmpty || string.toString.toLowerCase == "null" def escapeDimension(dimension: SearchFilterExpr.Dimension) = { - val tableName = escapedTableName - s"$tableName.${dimension.name}" + s"$escapedTableName.$qs${dimension.name}$qs" } def filterToSqlMultiple(operands: Seq[SearchFilterExpr]) = operands.collect { @@ -236,18 +237,17 @@ sealed trait SlickQueryBuilderParameters { /** * @param escapedMainTableName Should be escaped */ - protected def sortingToSql(escapedMainTableName: String, sorting: Sorting)( - implicit profile: JdbcProfile): SQLActionBuilder = { - import profile.api._ + protected def sortingToSql(escapedMainTableName: String, sorting: Sorting)(implicit profile: JdbcProfile): String = { sorting match { case Dimension(optSortingTableName, field, order) => - val sortingTableName = optSortingTableName.getOrElse(escapedMainTableName) - val fullName = s"$sortingTableName.$field" + val sortingTableName = + optSortingTableName.map(x => s"$qs$databaseName$qs.$qs$x$qs").getOrElse(escapedMainTableName) + val fullName = s"$sortingTableName.$qs$field$qs" - sql"#$fullName ${orderToSql(order)}" + s"$fullName ${orderToSql(order)}" case Sequential(xs) => - sql"${xs.map(sortingToSql(escapedMainTableName, _)).mkString(", ")}" + xs.map(sortingToSql(escapedMainTableName, _)).mkString(", ") } } @@ -267,7 +267,8 @@ sealed trait SlickQueryBuilderParameters { } -final case class SlickPostgresQueryBuilderParameters(tableData: SlickQueryBuilder.TableData, +final case class SlickPostgresQueryBuilderParameters(databaseName: String, + tableData: SlickQueryBuilder.TableData, links: Map[String, SlickTableLink] = Map.empty, filter: SearchFilterExpr = SearchFilterExpr.Empty, sorting: Sorting = Sorting.Empty, @@ -278,16 +279,19 @@ final case class SlickPostgresQueryBuilderParameters(tableData: SlickQueryBuilde import profile.api._ pagination.map { pagination => val startFrom = (pagination.pageNumber - 1) * pagination.pageSize - sql"limit ${pagination.pageSize} OFFSET $startFrom" + sql"limit #${pagination.pageSize} OFFSET #$startFrom" } getOrElse (sql"") } + val qs = """"""" + } /** * @param links Links to another tables grouped by foreignTableName */ -final case class SlickMysqlQueryBuilderParameters(tableData: SlickQueryBuilder.TableData, +final case class SlickMysqlQueryBuilderParameters(databaseName: String, + tableData: SlickQueryBuilder.TableData, links: Map[String, SlickTableLink] = Map.empty, filter: SearchFilterExpr = SearchFilterExpr.Empty, sorting: Sorting = Sorting.Empty, @@ -299,11 +303,13 @@ final case class SlickMysqlQueryBuilderParameters(tableData: SlickQueryBuilder.T pagination .map { pagination => val startFrom = (pagination.pageNumber - 1) * pagination.pageSize - sql"limit $startFrom, ${pagination.pageSize}" + sql"limit #$startFrom, #${pagination.pageSize}" } .getOrElse(sql"") } + val qs = """`""" + } abstract class SlickQueryBuilder[T](val parameters: SlickQueryBuilderParameters)( -- cgit v1.2.3 From df2d159dc7392e824013846b55cf1bf4b5502c3c Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Tue, 19 Sep 2017 20:07:58 -0700 Subject: Add cause to postgres connection errors --- src/main/scala/xyz/driver/pdsuicommon/db/PostgresContext.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/main') diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/PostgresContext.scala b/src/main/scala/xyz/driver/pdsuicommon/db/PostgresContext.scala index cbb23d4..7bdfd1b 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/db/PostgresContext.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/db/PostgresContext.scala @@ -28,7 +28,7 @@ object PostgresContext extends PhiLogging { case Success(dataSource) => new PostgresContext(dataSource, settings) case Failure(NonFatal(e)) => logger.error(phi"Can not load dataSource, error: ${Unsafe(e.getClass.getName)}") - throw new IllegalArgumentException("Can not load dataSource from config. Check your database and config") + throw new IllegalArgumentException("Can not load dataSource from config. Check your database and config", e) } } -- cgit v1.2.3 From d4b18efda238f506103dddbf3b400ae17c797276 Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Tue, 19 Sep 2017 22:02:44 -0700 Subject: Fix date generation --- .../scala/xyz/driver/pdsuidomain/fakes/entities/common.scala | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'src/main') diff --git a/src/main/scala/xyz/driver/pdsuidomain/fakes/entities/common.scala b/src/main/scala/xyz/driver/pdsuidomain/fakes/entities/common.scala index 52d7b98..b259b07 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/fakes/entities/common.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/fakes/entities/common.scala @@ -4,6 +4,7 @@ import java.time.{LocalDate, LocalDateTime, LocalTime} import xyz.driver.pdsuicommon.domain.{LongId, StringId, UuidId} import xyz.driver.pdsuidomain.entities.{Trial, TrialHistory} +import scala.util.Random object common { import xyz.driver.core.generators @@ -20,10 +21,11 @@ object common { def nextLocalDateTime = LocalDateTime.of(nextLocalDate, LocalTime.MIDNIGHT) - def nextLocalDate = { - val date = generators.nextDate() - LocalDate.of(date.year, date.month + 1, date.day + 1) - } + def nextLocalDate = LocalDate.of( + 1970 + Random.nextInt(68), + 1 + Random.nextInt(12), + 1 + Random.nextInt(28) // all months have at least 28 days + ) def nextCondition = generators.oneOf[Trial.Condition](Trial.Condition.All) -- cgit v1.2.3 From 8166ef497437567c8e7aaa9b2a8d6482752c017b Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Thu, 21 Sep 2017 16:42:49 +0700 Subject: Fixed meta attribute filling of MedicalRecord when one transforms it from ApiRecord; Fixed FuzzyValue fromString method: on proviedstring it transformes the string to lower case and capitalize it --- .../xyz/driver/pdsuicommon/domain/FuzzyValue.scala | 31 ++++--- .../formats/json/record/ApiRecord.scala | 94 ++++++++++++---------- 2 files changed, 73 insertions(+), 52 deletions(-) (limited to 'src/main') diff --git a/src/main/scala/xyz/driver/pdsuicommon/domain/FuzzyValue.scala b/src/main/scala/xyz/driver/pdsuicommon/domain/FuzzyValue.scala index 4e98f40..b0ee436 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/domain/FuzzyValue.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/domain/FuzzyValue.scala @@ -10,21 +10,34 @@ object FuzzyValue { case object No extends FuzzyValue case object Maybe extends FuzzyValue - val All: Set[FuzzyValue] = Set(Yes, No, Maybe) + private val yes = "Yes" + private val no = "No" + private val maybe = "Maybe" - def fromBoolean(x: Boolean): FuzzyValue = if (x) Yes else No + val All: Set[FuzzyValue] = { + Set(Yes, No, Maybe) + } + + def fromBoolean(x: Boolean): FuzzyValue = { + if (x) Yes else No + } + + implicit def toPhiString(x: FuzzyValue): PhiString = { + Unsafe(Utils.getClassSimpleName(x.getClass)) + } - implicit def toPhiString(x: FuzzyValue): PhiString = Unsafe(Utils.getClassSimpleName(x.getClass)) val fromString: PartialFunction[String, FuzzyValue] = { - case "Yes" => Yes - case "No" => No - case "Maybe" => Maybe + case fuzzy => fuzzy.toLowerCase.capitalize match { + case `yes` => Yes + case `no` => No + case `maybe` => Maybe + } } def valueToString(x: FuzzyValue): String = x match { - case Yes => "Yes" - case No => "No" - case Maybe => "Maybe" + case Yes => `yes` + case No => `no` + case Maybe => `maybe` } } diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiRecord.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiRecord.scala index b255892..6e85021 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiRecord.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiRecord.scala @@ -13,49 +13,11 @@ import play.api.libs.json._ import xyz.driver.pdsuicommon.json.JsonSerializer import xyz.driver.pdsuicommon.domain.{LongId, StringId, TextJson, UuidId} -final case class ApiRecord(id: Long, - patientId: String, - caseId: Option[String], - disease: String, - physician: Option[String], - lastUpdate: ZonedDateTime, - status: String, - previousStatus: Option[String], - assignee: Option[String], - previousAssignee: Option[String], - lastActiveUser: Option[String], - requestId: UUID, - meta: String) { - - private def extractStatus(status: String): Status = - Status - .fromString(status) - .getOrElse( - throw new NoSuchElementException(s"Status $status not found") - ) - - def toDomain = MedicalRecord( - id = LongId(this.id), - status = extractStatus(this.status), - previousStatus = this.previousStatus.map(extractStatus), - assignee = this.assignee.map(StringId(_)), - previousAssignee = this.previousAssignee.map(StringId(_)), - lastActiveUserId = this.lastActiveUser.map(StringId(_)), - patientId = UuidId(patientId), - requestId = RecordRequestId(this.requestId), - disease = this.disease, - caseId = caseId.map(CaseId(_)), - physician = this.physician, - meta = Some(TextJson(JsonSerializer.deserialize[List[MedicalRecord.Meta]](this.meta))), - predictedMeta = None, - predictedDocuments = None, - lastUpdate = this.lastUpdate.toLocalDateTime() - ) - -} - object ApiRecord { + private val emptyMeta: String = + "[]" + private val statusFormat = Format( Reads.StringReads.filter(ValidationError("unknown status")) { case x if MedicalRecordStatus.statusFromString.isDefinedAt(x) => true @@ -80,7 +42,7 @@ object ApiRecord { (JsPath \ "meta").format(Format(Reads { x => JsSuccess(Json.stringify(x)) }, Writes[String](Json.parse))) - )(ApiRecord.apply, unlift(ApiRecord.unapply)) + )(ApiRecord.apply, unlift(ApiRecord.unapply)) def fromDomain(record: MedicalRecord) = ApiRecord( id = record.id.id, @@ -95,6 +57,52 @@ object ApiRecord { previousAssignee = record.previousAssignee.map(_.id), lastActiveUser = record.lastActiveUserId.map(_.id), requestId = record.requestId.id, - meta = record.meta.map(x => JsonSerializer.serialize(x.content)).getOrElse("[]") + meta = record.meta.map(x => JsonSerializer.serialize(x.content)).getOrElse(emptyMeta) ) } + +final case class ApiRecord(id: Long, + patientId: String, + caseId: Option[String], + disease: String, + physician: Option[String], + lastUpdate: ZonedDateTime, + status: String, + previousStatus: Option[String], + assignee: Option[String], + previousAssignee: Option[String], + lastActiveUser: Option[String], + requestId: UUID, + meta: String) { + + private def extractStatus(status: String): Status = + Status + .fromString(status) + .getOrElse( + throw new NoSuchElementException(s"Status $status not found") + ) + + def toDomain = MedicalRecord( + id = LongId(this.id), + status = extractStatus(this.status), + previousStatus = this.previousStatus.map(extractStatus), + assignee = this.assignee.map(StringId(_)), + previousAssignee = this.previousAssignee.map(StringId(_)), + lastActiveUserId = this.lastActiveUser.map(StringId(_)), + patientId = UuidId(patientId), + requestId = RecordRequestId(this.requestId), + disease = this.disease, + caseId = caseId.map(CaseId(_)), + physician = this.physician, + meta = { + if (this.meta == ApiRecord.emptyMeta) { + None + } else { + Some(TextJson(JsonSerializer.deserialize[List[MedicalRecord.Meta]](this.meta))) + } + }, + predictedMeta = None, + predictedDocuments = None, + lastUpdate = this.lastUpdate.toLocalDateTime() + ) +} \ No newline at end of file -- cgit v1.2.3 From 722ee2a778f4e7f541e0704b2bd83225db607697 Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Thu, 21 Sep 2017 17:23:59 +0700 Subject: Fixed formatting code issues --- .../xyz/driver/pdsuicommon/domain/FuzzyValue.scala | 21 +++++++++------------ .../pdsuidomain/formats/json/record/ApiRecord.scala | 4 ++-- 2 files changed, 11 insertions(+), 14 deletions(-) (limited to 'src/main') diff --git a/src/main/scala/xyz/driver/pdsuicommon/domain/FuzzyValue.scala b/src/main/scala/xyz/driver/pdsuicommon/domain/FuzzyValue.scala index b0ee436..36c3de7 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/domain/FuzzyValue.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/domain/FuzzyValue.scala @@ -14,25 +14,22 @@ object FuzzyValue { private val no = "No" private val maybe = "Maybe" - val All: Set[FuzzyValue] = { + val All: Set[FuzzyValue] = Set(Yes, No, Maybe) - } - def fromBoolean(x: Boolean): FuzzyValue = { + def fromBoolean(x: Boolean): FuzzyValue = if (x) Yes else No - } - implicit def toPhiString(x: FuzzyValue): PhiString = { + implicit def toPhiString(x: FuzzyValue): PhiString = Unsafe(Utils.getClassSimpleName(x.getClass)) - } - val fromString: PartialFunction[String, FuzzyValue] = { - case fuzzy => fuzzy.toLowerCase.capitalize match { - case `yes` => Yes - case `no` => No - case `maybe` => Maybe - } + case fuzzy => + fuzzy.toLowerCase.capitalize match { + case `yes` => Yes + case `no` => No + case `maybe` => Maybe + } } def valueToString(x: FuzzyValue): String = x match { diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiRecord.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiRecord.scala index 6e85021..e7b58cd 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiRecord.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiRecord.scala @@ -42,7 +42,7 @@ object ApiRecord { (JsPath \ "meta").format(Format(Reads { x => JsSuccess(Json.stringify(x)) }, Writes[String](Json.parse))) - )(ApiRecord.apply, unlift(ApiRecord.unapply)) + )(ApiRecord.apply, unlift(ApiRecord.unapply)) def fromDomain(record: MedicalRecord) = ApiRecord( id = record.id.id, @@ -105,4 +105,4 @@ final case class ApiRecord(id: Long, predictedDocuments = None, lastUpdate = this.lastUpdate.toLocalDateTime() ) -} \ No newline at end of file +} -- cgit v1.2.3 From f1c217d2e6ff2e195e7374605878c7a347a074c7 Mon Sep 17 00:00:00 2001 From: kseniya Date: Thu, 21 Sep 2017 16:17:49 +0700 Subject: Fix slick query builder bug --- src/main/scala/xyz/driver/pdsuicommon/db/SlickQueryBuilder.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src/main') diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/SlickQueryBuilder.scala b/src/main/scala/xyz/driver/pdsuicommon/db/SlickQueryBuilder.scala index 79cb114..ab2757b 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/db/SlickQueryBuilder.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/db/SlickQueryBuilder.scala @@ -203,7 +203,7 @@ sealed trait SlickQueryBuilderParameters { val operator = op match { case Eq => sql"=" case NotEq => sql"!=" - case Like => sql"like" + case Like => sql" like " case Gt => sql">" case GtEq => sql">=" case Lt => sql"<" @@ -213,12 +213,13 @@ sealed trait SlickQueryBuilderParameters { case Atom.NAry(dimension, op, values) => val sqlOp = op match { - case SearchFilterNAryOperation.In => sql"in" - case SearchFilterNAryOperation.NotIn => sql"not in" + case SearchFilterNAryOperation.In => sql" in " + case SearchFilterNAryOperation.NotIn => sql" not in " } val formattedValues = if (values.nonEmpty) { - sql"${values.mkString(",")}" + val condition = s"(${values.map(v => "'" + v.toString + "'").mkString(",")})" + sql"#${condition}" } else sql"NULL" sql"#${escapeDimension(dimension)}" concat sqlOp concat formattedValues -- cgit v1.2.3 From 49cda2524a2537cb9330af488ca9c30e435f5849 Mon Sep 17 00:00:00 2001 From: kseniya Date: Fri, 22 Sep 2017 18:09:32 +0700 Subject: Added create and delete endpoint to intervention service --- .../json/intervention/ApiPartialIntervention.scala | 50 +++++++++++++++++++--- .../pdsuidomain/services/InterventionService.scala | 36 ++++++++++++++++ .../services/rest/RestInterventionService.scala | 23 +++++++++- 3 files changed, 103 insertions(+), 6 deletions(-) (limited to 'src/main') diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiPartialIntervention.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiPartialIntervention.scala index aa55506..09e0b23 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiPartialIntervention.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiPartialIntervention.scala @@ -1,11 +1,19 @@ package xyz.driver.pdsuidomain.formats.json.intervention -import xyz.driver.pdsuicommon.domain.LongId -import xyz.driver.pdsuidomain.entities.{InterventionArm, InterventionWithArms} +import play.api.data.validation.Invalid +import xyz.driver.pdsuicommon.domain.{LongId, StringId} +import xyz.driver.pdsuidomain.entities.{Intervention, InterventionArm, InterventionWithArms, Trial} import play.api.libs.functional.syntax._ import play.api.libs.json._ +import xyz.driver.pdsuicommon.json.JsonValidationException +import xyz.driver.pdsuicommon.validation.{AdditionalConstraints, JsonValidationErrors} -final case class ApiPartialIntervention(typeId: Option[Long], +import scala.collection.breakOut +import scala.util.Try + +final case class ApiPartialIntervention(name: Option[String], + trialId: Option[String], + typeId: Option[Long], dosage: Option[String], isActive: Option[Boolean], arms: Option[List[Long]]) { @@ -22,19 +30,51 @@ final case class ApiPartialIntervention(typeId: Option[Long], arms = draftArmList.getOrElse(orig.arms) ) } + + //TODO: need to discuss + def toDomain: Try[InterventionWithArms] = Try { + val validation = Map(JsPath \ "trialId" -> AdditionalConstraints.optionNonEmptyConstraint(trialId)) + + val validationErrors: JsonValidationErrors = validation.collect({ + case (fieldName, e: Invalid) => (fieldName, e.errors) + })(breakOut) + + if (validationErrors.isEmpty) { + InterventionWithArms( + intervention = Intervention( + id = LongId(0), + trialId = trialId.map(StringId[Trial]).get, + name = name.getOrElse(""), + originalName = name.getOrElse(""), + typeId = typeId.map(LongId(_)), + originalType = Option(""), + dosage = dosage.getOrElse(""), + originalDosage = dosage.getOrElse(""), + isActive = isActive.getOrElse(false) + ), + arms = arms.map(_.map(x => InterventionArm(LongId(x), LongId(0)))).getOrElse(List.empty) + ) + } else { + throw new JsonValidationException(validationErrors) + } + } } object ApiPartialIntervention { private val reads: Reads[ApiPartialIntervention] = ( - (JsPath \ "typeId").readNullable[Long] and + (JsPath \ "name").readNullable[String] and + (JsPath \ "trialId").readNullable[String] and + (JsPath \ "typeId").readNullable[Long] and (JsPath \ "dosage").readNullable[String] and (JsPath \ "isActive").readNullable[Boolean] and (JsPath \ "arms").readNullable[List[Long]] )(ApiPartialIntervention.apply _) private val writes: Writes[ApiPartialIntervention] = ( - (JsPath \ "typeId").writeNullable[Long] and + (JsPath \ "name").writeNullable[String] and + (JsPath \ "trialId").writeNullable[String] and + (JsPath \ "typeId").writeNullable[Long] and (JsPath \ "dosage").writeNullable[String] and (JsPath \ "isActive").writeNullable[Boolean] and (JsPath \ "arms").writeNullable[List[Long]] diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/InterventionService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/InterventionService.scala index 439e456..1e7c7f1 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/InterventionService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/InterventionService.scala @@ -61,6 +61,37 @@ object InterventionService { final case class CommonError(userMessage: String) extends UpdateReply with DomainError } + sealed trait CreateReply + object CreateReply { + final case class Created(x: InterventionWithArms) extends CreateReply + + type Error = CreateReply with DomainError + + case object AuthorizationError + extends CreateReply with DefaultAccessDeniedError with DomainError.AuthorizationError + + final case class CommonError(userMessage: String) extends CreateReply with DomainError + + implicit def toPhiString(reply: CreateReply): PhiString = reply match { + case Created(x) => phi"Created($x)" + case x: Error => DomainError.toPhiString(x) + } + } + + sealed trait DeleteReply + object DeleteReply { + case object Deleted extends DeleteReply + + type Error = DeleteReply with DomainError + + case object NotFoundError extends DeleteReply with DefaultNotFoundError with DomainError.NotFoundError + + case object AuthorizationError + extends DeleteReply with DefaultAccessDeniedError with DomainError.AuthorizationError + + final case class CommonError(userMessage: String) extends DeleteReply with DomainError + } + } trait InterventionService { @@ -76,4 +107,9 @@ trait InterventionService { def update(origIntervention: InterventionWithArms, draftIntervention: InterventionWithArms)( implicit requestContext: AuthenticatedRequestContext): Future[UpdateReply] + + def create(draftIntervention: InterventionWithArms)( + implicit requestContext: AuthenticatedRequestContext): Future[CreateReply] + + def delete(id: LongId[Intervention])(implicit requestContext: AuthenticatedRequestContext): Future[DeleteReply] } diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestInterventionService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestInterventionService.scala index e593c3b..025a48a 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestInterventionService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestInterventionService.scala @@ -1,7 +1,6 @@ package xyz.driver.pdsuidomain.services.rest import scala.concurrent.{ExecutionContext, Future} - import akka.http.scaladsl.marshalling.Marshal import akka.http.scaladsl.model._ import akka.stream.Materializer @@ -61,4 +60,26 @@ class RestInterventionService(transport: ServiceTransport, baseUri: Uri)( } } + def create(draftIntervention: InterventionWithArms)( + implicit requestContext: AuthenticatedRequestContext): Future[CreateReply] = { + for { + entity <- Marshal(ApiIntervention.fromDomain(draftIntervention)).to[RequestEntity] + request = HttpRequest(HttpMethods.POST, endpointUri(baseUri, "/v1/intervention")).withEntity(entity) + response <- transport.sendRequestGetResponse(requestContext)(request) + reply <- apiResponse[ApiIntervention](response) + } yield { + CreateReply.Created(reply.toDomain) + } + } + + def delete(id: LongId[Intervention])(implicit requestContext: AuthenticatedRequestContext): Future[DeleteReply] = { + val request = HttpRequest(HttpMethods.DELETE, endpointUri(baseUri, s"/v1/intervention/$id")) + for { + response <- transport.sendRequestGetResponse(requestContext)(request) + _ <- apiResponse[HttpEntity](response) + } yield { + DeleteReply.Deleted + } + } + } -- cgit v1.2.3 From 205b2a64084e3ad077c1a7b9b8f99cd0609d7760 Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Mon, 25 Sep 2017 12:32:50 +0700 Subject: Fixed ApiUpdateRecord 'meta' attribute --- .../xyz/driver/pdsuidomain/formats/json/record/ApiUpdateRecord.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/main') diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiUpdateRecord.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiUpdateRecord.scala index 47bc493..05d5a60 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiUpdateRecord.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/record/ApiUpdateRecord.scala @@ -37,7 +37,7 @@ object ApiUpdateRecord { JsSuccess(Json.stringify(x)) }) .map { - case Tristate.Present("{}") => Tristate.Absent + case Tristate.Present("[]") => Tristate.Absent case x => x } )(ApiUpdateRecord.apply _) -- cgit v1.2.3 From 25ccda10b3b7f16f9fcee0b41c4abd035b3d1330 Mon Sep 17 00:00:00 2001 From: kseniya Date: Mon, 25 Sep 2017 14:10:31 +0700 Subject: Created json format for create intervention --- .../utils/CustomSwaggerJsonFormats.scala | 2 +- .../json/intervention/ApiPartialIntervention.scala | 6 +-- .../formats/json/sprayformats/intervention.scala | 52 +++++++++++++++++++++- .../sprayformats/InterventionFormatSuite.scala | 12 ++++- 4 files changed, 65 insertions(+), 7 deletions(-) (limited to 'src/main') diff --git a/src/main/scala/xyz/driver/pdsuicommon/utils/CustomSwaggerJsonFormats.scala b/src/main/scala/xyz/driver/pdsuicommon/utils/CustomSwaggerJsonFormats.scala index c1a2c7c..6c87858 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/utils/CustomSwaggerJsonFormats.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/utils/CustomSwaggerJsonFormats.scala @@ -42,7 +42,7 @@ object CustomSwaggerJsonFormats { xyz.driver.pdsuidomain.fakes.entities.trialcuration.nextTrialIssue()), classOf[RichCriterion] -> richCriterionFormat.write( xyz.driver.pdsuidomain.fakes.entities.trialcuration.nextRichCriterion()), - classOf[InterventionWithArms] -> interventionWriter.write( + classOf[InterventionWithArms] -> interventionFormat.write( xyz.driver.pdsuidomain.fakes.entities.trialcuration.nextInterventionWithArms()), classOf[InterventionType] -> interventionTypeFormat.write( xyz.driver.pdsuidomain.fakes.entities.trialcuration.nextInterventionType()), diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiPartialIntervention.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiPartialIntervention.scala index 09e0b23..e7f69a9 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiPartialIntervention.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiPartialIntervention.scala @@ -20,7 +20,7 @@ final case class ApiPartialIntervention(name: Option[String], def applyTo(orig: InterventionWithArms): InterventionWithArms = { val origIntervention = orig.intervention - val draftArmList = arms.map(_.map(x => InterventionArm(LongId(x), orig.intervention.id))) + val draftArmList = arms.map(_.map(x => InterventionArm(armId = LongId(x), interventionId = orig.intervention.id))) orig.copy( intervention = origIntervention.copy( typeId = typeId.map(LongId(_)).orElse(origIntervention.typeId), @@ -31,7 +31,6 @@ final case class ApiPartialIntervention(name: Option[String], ) } - //TODO: need to discuss def toDomain: Try[InterventionWithArms] = Try { val validation = Map(JsPath \ "trialId" -> AdditionalConstraints.optionNonEmptyConstraint(trialId)) @@ -52,7 +51,8 @@ final case class ApiPartialIntervention(name: Option[String], originalDosage = dosage.getOrElse(""), isActive = isActive.getOrElse(false) ), - arms = arms.map(_.map(x => InterventionArm(LongId(x), LongId(0)))).getOrElse(List.empty) + arms = + arms.map(_.map(x => InterventionArm(armId = LongId(x), interventionId = LongId(0)))).getOrElse(List.empty) ) } else { throw new JsonValidationException(validationErrors) diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/intervention.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/intervention.scala index 9314391..717c1a9 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/intervention.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/intervention.scala @@ -1,14 +1,14 @@ package xyz.driver.pdsuidomain.formats.json.sprayformats import spray.json._ -import xyz.driver.pdsuicommon.domain.LongId +import xyz.driver.pdsuicommon.domain.{LongId, StringId} import xyz.driver.pdsuidomain.entities._ object intervention { import DefaultJsonProtocol._ import common._ - implicit val interventionWriter: JsonWriter[InterventionWithArms] = new JsonWriter[InterventionWithArms] { + implicit val interventionFormat: JsonFormat[InterventionWithArms] = new RootJsonFormat[InterventionWithArms] { override def write(obj: InterventionWithArms) = JsObject( "id" -> obj.intervention.id.toJson, @@ -22,6 +22,54 @@ object intervention { "originalDosage" -> obj.intervention.originalDosage.toJson, "originalType" -> obj.intervention.originalType.toJson ) + + override def read(json: JsValue): InterventionWithArms = json match { + case JsObject(fields) => + val trialId = fields + .get("trialId") + .map(_.convertTo[StringId[Trial]]) + .getOrElse(deserializationError(s"Intervention json object does not contain `trialId` field: $json")) + + val typeId = fields + .get("typeId") + .map(_.convertTo[LongId[InterventionType]]) + + val name = fields + .get("name") + .map(_.convertTo[String]) + .getOrElse("") + + val dosage = fields + .get("dosage") + .map(_.convertTo[String]) + .getOrElse("") + + val isActive = fields + .get("isActive") + .exists(_.convertTo[Boolean]) + + val arms = fields + .get("arms") + .map(_.convertTo[List[LongId[Arm]]].map(x => InterventionArm(armId = x, interventionId = LongId(0)))) + .getOrElse(List.empty[InterventionArm]) + + InterventionWithArms( + intervention = Intervention( + id = LongId(0), + trialId = trialId, + name = name, + originalName = name, + typeId = typeId, + originalType = None, + dosage = dosage, + originalDosage = dosage, + isActive = isActive + ), + arms = arms + ) + + case _ => deserializationError(s"Expected Json Object as create Intervention json, but got $json") + } } def applyUpdateToInterventionWithArms(json: JsValue, orig: InterventionWithArms): InterventionWithArms = json match { diff --git a/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/InterventionFormatSuite.scala b/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/InterventionFormatSuite.scala index 0f01d4a..a52c385 100644 --- a/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/InterventionFormatSuite.scala +++ b/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/InterventionFormatSuite.scala @@ -29,12 +29,22 @@ class InterventionFormatSuite extends FlatSpec with Matchers { intervention = intervention, arms = arms ) - val writtenJson = interventionWriter.write(orig) + val writtenJson = interventionFormat.write(orig) writtenJson should be( """{"id":1,"name":"intervention name","typeId":10,"dosage":"","isActive":true,"arms":[20,21,22], "trialId":"NCT000001","originalName":"orig name","originalDosage":"","originalType":"orig type"}""".parseJson) + val createInterventionJson = + """{"id":1,"name":"intervention name","typeId":10,"dosage":"","isActive":true,"arms":[20,21,22], + "trialId":"NCT000001"}""".parseJson + val parsedCreateIntervention = interventionFormat.read(createInterventionJson) + val expectedCreateIntervention = parsedCreateIntervention.copy( + intervention = intervention.copy(id = LongId(0), originalType = None, originalName = "intervention name"), + arms = arms.map(_.copy(interventionId = LongId(0))) + ) + parsedCreateIntervention should be(expectedCreateIntervention) + val updateInterventionJson = """{"dosage":"descr","arms":[21,22]}""".parseJson val expectedUpdatedIntervention = orig.copy( intervention = intervention.copy(dosage = "descr"), -- cgit v1.2.3 From 4e13dd7bc86b24ca0b71f46e4ee3115f563cf9bd Mon Sep 17 00:00:00 2001 From: kseniya Date: Mon, 25 Sep 2017 17:33:08 +0700 Subject: Fixed ACL for intervention --- src/main/scala/xyz/driver/pdsuicommon/acl/ACL.scala | 4 +++- .../formats/json/intervention/ApiPartialIntervention.scala | 1 + .../driver/pdsuidomain/formats/json/sprayformats/intervention.scala | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) (limited to 'src/main') diff --git a/src/main/scala/xyz/driver/pdsuicommon/acl/ACL.scala b/src/main/scala/xyz/driver/pdsuicommon/acl/ACL.scala index f2a0ef0..180ebf9 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/acl/ACL.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/acl/ACL.scala @@ -168,8 +168,10 @@ object ACL extends PhiLogging { object Intervention extends BaseACL( label = "intervention", + create = Set(TrialSummarizer, TrialAdmin), read = Set(TrialSummarizer, TrialAdmin), - update = Set(TrialSummarizer, TrialAdmin) + update = Set(TrialSummarizer, TrialAdmin), + delete = Set(TrialSummarizer, TrialAdmin) ) object InterventionType diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiPartialIntervention.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiPartialIntervention.scala index e7f69a9..74f16e1 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiPartialIntervention.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiPartialIntervention.scala @@ -23,6 +23,7 @@ final case class ApiPartialIntervention(name: Option[String], val draftArmList = arms.map(_.map(x => InterventionArm(armId = LongId(x), interventionId = orig.intervention.id))) orig.copy( intervention = origIntervention.copy( + name = name.getOrElse(origIntervention.name), typeId = typeId.map(LongId(_)).orElse(origIntervention.typeId), dosage = dosage.getOrElse(origIntervention.dosage), isActive = isActive.getOrElse(origIntervention.isActive) diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/intervention.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/intervention.scala index 717c1a9..8651932 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/intervention.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/intervention.scala @@ -74,6 +74,10 @@ object intervention { def applyUpdateToInterventionWithArms(json: JsValue, orig: InterventionWithArms): InterventionWithArms = json match { case JsObject(fields) => + val name = fields + .get("name") + .map(_.convertTo[String]) + val typeId = fields .get("typeId") .map(_.convertTo[LongId[InterventionType]]) @@ -93,6 +97,7 @@ object intervention { orig.copy( intervention = origIntervention.copy( + name = name.getOrElse(origIntervention.name), typeId = typeId.orElse(origIntervention.typeId), dosage = dosage.getOrElse(origIntervention.dosage), isActive = isActive.getOrElse(origIntervention.isActive) -- cgit v1.2.3 From c0ffb2a34240876c16f9ce62205cf257aa6d5991 Mon Sep 17 00:00:00 2001 From: Kseniya Tomskikh Date: Tue, 26 Sep 2017 16:55:38 +0700 Subject: PDSUI-2275 Added delivery_method field to intervention table --- .../xyz/driver/pdsuidomain/entities/Intervention.scala | 5 +++-- .../driver/pdsuidomain/fakes/entities/trialcuration.scala | 3 ++- .../formats/json/intervention/ApiIntervention.scala | 6 +++++- .../json/intervention/ApiPartialIntervention.scala | 9 +++++++-- .../formats/json/sprayformats/intervention.scala | 15 +++++++++++++-- .../json/sprayformats/InterventionFormatSuite.scala | 7 ++++--- 6 files changed, 34 insertions(+), 11 deletions(-) (limited to 'src/main') diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala index cb677cf..5dada55 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala @@ -29,12 +29,13 @@ final case class Intervention(id: LongId[Intervention], originalType: Option[String], dosage: String, originalDosage: String, - isActive: Boolean) + isActive: Boolean, + deliveryMethod: Option[String]) object Intervention { implicit def toPhiString(x: Intervention): PhiString = { import x._ - phi"Intervention(id=$id, trialId=$trialId, name=${Unsafe(name)}, typeId=$typeId, isActive=$isActive)" + phi"Intervention(id=$id, trialId=$trialId, name=${Unsafe(name)}, typeId=$typeId, isActive=$isActive, deliveryMethod=${Unsafe(deliveryMethod)})" } } diff --git a/src/main/scala/xyz/driver/pdsuidomain/fakes/entities/trialcuration.scala b/src/main/scala/xyz/driver/pdsuidomain/fakes/entities/trialcuration.scala index ecb6e0a..a2bdf43 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/fakes/entities/trialcuration.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/fakes/entities/trialcuration.scala @@ -76,7 +76,8 @@ object trialcuration { originalType = Option(generators.nextString()), dosage = generators.nextString(), originalDosage = generators.nextString(), - isActive = generators.nextBoolean() + isActive = generators.nextBoolean(), + deliveryMethod = Option(generators.nextString()) ) def nextInterventionArm(interventionId: LongId[Intervention]): InterventionArm = InterventionArm( diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiIntervention.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiIntervention.scala index f306a71..072ed25 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiIntervention.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiIntervention.scala @@ -12,6 +12,7 @@ final case class ApiIntervention(id: Long, isActive: Boolean, arms: List[Long], trialId: String, + deliveryMethod: Option[String], originalName: String, originalDosage: String, originalType: Option[String]) { @@ -26,7 +27,8 @@ final case class ApiIntervention(id: Long, originalType = this.originalType.map(id => id.toString), dosage = this.dosage, originalDosage = this.originalDosage, - isActive = this.isActive + isActive = this.isActive, + deliveryMethod = this.deliveryMethod ) InterventionWithArms(intervention, this.arms.map { armId => @@ -47,6 +49,7 @@ object ApiIntervention { (JsPath \ "isActive").format[Boolean] and (JsPath \ "arms").format[List[Long]] and (JsPath \ "trialId").format[String] and + (JsPath \ "deliveryMethod").formatNullable[String] and (JsPath \ "originalName").format[String] and (JsPath \ "originalDosage").format[String] and (JsPath \ "originalType").formatNullable[String] @@ -64,6 +67,7 @@ object ApiIntervention { isActive = intervention.isActive, arms = arms.map(_.armId.id), trialId = intervention.trialId.id, + deliveryMethod = intervention.deliveryMethod, originalName = intervention.originalName, originalDosage = intervention.originalDosage, originalType = intervention.originalType diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiPartialIntervention.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiPartialIntervention.scala index 74f16e1..28a8555 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiPartialIntervention.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiPartialIntervention.scala @@ -16,6 +16,7 @@ final case class ApiPartialIntervention(name: Option[String], typeId: Option[Long], dosage: Option[String], isActive: Option[Boolean], + deliveryMethod: Option[String], arms: Option[List[Long]]) { def applyTo(orig: InterventionWithArms): InterventionWithArms = { @@ -26,7 +27,8 @@ final case class ApiPartialIntervention(name: Option[String], name = name.getOrElse(origIntervention.name), typeId = typeId.map(LongId(_)).orElse(origIntervention.typeId), dosage = dosage.getOrElse(origIntervention.dosage), - isActive = isActive.getOrElse(origIntervention.isActive) + isActive = isActive.getOrElse(origIntervention.isActive), + deliveryMethod = deliveryMethod.orElse(origIntervention.deliveryMethod) ), arms = draftArmList.getOrElse(orig.arms) ) @@ -50,7 +52,8 @@ final case class ApiPartialIntervention(name: Option[String], originalType = Option(""), dosage = dosage.getOrElse(""), originalDosage = dosage.getOrElse(""), - isActive = isActive.getOrElse(false) + isActive = isActive.getOrElse(false), + deliveryMethod = deliveryMethod ), arms = arms.map(_.map(x => InterventionArm(armId = LongId(x), interventionId = LongId(0)))).getOrElse(List.empty) @@ -69,6 +72,7 @@ object ApiPartialIntervention { (JsPath \ "typeId").readNullable[Long] and (JsPath \ "dosage").readNullable[String] and (JsPath \ "isActive").readNullable[Boolean] and + (JsPath \ "deliveryMethod").readNullable[String] and (JsPath \ "arms").readNullable[List[Long]] )(ApiPartialIntervention.apply _) @@ -78,6 +82,7 @@ object ApiPartialIntervention { (JsPath \ "typeId").writeNullable[Long] and (JsPath \ "dosage").writeNullable[String] and (JsPath \ "isActive").writeNullable[Boolean] and + (JsPath \ "deliveryMethod").writeNullable[String] and (JsPath \ "arms").writeNullable[List[Long]] )(unlift(ApiPartialIntervention.unapply)) diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/intervention.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/intervention.scala index 8651932..daa28e4 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/intervention.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/intervention.scala @@ -18,6 +18,7 @@ object intervention { "isActive" -> obj.intervention.isActive.toJson, "arms" -> obj.arms.map(_.armId).toJson, "trialId" -> obj.intervention.trialId.toJson, + "deliveryMethod" -> obj.intervention.deliveryMethod.toJson, "originalName" -> obj.intervention.originalName.toJson, "originalDosage" -> obj.intervention.originalDosage.toJson, "originalType" -> obj.intervention.originalType.toJson @@ -48,6 +49,10 @@ object intervention { .get("isActive") .exists(_.convertTo[Boolean]) + val deliveryMethod = fields + .get("deliveryMethod") + .map(_.convertTo[String]) + val arms = fields .get("arms") .map(_.convertTo[List[LongId[Arm]]].map(x => InterventionArm(armId = x, interventionId = LongId(0)))) @@ -63,7 +68,8 @@ object intervention { originalType = None, dosage = dosage, originalDosage = dosage, - isActive = isActive + isActive = isActive, + deliveryMethod = deliveryMethod ), arms = arms ) @@ -90,6 +96,10 @@ object intervention { .get("isActive") .map(_.convertTo[Boolean]) + val deliveryMethod = fields + .get("deliveryMethod") + .map(_.convertTo[String]) + val origIntervention = orig.intervention val arms = fields .get("arms") @@ -100,7 +110,8 @@ object intervention { name = name.getOrElse(origIntervention.name), typeId = typeId.orElse(origIntervention.typeId), dosage = dosage.getOrElse(origIntervention.dosage), - isActive = isActive.getOrElse(origIntervention.isActive) + isActive = isActive.getOrElse(origIntervention.isActive), + deliveryMethod = deliveryMethod.orElse(origIntervention.deliveryMethod) ), arms = arms.getOrElse(orig.arms) ) diff --git a/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/InterventionFormatSuite.scala b/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/InterventionFormatSuite.scala index a52c385..ebb5f3d 100644 --- a/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/InterventionFormatSuite.scala +++ b/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/InterventionFormatSuite.scala @@ -18,7 +18,8 @@ class InterventionFormatSuite extends FlatSpec with Matchers { originalType = Some("orig type"), dosage = "", originalDosage = "", - isActive = true + isActive = true, + deliveryMethod = Some("pill") ) val arms = List( InterventionArm(interventionId = intervention.id, armId = LongId(20)), @@ -33,11 +34,11 @@ class InterventionFormatSuite extends FlatSpec with Matchers { writtenJson should be( """{"id":1,"name":"intervention name","typeId":10,"dosage":"","isActive":true,"arms":[20,21,22], - "trialId":"NCT000001","originalName":"orig name","originalDosage":"","originalType":"orig type"}""".parseJson) + "trialId":"NCT000001","deliveryMethod":"pill","originalName":"orig name","originalDosage":"","originalType":"orig type"}""".parseJson) val createInterventionJson = """{"id":1,"name":"intervention name","typeId":10,"dosage":"","isActive":true,"arms":[20,21,22], - "trialId":"NCT000001"}""".parseJson + "trialId":"NCT000001","deliveryMethod":"pill"}""".parseJson val parsedCreateIntervention = interventionFormat.read(createInterventionJson) val expectedCreateIntervention = parsedCreateIntervention.copy( intervention = intervention.copy(id = LongId(0), originalType = None, originalName = "intervention name"), -- cgit v1.2.3 From 41aa4fb74ab5a2ec6521f067fe82472986dfdbd3 Mon Sep 17 00:00:00 2001 From: vlad Date: Tue, 26 Sep 2017 13:29:20 -0700 Subject: Make number filter parsing attempt first, because number parsers are more specific. Otherwise fails for: https://records-processing.stable.sand.driver.network/rep-api/v1/document?filters=recordId+EQ+1&pageNumber=1&pageSize=9999 with: ERROR: operator does not exist: bigint = character varying Hint: No operator matches the given name and argument type(s). You might need to add explicit type casts. Position: 782 --- src/main/scala/xyz/driver/pdsuicommon/db/QueryBuilder.scala | 4 ++-- .../scala/xyz/driver/pdsuicommon/parsers/SearchFilterParser.scala | 2 +- src/main/scala/xyz/driver/pdsuidomain/entities/Document.scala | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) (limited to 'src/main') diff --git a/src/main/scala/xyz/driver/pdsuicommon/db/QueryBuilder.scala b/src/main/scala/xyz/driver/pdsuicommon/db/QueryBuilder.scala index aa32166..0bf1ed6 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/db/QueryBuilder.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/db/QueryBuilder.scala @@ -57,10 +57,10 @@ sealed trait QueryBuilderParameters { def toSql(countQuery: Boolean, fields: Set[String], namingStrategy: NamingStrategy): (String, QueryBuilder.Binder) = { val escapedTableName = namingStrategy.table(tableData.tableName) val fieldsSql: String = if (countQuery) { - val suffix: String = (tableData.lastUpdateFieldName match { + val suffix: String = tableData.lastUpdateFieldName match { case Some(lastUpdateField) => s", max($escapedTableName.${namingStrategy.column(lastUpdateField)})" case None => "" - }) + } "count(*)" + suffix } else { if (fields == QueryBuilderParameters.AllFields) { diff --git a/src/main/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParser.scala b/src/main/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParser.scala index 8aff397..3238ebc 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParser.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParser.scala @@ -93,8 +93,8 @@ object SearchFilterParser { private val binaryAtomParser: Parser[SearchFilterExpr.Atom.Binary] = P( dimensionParser ~ whitespaceParser ~ ( + (numericOperatorParser.! ~/ whitespaceParser ~/ numberParser.!) | (commonOperatorParser.! ~/ whitespaceParser ~/ AnyChar.rep(min = 1).!) - | (numericOperatorParser.! ~/ whitespaceParser ~/ numberParser.!) ) ~ End ).map { case BinaryAtomFromTuple(atom) => atom diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/Document.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/Document.scala index 1f73184..839fead 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/entities/Document.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/entities/Document.scala @@ -14,6 +14,8 @@ import xyz.driver.pdsuicommon.validation.Validators import xyz.driver.pdsuicommon.validation.Validators.Validator import xyz.driver.pdsuidomain.entities.Document.Meta +import scalaz.Equal + final case class ProviderType(id: LongId[ProviderType], name: String) object ProviderType { @@ -124,6 +126,8 @@ object DocumentType { } } + implicit def equal: Equal[DocumentType] = Equal.equal[DocumentType](_ == _) + implicit def toPhiString(x: DocumentType): PhiString = { import x._ phi"DocumentType(id=$id, name=${Unsafe(name)})" -- cgit v1.2.3 From cd1e685b22db2c69ad3d418b6b24b65fd63472fa Mon Sep 17 00:00:00 2001 From: vlad Date: Tue, 26 Sep 2017 13:36:16 -0700 Subject: Make number filter parsing attempt first, because number parsers are more specific. Otherwise fails for: https://records-processing.stable.sand.driver.network/rep-api/v1/document?filters=recordId+EQ+1&pageNumber=1&pageSize=9999 with: ERROR: operator does not exist: bigint = character varying Hint: No operator matches the given name and argument type(s). You might need to add explicit type casts. Position: 782 --- src/main/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParser.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/main') diff --git a/src/main/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParser.scala b/src/main/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParser.scala index 3238ebc..7e86eae 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParser.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParser.scala @@ -94,7 +94,7 @@ object SearchFilterParser { private val binaryAtomParser: Parser[SearchFilterExpr.Atom.Binary] = P( dimensionParser ~ whitespaceParser ~ ( (numericOperatorParser.! ~/ whitespaceParser ~/ numberParser.!) | - (commonOperatorParser.! ~/ whitespaceParser ~/ AnyChar.rep(min = 1).!) + (commonOperatorParser.! ~/ whitespaceParser ~/ AnyChar.rep(min = 1).!) ) ~ End ).map { case BinaryAtomFromTuple(atom) => atom -- cgit v1.2.3 From 45556d467b8dd22d9b7677c95d5c47f35905ca4a Mon Sep 17 00:00:00 2001 From: vlad Date: Tue, 26 Sep 2017 23:49:42 -0700 Subject: Fixing url in RestTrialService --- .../xyz/driver/pdsuidomain/services/rest/RestTrialService.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/main') diff --git a/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestTrialService.scala b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestTrialService.scala index f826b98..b77e6df 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestTrialService.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/services/rest/RestTrialService.scala @@ -34,9 +34,9 @@ class RestTrialService(transport: ServiceTransport, baseUri: Uri)(implicit prote } } - def getTrialWithLabels(trialId: StringId[Trial], condition: String)( + def getTrialWithLabels(trialId: StringId[Trial], disease: String)( implicit requestContext: AuthenticatedRequestContext): Future[GetTrialWithLabelsReply] = { - val request = HttpRequest(HttpMethods.GET, endpointUri(baseUri, s"/v1/export/trial/$trialId")) + val request = HttpRequest(HttpMethods.GET, endpointUri(baseUri, s"/v1/export/trial/$disease/$trialId")) for { response <- transport.sendRequestGetResponse(requestContext)(request) reply <- apiResponse[ApiExportTrialWithLabels](response) @@ -47,7 +47,7 @@ class RestTrialService(transport: ServiceTransport, baseUri: Uri)(implicit prote def getPdfSource(trialId: StringId[Trial])( implicit requestContext: AuthenticatedRequestContext): Future[Source[ByteString, NotUsed]] = { - val request = HttpRequest(HttpMethods.GET, endpointUri(baseUri, s"/v1/trial/${trialId}/source")) + val request = HttpRequest(HttpMethods.GET, endpointUri(baseUri, s"/v1/trial/$trialId/source")) for { response <- transport.sendRequestGetResponse(requestContext)(request) reply <- apiResponse[HttpEntity](response) -- cgit v1.2.3 From 3a9ba980e6ce08361f6f3cdaf6ab2a401101b35c Mon Sep 17 00:00:00 2001 From: Kseniya Tomskikh Date: Wed, 27 Sep 2017 15:50:35 +0700 Subject: PDSUI-2275 Added delivery methods to intervention type entrypoint --- .../driver/pdsuidomain/entities/Intervention.scala | 140 +++++++++++++++++++++ .../json/intervention/ApiInterventionType.scala | 18 ++- .../formats/json/sprayformats/intervention.scala | 33 ++++- .../sprayformats/InterventionFormatSuite.scala | 6 +- 4 files changed, 188 insertions(+), 9 deletions(-) (limited to 'src/main') diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala index 5dada55..6dc42d0 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala @@ -2,10 +2,150 @@ package xyz.driver.pdsuidomain.entities import xyz.driver.pdsuicommon.domain.{LongId, StringId} import xyz.driver.pdsuicommon.logging._ +import xyz.driver.pdsuidomain.entities.InterventionType.DeliveryMethod._ final case class InterventionType(id: LongId[InterventionType], name: String) object InterventionType { + + sealed trait InterventionTypeName + case object RadiationTherapy extends InterventionTypeName + case object Chemotherapy extends InterventionTypeName + case object TargetedTherapy extends InterventionTypeName + case object Immunotherapy extends InterventionTypeName + case object Surgery extends InterventionTypeName + case object HormoneTherapy extends InterventionTypeName + case object Other extends InterventionTypeName + case object Radiation extends InterventionTypeName + case object SurgeryProcedure extends InterventionTypeName + + def typeFromString: PartialFunction[String, InterventionTypeName] = { + case "Radiation therapy" => RadiationTherapy + case "Chemotherapy" => Chemotherapy + case "Targeted therapy" => TargetedTherapy + case "Immunotherapy" => Immunotherapy + case "Surgery" => Surgery + case "Hormone therapy" => HormoneTherapy + case "Other" => Other + case "Radiation" => Radiation + case "Surgery/Procedure" => SurgeryProcedure + } + + def typeToString(x: InterventionTypeName): String = x match { + case RadiationTherapy => "Radiation therapy" + case Chemotherapy => "Chemotherapy" + case TargetedTherapy => "Targeted therapy" + case Immunotherapy => "Immunotherapy" + case Surgery => "Surgery" + case HormoneTherapy => "Hormone therapy" + case Other => "Other" + case Radiation => "Radiation" + case SurgeryProcedure => "Surgery/Procedure" + } + + sealed trait DeliveryMethod + object DeliveryMethod { + case object IntravenousInfusionIV extends DeliveryMethod + case object IntramuscularInjection extends DeliveryMethod + case object SubcutaneousInjection extends DeliveryMethod + case object IntradermalInjection extends DeliveryMethod + case object SpinalInjection extends DeliveryMethod + case object Oral extends DeliveryMethod + case object Topical extends DeliveryMethod + case object TransdermalPatch extends DeliveryMethod + case object Inhalation extends DeliveryMethod + case object Rectal extends DeliveryMethod + case object ExternalRadiationTherapy extends DeliveryMethod + case object Brachytherapy extends DeliveryMethod + case object SystemicRadiationTherapyIV extends DeliveryMethod + case object SystemicRadiationTherapyOral extends DeliveryMethod + case object ProtonBeamTherapy extends DeliveryMethod + case object RadioFrequencyAblationRFA extends DeliveryMethod + case object Cryoablation extends DeliveryMethod + case object TherapeuticConventionalSurgery extends DeliveryMethod + case object RoboticAssistedLaparoscopicSurgery extends DeliveryMethod + + def fromString: PartialFunction[String, DeliveryMethod] = { + case "Intravenous Infusion (IV)" => IntravenousInfusionIV + case "Intramuscular Injection" => IntramuscularInjection + case "Subcutaneous Injection" => SubcutaneousInjection + case "Intradermal Injection" => IntradermalInjection + case "Spinal Injection" => SpinalInjection + case "Oral" => Oral + case "Topical" => Topical + case "Transdermal Patch" => TransdermalPatch + case "Inhalation" => Inhalation + case "Rectal" => Rectal + case "External Radiation Therapy" => ExternalRadiationTherapy + case "Brachytherapy" => Brachytherapy + case "Systemic Radiation Therapy (IV)" => SystemicRadiationTherapyIV + case "Systemic Radiation Therapy (Oral)" => SystemicRadiationTherapyOral + case "Proton Beam Therapy" => ProtonBeamTherapy + case "Radio-Frequency Ablation (RFA)" => RadioFrequencyAblationRFA + case "Cryoablation" => Cryoablation + case "Therapeutic Conventional Surgery" => TherapeuticConventionalSurgery + case "Robotic Assisted Laparoscopic Surgery" => RoboticAssistedLaparoscopicSurgery + } + + def methodToString(x: DeliveryMethod): String = x match { + case IntravenousInfusionIV => "Intravenous Infusion (IV)" + case IntramuscularInjection => "Intramuscular Injection" + case SubcutaneousInjection => "Subcutaneous Injection" + case IntradermalInjection => "Intradermal Injection" + case SpinalInjection => "Spinal Injection" + case Oral => "Oral" + case Topical => "Topical" + case TransdermalPatch => "Transdermal Patch" + case Inhalation => "Inhalation" + case Rectal => "Rectal" + case ExternalRadiationTherapy => "External Radiation Therapy" + case Brachytherapy => "Brachytherapy" + case SystemicRadiationTherapyIV => "Systemic Radiation Therapy (IV)" + case SystemicRadiationTherapyOral => "Systemic Radiation Therapy (Oral)" + case ProtonBeamTherapy => "Proton Beam Therapy" + case RadioFrequencyAblationRFA => "Radio-Frequency Ablation (RFA)" + case Cryoablation => "Cryoablation" + case TherapeuticConventionalSurgery => "Therapeutic Conventional Surgery" + case RoboticAssistedLaparoscopicSurgery => "Robotic Assisted Laparoscopic Surgery" + } + } + + val commonMethods = Set[DeliveryMethod]( + IntravenousInfusionIV, + IntramuscularInjection, + SubcutaneousInjection, + IntradermalInjection, + SpinalInjection, + Oral, + Topical, + TransdermalPatch, + Inhalation, + Rectal + ) + + val deliveryMethodGroups: Map[LongId[InterventionType], Set[DeliveryMethod]] = Map( + LongId(1) -> commonMethods, + LongId(2) -> commonMethods, + LongId(3) -> commonMethods, + LongId(4) -> commonMethods, + LongId(5) -> commonMethods, + LongId(6) -> commonMethods, + LongId(7) -> commonMethods, + LongId(8) -> Set( + ExternalRadiationTherapy, + Brachytherapy, + SystemicRadiationTherapyIV, + SystemicRadiationTherapyOral, + ProtonBeamTherapy + ), + LongId(9) -> Set( + RadioFrequencyAblationRFA, + Cryoablation, + TherapeuticConventionalSurgery, + RoboticAssistedLaparoscopicSurgery + ) + ) + implicit def toPhiString(x: InterventionType): PhiString = { import x._ phi"InterventionType(id=$id, name=${Unsafe(name)})" diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiInterventionType.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiInterventionType.scala index ebef225..c28c5b4 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiInterventionType.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiInterventionType.scala @@ -4,8 +4,9 @@ import play.api.libs.functional.syntax._ import play.api.libs.json.{Format, JsPath} import xyz.driver.pdsuicommon.domain.LongId import xyz.driver.pdsuidomain.entities.InterventionType +import xyz.driver.pdsuidomain.entities.InterventionType.DeliveryMethod -final case class ApiInterventionType(id: Long, name: String) { +final case class ApiInterventionType(id: Long, name: String, deliveryMethods: List[String]) { def toDomain = InterventionType(id = LongId[InterventionType](id), name = name) } @@ -14,11 +15,16 @@ object ApiInterventionType { implicit val format: Format[ApiInterventionType] = ( (JsPath \ "id").format[Long] and - (JsPath \ "name").format[String] + (JsPath \ "name").format[String] and + (JsPath \ "deliveryMethods").format[List[String]] )(ApiInterventionType.apply, unlift(ApiInterventionType.unapply)) - def fromDomain(interventionType: InterventionType) = ApiInterventionType( - id = interventionType.id.id, - name = interventionType.name - ) + def fromDomain(interventionType: InterventionType) = { + val typeMethods = InterventionType.deliveryMethodGroups.getOrElse(interventionType.id, Set.empty[DeliveryMethod]) + ApiInterventionType( + id = interventionType.id.id, + name = interventionType.name, + deliveryMethods = typeMethods.map(DeliveryMethod.methodToString).toList + ) + } } diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/intervention.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/intervention.scala index daa28e4..4bd5bad 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/intervention.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/intervention.scala @@ -2,6 +2,7 @@ package xyz.driver.pdsuidomain.formats.json.sprayformats import spray.json._ import xyz.driver.pdsuicommon.domain.{LongId, StringId} +import xyz.driver.pdsuidomain.entities.InterventionType.DeliveryMethod import xyz.driver.pdsuidomain.entities._ object intervention { @@ -119,6 +120,36 @@ object intervention { case _ => deserializationError(s"Expected Json Object as partial Intervention, but got $json") } - implicit val interventionTypeFormat: RootJsonFormat[InterventionType] = jsonFormat2(InterventionType.apply) + implicit val interventionTypeFormat: JsonFormat[InterventionType] = new RootJsonFormat[InterventionType] { + override def read(json: JsValue) = json match { + case JsObject(fields) => + val id = fields + .get("id") + .map(_.convertTo[LongId[InterventionType]]) + .getOrElse(deserializationError(s"Intervention type json object does not contain `id` field: $json")) + + val name = fields + .get("name") + .map(_.convertTo[String]) + .getOrElse(deserializationError(s"Intervention type json object does not contain `name` field: $json")) + + InterventionType(id, name) + + case _ => deserializationError(s"Expected Json Object as Intervention type, but got $json") + } + + override def write(obj: InterventionType) = { + val typeMethods = InterventionType.deliveryMethodGroups + .getOrElse(obj.id, Set.empty[DeliveryMethod]) + .map(DeliveryMethod.methodToString) + .toList + + JsObject( + "id" -> obj.id.toJson, + "name" -> obj.name.toJson, + "deliveryMethods" -> typeMethods.toJson + ) + } + } } diff --git a/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/InterventionFormatSuite.scala b/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/InterventionFormatSuite.scala index ebb5f3d..d406162 100644 --- a/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/InterventionFormatSuite.scala +++ b/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/InterventionFormatSuite.scala @@ -60,12 +60,14 @@ class InterventionFormatSuite extends FlatSpec with Matchers { "Json format for InterventionType" should "read and write correct JSON" in { val interventionType = InterventionType( - id = LongId(10), + id = LongId(9), name = "type name" ) val writtenJson = interventionTypeFormat.write(interventionType) - writtenJson should be("""{"id":10,"name":"type name"}""".parseJson) + writtenJson should be( + """{"id":9,"name":"type name","deliveryMethods":["Radio-Frequency Ablation (RFA)", + "Cryoablation","Therapeutic Conventional Surgery","Robotic Assisted Laparoscopic Surgery"]}""".parseJson) val parsedInterventionType = interventionTypeFormat.read(writtenJson) parsedInterventionType should be(interventionType) -- cgit v1.2.3 From 36d217f00374525f604233e2ec248b9cca155bea Mon Sep 17 00:00:00 2001 From: vlad Date: Wed, 27 Sep 2017 02:18:27 -0700 Subject: Allow parser to parse long numbers --- .../driver/pdsuicommon/parsers/SearchFilterParser.scala | 17 ++++++++++++----- .../pdsuicommon/parsers/SearchFilterParserSuite.scala | 10 +++++++++- 2 files changed, 21 insertions(+), 6 deletions(-) (limited to 'src/main') diff --git a/src/main/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParser.scala b/src/main/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParser.scala index 7e86eae..11d336e 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParser.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParser.scala @@ -13,10 +13,15 @@ import scala.util.Try object SearchFilterParser { private object BinaryAtomFromTuple { - def unapply(input: (SearchFilterExpr.Dimension, (String, String))): Option[SearchFilterExpr.Atom.Binary] = { + def unapply(input: (SearchFilterExpr.Dimension, (String, Any))): Option[SearchFilterExpr.Atom.Binary] = { val (dimensionName, (strOperation, value)) = input + val updatedValue = value match { + case s: String => s.safeTrim + case a => a + } + parseOperation(strOperation.toLowerCase).map { op => - SearchFilterExpr.Atom.Binary(dimensionName, op, value.safeTrim) + SearchFilterExpr.Atom.Binary(dimensionName, op, updatedValue.asInstanceOf[AnyRef]) } } } @@ -68,7 +73,7 @@ object SearchFilterParser { } private val numericOperatorParser: Parser[String] = { - P((IgnoreCase("gt") | IgnoreCase("lt")) ~ IgnoreCase("eq").?).! + P(IgnoreCase("eq") | ((IgnoreCase("gt") | IgnoreCase("lt")) ~ IgnoreCase("eq").?)).! } private val naryOperatorParser: Parser[String] = P(IgnoreCase("in")).! @@ -91,10 +96,12 @@ object SearchFilterParser { private val nAryValueParser: Parser[String] = P(CharPred(_ != ',').rep(min = 1).!) + private val longParser: Parser[Long] = P(CharIn('0'to'9').rep(1).!.map(_.toLong)) + private val binaryAtomParser: Parser[SearchFilterExpr.Atom.Binary] = P( dimensionParser ~ whitespaceParser ~ ( - (numericOperatorParser.! ~/ whitespaceParser ~/ numberParser.!) | - (commonOperatorParser.! ~/ whitespaceParser ~/ AnyChar.rep(min = 1).!) + (numericOperatorParser.! ~ whitespaceParser ~ (longParser | numberParser.!)) | + (commonOperatorParser.! ~ whitespaceParser ~ AnyChar.rep(min = 1).!) ) ~ End ).map { case BinaryAtomFromTuple(atom) => atom diff --git a/src/test/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParserSuite.scala b/src/test/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParserSuite.scala index f47f4c2..ba67d13 100644 --- a/src/test/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParserSuite.scala +++ b/src/test/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParserSuite.scala @@ -9,6 +9,7 @@ import org.scalacheck.Arbitrary.arbitrary import org.scalacheck.{Gen, Prop} import org.scalatest.FreeSpecLike import org.scalatest.prop.Checkers +import xyz.driver.pdsuicommon.db.SearchFilterBinaryOperation.Eq import xyz.driver.pdsuicommon.db.SearchFilterNAryOperation.In import xyz.driver.pdsuicommon.utils.Utils import xyz.driver.pdsuicommon.utils.Utils._ @@ -104,6 +105,13 @@ class SearchFilterParserSuite extends FreeSpecLike with Checkers { } } + "actual recordId" - { + "should not be parsed with text values" in { + val filter = SearchFilterParser.parse(Seq("filters" -> "recordId EQ 1")) + assert(filter === Success(SearchFilterExpr.Atom.Binary(Dimension(None, "record_id"), Eq, Long.box(1)))) + } + } + "all operators" - { "should be parsed with numeric values" in check { val testQueryGen = queryGen( @@ -181,7 +189,7 @@ class SearchFilterParserSuite extends FreeSpecLike with Checkers { private val nonEmptyString = arbitrary[String].filter { s => !s.safeTrim.isEmpty } - private val numericBinaryAtomValuesGen: Gen[String] = arbitrary[BigInt].map(_.toString) + private val numericBinaryAtomValuesGen: Gen[String] = arbitrary[Long].map(_.toString) private val inValueGen: Gen[String] = { Gen.nonEmptyContainerOf[Seq, Char](inValueCharsGen).map(_.mkString).filter(_.safeTrim.nonEmpty) } -- cgit v1.2.3 From 68b02317d9a296da8c4b8e389b726c1a122a5b5e Mon Sep 17 00:00:00 2001 From: vlad Date: Wed, 27 Sep 2017 02:25:56 -0700 Subject: Scalafmt --- .../scala/xyz/driver/pdsuicommon/parsers/SearchFilterParser.scala | 6 +++--- .../pdsuidomain/entities/export/trial/ExportTrialWithLabels.scala | 7 ++----- 2 files changed, 5 insertions(+), 8 deletions(-) (limited to 'src/main') diff --git a/src/main/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParser.scala b/src/main/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParser.scala index 11d336e..e0adeb8 100644 --- a/src/main/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParser.scala +++ b/src/main/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParser.scala @@ -17,7 +17,7 @@ object SearchFilterParser { val (dimensionName, (strOperation, value)) = input val updatedValue = value match { case s: String => s.safeTrim - case a => a + case a => a } parseOperation(strOperation.toLowerCase).map { op => @@ -96,12 +96,12 @@ object SearchFilterParser { private val nAryValueParser: Parser[String] = P(CharPred(_ != ',').rep(min = 1).!) - private val longParser: Parser[Long] = P(CharIn('0'to'9').rep(1).!.map(_.toLong)) + private val longParser: Parser[Long] = P(CharIn('0' to '9').rep(1).!.map(_.toLong)) private val binaryAtomParser: Parser[SearchFilterExpr.Atom.Binary] = P( dimensionParser ~ whitespaceParser ~ ( (numericOperatorParser.! ~ whitespaceParser ~ (longParser | numberParser.!)) | - (commonOperatorParser.! ~ whitespaceParser ~ AnyChar.rep(min = 1).!) + (commonOperatorParser.! ~ whitespaceParser ~ AnyChar.rep(min = 1).!) ) ~ End ).map { case BinaryAtomFromTuple(atom) => atom diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialWithLabels.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialWithLabels.scala index 60b74ff..cf55694 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialWithLabels.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/entities/export/trial/ExportTrialWithLabels.scala @@ -27,7 +27,7 @@ object ExportTrialWithLabels { def fromRaw(rawData: List[RawTrialLabel]): ExportTrialWithLabels = { val trials: Set[StringId[Trial]] = rawData.map(_.nctId)(breakOut) - assert(trials.size == 1, "There are more than one trials in the rawData") + assert(trials.size == 1, "There are more than one trial in the rawData") val trial = rawData.head ExportTrialWithLabels( @@ -43,9 +43,7 @@ object ExportTrialWithLabels { ExportTrialArm(armId, rawTrials.head.armName) }(breakOut), criteria = rawData - .groupBy { x => - (x.criterionId, x.labelId) - } + .groupBy(x => (x.criterionId, x.labelId)) .map { case (_, rawTrialLabels) => val armIds = rawTrialLabels.map(_.criterionArmId).toSet @@ -53,5 +51,4 @@ object ExportTrialWithLabels { }(breakOut) ) } - } -- cgit v1.2.3 From 1fa90814e5930ad0bbe61ba5b082747781e1dc92 Mon Sep 17 00:00:00 2001 From: Kseniya Tomskikh Date: Wed, 27 Sep 2017 16:41:04 +0700 Subject: PDSUI-2275 Review fixes --- .../driver/pdsuidomain/entities/Intervention.scala | 120 ++++++++++++--------- .../pdsuidomain/fakes/entities/trialcuration.scala | 5 +- .../json/intervention/ApiInterventionType.scala | 16 ++- .../formats/json/sprayformats/intervention.scala | 17 +-- .../sprayformats/InterventionFormatSuite.scala | 7 +- 5 files changed, 84 insertions(+), 81 deletions(-) (limited to 'src/main') diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala index 6dc42d0..f5b486b 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala @@ -2,24 +2,83 @@ package xyz.driver.pdsuidomain.entities import xyz.driver.pdsuicommon.domain.{LongId, StringId} import xyz.driver.pdsuicommon.logging._ +import xyz.driver.pdsuidomain.entities.InterventionType.DeliveryMethod import xyz.driver.pdsuidomain.entities.InterventionType.DeliveryMethod._ -final case class InterventionType(id: LongId[InterventionType], name: String) +sealed trait InterventionType { + val id: LongId[InterventionType] + val name: String + val deliveryMethod: Set[DeliveryMethod] +} object InterventionType { - sealed trait InterventionTypeName - case object RadiationTherapy extends InterventionTypeName - case object Chemotherapy extends InterventionTypeName - case object TargetedTherapy extends InterventionTypeName - case object Immunotherapy extends InterventionTypeName - case object Surgery extends InterventionTypeName - case object HormoneTherapy extends InterventionTypeName - case object Other extends InterventionTypeName - case object Radiation extends InterventionTypeName - case object SurgeryProcedure extends InterventionTypeName - - def typeFromString: PartialFunction[String, InterventionTypeName] = { + final case object RadiationTherapy extends InterventionType { + val id: LongId[InterventionType] = LongId[InterventionType](1) + val name: String = "Radiation therapy" + val deliveryMethod: Set[DeliveryMethod] = commonMethods + } + + final case object Chemotherapy extends InterventionType { + val id: LongId[InterventionType] = LongId[InterventionType](2) + val name: String = "Chemotherapy" + val deliveryMethod: Set[DeliveryMethod] = commonMethods + } + + final case object TargetedTherapy extends InterventionType { + val id: LongId[InterventionType] = LongId[InterventionType](3) + val name: String = "Targeted therapy" + val deliveryMethod: Set[DeliveryMethod] = commonMethods + } + + final case object Immunotherapy extends InterventionType { + val id: LongId[InterventionType] = LongId[InterventionType](4) + val name: String = "Immunotherapy" + val deliveryMethod: Set[DeliveryMethod] = commonMethods + } + + final case object Surgery extends InterventionType { + val id: LongId[InterventionType] = LongId[InterventionType](5) + val name: String = "Surgery" + val deliveryMethod: Set[DeliveryMethod] = commonMethods + } + + final case object HormoneTherapy extends InterventionType { + val id: LongId[InterventionType] = LongId[InterventionType](6) + val name: String = "Hormone therapy" + val deliveryMethod: Set[DeliveryMethod] = commonMethods + } + + final case object Other extends InterventionType { + val id: LongId[InterventionType] = LongId[InterventionType](7) + val name: String = "Other" + val deliveryMethod: Set[DeliveryMethod] = commonMethods + } + + final case object Radiation extends InterventionType { + val id: LongId[InterventionType] = LongId[InterventionType](8) + val name: String = "Radiation" + val deliveryMethod: Set[DeliveryMethod] = Set( + ExternalRadiationTherapy, + Brachytherapy, + SystemicRadiationTherapyIV, + SystemicRadiationTherapyOral, + ProtonBeamTherapy + ) + } + + final case object SurgeryProcedure extends InterventionType { + val id: LongId[InterventionType] = LongId[InterventionType](9) + val name: String = "Surgery/Procedure" + val deliveryMethod: Set[DeliveryMethod] = Set( + RadioFrequencyAblationRFA, + Cryoablation, + TherapeuticConventionalSurgery, + RoboticAssistedLaparoscopicSurgery + ) + } + + def typeFromString: PartialFunction[String, InterventionType] = { case "Radiation therapy" => RadiationTherapy case "Chemotherapy" => Chemotherapy case "Targeted therapy" => TargetedTherapy @@ -31,18 +90,6 @@ object InterventionType { case "Surgery/Procedure" => SurgeryProcedure } - def typeToString(x: InterventionTypeName): String = x match { - case RadiationTherapy => "Radiation therapy" - case Chemotherapy => "Chemotherapy" - case TargetedTherapy => "Targeted therapy" - case Immunotherapy => "Immunotherapy" - case Surgery => "Surgery" - case HormoneTherapy => "Hormone therapy" - case Other => "Other" - case Radiation => "Radiation" - case SurgeryProcedure => "Surgery/Procedure" - } - sealed trait DeliveryMethod object DeliveryMethod { case object IntravenousInfusionIV extends DeliveryMethod @@ -123,29 +170,6 @@ object InterventionType { Rectal ) - val deliveryMethodGroups: Map[LongId[InterventionType], Set[DeliveryMethod]] = Map( - LongId(1) -> commonMethods, - LongId(2) -> commonMethods, - LongId(3) -> commonMethods, - LongId(4) -> commonMethods, - LongId(5) -> commonMethods, - LongId(6) -> commonMethods, - LongId(7) -> commonMethods, - LongId(8) -> Set( - ExternalRadiationTherapy, - Brachytherapy, - SystemicRadiationTherapyIV, - SystemicRadiationTherapyOral, - ProtonBeamTherapy - ), - LongId(9) -> Set( - RadioFrequencyAblationRFA, - Cryoablation, - TherapeuticConventionalSurgery, - RoboticAssistedLaparoscopicSurgery - ) - ) - implicit def toPhiString(x: InterventionType): PhiString = { import x._ phi"InterventionType(id=$id, name=${Unsafe(name)})" diff --git a/src/main/scala/xyz/driver/pdsuidomain/fakes/entities/trialcuration.scala b/src/main/scala/xyz/driver/pdsuidomain/fakes/entities/trialcuration.scala index a2bdf43..19dd95e 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/fakes/entities/trialcuration.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/fakes/entities/trialcuration.scala @@ -130,9 +130,6 @@ object trialcuration { name = generators.nextString() ) - def nextInterventionType(): InterventionType = InterventionType( - id = nextLongId[InterventionType], - name = generators.nextString() - ) + def nextInterventionType(): InterventionType = InterventionType.typeFromString("Other") } diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiInterventionType.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiInterventionType.scala index c28c5b4..3550437 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiInterventionType.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiInterventionType.scala @@ -2,13 +2,12 @@ package xyz.driver.pdsuidomain.formats.json.intervention import play.api.libs.functional.syntax._ import play.api.libs.json.{Format, JsPath} -import xyz.driver.pdsuicommon.domain.LongId import xyz.driver.pdsuidomain.entities.InterventionType import xyz.driver.pdsuidomain.entities.InterventionType.DeliveryMethod final case class ApiInterventionType(id: Long, name: String, deliveryMethods: List[String]) { - def toDomain = InterventionType(id = LongId[InterventionType](id), name = name) + def toDomain = InterventionType.typeFromString(name) } object ApiInterventionType { @@ -19,12 +18,9 @@ object ApiInterventionType { (JsPath \ "deliveryMethods").format[List[String]] )(ApiInterventionType.apply, unlift(ApiInterventionType.unapply)) - def fromDomain(interventionType: InterventionType) = { - val typeMethods = InterventionType.deliveryMethodGroups.getOrElse(interventionType.id, Set.empty[DeliveryMethod]) - ApiInterventionType( - id = interventionType.id.id, - name = interventionType.name, - deliveryMethods = typeMethods.map(DeliveryMethod.methodToString).toList - ) - } + def fromDomain(interventionType: InterventionType) = ApiInterventionType( + id = interventionType.id.id, + name = interventionType.name, + deliveryMethods = interventionType.deliveryMethod.map(DeliveryMethod.methodToString).toList + ) } diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/intervention.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/intervention.scala index 4bd5bad..2bf1f2b 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/intervention.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/intervention.scala @@ -123,33 +123,22 @@ object intervention { implicit val interventionTypeFormat: JsonFormat[InterventionType] = new RootJsonFormat[InterventionType] { override def read(json: JsValue) = json match { case JsObject(fields) => - val id = fields - .get("id") - .map(_.convertTo[LongId[InterventionType]]) - .getOrElse(deserializationError(s"Intervention type json object does not contain `id` field: $json")) - val name = fields .get("name") .map(_.convertTo[String]) .getOrElse(deserializationError(s"Intervention type json object does not contain `name` field: $json")) - InterventionType(id, name) + InterventionType.typeFromString(name) case _ => deserializationError(s"Expected Json Object as Intervention type, but got $json") } - override def write(obj: InterventionType) = { - val typeMethods = InterventionType.deliveryMethodGroups - .getOrElse(obj.id, Set.empty[DeliveryMethod]) - .map(DeliveryMethod.methodToString) - .toList - + override def write(obj: InterventionType) = JsObject( "id" -> obj.id.toJson, "name" -> obj.name.toJson, - "deliveryMethods" -> typeMethods.toJson + "deliveryMethods" -> obj.deliveryMethod.map(DeliveryMethod.methodToString).toJson ) - } } } diff --git a/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/InterventionFormatSuite.scala b/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/InterventionFormatSuite.scala index d406162..dad39c8 100644 --- a/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/InterventionFormatSuite.scala +++ b/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/InterventionFormatSuite.scala @@ -59,14 +59,11 @@ class InterventionFormatSuite extends FlatSpec with Matchers { } "Json format for InterventionType" should "read and write correct JSON" in { - val interventionType = InterventionType( - id = LongId(9), - name = "type name" - ) + val interventionType = InterventionType.typeFromString("Surgery/Procedure") val writtenJson = interventionTypeFormat.write(interventionType) writtenJson should be( - """{"id":9,"name":"type name","deliveryMethods":["Radio-Frequency Ablation (RFA)", + """{"id":9,"name":"Surgery/Procedure","deliveryMethods":["Radio-Frequency Ablation (RFA)", "Cryoablation","Therapeutic Conventional Surgery","Robotic Assisted Laparoscopic Surgery"]}""".parseJson) val parsedInterventionType = interventionTypeFormat.read(writtenJson) -- cgit v1.2.3 From f10c767bf16f14921a194936188c47dd138ffee7 Mon Sep 17 00:00:00 2001 From: Kseniya Tomskikh Date: Wed, 27 Sep 2017 17:10:59 +0700 Subject: PDSUI-2275 Review fixes --- .../driver/pdsuidomain/entities/Intervention.scala | 48 +++++++++++----------- .../pdsuidomain/fakes/entities/trialcuration.scala | 13 +++++- .../json/intervention/ApiInterventionType.scala | 2 +- .../formats/json/sprayformats/intervention.scala | 2 +- 4 files changed, 38 insertions(+), 27 deletions(-) (limited to 'src/main') diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala index f5b486b..d0eefa9 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala @@ -8,57 +8,57 @@ import xyz.driver.pdsuidomain.entities.InterventionType.DeliveryMethod._ sealed trait InterventionType { val id: LongId[InterventionType] val name: String - val deliveryMethod: Set[DeliveryMethod] + val deliveryMethods: Set[DeliveryMethod] } object InterventionType { final case object RadiationTherapy extends InterventionType { - val id: LongId[InterventionType] = LongId[InterventionType](1) - val name: String = "Radiation therapy" - val deliveryMethod: Set[DeliveryMethod] = commonMethods + val id: LongId[InterventionType] = LongId[InterventionType](1) + val name: String = "Radiation therapy" + val deliveryMethods: Set[DeliveryMethod] = commonMethods } final case object Chemotherapy extends InterventionType { - val id: LongId[InterventionType] = LongId[InterventionType](2) - val name: String = "Chemotherapy" - val deliveryMethod: Set[DeliveryMethod] = commonMethods + val id: LongId[InterventionType] = LongId[InterventionType](2) + val name: String = "Chemotherapy" + val deliveryMethods: Set[DeliveryMethod] = commonMethods } final case object TargetedTherapy extends InterventionType { - val id: LongId[InterventionType] = LongId[InterventionType](3) - val name: String = "Targeted therapy" - val deliveryMethod: Set[DeliveryMethod] = commonMethods + val id: LongId[InterventionType] = LongId[InterventionType](3) + val name: String = "Targeted therapy" + val deliveryMethods: Set[DeliveryMethod] = commonMethods } final case object Immunotherapy extends InterventionType { - val id: LongId[InterventionType] = LongId[InterventionType](4) - val name: String = "Immunotherapy" - val deliveryMethod: Set[DeliveryMethod] = commonMethods + val id: LongId[InterventionType] = LongId[InterventionType](4) + val name: String = "Immunotherapy" + val deliveryMethods: Set[DeliveryMethod] = commonMethods } final case object Surgery extends InterventionType { - val id: LongId[InterventionType] = LongId[InterventionType](5) - val name: String = "Surgery" - val deliveryMethod: Set[DeliveryMethod] = commonMethods + val id: LongId[InterventionType] = LongId[InterventionType](5) + val name: String = "Surgery" + val deliveryMethods: Set[DeliveryMethod] = commonMethods } final case object HormoneTherapy extends InterventionType { - val id: LongId[InterventionType] = LongId[InterventionType](6) - val name: String = "Hormone therapy" - val deliveryMethod: Set[DeliveryMethod] = commonMethods + val id: LongId[InterventionType] = LongId[InterventionType](6) + val name: String = "Hormone therapy" + val deliveryMethods: Set[DeliveryMethod] = commonMethods } final case object Other extends InterventionType { - val id: LongId[InterventionType] = LongId[InterventionType](7) - val name: String = "Other" - val deliveryMethod: Set[DeliveryMethod] = commonMethods + val id: LongId[InterventionType] = LongId[InterventionType](7) + val name: String = "Other" + val deliveryMethods: Set[DeliveryMethod] = commonMethods } final case object Radiation extends InterventionType { val id: LongId[InterventionType] = LongId[InterventionType](8) val name: String = "Radiation" - val deliveryMethod: Set[DeliveryMethod] = Set( + val deliveryMethods: Set[DeliveryMethod] = Set( ExternalRadiationTherapy, Brachytherapy, SystemicRadiationTherapyIV, @@ -70,7 +70,7 @@ object InterventionType { final case object SurgeryProcedure extends InterventionType { val id: LongId[InterventionType] = LongId[InterventionType](9) val name: String = "Surgery/Procedure" - val deliveryMethod: Set[DeliveryMethod] = Set( + val deliveryMethods: Set[DeliveryMethod] = Set( RadioFrequencyAblationRFA, Cryoablation, TherapeuticConventionalSurgery, diff --git a/src/main/scala/xyz/driver/pdsuidomain/fakes/entities/trialcuration.scala b/src/main/scala/xyz/driver/pdsuidomain/fakes/entities/trialcuration.scala index 19dd95e..fe5bf09 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/fakes/entities/trialcuration.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/fakes/entities/trialcuration.scala @@ -7,6 +7,7 @@ import xyz.driver.pdsuidomain.services.CriterionService.RichCriterion object trialcuration { import xyz.driver.core.generators import common._ + import xyz.driver.pdsuidomain.entities.InterventionType._ def nextTrial(): Trial = Trial( id = nextStringId[Trial], @@ -130,6 +131,16 @@ object trialcuration { name = generators.nextString() ) - def nextInterventionType(): InterventionType = InterventionType.typeFromString("Other") + def nextInterventionType(): InterventionType = generators.oneOf[InterventionType]( + RadiationTherapy, + Chemotherapy, + TargetedTherapy, + Immunotherapy, + Surgery, + HormoneTherapy, + Other, + Radiation, + SurgeryProcedure + ) } diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiInterventionType.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiInterventionType.scala index 3550437..3db8bfa 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiInterventionType.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/intervention/ApiInterventionType.scala @@ -21,6 +21,6 @@ object ApiInterventionType { def fromDomain(interventionType: InterventionType) = ApiInterventionType( id = interventionType.id.id, name = interventionType.name, - deliveryMethods = interventionType.deliveryMethod.map(DeliveryMethod.methodToString).toList + deliveryMethods = interventionType.deliveryMethods.map(DeliveryMethod.methodToString).toList ) } diff --git a/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/intervention.scala b/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/intervention.scala index 2bf1f2b..62cb9fa 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/intervention.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/intervention.scala @@ -137,7 +137,7 @@ object intervention { JsObject( "id" -> obj.id.toJson, "name" -> obj.name.toJson, - "deliveryMethods" -> obj.deliveryMethod.map(DeliveryMethod.methodToString).toJson + "deliveryMethods" -> obj.deliveryMethods.map(DeliveryMethod.methodToString).toJson ) } -- cgit v1.2.3 From c24679f1ae7d7ccc4e6693535b0aa3ac0e1ca225 Mon Sep 17 00:00:00 2001 From: Kseniya Tomskikh Date: Wed, 27 Sep 2017 18:28:00 +0700 Subject: PDSUI-2275 Added map with all intervention types --- .../driver/pdsuidomain/entities/Intervention.scala | 24 +++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) (limited to 'src/main') diff --git a/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala b/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala index d0eefa9..9ccc267 100644 --- a/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala +++ b/src/main/scala/xyz/driver/pdsuidomain/entities/Intervention.scala @@ -170,6 +170,18 @@ object InterventionType { Rectal ) + val All: Map[LongId[InterventionType], InterventionType] = Map[LongId[InterventionType], InterventionType]( + LongId[InterventionType](1) -> RadiationTherapy, + LongId[InterventionType](2) -> Chemotherapy, + LongId[InterventionType](3) -> TargetedTherapy, + LongId[InterventionType](4) -> Immunotherapy, + LongId[InterventionType](5) -> Surgery, + LongId[InterventionType](6) -> HormoneTherapy, + LongId[InterventionType](7) -> Other, + LongId[InterventionType](8) -> Radiation, + LongId[InterventionType](9) -> SurgeryProcedure + ) + implicit def toPhiString(x: InterventionType): PhiString = { import x._ phi"InterventionType(id=$id, name=${Unsafe(name)})" @@ -194,7 +206,17 @@ final case class Intervention(id: LongId[Intervention], dosage: String, originalDosage: String, isActive: Boolean, - deliveryMethod: Option[String]) + deliveryMethod: Option[String]) { + def deliveryMethodIsCorrect: Boolean = { + if (this.typeId.nonEmpty) { + this.deliveryMethod.nonEmpty && + InterventionType.All + .getOrElse(this.typeId.get, throw new IllegalArgumentException(s"Not found Intervention type ${this.typeId}")) + .deliveryMethods + .contains(DeliveryMethod.fromString(this.deliveryMethod.get)) + } else true + } +} object Intervention { implicit def toPhiString(x: Intervention): PhiString = { -- cgit v1.2.3