Skip to content
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ language: scala
scala:
- "2.11.5"
jdk:
- openjdk7
- oraclejdk8

script: sbt -no-colors ++$TRAVIS_SCALA_VERSION clean coverage test
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand Down
11 changes: 7 additions & 4 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ organization := "org.virtuslab"

name := "beholder"

version := "0.2.10-SNAPSHOT"
version := "0.2.11-SNAPSHOT"

scalaVersion := "2.11.7"

Expand Down Expand Up @@ -65,17 +65,20 @@ pomExtra := <url>https://github.com/VirtusLab/beholder</url>
</developer>
</developers>

//xerial.sbt.Sonatype.sonatypeSettings

// Scoverage setup

ScoverageSbtPlugin.ScoverageKeys.coverageMinimum := 48
ScoverageSbtPlugin.ScoverageKeys.coverageMinimum := 65

ScoverageSbtPlugin.ScoverageKeys.coverageFailOnMinimum := true

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

23 changes: 13 additions & 10 deletions src/main/scala/org/virtuslab/beholder/filters/BaseFilter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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] = {
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand All @@ -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
Expand Down Expand Up @@ -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
*/
Expand All @@ -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
}
Expand All @@ -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]] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]])
Expand Down
Loading