Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into directoryGroupErro…
Browse files Browse the repository at this point in the history
…rHandling
  • Loading branch information
ghsolomon committed Apr 2, 2024
2 parents dc7365b + e1c11ff commit 90645c2
Show file tree
Hide file tree
Showing 9 changed files with 584 additions and 56 deletions.
67 changes: 67 additions & 0 deletions .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle

name: Java CI with Gradle

on:
push:
branches: [ "develop" ]
pull_request:
branches: [ "develop" ]

jobs:
build:

runs-on: ubuntu-latest
permissions:
contents: read

steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'zulu'

# Configure Gradle for optimal use in GiHub Actions, including caching of downloaded dependencies.
# See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md
- name: Setup Gradle
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0

- name: Build with Gradle Wrapper
run: ./gradlew build

# NOTE: The Gradle Wrapper is the default and recommended way to run Gradle (https://docs.gradle.org/current/userguide/gradle_wrapper.html).
# If your project does not have the Gradle Wrapper configured, you can use the following configuration to run Gradle with a specified version.
#
# - name: Setup Gradle
# uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0
# with:
# gradle-version: '8.5'
#
# - name: Build with Gradle 8.5
# run: gradle build

dependency-submission:

runs-on: ubuntu-latest
permissions:
contents: write

steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'zulu'

# Generates and submits a dependency graph, enabling Dependabot Alerts for all project dependencies.
# See: https://github.com/gradle/actions/blob/main/dependency-submission/README.md
- name: Generate and submit dependency graph
uses: gradle/actions/dependency-submission@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0
1 change: 0 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ dependencies {

runtimeOnly "org.glassfish.web:el-impl:2.2.1-b05"
runtimeOnly "javax.xml.bind:jaxb-api:2.3.1"
runtimeOnly "com.h2database:h2"

//--------------------
// Hoist Additions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,65 +7,33 @@

package io.xh.hoist.admin

import grails.gorm.transactions.ReadOnly
import io.xh.hoist.BaseController
import io.xh.hoist.data.filter.Filter
import io.xh.hoist.security.Access
import io.xh.hoist.track.TrackLog
import io.xh.hoist.track.TrackService
import io.xh.hoist.track.TrackLogAdminService

import java.time.LocalDate

import static io.xh.hoist.util.DateTimeUtils.appDay
import static io.xh.hoist.util.DateTimeUtils.parseLocalDate

import static io.xh.hoist.util.DateTimeUtils.*
import static java.lang.Integer.parseInt

@Access(['HOIST_ADMIN_READER'])
class TrackLogAdminController extends BaseController {

TrackService trackService
TrackLogAdminService trackLogAdminService

@ReadOnly
def index() {
if (!trackService.enabled) {
renderJSON([])
}

def startDay = parseLocalDate(params.startDay),
endDay = parseLocalDate(params.endDay)

// NOTE that querying + serializing large numbers of TrackLogs below requires a significant
// allocation of memory. Be mindful if customizing maxRow-related configs above defaults!
def conf = trackService.conf,
maxDefault = conf.maxRows.default as Integer,
maxLimit = conf.maxRows.limit as Integer,
maxRows = [(params.maxRows ? parseInt(params.maxRows) : maxDefault), maxLimit].min()
def query = parseRequestJSON(),
startDay = query.startDay ? parseLocalDate(query.startDay) : LocalDate.of(1970, 1, 1),
endDay = query.endDay ? parseLocalDate(query.endDay) : appDay(),
filter = Filter.parse(query.filters),
maxRows = query.maxRows

def results = TrackLog.findAll(max: maxRows, sort: 'dateCreated', order: 'desc') {
if (startDay) dateCreated >= appStartOfDay(startDay)
if (endDay) dateCreated <= appEndOfDay(endDay)
if (params.category) category =~ "%$params.category%"
if (params.username) username =~ "%$params.username%"
if (params.browser) browser =~ "%$params.browser%"
if (params.device) device =~ "%$params.device%"
if (params.msg) msg =~ "%$params.msg%"
}

renderJSON(results)
renderJSON(trackLogAdminService.queryTrackLog(startDay, endDay, filter, maxRows))
}

def lookups() {
renderJSON([
categories: distinctVals('category'),
browsers: distinctVals('browser'),
devices: distinctVals('device'),
usernames: distinctVals('username'),
])
}

//------------------------
// Implementation
//------------------------
private List distinctVals(String property) {
return TrackLog.createCriteria().list {
projections { distinct(property) }
}.sort()
renderJSON(trackLogAdminService.lookups())
}

}
65 changes: 65 additions & 0 deletions grails-app/services/io/xh/hoist/track/TrackLogAdminService.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package io.xh.hoist.track

import grails.gorm.transactions.ReadOnly
import io.xh.hoist.BaseService;
import io.xh.hoist.config.ConfigService
import io.xh.hoist.data.filter.Filter
import io.xh.hoist.exception.DataNotAvailableException
import org.hibernate.Criteria
import org.hibernate.SessionFactory
import java.time.LocalDate;

import static io.xh.hoist.util.DateTimeUtils.appEndOfDay
import static io.xh.hoist.util.DateTimeUtils.appStartOfDay
import static org.hibernate.criterion.Order.desc
import static org.hibernate.criterion.Restrictions.between

class TrackLogAdminService extends BaseService {
ConfigService configService
SessionFactory sessionFactory

Boolean getEnabled() {
return conf.enabled == true
}

@ReadOnly
List<TrackLog> queryTrackLog(LocalDate startDay, LocalDate endDay, Filter filter, Integer maxRows = null) {
if (!enabled) throw new DataNotAvailableException('TrackService not available.')

def maxDefault = conf.maxRows.default as Integer,
maxLimit = conf.maxRows.limit as Integer

maxRows = [(maxRows ? maxRows : maxDefault), maxLimit].min()

def session = sessionFactory.currentSession
Criteria c = session.createCriteria(TrackLog)
c.maxResults = maxRows
c.addOrder(desc('dateCreated'))
c.add(between('dateCreated', appStartOfDay(startDay), appEndOfDay(endDay)))
if (filter) {
c.add(filter.criterion)
}
c.list() as List<TrackLog>
}

@ReadOnly
Map lookups() {[
category: distinctVals('category'),
browser: distinctVals('browser'),
device: distinctVals('device'),
username: distinctVals('username')
] }

//------------------------
// Implementation
//------------------------
private List distinctVals(String property) {
TrackLog.createCriteria().list {
projections { distinct(property) }
}.sort()
}

private Map getConf() {
configService.getMap('xhActivityTrackingConfig')
}
}
21 changes: 13 additions & 8 deletions src/main/groovy/io/xh/hoist/configuration/RuntimeConfig.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

package io.xh.hoist.configuration

import org.hibernate.dialect.H2Dialect

import static io.xh.hoist.util.InstanceConfigUtils.getInstanceConfig
import static io.xh.hoist.util.Utils.withDelegate

Expand All @@ -18,8 +20,7 @@ import static io.xh.hoist.util.Utils.withDelegate
class RuntimeConfig {

/**
* All apps should call this from runtime.groovy
* to setup necessary default configurations
* All apps should call this from runtime.groovy to setup necessary default configurations
*/
static void defaultConfig(Script script) {
withDelegate(script) {
Expand All @@ -28,26 +29,30 @@ class RuntimeConfig {
}

/**
* Call this from runtime.groovy
* to setup an in memory H2 DB instead of
* a MySQL or SQL Server DB. This H2 DB option
* is intended only for early stages of development,
* before a production ready DB has been set up.
* Call this from runtime.groovy to setup an in memory H2 DB instead of MySQL or SQL Server.
* This option is intended only for early stages of development, before a production-ready
* database has been provisioned. Data is transient, NOT intended for actual deployments!
*
* Note you will need to add a dependency to your app's build.gradle file:
* `runtimeOnly "com.h2database:h2:2.2.224"` (check and use latest/suitable version).
*/
static void h2Config(Script script) {
withDelegate(script) {
dataSource {
pooled = true
jmxExport = true
driverClassName = "org.h2.Driver"
dialect = H2Dialect
username = "sa"
password = ""
}
environments {
development {
dataSource {
dbCreate = "create-drop"
url = "jdbc:h2:mem:devDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE"
// `value` is a reserved word in H2 v2.x but used by Hoist AppConfig.
// We can workaround with NON_KEYWORDS=VALUE in the JDBC URL below.
url = "jdbc:h2:mem:devDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE;NON_KEYWORDS=VALUE"
}
}
}
Expand Down
62 changes: 62 additions & 0 deletions src/main/groovy/io/xh/hoist/data/filter/CompoundFilter.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* This file belongs to Hoist, an application development toolkit
* developed by Extremely Heavy Industries (www.xh.io | [email protected])
*
* Copyright © 2024 Extremely Heavy Industries Inc.
*/

package io.xh.hoist.data.filter

import io.xh.hoist.json.JSONFormat
import org.hibernate.criterion.Criterion

/**
* Combines multiple filters (including other nested CompoundFilters) via an AND or OR operator.
*/
class CompoundFilter extends Filter implements JSONFormat {

final List<Filter> filters
final String op

CompoundFilter(List filters, String op) {
op = op ? op.toUpperCase() : 'AND'
if (op != 'AND' && op != 'OR') throw new RuntimeException('CompoundFilter requires "op" value of "AND" or "OR"')
this.filters = filters.collect { parse(it) }.findAll()
this.op = op
}

Map formatForJSON() {
return [
filters: filters,
op: op
]
}

//---------------------
// Overrides
//----------------------
List<String> getAllFields() {
filters.collectMany { it.allFields }.unique()
}

Criterion getCriterion() {
op == 'AND' ? and(filters*.criterion) : or(filters*.criterion)
}

Closure<Boolean> getTestFn() {
if (!filters) return { true }
def tests = filters*.testFn
return op == 'AND' ?
{ tests.every { test -> test(it) } } :
{ tests.any { test -> test(it) } }
}

boolean equals(Filter other) {
if (other === this) return true;
return (
other instanceof CompoundFilter &&
other.op == op &&
other.filters == filters
)
}
}
Loading

0 comments on commit 90645c2

Please sign in to comment.