Skip to content

Commit

Permalink
Harmonize TrackLog and ClientError (#3619)
Browse files Browse the repository at this point in the history
+ Update to new APIs just merged into hoist-core (xh/hoist-core#344) for posting client error and activity tracking queries
+ Update Admin Client Error query tool to post filter params to server for DB-level querying, as was recently done with Activity Tracking
  • Loading branch information
jacob-xhio authored Apr 3, 2024
1 parent aff2061 commit 95428d3
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 54 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
### 🎁 New Features

* `MenuItem` now supports a `className` prop.
* `TrackService` now posts `url` and `appVersion` fields to the server when tracking an event.
* Admin `ActivityTracking` and `ClientError` modules now use server-side filtering for better
support of large datasets.

### 💥 Breaking Changes (upgrade difficulty: 🟠 MEDIUM - for apps with styling overrides for or direct use of Blueprint components)

Expand Down Expand Up @@ -39,6 +42,11 @@ Below are breaking changes that most apps will need to address:
Hoist's `dateInput`, you may need to update your code to use the
new [v8 `DatePickerProps`](https://react-day-picker.js.org/api/interfaces/DayPickerSingleProps).

#### Other Breaking Changes

* Requires `hoist-core >= v19.0.0` to support updates to activity tracking and client error endpoints.


### 🐞 Bug Fixes

* `ZoneGrid` columns' renders always need to be marked as complex.
Expand Down
84 changes: 45 additions & 39 deletions admin/tabs/activity/clienterrors/ClientErrorsModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import {FilterChooserModel} from '@xh/hoist/cmp/filter';
import {FormModel} from '@xh/hoist/cmp/form';
import {GridModel} from '@xh/hoist/cmp/grid';
import {HoistModel, LoadSpec, managed, XH} from '@xh/hoist/core';
import {fmtDate, fmtJson} from '@xh/hoist/format';
import {fmtJson} from '@xh/hoist/format';
import {action, bindable, observable, makeObservable, comparer} from '@xh/hoist/mobx';
import {LocalDate} from '@xh/hoist/utils/datetime';
import * as Col from '@xh/hoist/admin/columns';
import moment from 'moment';
import {computed} from 'mobx';

export class ClientErrorsModel extends HoistModel {
override persistWith = {localStorageKey: 'xhAdminClientErrorsState'};
Expand Down Expand Up @@ -68,51 +68,46 @@ export class ClientErrorsModel extends HoistModel {
});

this.filterChooserModel = new FilterChooserModel({
bind: this.gridModel.store,
fieldSpecs: [
'username',
'browser',
'device',
'appVersion',
'appEnvironment',
'userAlerted',
{
field: 'userAgent',
enableValues: false
field: 'username',
enableValues: true
},
{
field: 'msg',
enableValues: false
field: 'browser',
enableValues: true
},
{
field: 'error',
enableValues: false
field: 'device',
enableValues: true
},
{
field: 'url',
enableValues: false
field: 'appVersion'
},
{
field: 'dateCreated',
example: 'YYYY-MM-DD',
valueParser: (v, op) => {
let ret = moment(v, ['YYYY-MM-DD', 'YYYYMMDD'], true);
if (!ret.isValid()) return null;

// Note special handling for '>' & '<=' queries.
if (['>', '<='].includes(op)) {
ret = moment(ret).endOf('day');
}

return ret.toDate();
},
valueRenderer: v => fmtDate(v),
ops: ['>', '>=', '<', '<=']
field: 'appEnvironment',
enableValues: true
},
{
field: 'userAlerted'
},
{
field: 'userAgent'
},
{
field: 'msg'
},
{
field: 'error'
},
{
field: 'url'
}
],
persistWith: this.persistWith
]
});

this.loadFieldSpecValues();

this.formModel = new FormModel({
readonly: true,
fields: this.gridModel
Expand All @@ -121,7 +116,7 @@ export class ClientErrorsModel extends HoistModel {
});

this.addReaction({
track: () => this.getParams(),
track: () => this.query,
run: () => this.loadAsync(),
equals: comparer.structural
});
Expand All @@ -143,9 +138,9 @@ export class ClientErrorsModel extends HoistModel {
const {gridModel} = this;

try {
const data = await XH.fetchJson({
const data = await XH.fetchService.postJson({
url: 'clientErrorAdmin',
params: this.getParams(),
body: this.query,
loadSpec
});

Expand Down Expand Up @@ -201,10 +196,12 @@ export class ClientErrorsModel extends HoistModel {
this.startDay = this.endDay.subtract(value, unit).nextDay();
}

getParams() {
@computed
private get query() {
return {
startDay: this.startDay,
endDay: this.endDay
endDay: this.endDay,
filters: this.filterChooserModel.value
};
}

Expand All @@ -214,4 +211,13 @@ export class ClientErrorsModel extends HoistModel {
getDefaultEndDay() {
return LocalDate.currentAppDay();
}

private async loadFieldSpecValues() {
const lookups = await XH.fetchJson({url: 'clientErrorAdmin/lookups'});

this.filterChooserModel.fieldSpecs.forEach(spec => {
const {field} = spec;
if (lookups[field]) spec.values = lookups[field];
});
}
}
8 changes: 6 additions & 2 deletions core/exception/ExceptionHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,15 +193,19 @@ export class ExceptionHandler {
return false;
}

await XH.fetchJson({
await XH.fetchService.postJson({
url: 'xh/submitError',
params: {
body: {
error,
msg: userMessage ? stripTags(userMessage) : '',
appVersion: XH.getEnv('clientVersion'),
url: window.location.href,
userAlerted,
clientUsername: username
},
// Post clientUsername as a parameter to ensure client username matches session.
params: {
clientUsername: username
}
});
return true;
Expand Down
33 changes: 20 additions & 13 deletions svc/TrackService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,34 +78,41 @@ export class TrackService extends HoistService {
//------------------
private async doTrackAsync(options: TrackOptions) {
try {
const params: any = {
const query: any = {
msg: stripTags(options.message),
clientUsername: XH.getUsername()
clientUsername: XH.getUsername(),
appVersion: XH.getEnv('clientVersion'),
url: window.location.href
};

if (options.category) params.category = options.category;
if (options.data) params.data = JSON.stringify(options.data);
if (options.severity) params.severity = options.severity;
if (options.logData !== undefined) params.logData = options.logData.toString();
if (options.elapsed !== undefined) params.elapsed = options.elapsed;
if (options.category) query.category = options.category;
if (options.data) query.data = options.data;
if (options.severity) query.severity = options.severity;
if (options.logData !== undefined) query.logData = options.logData;
if (options.elapsed !== undefined) query.elapsed = options.elapsed;

const {maxDataLength} = this.conf;
if (params.data?.length > maxDataLength) {
if (query.data?.length > maxDataLength) {
this.logWarn(
`Track log includes ${params.data.length} chars of JSON data`,
`Track log includes ${query.data.length} chars of JSON data`,
`exceeds limit of ${maxDataLength}`,
'data will not be persisted',
options.data
);
params.data = null;
query.data = null;
}

const elapsedStr = params.elapsed != null ? `${params.elapsed}ms` : null,
consoleMsgs = [params.category, params.msg, elapsedStr].filter(it => it != null);
const elapsedStr = query.elapsed != null ? `${query.elapsed}ms` : null,
consoleMsgs = [query.category, query.msg, elapsedStr].filter(it => it != null);

this.logInfo(...consoleMsgs);

await XH.fetchJson({url: 'xh/track', params});
await XH.fetchService.postJson({
url: 'xh/track',
body: query,
// Post clientUsername as a parameter to ensure client username matches session.
params: {clientUsername: query.clientUsername}
});
} catch (e) {
this.logError('Failed to persist track log', options, e);
}
Expand Down

0 comments on commit 95428d3

Please sign in to comment.