Skip to content

Commit

Permalink
Merge branch 'develop' into directoryGroupErrorHandling
Browse files Browse the repository at this point in the history
# Conflicts:
#	CHANGELOG.md
  • Loading branch information
lbwexler committed Apr 3, 2024
2 parents 90645c2 + a373055 commit 1f7cfbd
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 50 deletions.
39 changes: 37 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,49 @@

## 19.0-SNAPSHOT - unreleased

### 🎁 New Features

* New `TrackLogAdminService` and `ClientErrorAdminService` services provide a more bespoke set of methods
for querying `TrackLog` and `ClientError` records.
* These services utilize HQL criterion to allow for filters posted by the Hoist Admin Console to be applied
directly to the server-side query.
* Client error reports now include `impersonating` field for additional troubleshooting context.
* ⚠ NOTE - this requires a new, nullable varchar(50) column be added to the xh_client_error
table in your app's configuration database. Review and run the following SQL, or an equivalent
suitable for the particular database you are using:

```sql
ALTER TABLE `xh_client_error` ADD COLUMN `impersonating` VARCHAR(50) NULL;
```
* Track log reports now include `appVersion`, `appEnvironment`, and `url`, fields for additional
activity context.
* ⚠ NOTE - this requires new, nullable varchar(100), nullable varchar(100), and nullable varchar(500)
columns to be added to the xh_track_log table in your app's configuration database for `appVersion`,
`appEnvironment`, and `url` respectively. Review and run the following SQL, or an equivalent
suitable for the particular database you are using:
```sql
ALTER TABLE `xh_track_log` ADD COLUMN `appVersion` VARCHAR(100) NULL;
ALTER TABLE `xh_track_log` ADD COLUMN `appEnvironment` VARCHAR(100) NULL;
ALTER TABLE `xh_track_log` ADD COLUMN `url` VARCHAR(500) NULL;
```
* `TrackService` now logs `appVersion`, `appEnvironment`, and `url` fields in `TrackLog` records.
### ⚙️ Technical
* `XhController` endpoints `track` and `submitError` now expect to be visited via a `postJSON` request with a
`JSON` object posted in the `body`. This is pattern is preferred over using a `fetchJSON` request with
`params` posted in the request header.
* `DefaultRoleService` has improved error handling for failed directory group lookups.
* `LdapService` now optionally throws if a query fails rather than returning an empty result. This is not
expected to affect most applications, but may require some to adjust their error handling.
### 💥 Breaking Changes
* `LdapService` now throws if a query fails rather than returning an empty result. This is not
expected to affect most applications, but may require some to adjust their error handling.
* Requires `hoist-react >= 63.0` for client-side support of the new `track` and `submitError` endpoints.
## 18.5.1 - 2024-03-08
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,47 +9,34 @@ package io.xh.hoist.admin

import grails.gorm.transactions.ReadOnly
import io.xh.hoist.BaseController
import io.xh.hoist.clienterror.ClientError
import io.xh.hoist.clienterror.ClientErrorAdminService
import io.xh.hoist.security.Access

import static io.xh.hoist.util.DateTimeUtils.appStartOfDay
import static io.xh.hoist.util.DateTimeUtils.appEndOfDay
import static io.xh.hoist.util.DateTimeUtils.appDay
import static io.xh.hoist.util.DateTimeUtils.parseLocalDate
import static java.lang.Integer.parseInt
import io.xh.hoist.data.filter.Filter
import java.time.LocalDate



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

ClientErrorAdminService clientErrorAdminService
static int DEFAULT_MAX_ROWS = 25000

@ReadOnly
def index() {
def startDay = parseLocalDate(params.startDay),
endDay = parseLocalDate(params.endDay),
maxRows = params.maxRows ? parseInt(params.maxRows) : DEFAULT_MAX_ROWS

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

renderJSON(results)
}
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 ?: DEFAULT_MAX_ROWS

def lookups() {
renderJSON([
usernames: distinctVals('username')
])
renderJSON(clientErrorAdminService.queryClientError(startDay, endDay, filter, maxRows))
}


private List distinctVals(String property) {
return ClientError.createCriteria().list {
projections { distinct(property) }
}.sort()
def lookups() {
renderJSON(clientErrorAdminService.lookups())
}

}
30 changes: 17 additions & 13 deletions grails-app/controllers/io/xh/hoist/impl/XhController.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,18 @@ class XhController extends BaseController {
//------------------------
// Tracking
//------------------------
def track(String category, String msg, String data, String logData, int elapsed, String severity) {
def track() {
ensureClientUsernameMatchesSession()
def query = parseRequestJSON([safeEncode: true])
trackService.track(
category: safeEncode(category),
msg: safeEncode(msg),
data: data ? parseObjectOrArray(safeEncode(data)) : null,
logData: logData == 'true' || logData == 'false' ? parseBoolean(logData) : logData?.split(','),
elapsed: elapsed,
severity: safeEncode(severity)
category: query.category,
msg: query.msg,
data: query.data,
logData: query.logData,
elapsed: query.elapsed,
severity: query.severity,
url: query.url,
appVersion: query.appVersion
)
renderJSON(success: true)
}
Expand Down Expand Up @@ -227,14 +230,15 @@ class XhController extends BaseController {
//------------------------
// Client Errors
//------------------------
def submitError(String msg, String error, String appVersion, String url, boolean userAlerted) {
def submitError() {
ensureClientUsernameMatchesSession()
def query = parseRequestJSON([safeEncode: true])
clientErrorService.submit(
safeEncode(msg),
safeEncode(error),
safeEncode(appVersion),
safeEncode(url),
userAlerted
query.msg as String,
query.error as String,
query.appVersion as String,
query.url as String,
query.userAlerted as Boolean
)
renderJSON(success: true)
}
Expand Down
12 changes: 8 additions & 4 deletions grails-app/domain/io/xh/hoist/clienterror/ClientError.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,24 @@
package io.xh.hoist.clienterror

import io.xh.hoist.json.JSONFormat
import io.xh.hoist.util.Utils

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

class ClientError implements JSONFormat {

String username
String msg
String error
String username
String userAgent
String browser
String device
String userAgent
String appVersion
String appEnvironment
String url
boolean userAlerted = false
Date dateCreated
String impersonating

static mapping = {
table 'xh_client_error'
Expand All @@ -38,14 +40,15 @@ class ClientError implements JSONFormat {

static constraints = {
msg(nullable: true)
error(nullable: true)
username(maxSize: 50)
error(nullable: true)
browser(nullable: true, maxSize: 100)
device(nullable: true, maxSize: 100)
userAgent(nullable: true)
appVersion(nullable: true, maxSize: 100)
appEnvironment(nullable: true, maxSize: 100)
url(nullable: true, maxSize: 500)
impersonating(nullable: true, maxSize: 50)
}

Map formatForJSON() {
Expand All @@ -62,7 +65,8 @@ class ClientError implements JSONFormat {
url : url,
userAlerted : userAlerted,
dateCreated : dateCreated,
day : appDay(dateCreated)
day : appDay(dateCreated),
impersonating: impersonating
]
}

Expand Down
15 changes: 12 additions & 3 deletions grails-app/domain/io/xh/hoist/track/TrackLog.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ class TrackLog implements JSONFormat {
String device
String userAgent
String data
String appVersion
String appEnvironment
String url
Integer elapsed
String severity
Date dateCreated
Expand All @@ -34,13 +37,16 @@ class TrackLog implements JSONFormat {
}

static constraints = {
msg(maxSize: 255)
username(maxSize: 50)
category(maxSize: 100)
msg(maxSize: 255)
browser(nullable: true, maxSize: 100)
device(nullable: true, maxSize: 100)
userAgent(nullable: true)
data(nullable: true, validator: {Utils.isJSON(it) ?: 'default.invalid.json.message'})
data(nullable: true, validator: { Utils.isJSON(it) ?: 'default.invalid.json.message'})
appVersion(nullable: true, maxSize: 100)
appEnvironment(nullable: true, maxSize: 100)
url(nullable: true, maxSize: 500)
elapsed(nullable: true)
impersonating(nullable: true, maxSize: 50)
}
Expand All @@ -60,7 +66,10 @@ class TrackLog implements JSONFormat {
data: data,
elapsed: elapsed,
severity: severity,
impersonating: impersonating
impersonating: impersonating,
appVersion : appVersion,
appEnvironment: appEnvironment,
url : url,
]
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package io.xh.hoist.clienterror

import grails.gorm.transactions.ReadOnly
import io.xh.hoist.BaseService
import io.xh.hoist.data.filter.Filter
import io.xh.hoist.track.TrackLog
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 ClientErrorAdminService extends BaseService {
SessionFactory sessionFactory

@ReadOnly
List<ClientError> queryClientError(LocalDate startDay, LocalDate endDay, Filter filter, int maxRows) {
def session = sessionFactory.currentSession
Criteria c = session.createCriteria(ClientError)
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<ClientError>
}

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

//------------------------
// Implementation
//------------------------
private List distinctVals(String property) {
ClientError.createCriteria().list {
projections { distinct(property) }
}.sort()
}
}
6 changes: 5 additions & 1 deletion grails-app/services/io/xh/hoist/track/TrackService.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import grails.events.EventPublisher
import groovy.transform.CompileStatic
import io.xh.hoist.BaseService
import io.xh.hoist.config.ConfigService
import io.xh.hoist.util.Utils

import static io.xh.hoist.browser.Utils.getBrowser
import static io.xh.hoist.browser.Utils.getDevice
Expand Down Expand Up @@ -109,7 +110,10 @@ class TrackService extends BaseService implements EventPublisher {
device: getDevice(userAgent),
elapsed: params.elapsed,
severity: params.severity ?: 'INFO',
data: data
data: data,
url: params.url,
appVersion: params.appVersion ?: Utils.appVersion,
appEnvironment: Utils.appEnvironment
]

// Execute asynchronously after we get info from request, don't block application thread.
Expand Down

0 comments on commit 1f7cfbd

Please sign in to comment.