diff --git a/.travis.yml b/.travis.yml index 1cfce33..76b760f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ language: scala scala: - "2.11.5" jdk: - - openjdk7 - oraclejdk8 script: sbt -no-colors ++$TRAVIS_SCALA_VERSION clean coverage test \ No newline at end of file diff --git a/README.md b/README.md index 0425a1c..b090a37 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Authors: * [Krzysztof Romanowski](https://github.com/romanowski) * [Jerzy Müller](https://github.com/Kwestor) * [Mikołaj Jakubowski](https://github.com/mkljakubowski) +* [Jan Paw](https://github.com/explicite) * [Krzysztof Borowski](https://github.com/liosedhel) Feel free to use it, test it and to contribute! diff --git a/build.sbt b/build.sbt index 03226b1..9d0b615 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ organization := "org.virtuslab" name := "beholder" -version := "0.2.10-SNAPSHOT" +version := "0.2.11-SNAPSHOT" scalaVersion := "2.11.7" @@ -65,11 +65,10 @@ pomExtra := https://github.com/VirtusLab/beholder -//xerial.sbt.Sonatype.sonatypeSettings // Scoverage setup -ScoverageSbtPlugin.ScoverageKeys.coverageMinimum := 48 +ScoverageSbtPlugin.ScoverageKeys.coverageMinimum := 65 ScoverageSbtPlugin.ScoverageKeys.coverageFailOnMinimum := true @@ -77,5 +76,9 @@ ScoverageSbtPlugin.ScoverageKeys.coverageExcludedPackages := Seq( "org.virtuslab.beholder.utils.generators.*", // only BaseView5 is tested, all are generated, so there is no need to check them all "org.virtuslab.beholder.views.FilterableViews.*", - "org.virtuslab.beholder.views.FilterableViewsGenerateCode.BaseView[^5].*" + "org.virtuslab.beholder.views.FilterableViewsGenerateCode.BaseView[^6].*", + "org.virtuslab.beholder.filters.FilterFactoryMethods.*" ).mkString(";") + +xerial.sbt.Sonatype.sonatypeSettings + diff --git a/src/main/scala/org/virtuslab/beholder/filters/BaseFilter.scala b/src/main/scala/org/virtuslab/beholder/filters/BaseFilter.scala index 75b868c..9df3775 100644 --- a/src/main/scala/org/virtuslab/beholder/filters/BaseFilter.scala +++ b/src/main/scala/org/virtuslab/beholder/filters/BaseFilter.scala @@ -79,14 +79,16 @@ abstract class BaseFilter[Id, Entity, FilterTable <: BaseView[Id, Entity], Field private def createFilter(data: FilterDefinition, initialFilter: FilterTable => Column[Boolean]): FilterQuery = { table.filter(filters(data.data, initialFilter)) - .sortBy { - inQueryTable => - val globalColumns = - order(data)(inQueryTable).map { - case (column, asc) => if (asc) column.asc else column.desc - }.toSeq.flatMap(_.columns) - new Ordered(globalColumns ++ inQueryTable.id.asc.columns) - } + } + + private def addOrdering(data: FilterDefinition, query: FilterQuery): FilterQuery = { + query.sortBy { + inQueryTable => + val globalColumns = order(data)(inQueryTable).map { + case (column, asc) => if (asc) column.asc else column.desc + }.toSeq.flatMap(_.columns) + new Ordered(globalColumns ++ inQueryTable.id.asc.columns) + } } private def takeAndSkip(data: FilterDefinition, filter: FilterQuery)(implicit session: Session): Seq[Entity] = { @@ -100,14 +102,15 @@ abstract class BaseFilter[Id, Entity, FilterTable <: BaseView[Id, Entity], Field data: FilterDefinition, initialFilter: FilterTable => Column[Boolean] )(implicit session: Session): Seq[Entity] = - takeAndSkip(data, createFilter(data, initialFilter)) + takeAndSkip(data, addOrdering(data, createFilter(data, initialFilter))) override protected def doFilterWithTotalEntitiesNumber( data: FilterDefinition, initialFilter: FilterTable => Column[Boolean] )(implicit session: Session): FilterResult[Entity] = { val filter = createFilter(data, initialFilter) - FilterResult(takeAndSkip(data, filter), filter.length.run) + val filterWithOrdering = addOrdering(data, filter) + FilterResult(takeAndSkip(data, filterWithOrdering), filter.length.run) } //ordering diff --git a/src/main/scala/org/virtuslab/beholder/filters/FilterFactoryMethods.scala b/src/main/scala/org/virtuslab/beholder/filters/FilterFactoryMethods.scala index 188db0e..28a5c71 100644 --- a/src/main/scala/org/virtuslab/beholder/filters/FilterFactoryMethods.scala +++ b/src/main/scala/org/virtuslab/beholder/filters/FilterFactoryMethods.scala @@ -468,4 +468,158 @@ abstract class FilterFactoryMethods[Entity, FieldType[_, _] <: MappedFilterField } } + def create[A1: TypedType, A2: TypedType, A3: TypedType, A4: TypedType, A5: TypedType, A6: TypedType, A7: TypedType, A8: TypedType, A9: TypedType, A10: TypedType, A11: TypedType, A12: TypedType, A13: TypedType, A14: TypedType, A15: TypedType, A16: TypedType, A17: TypedType, A18: TypedType, A19: TypedType, B1, B2, B3, B4, B5, B6, B7, B8, B9, B10, B11, B12, B13, B14, B15, B16, B17, B18, B19, T <: BaseView19[Entity, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19]]( + table: TableQuery[T], + c1Mapping: FieldType[A1, B1], + c2Mapping: FieldType[A2, B2], + c3Mapping: FieldType[A3, B3], + c4Mapping: FieldType[A4, B4], + c5Mapping: FieldType[A5, B5], + c6Mapping: FieldType[A6, B6], + c7Mapping: FieldType[A7, B7], + c8Mapping: FieldType[A8, B8], + c9Mapping: FieldType[A9, B9], + c10Mapping: FieldType[A10, B10], + c11Mapping: FieldType[A11, B11], + c12Mapping: FieldType[A12, B12], + c13Mapping: FieldType[A13, B13], + c14Mapping: FieldType[A14, B14], + c15Mapping: FieldType[A15, B15], + c16Mapping: FieldType[A16, B16], + c17Mapping: FieldType[A17, B17], + c18Mapping: FieldType[A18, B18], + c19Mapping: FieldType[A19, B19] + ): TableFilterAPI[Entity, Formatter, T] = { + + new BaseFilter[A1, Entity, T, FieldType[_, _], Formatter](table) { + override val formatter: Formatter = createFormatter(this) + + override protected def emptyFilterDataInner: Seq[Option[Any]] = Seq.fill(19)(None) + + override def filterFields: Seq[FieldType[_, _]] = + Seq[FieldType[_, _]](c1Mapping, c2Mapping, c3Mapping, c4Mapping, c5Mapping, c6Mapping, c7Mapping, c8Mapping, c9Mapping, c10Mapping, c11Mapping, c12Mapping, c13Mapping, c14Mapping, c15Mapping, c16Mapping, c17Mapping, c18Mapping, c19Mapping) + + override protected def tableColumns(table: T): Seq[LongUnicornPlay.driver.simple.Column[_]] = Seq( + table.c1, table.c2, table.c3, table.c4, table.c5, table.c6, table.c7, table.c8, table.c9, table.c10, table.c11, table.c12, table.c13, table.c14, table.c15, table.c16, table.c17, table.c18, table.c19 + ) + } + } + + def create[A1: TypedType, A2: TypedType, A3: TypedType, A4: TypedType, A5: TypedType, A6: TypedType, A7: TypedType, A8: TypedType, A9: TypedType, A10: TypedType, A11: TypedType, A12: TypedType, A13: TypedType, A14: TypedType, A15: TypedType, A16: TypedType, A17: TypedType, A18: TypedType, A19: TypedType, A20: TypedType, B1, B2, B3, B4, B5, B6, B7, B8, B9, B10, B11, B12, B13, B14, B15, B16, B17, B18, B19, B20, T <: BaseView20[Entity, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20]]( + table: TableQuery[T], + c1Mapping: FieldType[A1, B1], + c2Mapping: FieldType[A2, B2], + c3Mapping: FieldType[A3, B3], + c4Mapping: FieldType[A4, B4], + c5Mapping: FieldType[A5, B5], + c6Mapping: FieldType[A6, B6], + c7Mapping: FieldType[A7, B7], + c8Mapping: FieldType[A8, B8], + c9Mapping: FieldType[A9, B9], + c10Mapping: FieldType[A10, B10], + c11Mapping: FieldType[A11, B11], + c12Mapping: FieldType[A12, B12], + c13Mapping: FieldType[A13, B13], + c14Mapping: FieldType[A14, B14], + c15Mapping: FieldType[A15, B15], + c16Mapping: FieldType[A16, B16], + c17Mapping: FieldType[A17, B17], + c18Mapping: FieldType[A18, B18], + c19Mapping: FieldType[A19, B19], + c20Mapping: FieldType[A20, B20] + ): TableFilterAPI[Entity, Formatter, T] = { + + new BaseFilter[A1, Entity, T, FieldType[_, _], Formatter](table) { + override val formatter: Formatter = createFormatter(this) + + override protected def emptyFilterDataInner: Seq[Option[Any]] = Seq.fill(20)(None) + + override def filterFields: Seq[FieldType[_, _]] = + Seq[FieldType[_, _]](c1Mapping, c2Mapping, c3Mapping, c4Mapping, c5Mapping, c6Mapping, c7Mapping, c8Mapping, c9Mapping, c10Mapping, c11Mapping, c12Mapping, c13Mapping, c14Mapping, c15Mapping, c16Mapping, c17Mapping, c18Mapping, c19Mapping, c20Mapping) + + override protected def tableColumns(table: T): Seq[LongUnicornPlay.driver.simple.Column[_]] = Seq( + table.c1, table.c2, table.c3, table.c4, table.c5, table.c6, table.c7, table.c8, table.c9, table.c10, table.c11, table.c12, table.c13, table.c14, table.c15, table.c16, table.c17, table.c18, table.c19, table.c20 + ) + } + } + + def create[A1: TypedType, A2: TypedType, A3: TypedType, A4: TypedType, A5: TypedType, A6: TypedType, A7: TypedType, A8: TypedType, A9: TypedType, A10: TypedType, A11: TypedType, A12: TypedType, A13: TypedType, A14: TypedType, A15: TypedType, A16: TypedType, A17: TypedType, A18: TypedType, A19: TypedType, A20: TypedType, A21: TypedType, B1, B2, B3, B4, B5, B6, B7, B8, B9, B10, B11, B12, B13, B14, B15, B16, B17, B18, B19, B20, B21, T <: BaseView21[Entity, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21]]( + table: TableQuery[T], + c1Mapping: FieldType[A1, B1], + c2Mapping: FieldType[A2, B2], + c3Mapping: FieldType[A3, B3], + c4Mapping: FieldType[A4, B4], + c5Mapping: FieldType[A5, B5], + c6Mapping: FieldType[A6, B6], + c7Mapping: FieldType[A7, B7], + c8Mapping: FieldType[A8, B8], + c9Mapping: FieldType[A9, B9], + c10Mapping: FieldType[A10, B10], + c11Mapping: FieldType[A11, B11], + c12Mapping: FieldType[A12, B12], + c13Mapping: FieldType[A13, B13], + c14Mapping: FieldType[A14, B14], + c15Mapping: FieldType[A15, B15], + c16Mapping: FieldType[A16, B16], + c17Mapping: FieldType[A17, B17], + c18Mapping: FieldType[A18, B18], + c19Mapping: FieldType[A19, B19], + c20Mapping: FieldType[A20, B20], + c21Mapping: FieldType[A21, B21] + ): TableFilterAPI[Entity, Formatter, T] = { + + new BaseFilter[A1, Entity, T, FieldType[_, _], Formatter](table) { + override val formatter: Formatter = createFormatter(this) + + override protected def emptyFilterDataInner: Seq[Option[Any]] = Seq.fill(21)(None) + + override def filterFields: Seq[FieldType[_, _]] = + Seq[FieldType[_, _]](c1Mapping, c2Mapping, c3Mapping, c4Mapping, c5Mapping, c6Mapping, c7Mapping, c8Mapping, c9Mapping, c10Mapping, c11Mapping, c12Mapping, c13Mapping, c14Mapping, c15Mapping, c16Mapping, c17Mapping, c18Mapping, c19Mapping, c20Mapping, c21Mapping) + + override protected def tableColumns(table: T): Seq[LongUnicornPlay.driver.simple.Column[_]] = Seq( + table.c1, table.c2, table.c3, table.c4, table.c5, table.c6, table.c7, table.c8, table.c9, table.c10, table.c11, table.c12, table.c13, table.c14, table.c15, table.c16, table.c17, table.c18, table.c19, table.c20, table.c21 + ) + } + } + + def create[A1: TypedType, A2: TypedType, A3: TypedType, A4: TypedType, A5: TypedType, A6: TypedType, A7: TypedType, A8: TypedType, A9: TypedType, A10: TypedType, A11: TypedType, A12: TypedType, A13: TypedType, A14: TypedType, A15: TypedType, A16: TypedType, A17: TypedType, A18: TypedType, A19: TypedType, A20: TypedType, A21: TypedType, A22: TypedType, B1, B2, B3, B4, B5, B6, B7, B8, B9, B10, B11, B12, B13, B14, B15, B16, B17, B18, B19, B20, B21, B22, T <: BaseView22[Entity, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22]]( + table: TableQuery[T], + c1Mapping: FieldType[A1, B1], + c2Mapping: FieldType[A2, B2], + c3Mapping: FieldType[A3, B3], + c4Mapping: FieldType[A4, B4], + c5Mapping: FieldType[A5, B5], + c6Mapping: FieldType[A6, B6], + c7Mapping: FieldType[A7, B7], + c8Mapping: FieldType[A8, B8], + c9Mapping: FieldType[A9, B9], + c10Mapping: FieldType[A10, B10], + c11Mapping: FieldType[A11, B11], + c12Mapping: FieldType[A12, B12], + c13Mapping: FieldType[A13, B13], + c14Mapping: FieldType[A14, B14], + c15Mapping: FieldType[A15, B15], + c16Mapping: FieldType[A16, B16], + c17Mapping: FieldType[A17, B17], + c18Mapping: FieldType[A18, B18], + c19Mapping: FieldType[A19, B19], + c20Mapping: FieldType[A20, B20], + c21Mapping: FieldType[A21, B21], + c22Mapping: FieldType[A22, B22] + ): TableFilterAPI[Entity, Formatter, T] = { + + new BaseFilter[A1, Entity, T, FieldType[_, _], Formatter](table) { + override val formatter: Formatter = createFormatter(this) + + override protected def emptyFilterDataInner: Seq[Option[Any]] = Seq.fill(22)(None) + + override def filterFields: Seq[FieldType[_, _]] = + Seq[FieldType[_, _]](c1Mapping, c2Mapping, c3Mapping, c4Mapping, c5Mapping, c6Mapping, c7Mapping, c8Mapping, c9Mapping, c10Mapping, c11Mapping, c12Mapping, c13Mapping, c14Mapping, c15Mapping, c16Mapping, c17Mapping, c18Mapping, c19Mapping, c20Mapping, c21Mapping, c22Mapping) + + override protected def tableColumns(table: T): Seq[LongUnicornPlay.driver.simple.Column[_]] = Seq( + table.c1, table.c2, table.c3, table.c4, table.c5, table.c6, table.c7, table.c8, table.c9, table.c10, table.c11, table.c12, table.c13, table.c14, table.c15, table.c16, table.c17, table.c18, table.c19, table.c20, table.c21, table.c22 + ) + } + } + } diff --git a/src/main/scala/org/virtuslab/beholder/filters/forms/FormFilterField.scala b/src/main/scala/org/virtuslab/beholder/filters/forms/FormFilterField.scala index df82131..1a663eb 100644 --- a/src/main/scala/org/virtuslab/beholder/filters/forms/FormFilterField.scala +++ b/src/main/scala/org/virtuslab/beholder/filters/forms/FormFilterField.scala @@ -4,7 +4,7 @@ import org.virtuslab.beholder.filters.{ FilterRange, MappedFilterField } import org.virtuslab.beholder.utils.ILikeExtension._ import org.virtuslab.unicorn.LongUnicornPlay.driver.simple._ import play.api.data.Forms._ -import play.api.data.format.Formatter +import play.api.data.format.{ Formats, Formatter } import play.api.data.validation.Constraint import play.api.data.{ FormError, Mapping } @@ -19,6 +19,8 @@ abstract class FormFilterField[A: TypedType, B](mapping: Mapping[B]) extends Map object FromFilterFields { + import org.virtuslab.beholder.utils.SeqParametersHelper._ + private def ignoreMapping[T] = new Mapping[T] { val key = "" val mappings: Seq[Mapping[_]] = Nil @@ -49,6 +51,15 @@ object FromFilterFields { override def filterOnColumn(column: Column[Int])(data: Int): Column[Option[Boolean]] = column === data } + /** + * check if value is in given sequence + */ + object inIntFieldSeq extends FormFilterField[Int, Seq[Int]](seq(number)) { + override protected def filterOnColumn(column: Column[Int])(dataSeq: Seq[Int]): Column[Option[Boolean]] = { + isColumnValueInsideSeq(column)(dataSeq)((column, data) => column === data) + } + } + /** * simple check boolean */ @@ -64,8 +75,14 @@ object FromFilterFields { } /** - * search in text (ilike) + * check if text is in given text sequence (ilike) */ + object inTextSeq extends FormFilterField[String, Seq[String]](seq(text)) { + override def filterOnColumn(column: Column[String])(dataSeq: Seq[String]): Column[Option[Boolean]] = { + isColumnValueInsideSeq(column)(dataSeq)((column, d) => column ilike s"%${escape(d)}%") + } + } + object inBigDecimal extends FormFilterField[BigDecimal, BigDecimal](bigDecimal) { override def filterOnColumn(column: Column[BigDecimal])(data: BigDecimal): Column[Option[Boolean]] = column === data } @@ -82,13 +99,26 @@ object FromFilterFields { * @tparam T - enum class (eg. Colors.type) */ def inEnum[T <: Enumeration](implicit tm: BaseTypedType[T#Value], formatter: Formatter[T#Value]): FormFilterField[T#Value, T#Value] = - inField[T#Value] + new FormFilterField[T#Value, T#Value](of[T#Value]) { + override def filterOnColumn(column: Column[T#Value])(data: T#Value): Column[Option[Boolean]] = column === data + } + + def inEnumSeq[T <: Enumeration](implicit tm: BaseTypedType[T#Value], formatter: Formatter[T#Value]): FormFilterField[T#Value, Seq[T#Value]] = { + inFieldSeq[T#Value] + } def inField[T](implicit tm: BaseTypedType[T], formatter: Formatter[T]): FormFilterField[T, T] = new FormFilterField[T, T](of[T]) { override def filterOnColumn(column: Column[T])(data: T): Column[Option[Boolean]] = column === data } + def inFieldSeq[T](implicit tm: BaseTypedType[T], formatter: Formatter[T]): FormFilterField[T, Seq[T]] = + new FormFilterField[T, Seq[T]](seq(of[T])) { + override def filterOnColumn(column: Column[T])(dataSeq: Seq[T]): Column[Option[Boolean]] = { + isColumnValueInsideSeq(column)(dataSeq)((column, data) => column === data) + } + } + def inRange[T](implicit tm: BaseTypedType[T], f: Formatter[T]): FormFilterField[T, FilterRange[T]] = new FormFilterField[T, FilterRange[T]](rangeMapping[T]) { override def filterOnColumn(column: Column[T])(value: FilterRange[T]): Column[Option[Boolean]] = { diff --git a/src/main/scala/org/virtuslab/beholder/filters/json/FilterController.scala b/src/main/scala/org/virtuslab/beholder/filters/json/FilterController.scala index b3937c8..cb2b5e6 100644 --- a/src/main/scala/org/virtuslab/beholder/filters/json/FilterController.scala +++ b/src/main/scala/org/virtuslab/beholder/filters/json/FilterController.scala @@ -21,14 +21,17 @@ trait FilterControllerBase[Context, Entity <: Product] extends Controller { request.body.asJson.map(formatter.filterDefinition).map { fd => fd.map { filterDefinition => - val filterResult = callFilter(context, mapFilterData(filterDefinition)) - formatter.results(filterDefinition, filterResult) + val filterResult = callFilter(context, mapFilterData(filterDefinition, context)) + formatter.results(filterDefinition, modifyFilterResults(filterResult, filterDefinition, context)) } }.getOrElse(JsError("json expected")) } //for filter modification such us setting default parameters etc. - protected def mapFilterData(data: FilterDefinition) = data + protected def mapFilterData(data: FilterDefinition, context: Context) = data + + //for result modification such as sorting or fetching additional data + protected def modifyFilterResults(results: FilterResult[Entity], filterDefinition: FilterDefinition, context: Context) = results } abstract class FilterController[Entity <: Product](filter: FilterAPI[Entity, JsonFormatter[Entity]]) diff --git a/src/main/scala/org/virtuslab/beholder/filters/json/JsonFilterField.scala b/src/main/scala/org/virtuslab/beholder/filters/json/JsonFilterField.scala index 0fd63a0..3564fb7 100644 --- a/src/main/scala/org/virtuslab/beholder/filters/json/JsonFilterField.scala +++ b/src/main/scala/org/virtuslab/beholder/filters/json/JsonFilterField.scala @@ -37,13 +37,24 @@ abstract class ImplicitlyJsonFilterFiled[A: TypedType: Writes, B: Format](dataTy object JsonFilterFields { + import org.virtuslab.beholder.utils.SeqParametersHelper._ + /** - * search in text (ilike) + * find exact number */ object inIntField extends ImplicitlyJsonFilterFiled[Int, Int]("Int") { override protected def filterOnColumn(column: Column[Int])(data: Int): Column[Option[Boolean]] = column === data } + /** + * check if value is in given sequence + */ + object inIntFieldSeq extends ImplicitlyJsonFilterFiled[Int, Seq[Int]]("IntSeq") { + override protected def filterOnColumn(column: Column[Int])(dataSeq: Seq[Int]): Column[Option[Boolean]] = { + isColumnValueInsideSeq(column)(dataSeq)((column, data) => column === data) + } + } + object inBigDecimal extends ImplicitlyJsonFilterFiled[BigDecimal, BigDecimal]("bigDecimal") { override protected def filterOnColumn(column: Column[BigDecimal])(data: BigDecimal): Column[Option[Boolean]] = column === data } @@ -62,6 +73,15 @@ object JsonFilterFields { override def filterOnColumn(column: Column[String])(data: String): Column[Option[Boolean]] = column ilike s"%${escape(data)}%" } + /** + * check if text is in given text sequence (ilike) + */ + object inTextSeq extends ImplicitlyJsonFilterFiled[String, Seq[String]]("TextSeq") { + override def filterOnColumn(column: Column[String])(data: Seq[String]): Column[Option[Boolean]] = { + isColumnValueInsideSeq(column)(data)((column, d) => column ilike s"%${escape(d)}%") + } + } + /** * search in text (ilike) for optional fields */ @@ -103,6 +123,30 @@ object JsonFilterFields { } } + /** + * check if enum value is in given sequence + * @tparam T - enum class (eg. Colors.type) + */ + def inEnumSeq[T <: Enumeration](enum: T)(implicit tm: BaseTypedType[T#Value], formatter: Format[T#Value]): JsonFilterField[T#Value, Seq[T#Value]] = { + new JsonFilterField[T#Value, Seq[T#Value]] { + override def fieldTypeDefinition: JsValue = JsArray( + enum.values.toList.map(v => Json.toJson(v.asInstanceOf[T#Value])) + ) + + override protected[json] def valueWrite: Writes[T#Value] = formatter + + override protected def filterFormat: Format[Seq[T#Value]] = new Format[Seq[T#Value]] { + override def reads(json: JsValue): JsResult[Seq[T#Value]] = JsSuccess(json.as[Seq[T#Value]]) + + override def writes(o: Seq[T#Value]): JsValue = JsArray(o.map(Json.toJson(_))) + } + + override protected def filterOnColumn(column: Column[T#Value])(dataSeq: Seq[T#Value]): Column[Option[Boolean]] = { + isColumnValueInsideSeq(column)(dataSeq)((column, data) => column === data) + } + } + } + private implicit def rangeFormat[T: Format]: Format[FilterRange[T]] = ((__ \ "from").formatNullable[T] and (__ \ "to").formatNullable[T])(FilterRange.apply, unlift(FilterRange.unapply)) @@ -112,6 +156,13 @@ object JsonFilterFields { override def filterOnColumn(column: Column[T])(data: T): Column[Option[Boolean]] = column === data } + def inFieldSeq[T: BaseTypedType: Format](typeName: String) = + new ImplicitlyJsonFilterFiled[T, Seq[T]](typeName) { + override def filterOnColumn(column: Column[T])(dataSeq: Seq[T]): Column[Option[Boolean]] = { + isColumnValueInsideSeq(column)(dataSeq)((column, data) => column === data) + } + } + def inRange[T: BaseTypedType: Format](baseType: JsonFilterField[T, T]): JsonFilterField[T, FilterRange[T]] = new JsonFilterField[T, FilterRange[T]] { override def filterOnColumn(column: Column[T])(value: FilterRange[T]): Column[Option[Boolean]] = { diff --git a/src/main/scala/org/virtuslab/beholder/utils/SeqParametersHelper.scala b/src/main/scala/org/virtuslab/beholder/utils/SeqParametersHelper.scala new file mode 100644 index 0000000..d8f24d3 --- /dev/null +++ b/src/main/scala/org/virtuslab/beholder/utils/SeqParametersHelper.scala @@ -0,0 +1,14 @@ +package org.virtuslab.beholder.utils + +import org.virtuslab.unicorn.LongUnicornPlay.driver.simple._ + +object SeqParametersHelper { + + def isColumnValueInsideSeq[A](column: Column[A])(dataSeq: Seq[A])(isEqual: (Column[A], A) => Column[Option[Boolean]]): Column[Option[Boolean]] = { + if (dataSeq.isEmpty) { + LiteralColumn(Option(true)) + } else { + dataSeq.foldLeft(Option(false): Column[Option[Boolean]])((acc, data) => acc || isEqual(column, data)) + } + } +} diff --git a/src/main/scala/org/virtuslab/beholder/views/BaseView.scala b/src/main/scala/org/virtuslab/beholder/views/BaseView.scala index f70e3e4..facd0e8 100644 --- a/src/main/scala/org/virtuslab/beholder/views/BaseView.scala +++ b/src/main/scala/org/virtuslab/beholder/views/BaseView.scala @@ -44,7 +44,7 @@ abstract class BaseView[Id, Entity](tag: Tag, val viewName: String) extends Base object BaseView { implicit class WithViewDDL(val query: TableQuery[_ <: BaseView[_, _]]) extends AnyVal { - def viewDDL = ViewDDL(query.shaped.value) + def viewDDL = ViewDDL(query.baseTableRow) } case class ViewDDL(table: BaseView[_, _]) extends DDL { diff --git a/src/main/scala/org/virtuslab/beholder/views/FilterableViewsGenerateCode.scala b/src/main/scala/org/virtuslab/beholder/views/FilterableViewsGenerateCode.scala index 98b6fdc..84801c4 100644 --- a/src/main/scala/org/virtuslab/beholder/views/FilterableViewsGenerateCode.scala +++ b/src/main/scala/org/virtuslab/beholder/views/FilterableViewsGenerateCode.scala @@ -1083,4 +1083,352 @@ private[beholder] trait FilterableViewsGenerateCode { def * = (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15, c16, c17, c18) <> (apply.tupled, unapply) } + def createView[T: ClassTag, E, A1: TypedType, A2: TypedType, A3: TypedType, A4: TypedType, A5: TypedType, A6: TypedType, A7: TypedType, A8: TypedType, A9: TypedType, A10: TypedType, A11: TypedType, A12: TypedType, A13: TypedType, A14: TypedType, A15: TypedType, A16: TypedType, A17: TypedType, A18: TypedType, A19: TypedType]( + name: String, + apply: (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19) => T, + unapply: T => Option[(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19)], + baseQuery: Query[E, _, Seq] + )( + mappings: E => ((String, Column[A1]), (String, Column[A2]), (String, Column[A3]), (String, Column[A4]), (String, Column[A5]), (String, Column[A6]), (String, Column[A7]), (String, Column[A8]), (String, Column[A9]), (String, Column[A10]), (String, Column[A11]), (String, Column[A12]), (String, Column[A13]), (String, Column[A14]), (String, Column[A15]), (String, Column[A16]), (String, Column[A17]), (String, Column[A18]), (String, Column[A19])) + ): TableQuery[BaseView19[T, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19]] = { + + var columnsNames = Seq[String]() + + val preparedQuery: Query[_, T, Seq] = { + val mappedQuery = baseQuery.map { + t => + mappings(t) match { + case ((name1, c1), (name2, c2), (name3, c3), (name4, c4), (name5, c5), (name6, c6), (name7, c7), (name8, c8), (name9, c9), (name10, c10), (name11, c11), (name12, c12), (name13, c13), (name14, c14), (name15, c15), (name16, c16), (name17, c17), (name18, c18), (name19, c19)) => + columnsNames = Seq(name1, name2, name3, name4, name5, name6, name7, name8, name9, name10, name11, name12, name13, name14, name15, name16, name17, name18, name19) + (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15, c16, c17, c18, c19) <> (apply.tupled, unapply) + } + } + + for { + a <- mappedQuery + } yield a + } + TableQuery.apply(tag => new BaseView19[T, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19](tag, name, columnsNames, apply, unapply, preparedQuery)) + + } + + class BaseView19[T: ClassTag, A1: TypedType, A2: TypedType, A3: TypedType, A4: TypedType, A5: TypedType, A6: TypedType, A7: TypedType, A8: TypedType, A9: TypedType, A10: TypedType, A11: TypedType, A12: TypedType, A13: TypedType, A14: TypedType, A15: TypedType, A16: TypedType, A17: TypedType, A18: TypedType, A19: TypedType]( + tag: Tag, + name: String, + val columnNames: Seq[String], + apply: (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19) => T, + unapply: T => Option[(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19)], + val query: Query[_, T, Seq] + ) extends BaseView[A1, T](tag, name) { + def c1 = column[A1](columnNames(0)) + def c2 = column[A2](columnNames(1)) + def c3 = column[A3](columnNames(2)) + def c4 = column[A4](columnNames(3)) + def c5 = column[A5](columnNames(4)) + def c6 = column[A6](columnNames(5)) + def c7 = column[A7](columnNames(6)) + def c8 = column[A8](columnNames(7)) + def c9 = column[A9](columnNames(8)) + def c10 = column[A10](columnNames(9)) + def c11 = column[A11](columnNames(10)) + def c12 = column[A12](columnNames(11)) + def c13 = column[A13](columnNames(12)) + def c14 = column[A14](columnNames(13)) + def c15 = column[A15](columnNames(14)) + def c16 = column[A16](columnNames(15)) + def c17 = column[A17](columnNames(16)) + def c18 = column[A18](columnNames(17)) + def c19 = column[A19](columnNames(18)) + + override def id = c1 + + override protected val columns: Seq[(String, this.type => Column[_])] = Seq( + columnNames(0) -> (_.c1), + columnNames(1) -> (_.c2), + columnNames(2) -> (_.c3), + columnNames(3) -> (_.c4), + columnNames(4) -> (_.c5), + columnNames(5) -> (_.c6), + columnNames(6) -> (_.c7), + columnNames(7) -> (_.c8), + columnNames(8) -> (_.c9), + columnNames(9) -> (_.c10), + columnNames(10) -> (_.c11), + columnNames(11) -> (_.c12), + columnNames(12) -> (_.c13), + columnNames(13) -> (_.c14), + columnNames(14) -> (_.c15), + columnNames(15) -> (_.c16), + columnNames(16) -> (_.c17), + columnNames(17) -> (_.c18), + columnNames(18) -> (_.c19) + ) + + def * = (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15, c16, c17, c18, c19) <> (apply.tupled, unapply) + } + + def createView[T: ClassTag, E, A1: TypedType, A2: TypedType, A3: TypedType, A4: TypedType, A5: TypedType, A6: TypedType, A7: TypedType, A8: TypedType, A9: TypedType, A10: TypedType, A11: TypedType, A12: TypedType, A13: TypedType, A14: TypedType, A15: TypedType, A16: TypedType, A17: TypedType, A18: TypedType, A19: TypedType, A20: TypedType]( + name: String, + apply: (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20) => T, + unapply: T => Option[(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20)], + baseQuery: Query[E, _, Seq] + )( + mappings: E => ((String, Column[A1]), (String, Column[A2]), (String, Column[A3]), (String, Column[A4]), (String, Column[A5]), (String, Column[A6]), (String, Column[A7]), (String, Column[A8]), (String, Column[A9]), (String, Column[A10]), (String, Column[A11]), (String, Column[A12]), (String, Column[A13]), (String, Column[A14]), (String, Column[A15]), (String, Column[A16]), (String, Column[A17]), (String, Column[A18]), (String, Column[A19]), (String, Column[A20])) + ): TableQuery[BaseView20[T, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20]] = { + + var columnsNames = Seq[String]() + + val preparedQuery: Query[_, T, Seq] = { + val mappedQuery = baseQuery.map { + t => + mappings(t) match { + case ((name1, c1), (name2, c2), (name3, c3), (name4, c4), (name5, c5), (name6, c6), (name7, c7), (name8, c8), (name9, c9), (name10, c10), (name11, c11), (name12, c12), (name13, c13), (name14, c14), (name15, c15), (name16, c16), (name17, c17), (name18, c18), (name19, c19), (name20, c20)) => + columnsNames = Seq(name1, name2, name3, name4, name5, name6, name7, name8, name9, name10, name11, name12, name13, name14, name15, name16, name17, name18, name19, name20) + (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15, c16, c17, c18, c19, c20) <> (apply.tupled, unapply) + } + } + + for { + a <- mappedQuery + } yield a + } + TableQuery.apply(tag => new BaseView20[T, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20](tag, name, columnsNames, apply, unapply, preparedQuery)) + + } + + class BaseView20[T: ClassTag, A1: TypedType, A2: TypedType, A3: TypedType, A4: TypedType, A5: TypedType, A6: TypedType, A7: TypedType, A8: TypedType, A9: TypedType, A10: TypedType, A11: TypedType, A12: TypedType, A13: TypedType, A14: TypedType, A15: TypedType, A16: TypedType, A17: TypedType, A18: TypedType, A19: TypedType, A20: TypedType]( + tag: Tag, + name: String, + val columnNames: Seq[String], + apply: (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20) => T, + unapply: T => Option[(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20)], + val query: Query[_, T, Seq] + ) extends BaseView[A1, T](tag, name) { + def c1 = column[A1](columnNames(0)) + def c2 = column[A2](columnNames(1)) + def c3 = column[A3](columnNames(2)) + def c4 = column[A4](columnNames(3)) + def c5 = column[A5](columnNames(4)) + def c6 = column[A6](columnNames(5)) + def c7 = column[A7](columnNames(6)) + def c8 = column[A8](columnNames(7)) + def c9 = column[A9](columnNames(8)) + def c10 = column[A10](columnNames(9)) + def c11 = column[A11](columnNames(10)) + def c12 = column[A12](columnNames(11)) + def c13 = column[A13](columnNames(12)) + def c14 = column[A14](columnNames(13)) + def c15 = column[A15](columnNames(14)) + def c16 = column[A16](columnNames(15)) + def c17 = column[A17](columnNames(16)) + def c18 = column[A18](columnNames(17)) + def c19 = column[A19](columnNames(18)) + def c20 = column[A20](columnNames(19)) + + override def id = c1 + + override protected val columns: Seq[(String, this.type => Column[_])] = Seq( + columnNames(0) -> (_.c1), + columnNames(1) -> (_.c2), + columnNames(2) -> (_.c3), + columnNames(3) -> (_.c4), + columnNames(4) -> (_.c5), + columnNames(5) -> (_.c6), + columnNames(6) -> (_.c7), + columnNames(7) -> (_.c8), + columnNames(8) -> (_.c9), + columnNames(9) -> (_.c10), + columnNames(10) -> (_.c11), + columnNames(11) -> (_.c12), + columnNames(12) -> (_.c13), + columnNames(13) -> (_.c14), + columnNames(14) -> (_.c15), + columnNames(15) -> (_.c16), + columnNames(16) -> (_.c17), + columnNames(17) -> (_.c18), + columnNames(18) -> (_.c19), + columnNames(19) -> (_.c20) + ) + + def * = (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15, c16, c17, c18, c19, c20) <> (apply.tupled, unapply) + } + + def createView[T: ClassTag, E, A1: TypedType, A2: TypedType, A3: TypedType, A4: TypedType, A5: TypedType, A6: TypedType, A7: TypedType, A8: TypedType, A9: TypedType, A10: TypedType, A11: TypedType, A12: TypedType, A13: TypedType, A14: TypedType, A15: TypedType, A16: TypedType, A17: TypedType, A18: TypedType, A19: TypedType, A20: TypedType, A21: TypedType]( + name: String, + apply: (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21) => T, + unapply: T => Option[(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21)], + baseQuery: Query[E, _, Seq] + )( + mappings: E => ((String, Column[A1]), (String, Column[A2]), (String, Column[A3]), (String, Column[A4]), (String, Column[A5]), (String, Column[A6]), (String, Column[A7]), (String, Column[A8]), (String, Column[A9]), (String, Column[A10]), (String, Column[A11]), (String, Column[A12]), (String, Column[A13]), (String, Column[A14]), (String, Column[A15]), (String, Column[A16]), (String, Column[A17]), (String, Column[A18]), (String, Column[A19]), (String, Column[A20]), (String, Column[A21])) + ): TableQuery[BaseView21[T, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21]] = { + + var columnsNames = Seq[String]() + + val preparedQuery: Query[_, T, Seq] = { + val mappedQuery = baseQuery.map { + t => + mappings(t) match { + case ((name1, c1), (name2, c2), (name3, c3), (name4, c4), (name5, c5), (name6, c6), (name7, c7), (name8, c8), (name9, c9), (name10, c10), (name11, c11), (name12, c12), (name13, c13), (name14, c14), (name15, c15), (name16, c16), (name17, c17), (name18, c18), (name19, c19), (name20, c20), (name21, c21)) => + columnsNames = Seq(name1, name2, name3, name4, name5, name6, name7, name8, name9, name10, name11, name12, name13, name14, name15, name16, name17, name18, name19, name20, name21) + (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15, c16, c17, c18, c19, c20, c21) <> (apply.tupled, unapply) + } + } + + for { + a <- mappedQuery + } yield a + } + TableQuery.apply(tag => new BaseView21[T, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21](tag, name, columnsNames, apply, unapply, preparedQuery)) + + } + + class BaseView21[T: ClassTag, A1: TypedType, A2: TypedType, A3: TypedType, A4: TypedType, A5: TypedType, A6: TypedType, A7: TypedType, A8: TypedType, A9: TypedType, A10: TypedType, A11: TypedType, A12: TypedType, A13: TypedType, A14: TypedType, A15: TypedType, A16: TypedType, A17: TypedType, A18: TypedType, A19: TypedType, A20: TypedType, A21: TypedType]( + tag: Tag, + name: String, + val columnNames: Seq[String], + apply: (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21) => T, + unapply: T => Option[(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21)], + val query: Query[_, T, Seq] + ) extends BaseView[A1, T](tag, name) { + def c1 = column[A1](columnNames(0)) + def c2 = column[A2](columnNames(1)) + def c3 = column[A3](columnNames(2)) + def c4 = column[A4](columnNames(3)) + def c5 = column[A5](columnNames(4)) + def c6 = column[A6](columnNames(5)) + def c7 = column[A7](columnNames(6)) + def c8 = column[A8](columnNames(7)) + def c9 = column[A9](columnNames(8)) + def c10 = column[A10](columnNames(9)) + def c11 = column[A11](columnNames(10)) + def c12 = column[A12](columnNames(11)) + def c13 = column[A13](columnNames(12)) + def c14 = column[A14](columnNames(13)) + def c15 = column[A15](columnNames(14)) + def c16 = column[A16](columnNames(15)) + def c17 = column[A17](columnNames(16)) + def c18 = column[A18](columnNames(17)) + def c19 = column[A19](columnNames(18)) + def c20 = column[A20](columnNames(19)) + def c21 = column[A21](columnNames(20)) + + override def id = c1 + + override protected val columns: Seq[(String, this.type => Column[_])] = Seq( + columnNames(0) -> (_.c1), + columnNames(1) -> (_.c2), + columnNames(2) -> (_.c3), + columnNames(3) -> (_.c4), + columnNames(4) -> (_.c5), + columnNames(5) -> (_.c6), + columnNames(6) -> (_.c7), + columnNames(7) -> (_.c8), + columnNames(8) -> (_.c9), + columnNames(9) -> (_.c10), + columnNames(10) -> (_.c11), + columnNames(11) -> (_.c12), + columnNames(12) -> (_.c13), + columnNames(13) -> (_.c14), + columnNames(14) -> (_.c15), + columnNames(15) -> (_.c16), + columnNames(16) -> (_.c17), + columnNames(17) -> (_.c18), + columnNames(18) -> (_.c19), + columnNames(19) -> (_.c20), + columnNames(20) -> (_.c21) + ) + + def * = (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15, c16, c17, c18, c19, c20, c21) <> (apply.tupled, unapply) + } + + def createView[T: ClassTag, E, A1: TypedType, A2: TypedType, A3: TypedType, A4: TypedType, A5: TypedType, A6: TypedType, A7: TypedType, A8: TypedType, A9: TypedType, A10: TypedType, A11: TypedType, A12: TypedType, A13: TypedType, A14: TypedType, A15: TypedType, A16: TypedType, A17: TypedType, A18: TypedType, A19: TypedType, A20: TypedType, A21: TypedType, A22: TypedType]( + name: String, + apply: (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22) => T, + unapply: T => Option[(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22)], + baseQuery: Query[E, _, Seq] + )( + mappings: E => ((String, Column[A1]), (String, Column[A2]), (String, Column[A3]), (String, Column[A4]), (String, Column[A5]), (String, Column[A6]), (String, Column[A7]), (String, Column[A8]), (String, Column[A9]), (String, Column[A10]), (String, Column[A11]), (String, Column[A12]), (String, Column[A13]), (String, Column[A14]), (String, Column[A15]), (String, Column[A16]), (String, Column[A17]), (String, Column[A18]), (String, Column[A19]), (String, Column[A20]), (String, Column[A21]), (String, Column[A22])) + ): TableQuery[BaseView22[T, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22]] = { + + var columnsNames = Seq[String]() + + val preparedQuery: Query[_, T, Seq] = { + val mappedQuery = baseQuery.map { + t => + mappings(t) match { + case ((name1, c1), (name2, c2), (name3, c3), (name4, c4), (name5, c5), (name6, c6), (name7, c7), (name8, c8), (name9, c9), (name10, c10), (name11, c11), (name12, c12), (name13, c13), (name14, c14), (name15, c15), (name16, c16), (name17, c17), (name18, c18), (name19, c19), (name20, c20), (name21, c21), (name22, c22)) => + columnsNames = Seq(name1, name2, name3, name4, name5, name6, name7, name8, name9, name10, name11, name12, name13, name14, name15, name16, name17, name18, name19, name20, name21, name22) + (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15, c16, c17, c18, c19, c20, c21, c22) <> (apply.tupled, unapply) + } + } + + for { + a <- mappedQuery + } yield a + } + TableQuery.apply(tag => new BaseView22[T, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22](tag, name, columnsNames, apply, unapply, preparedQuery)) + + } + + class BaseView22[T: ClassTag, A1: TypedType, A2: TypedType, A3: TypedType, A4: TypedType, A5: TypedType, A6: TypedType, A7: TypedType, A8: TypedType, A9: TypedType, A10: TypedType, A11: TypedType, A12: TypedType, A13: TypedType, A14: TypedType, A15: TypedType, A16: TypedType, A17: TypedType, A18: TypedType, A19: TypedType, A20: TypedType, A21: TypedType, A22: TypedType]( + tag: Tag, + name: String, + val columnNames: Seq[String], + apply: (A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22) => T, + unapply: T => Option[(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20, A21, A22)], + val query: Query[_, T, Seq] + ) extends BaseView[A1, T](tag, name) { + def c1 = column[A1](columnNames(0)) + def c2 = column[A2](columnNames(1)) + def c3 = column[A3](columnNames(2)) + def c4 = column[A4](columnNames(3)) + def c5 = column[A5](columnNames(4)) + def c6 = column[A6](columnNames(5)) + def c7 = column[A7](columnNames(6)) + def c8 = column[A8](columnNames(7)) + def c9 = column[A9](columnNames(8)) + def c10 = column[A10](columnNames(9)) + def c11 = column[A11](columnNames(10)) + def c12 = column[A12](columnNames(11)) + def c13 = column[A13](columnNames(12)) + def c14 = column[A14](columnNames(13)) + def c15 = column[A15](columnNames(14)) + def c16 = column[A16](columnNames(15)) + def c17 = column[A17](columnNames(16)) + def c18 = column[A18](columnNames(17)) + def c19 = column[A19](columnNames(18)) + def c20 = column[A20](columnNames(19)) + def c21 = column[A21](columnNames(20)) + def c22 = column[A22](columnNames(21)) + + override def id = c1 + + override protected val columns: Seq[(String, this.type => Column[_])] = Seq( + columnNames(0) -> (_.c1), + columnNames(1) -> (_.c2), + columnNames(2) -> (_.c3), + columnNames(3) -> (_.c4), + columnNames(4) -> (_.c5), + columnNames(5) -> (_.c6), + columnNames(6) -> (_.c7), + columnNames(7) -> (_.c8), + columnNames(8) -> (_.c9), + columnNames(9) -> (_.c10), + columnNames(10) -> (_.c11), + columnNames(11) -> (_.c12), + columnNames(12) -> (_.c13), + columnNames(13) -> (_.c14), + columnNames(14) -> (_.c15), + columnNames(15) -> (_.c16), + columnNames(16) -> (_.c17), + columnNames(17) -> (_.c18), + columnNames(18) -> (_.c19), + columnNames(19) -> (_.c20), + columnNames(20) -> (_.c21), + columnNames(21) -> (_.c22) + ) + + def * = (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15, c16, c17, c18, c19, c20, c21, c22) <> (apply.tupled, unapply) + } + } diff --git a/src/test/scala/org/virtuslab/beholder/BaseTest.scala b/src/test/scala/org/virtuslab/beholder/BaseTest.scala index bcaf0f4..ebc2fb2 100644 --- a/src/test/scala/org/virtuslab/beholder/BaseTest.scala +++ b/src/test/scala/org/virtuslab/beholder/BaseTest.scala @@ -40,8 +40,8 @@ trait ModelIncluded { ).map(user => user.copy(id = Some(UsersRepository.save(user)))) val machines = Seq( - Machine(None, "a.a.pl", "Ubuntu", 4, new Date(DateTime.now().minusHours(24).getMillis), Some(1)), - Machine(None, "o.a.pl", "Fedora", 1, new Date(DateTime.now().getMillis), Some(3)) + Machine(None, "a.a.pl", "Ubuntu", 4, new Date(DateTime.now().minusHours(24).getMillis), Some(1), MachineStatus.Inactive), + Machine(None, "o.a.pl", "Fedora", 1, new Date(DateTime.now().getMillis), Some(3), MachineStatus.Active) ).map(machine => machine.copy(id = Some(MachineRepository.save(machine)))) val Seq(user1, user2) = users diff --git a/src/test/scala/org/virtuslab/beholder/FormFiltersTests.scala b/src/test/scala/org/virtuslab/beholder/FormFiltersTests.scala index 267e3d0..5cdbc87 100644 --- a/src/test/scala/org/virtuslab/beholder/FormFiltersTests.scala +++ b/src/test/scala/org/virtuslab/beholder/FormFiltersTests.scala @@ -5,7 +5,8 @@ import java.sql.Date import org.virtuslab.beholder.filters.forms.FromFilterFields._ import org.virtuslab.beholder.filters.forms.{ FormFilters, FormFormatter, FromFilterFields } import org.virtuslab.beholder.filters.{ FilterAPI, FilterDefinition } -import org.virtuslab.beholder.suites.{ InitialQueryTestSuite, BaseSuite, FiltersTestSuite, RangeFiltersSuite } +import org.virtuslab.beholder.model.MachineStatus +import org.virtuslab.beholder.suites._ import org.virtuslab.unicorn.LongUnicornPlay._ import org.virtuslab.unicorn.LongUnicornPlay.driver.simple._ @@ -15,6 +16,7 @@ trait FormFiltersTestsBase { override def doFilters(data: BaseFilterData, currentFilter: FilterDefinition): Seq[UserMachineViewRow] = { val formatter = data.filter.formatter val formData = formatter.filterForm.fill(currentFilter) + formatter.filterForm.bind(formData.data).fold( errors => fail(s"Form errors ${errors.errors.mkString}"), fromForm => data.filter.filter(fromForm)(data.session) @@ -33,7 +35,8 @@ class FormFiltersTests extends AppTest with FiltersTestSuite[FormFormatter] with inText, inIntField, inRange[Date], - FromFilterFields.ignore[Option[BigDecimal]] + FromFilterFields.ignore[Option[BigDecimal]], + FromFilterFields.ignore[MachineStatus.Value] ) }.filterGenerator } @@ -51,7 +54,44 @@ class FormRangeFiltersTests extends AppTest with RangeFiltersSuite[FormFormatter inText, inRange[Int], inRange[Date], - inOptionRange[BigDecimal] + inOptionRange[BigDecimal], + FromFilterFields.ignore[MachineStatus.Value] + ) + }.filterGenerator + +} + +class FormEnumFiltersTests extends AppTest with EnumFilterTestSuite[FormFormatter] with FormFiltersTestsBase { + def createFilter(data: BaseFilterData): FilterAPI[UserMachineViewRow, FormFormatter] = new CustomTypeMappers { + + import play.api.data.format.Formats._ + + val filterGenerator = new FormFilters[UserMachineViewRow].create( + data.view, + inText, + inText, + inIntField, + inRange[Date], + inOptionRange[BigDecimal], + inEnum[MachineStatus.type] + ) + }.filterGenerator + +} + +class FormSeqFiltersTests extends AppTest with SeqFilterTestSuite[FormFormatter] with FormFiltersTestsBase { + def createFilter(data: BaseFilterData): FilterAPI[UserMachineViewRow, FormFormatter] = new CustomTypeMappers { + + import play.api.data.format.Formats._ + + val filterGenerator = new FormFilters[UserMachineViewRow].create( + data.view, + inText, + inText, + inIntFieldSeq, + inRange[Date], + inOptionRange[BigDecimal], + inEnumSeq[MachineStatus.type] ) }.filterGenerator diff --git a/src/test/scala/org/virtuslab/beholder/JsonFiltersTests.scala b/src/test/scala/org/virtuslab/beholder/JsonFiltersTests.scala index 2a7b2d1..41cf753 100644 --- a/src/test/scala/org/virtuslab/beholder/JsonFiltersTests.scala +++ b/src/test/scala/org/virtuslab/beholder/JsonFiltersTests.scala @@ -2,12 +2,16 @@ package org.virtuslab.beholder import java.sql.Date -import org.virtuslab.beholder.filters.json.JsonFilterFields.{ inIntField, inOptionRange, inRange, _ } +import org.virtuslab.beholder.filters.json.JsonFilterFields.inField +import org.virtuslab.beholder.filters.json.JsonFilterFields.inIntFieldSeq +import org.virtuslab.beholder.filters.json.JsonFilterFields.inText +import org.virtuslab.beholder.filters.json.JsonFilterFields.{ inIntField, inOptionRange, inRange, inEnum, inEnumSeq } import org.virtuslab.beholder.filters.json.{ JsonFilterFields, JsonFilters, JsonFormatter } import org.virtuslab.beholder.filters.{ FilterAPI, FilterDefinition } -import org.virtuslab.beholder.suites.{ InitialQueryTestSuite, BaseSuite, FiltersTestSuite, RangeFiltersSuite } +import org.virtuslab.beholder.model.MachineStatus +import org.virtuslab.beholder.suites._ import org.virtuslab.unicorn.LongUnicornPlay.driver.simple._ -import play.api.libs.json.JsObject +import play.api.libs.json.{ JsSuccess, JsObject } trait JsonFiltersTestsBase { self: AppTest with BaseSuite[JsonFormatter[UserMachineViewRow]] => @@ -18,7 +22,7 @@ trait JsonFiltersTestsBase { filter.formatter.results(currentFilter, result) match { case JsObject(Seq(("filter", jsonFilter), _)) => - filter.formatter.filterDefinition(jsonFilter) should equal(Some(currentFilter)) + filter.formatter.filterDefinition(jsonFilter) should equal(JsSuccess(currentFilter)) } result.content @@ -33,7 +37,8 @@ class JsonFiltersTests extends AppTest with FiltersTestSuite[JsonFormatter[UserM inText, inIntField, inRange(inField[Date]("date")), - JsonFilterFields.ignore[Option[BigDecimal]] + JsonFilterFields.ignore[Option[BigDecimal]], + inEnum(MachineStatus) ) } @@ -47,6 +52,33 @@ class JsonFiltersRangeTests extends AppTest with RangeFiltersSuite[JsonFormatter inText, inRange(inIntField), inRange(inField[Date]("date")), - inOptionRange(inField[BigDecimal]("number")) + inOptionRange(inField[BigDecimal]("number")), + inEnum(MachineStatus) + ) +} + +class JsonFiltersEnumTests extends AppTest with EnumFilterTestSuite[JsonFormatter[UserMachineViewRow]] with JsonFiltersTestsBase { + def createFilter(data: BaseFilterData): FilterAPI[UserMachineViewRow, JsonFormatter[UserMachineViewRow]] = + new JsonFilters[UserMachineViewRow](identity).create( + data.view, + inText, + inText, + inIntFieldSeq, + inRange(inField[Date]("date")), + inOptionRange(inField[BigDecimal]("number")), + inEnum(MachineStatus) + ) +} + +class JsonFiltersSeqTests extends AppTest with SeqFilterTestSuite[JsonFormatter[UserMachineViewRow]] with JsonFiltersTestsBase { + def createFilter(data: BaseFilterData): FilterAPI[UserMachineViewRow, JsonFormatter[UserMachineViewRow]] = + new JsonFilters[UserMachineViewRow](identity).create( + data.view, + inText, + inText, + inIntFieldSeq, + inRange(inField[Date]("date")), + inOptionRange(inField[BigDecimal]("number")), + inEnumSeq(MachineStatus) ) } \ No newline at end of file diff --git a/src/test/scala/org/virtuslab/beholder/UserMachinesView.scala b/src/test/scala/org/virtuslab/beholder/UserMachinesView.scala index 8a00a1f..98fba57 100644 --- a/src/test/scala/org/virtuslab/beholder/UserMachinesView.scala +++ b/src/test/scala/org/virtuslab/beholder/UserMachinesView.scala @@ -2,17 +2,19 @@ package org.virtuslab.beholder import java.sql.Date -import org.virtuslab.beholder.model.{ Machines, Users } +import org.virtuslab.beholder.model.{ MachineStatus, Machines, Users } import org.virtuslab.beholder.views.FilterableViews import org.virtuslab.unicorn.LongUnicornPlay._ import org.virtuslab.unicorn.LongUnicornPlay.driver.simple._ +import play.api.libs.json._ case class UserMachineViewRow( email: String, system: String, cores: Int, created: Date, - capacity: Option[BigDecimal] + capacity: Option[BigDecimal], + status: MachineStatus.Value ) trait UserMachinesView extends ModelIncluded { @@ -28,7 +30,7 @@ trait UserMachinesView extends ModelIncluded { } yield (user, machine) val tableQuery = FilterableViews.createView( - name = "USERS_MACHINE_VIEW", + name = "USER_MACHINE_VIEW", UserMachineViewRow.apply _, UserMachineViewRow.unapply _, baseQuery = usersMachinesQuery @@ -39,7 +41,8 @@ trait UserMachinesView extends ModelIncluded { "system" -> machine.system, "cores" -> machine.cores, "created" -> machine.created, - "capacity" -> machine.capacity) + "capacity" -> machine.capacity, + "status" -> machine.status) } tableQuery.viewDDL.create diff --git a/src/test/scala/org/virtuslab/beholder/json/JsonFormatterTest.scala b/src/test/scala/org/virtuslab/beholder/json/JsonFormatterTest.scala index 3ca2362..f2470ae 100644 --- a/src/test/scala/org/virtuslab/beholder/json/JsonFormatterTest.scala +++ b/src/test/scala/org/virtuslab/beholder/json/JsonFormatterTest.scala @@ -5,9 +5,10 @@ import java.sql.Date import org.virtuslab.beholder.filters.FilterDefinition import org.virtuslab.beholder.filters.json.JsonFilterFields._ import org.virtuslab.beholder.filters.json.{ JsonFilterFields, JsonFilters } +import org.virtuslab.beholder.model.MachineStatus import org.virtuslab.beholder.{ UserMachineViewRow, _ } import org.virtuslab.unicorn.LongUnicornPlay.driver.simple._ -import play.api.libs.json.{ JsArray, JsObject, JsString } +import play.api.libs.json.{ JsSuccess, JsArray, JsObject, JsString } class JsonFormatterTest extends AppTest with UserMachinesView with ModelIncluded { @@ -18,10 +19,11 @@ class JsonFormatterTest extends AppTest with UserMachinesView with ModelIncluded new JsonFilters[UserMachineViewRow](labels).create( view, inText, - inText, - inIntField, + inTextSeq, + inIntFieldSeq, inRange(inField[Date]("date")), - JsonFilterFields.ignore[Option[BigDecimal]] + JsonFilterFields.ignore[Option[BigDecimal]], + JsonFilterFields.ignore[MachineStatus.Value] ) } @@ -32,9 +34,9 @@ class JsonFormatterTest extends AppTest with UserMachinesView with ModelIncluded val req = JsObject(Seq("data" -> JsObject(Seq("email" -> JsString("ala"))))) - val data = FilterDefinition(None, None, None, Seq(Some("ala"), None, None, None, None)) + val data = FilterDefinition(None, None, None, Seq(Some("ala"), None, None, None, None, None)) - filter.formatter.filterDefinition(req) shouldEqual Some(data) + filter.formatter.filterDefinition(req) shouldEqual JsSuccess(data) } it should "create json definition correctly" in rollbackWithModel { @@ -44,8 +46,6 @@ class JsonFormatterTest extends AppTest with UserMachinesView with ModelIncluded val definition = filter.formatter.jsonDefinition - println(definition) - def stringValue(on: JsObject, name: String) = on \ name match { case JsString(value) => value case _ => fail(s"Field $name fro $on is not string!") diff --git a/src/test/scala/org/virtuslab/beholder/model/Machine.scala b/src/test/scala/org/virtuslab/beholder/model/Machine.scala index feafd80..5e5a6e8 100644 --- a/src/test/scala/org/virtuslab/beholder/model/Machine.scala +++ b/src/test/scala/org/virtuslab/beholder/model/Machine.scala @@ -4,7 +4,48 @@ import java.sql.Date import org.virtuslab.unicorn.LongUnicornPlay._ import org.virtuslab.unicorn.LongUnicornPlay.driver.simple._ +import play.api.data.FormError +import play.api.libs.json._ +import play.api.data.format.{ Formats, Formatter } +/** + * commons enum method for use with slick and play + */ +trait BaseEnum { + self: Enumeration => + + /** + * Type mapper placed here is resolved automatically and does not need to be imported anywhere. + */ + implicit val typeMapper: BaseColumnType[Value] = MappedColumnType.base[Value, Int](int => int.id, id => apply(id)) + + //for play forms + implicit lazy val mappingFormatter: Formatter[Value] = new Formatter[Value] { + + override val format = Some(("format.numeric", Nil)) + + override def bind(key: String, data: Map[String, String]): Either[Seq[FormError], Value] = + Formats.intFormat.bind(key, data).right.map(apply) + + override def unbind(key: String, value: Value): Map[String, String] = + Map(key -> value.id.toString) + } + + //for json forms + implicit val format = new Format[Value] { + override def writes(o: Value): JsValue = JsNumber(o.id) + override def reads(json: JsValue): JsResult[Value] = json.asOpt[Int].map(apply).map(JsSuccess(_)).getOrElse(JsError("format invalid")) + } + +} + +object MachineStatus extends Enumeration with BaseEnum { + + val Active = Value(1, "active") + val Inactive = Value(2, "inactive") + val Broken = Value(3, "broken") + +} /** Id class for type-safe joins and queries. */ case class MachineId(id: Long) extends AnyVal with BaseId @@ -29,7 +70,8 @@ case class Machine( system: String, cores: Int, created: Date, - capacity: Option[BigDecimal] + capacity: Option[BigDecimal], + status: MachineStatus.Value ) extends WithId[MachineId] /** Table definition for machines. */ @@ -45,5 +87,7 @@ class Machines(tag: Tag) extends IdTable[MachineId, Machine](tag, "MACHINES") { def capacity = column[Option[BigDecimal]]("capacity") - override def * = (id.?, url, system, cores, created, capacity) <> (Machine.tupled, Machine.unapply) + def status = column[MachineStatus.Value]("status") + + override def * = (id.?, url, system, cores, created, capacity, status) <> (Machine.tupled, Machine.unapply) } diff --git a/src/test/scala/org/virtuslab/beholder/suites/EnumFilterTestSuite.scala b/src/test/scala/org/virtuslab/beholder/suites/EnumFilterTestSuite.scala new file mode 100644 index 0000000..5721fe6 --- /dev/null +++ b/src/test/scala/org/virtuslab/beholder/suites/EnumFilterTestSuite.scala @@ -0,0 +1,22 @@ +package org.virtuslab.beholder.suites + +import org.virtuslab.beholder.AppTest +import org.virtuslab.beholder.model.MachineStatus + +trait EnumFilterTestSuite[Formatter] extends BaseSuite[Formatter] { + self: AppTest => + + it should "filter all users with inactive machines" in baseFilterTest { + data => + import data._ + + val a = baseFilter.data + val inactive = Some(MachineStatus.Inactive) + + val usersWithInactiveMachines = doFilters(data, baseFilter.copy(data = a.updated(5, inactive))) + + //usersWithInactiveMachines.size should be(2) + usersWithInactiveMachines should contain theSameElementsAs allFromDb.filter(machine => machine.status == MachineStatus.Inactive) + } + +} diff --git a/src/test/scala/org/virtuslab/beholder/suites/InitialQueryTestSuite.scala b/src/test/scala/org/virtuslab/beholder/suites/InitialQueryTestSuite.scala index 55fa582..5b4cce2 100644 --- a/src/test/scala/org/virtuslab/beholder/suites/InitialQueryTestSuite.scala +++ b/src/test/scala/org/virtuslab/beholder/suites/InitialQueryTestSuite.scala @@ -3,7 +3,7 @@ package org.virtuslab.beholder.suites import java.sql.Date import org.joda.time.DateTime -import org.virtuslab.beholder.model.{ Machine, User } +import org.virtuslab.beholder.model.{ MachineStatus, Machine, User } import org.virtuslab.beholder.views.FilterableViews import org.virtuslab.beholder.{ UserMachineViewRow, AppTest } import org.virtuslab.beholder.filters.{ TableFilterAPI, FilterAPI } @@ -11,9 +11,6 @@ import org.virtuslab.unicorn.LongUnicornPlay.driver.simple._ import scala.tools.nsc.doc.model.Entity -/** - * Author: Krzysztof Romanowski - */ trait InitialQueryTestSuite[Formatter] extends FiltersTestSuite[Formatter] { self: AppTest => override protected def baseFilterTest[A](testImplementation: (BaseFilterData) => A): A = @@ -28,7 +25,7 @@ trait InitialQueryTestSuite[Formatter] extends FiltersTestSuite[Formatter] { class InitialQueryFilterData(implicit session: Session) extends BaseFilterData { override lazy val filter: FilterAPI[UserMachineViewRow, Formatter] = createFilter(this) - .asInstanceOf[TableFilterAPI[UserMachineViewRow, Formatter, FilterableViews.BaseView5[_, String, _, _, _, _]]] + .asInstanceOf[TableFilterAPI[UserMachineViewRow, Formatter, FilterableViews.BaseView6[_, String, _, _, _, _, _]]] .withInitialFilter(table => !(table.c1 === newMail)) //cos !== is not working val newUser = { @@ -37,7 +34,7 @@ trait InitialQueryTestSuite[Formatter] extends FiltersTestSuite[Formatter] { } val newMachine = { - val m = Machine(None, "b.pl", "Windows", 6, new Date(DateTime.now().getMillis), Some(12)) + val m = Machine(None, "b.pl", "Windows", 6, new Date(DateTime.now().getMillis), Some(12), MachineStatus.Inactive) m.copy(id = Some(MachineRepository.save(m))) } diff --git a/src/test/scala/org/virtuslab/beholder/suites/SeqFilterTestSuite.scala b/src/test/scala/org/virtuslab/beholder/suites/SeqFilterTestSuite.scala new file mode 100644 index 0000000..356b072 --- /dev/null +++ b/src/test/scala/org/virtuslab/beholder/suites/SeqFilterTestSuite.scala @@ -0,0 +1,46 @@ +package org.virtuslab.beholder.suites + +import org.virtuslab.beholder.AppTest +import org.virtuslab.beholder.model.MachineStatus + +trait SeqFilterTestSuite[Formatter] extends BaseSuite[Formatter] { + self: AppTest => + + it should "filter by seq(int) only users with one and four core machines" in baseFilterTest { + data => + import data._ + + val a = baseFilter.data + val usersWithOneOrFourCore = Some(Seq(1, 4)) + + val usersWithOneOrFourCoreMachines = doFilters(data, baseFilter.copy(data = a.updated(2, usersWithOneOrFourCore))) + + usersWithOneOrFourCoreMachines should contain theSameElementsAs allFromDb + } + + it should "filter by seq(int) only users with one and three core machine" in baseFilterTest { + data => + import data._ + + val a = baseFilter.data + val oneOrThreeCore = Some(Seq(1, 3)) + + val usersWithOneOrThreeCoreMachines = doFilters(data, baseFilter.copy(data = a.updated(2, oneOrThreeCore))) + + usersWithOneOrThreeCoreMachines should contain theSameElementsAs allFromDb.filter(machine => machine.cores == 1 || machine.cores == 3) + } + + it should "filter by seq(enum) all users together with inactive and broken machines" in baseFilterTest { + data => + import data._ + + val a = baseFilter.data + val inactiveAndBroken = Some(Seq(MachineStatus.Inactive, MachineStatus.Broken)) + + val usersWithInactiveAndBrokenMachines = doFilters(data, baseFilter.copy(data = a.updated(5, inactiveAndBroken))) + + usersWithInactiveAndBrokenMachines.size should be(2) + usersWithInactiveAndBrokenMachines should contain theSameElementsAs allFromDb.filter(machine => machine.status == MachineStatus.Inactive) + } + +}