dce-reactkit 3.2.2-beta.5 → 3.2.2-beta.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +728 -433
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/ButtonInputGroup.d.ts +1 -0
- package/dist/cjs/types/components/Drawer.d.ts +1 -0
- package/dist/cjs/types/components/IntelliTable.d.ts +1 -0
- package/dist/cjs/types/components/ItemPicker/NestableItemList.d.ts +1 -0
- package/dist/cjs/types/components/ItemPicker/index.d.ts +1 -0
- package/dist/cjs/types/types/LogAction.d.ts +16 -16
- package/dist/cjs/types/types/LogBuiltInMetadata.d.ts +1 -1
- package/dist/cjs/types/types/LogMetadataContextMap.d.ts +11 -0
- package/dist/cjs/types/types/LogMetadataTagMap.d.ts +8 -0
- package/dist/cjs/types/types/LogMetadataTargetMap.d.ts +8 -0
- package/dist/cjs/types/types/LogMetadataType.d.ts +6 -12
- package/dist/esm/index.js +729 -434
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/ButtonInputGroup.d.ts +1 -0
- package/dist/esm/types/components/Drawer.d.ts +1 -0
- package/dist/esm/types/components/IntelliTable.d.ts +1 -0
- package/dist/esm/types/components/ItemPicker/NestableItemList.d.ts +1 -0
- package/dist/esm/types/components/ItemPicker/index.d.ts +1 -0
- package/dist/esm/types/types/LogAction.d.ts +16 -16
- package/dist/esm/types/types/LogBuiltInMetadata.d.ts +1 -1
- package/dist/esm/types/types/LogMetadataContextMap.d.ts +11 -0
- package/dist/esm/types/types/LogMetadataTagMap.d.ts +8 -0
- package/dist/esm/types/types/LogMetadataTargetMap.d.ts +8 -0
- package/dist/esm/types/types/LogMetadataType.d.ts +6 -12
- package/dist/index.d.ts +51 -29
- package/package.json +1 -1
- package/src/components/ButtonInputGroup.tsx +5 -2
- package/src/components/CSVDownloadButton.tsx +2 -2
- package/src/components/Drawer.tsx +9 -1
- package/src/components/IntelliTable.tsx +181 -26
- package/src/components/ItemPicker/NestableItemList.tsx +32 -14
- package/src/components/ItemPicker/index.tsx +4 -0
- package/src/components/LogReviewer.tsx +635 -406
- package/src/components/SimpleDateChooser.tsx +27 -18
- package/src/helpers/genCSV.ts +22 -4
- package/src/helpers/genRouteHandler.ts +2 -2
- package/src/helpers/logClientEvent.tsx +1 -1
- package/src/server/initServer.ts +10 -3
- package/src/types/LogAction.ts +16 -16
- package/src/types/LogBuiltInMetadata.ts +5 -5
- package/src/types/LogMetadataContextMap.ts +15 -0
- package/src/types/LogMetadataTagMap.ts +9 -0
- package/src/types/LogMetadataTargetMap.ts +9 -0
- package/src/types/LogMetadataType.ts +8 -15
|
@@ -21,7 +21,8 @@ import {
|
|
|
21
21
|
// Import shared helpers
|
|
22
22
|
import visitServerEndpoint from '../helpers/visitServerEndpoint';
|
|
23
23
|
import getTimeInfoInET from '../helpers/getTimeInfoInET';
|
|
24
|
-
import { showFatalError } from './AppWrapper';
|
|
24
|
+
import { alert, showFatalError } from './AppWrapper';
|
|
25
|
+
import getHumanReadableDate from '../helpers/getHumanReadableDate';
|
|
25
26
|
|
|
26
27
|
// Import shared constants
|
|
27
28
|
import LOG_REVIEW_ROUTE_PATH_PREFIX from '../constants/LOG_REVIEW_ROUTE_PATH_PREFIX';
|
|
@@ -34,6 +35,9 @@ import LogType from '../types/LogType';
|
|
|
34
35
|
import LogAction from '../types/LogAction';
|
|
35
36
|
import ParamType from '../types/ParamType';
|
|
36
37
|
import IntelliTableColumn from '../types/IntelliTableColumn';
|
|
38
|
+
import LogBuiltInMetadata from '../types/LogBuiltInMetadata';
|
|
39
|
+
import LogMetadataContextMap from '../types/LogMetadataContextMap';
|
|
40
|
+
import LogMetadataTargetMap from '../types/LogMetadataTargetMap';
|
|
37
41
|
|
|
38
42
|
// Import shared components
|
|
39
43
|
import SimpleDateChooser from './SimpleDateChooser';
|
|
@@ -121,7 +125,7 @@ type ContextFilterState = {
|
|
|
121
125
|
|
|
122
126
|
// Tag filter state
|
|
123
127
|
type TagFilterState = {
|
|
124
|
-
[k: string]: boolean
|
|
128
|
+
[k: string]: boolean // tag => true if in the list of tags to show
|
|
125
129
|
};
|
|
126
130
|
|
|
127
131
|
// Action filter state (only relevant for action logs)
|
|
@@ -206,6 +210,11 @@ const style = `
|
|
|
206
210
|
border: 0.05rem solid black;
|
|
207
211
|
border-radius: 0.5rem;
|
|
208
212
|
overflow: hidden;
|
|
213
|
+
padding: 0.7rem;
|
|
214
|
+
|
|
215
|
+
/* Solid background */
|
|
216
|
+
background-color: white;
|
|
217
|
+
color: black;
|
|
209
218
|
|
|
210
219
|
/* Place contents in flex column */
|
|
211
220
|
flex-direction: column;
|
|
@@ -232,8 +241,220 @@ const style = `
|
|
|
232
241
|
/* Vertical scroll */
|
|
233
242
|
overflow-y: auto;
|
|
234
243
|
}
|
|
244
|
+
|
|
245
|
+
.LogReviewer-header-close-button {
|
|
246
|
+
border: 0 !important;
|
|
247
|
+
background-color: transparent !important;
|
|
248
|
+
padding-top: 0 !important;
|
|
249
|
+
padding-bottom: 0 !important;
|
|
250
|
+
padding-right: 1em !important;
|
|
251
|
+
margin: 0 !important;
|
|
252
|
+
color: #444 !important;
|
|
253
|
+
|
|
254
|
+
right: 0 !important;
|
|
255
|
+
position: absolute !important;
|
|
256
|
+
}
|
|
257
|
+
.LogReviewer-header-close-button:hover {
|
|
258
|
+
border: 0 !important;
|
|
259
|
+
background-color: transparent !important;
|
|
260
|
+
padding-top: 0 !important;
|
|
261
|
+
padding-bottom: 0 !important;
|
|
262
|
+
margin: 0 !important;
|
|
263
|
+
color: #000 !important;
|
|
264
|
+
}
|
|
235
265
|
`;
|
|
236
266
|
|
|
267
|
+
/*------------------------------------------------------------------------*/
|
|
268
|
+
/* Constants */
|
|
269
|
+
/*------------------------------------------------------------------------*/
|
|
270
|
+
|
|
271
|
+
const columns: IntelliTableColumn[] = [
|
|
272
|
+
{
|
|
273
|
+
title: 'First Name',
|
|
274
|
+
param: 'userFirstName',
|
|
275
|
+
type: ParamType.String,
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
title: 'Last Name',
|
|
279
|
+
param: 'userLastName',
|
|
280
|
+
type: ParamType.String,
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
title: 'Email',
|
|
284
|
+
param: 'userEmail',
|
|
285
|
+
type: ParamType.String,
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
title: 'Canvas Id',
|
|
289
|
+
param: 'userId',
|
|
290
|
+
type: ParamType.Int,
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
title: 'Student?',
|
|
294
|
+
param: 'isLearner',
|
|
295
|
+
type: ParamType.Boolean,
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
title: 'Teaching Staff?',
|
|
299
|
+
param: 'isTTM',
|
|
300
|
+
type: ParamType.Boolean,
|
|
301
|
+
startsHidden: true,
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
title: 'Admin?',
|
|
305
|
+
param: 'isAdmin',
|
|
306
|
+
type: ParamType.Boolean,
|
|
307
|
+
startsHidden: true,
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
title: 'Course Canvas Id',
|
|
311
|
+
param: 'courseId',
|
|
312
|
+
type: ParamType.Int,
|
|
313
|
+
startsHidden: true,
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
title: 'Course Name',
|
|
317
|
+
param: 'courseName',
|
|
318
|
+
type: ParamType.String,
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
title: 'Browser Name',
|
|
322
|
+
param: 'browser.name',
|
|
323
|
+
type: ParamType.String,
|
|
324
|
+
startsHidden: true,
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
title: 'Browser Version',
|
|
328
|
+
param: 'browser.version',
|
|
329
|
+
type: ParamType.String,
|
|
330
|
+
startsHidden: true,
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
title: 'OS',
|
|
334
|
+
param: 'device.os',
|
|
335
|
+
type: ParamType.String,
|
|
336
|
+
startsHidden: true,
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
title: 'Mobile?',
|
|
340
|
+
param: 'device.isMobile',
|
|
341
|
+
type: ParamType.Boolean,
|
|
342
|
+
startsHidden: true,
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
title: 'Year',
|
|
346
|
+
param: 'year',
|
|
347
|
+
type: ParamType.Int,
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
title: 'Month',
|
|
351
|
+
param: 'month',
|
|
352
|
+
type: ParamType.Int,
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
title: 'Day',
|
|
356
|
+
param: 'day',
|
|
357
|
+
type: ParamType.Int,
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
title: 'Hour',
|
|
361
|
+
param: 'hour',
|
|
362
|
+
type: ParamType.Int,
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
title: 'Minute',
|
|
366
|
+
param: 'minute',
|
|
367
|
+
type: ParamType.Int,
|
|
368
|
+
startsHidden: true,
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
title: 'Timestamp',
|
|
372
|
+
param: 'timestamp',
|
|
373
|
+
type: ParamType.Int,
|
|
374
|
+
startsHidden: true,
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
title: 'Context',
|
|
378
|
+
param: 'context',
|
|
379
|
+
type: ParamType.String,
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
title: 'Subcontext',
|
|
383
|
+
param: 'subcontext',
|
|
384
|
+
type: ParamType.String,
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
title: 'Tags',
|
|
388
|
+
param: 'tags',
|
|
389
|
+
type: ParamType.JSON,
|
|
390
|
+
startsHidden: true,
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
title: 'Log Level',
|
|
394
|
+
param: 'level',
|
|
395
|
+
type: ParamType.String,
|
|
396
|
+
startsHidden: true,
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
title: 'Metadata',
|
|
400
|
+
param: 'metadata',
|
|
401
|
+
type: ParamType.JSON,
|
|
402
|
+
startsHidden: true,
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
title: 'Source',
|
|
406
|
+
param: 'source',
|
|
407
|
+
type: ParamType.String,
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
title: 'Server Route Path',
|
|
411
|
+
param: 'routePath',
|
|
412
|
+
type: ParamType.String,
|
|
413
|
+
startsHidden: true,
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
title: 'Server Route Template',
|
|
417
|
+
param: 'routeTemplate',
|
|
418
|
+
type: ParamType.String,
|
|
419
|
+
startsHidden: true,
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
title: 'Type',
|
|
423
|
+
param: 'type',
|
|
424
|
+
type: ParamType.String,
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
title: 'Error Message',
|
|
428
|
+
param: 'errorMessage',
|
|
429
|
+
type: ParamType.String,
|
|
430
|
+
startsHidden: true,
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
title: 'Error Code',
|
|
434
|
+
param: 'errorCode',
|
|
435
|
+
type: ParamType.String,
|
|
436
|
+
startsHidden: true,
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
title: 'Error Stack',
|
|
440
|
+
param: 'errorStack',
|
|
441
|
+
type: ParamType.String,
|
|
442
|
+
startsHidden: true,
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
title: 'Action Target',
|
|
446
|
+
param: 'target',
|
|
447
|
+
type: ParamType.String,
|
|
448
|
+
startsHidden: true,
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
title: 'Action Type',
|
|
452
|
+
param: 'action',
|
|
453
|
+
type: ParamType.String,
|
|
454
|
+
startsHidden: true,
|
|
455
|
+
},
|
|
456
|
+
];
|
|
457
|
+
|
|
237
458
|
/*------------------------------------------------------------------------*/
|
|
238
459
|
/* Static Functions */
|
|
239
460
|
/*------------------------------------------------------------------------*/
|
|
@@ -254,7 +475,7 @@ const genHumanReadableName = (machineReadableName: string) => {
|
|
|
254
475
|
// Uppercase! Add a space before
|
|
255
476
|
humanReadableName += ' ';
|
|
256
477
|
}
|
|
257
|
-
humanReadableName +=
|
|
478
|
+
humanReadableName += char;
|
|
258
479
|
});
|
|
259
480
|
|
|
260
481
|
// Trim and return
|
|
@@ -437,9 +658,25 @@ const reducer = (state: State, action: Action): State => {
|
|
|
437
658
|
};
|
|
438
659
|
}
|
|
439
660
|
case ActionType.UpdateTagFilterState: {
|
|
661
|
+
const { tagFilterState } = action;
|
|
662
|
+
|
|
663
|
+
// Select all if every tag is deselected
|
|
664
|
+
const numTagsSelected = (
|
|
665
|
+
Object.values(tagFilterState)
|
|
666
|
+
.filter((isSelected) => {
|
|
667
|
+
return isSelected;
|
|
668
|
+
})
|
|
669
|
+
.length
|
|
670
|
+
);
|
|
671
|
+
if (numTagsSelected === 0) {
|
|
672
|
+
Object.keys(tagFilterState).forEach((tag) => {
|
|
673
|
+
tagFilterState[tag] = true;
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
|
|
440
677
|
return {
|
|
441
678
|
...state,
|
|
442
|
-
tagFilterState
|
|
679
|
+
tagFilterState,
|
|
443
680
|
};
|
|
444
681
|
}
|
|
445
682
|
case ActionType.UpdateActionErrorFilterState: {
|
|
@@ -477,6 +714,36 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
477
714
|
onClose,
|
|
478
715
|
} = props;
|
|
479
716
|
|
|
717
|
+
// Create complete map of contexts
|
|
718
|
+
const contextMap: LogMetadataContextMap = {};
|
|
719
|
+
[
|
|
720
|
+
(LogMetadata.Context ?? {}),
|
|
721
|
+
LogBuiltInMetadata.Context,
|
|
722
|
+
].forEach((partialContextMap) => {
|
|
723
|
+
Object.keys(partialContextMap).forEach((context) => {
|
|
724
|
+
// Add context
|
|
725
|
+
contextMap[context] = context;
|
|
726
|
+
|
|
727
|
+
// If context has children, add an "uncategorized" subcontext
|
|
728
|
+
if (typeof contextMap[context] !== 'string') {
|
|
729
|
+
(contextMap[context] as any)[LogBuiltInMetadata.Context.Uncategorized] = (
|
|
730
|
+
LogBuiltInMetadata.Context.Uncategorized
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
// Create complete map of targets
|
|
737
|
+
const targetMap: LogMetadataTargetMap = {};
|
|
738
|
+
[
|
|
739
|
+
(LogMetadata.Target ?? {}),
|
|
740
|
+
LogBuiltInMetadata.Target,
|
|
741
|
+
].forEach((partialTargetMap) => {
|
|
742
|
+
Object.keys(partialTargetMap).forEach((target) => {
|
|
743
|
+
targetMap[target] = target;
|
|
744
|
+
});
|
|
745
|
+
});
|
|
746
|
+
|
|
480
747
|
/* -------------- State ------------- */
|
|
481
748
|
|
|
482
749
|
// Create initial date filter state
|
|
@@ -501,22 +768,27 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
501
768
|
Object.keys(LogMetadata.Context ?? {}).forEach((context) => {
|
|
502
769
|
const contextValue = (LogMetadata.Context ?? {})[context];
|
|
503
770
|
if (typeof contextValue === 'string') {
|
|
504
|
-
// Case: no subcontexts
|
|
771
|
+
// Case: no subcontexts, init as checked
|
|
505
772
|
initContextFilterState[contextValue] = true;
|
|
506
773
|
} else {
|
|
507
774
|
// Case: subcontexts exist
|
|
508
|
-
initContextFilterState[
|
|
775
|
+
initContextFilterState[context] = {};
|
|
509
776
|
Object.values((LogMetadata.Context ?? {})[context]).forEach((subcontext) => {
|
|
510
|
-
|
|
511
|
-
(
|
|
777
|
+
// Skip self ("_")
|
|
778
|
+
if (subcontext === '_') {
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Initialize as checked
|
|
783
|
+
(initContextFilterState[context] as any)[subcontext] = true;
|
|
512
784
|
});
|
|
513
785
|
}
|
|
514
786
|
});
|
|
515
787
|
|
|
516
788
|
// Create initial tag filter state
|
|
517
789
|
const initTagFilterState: TagFilterState = {};
|
|
518
|
-
Object.
|
|
519
|
-
initTagFilterState[
|
|
790
|
+
Object.keys(LogMetadata.Tag ?? {}).forEach((tag) => {
|
|
791
|
+
initTagFilterState[tag] = false;
|
|
520
792
|
});
|
|
521
793
|
|
|
522
794
|
// Create advanced filter state
|
|
@@ -550,6 +822,10 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
550
822
|
Object.values(LogAction).forEach((action) => {
|
|
551
823
|
initActionErrorFilterState.action[action] = true;
|
|
552
824
|
});
|
|
825
|
+
// Add built-in targets
|
|
826
|
+
Object.values(LogBuiltInMetadata.Target).forEach((target) => {
|
|
827
|
+
initActionErrorFilterState.target[target] = true;
|
|
828
|
+
});
|
|
553
829
|
|
|
554
830
|
// Initial state
|
|
555
831
|
const initialState: State = {
|
|
@@ -600,18 +876,23 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
600
876
|
let month = newDateFilterState.startDate.month;
|
|
601
877
|
while (
|
|
602
878
|
// Earlier year
|
|
603
|
-
(year
|
|
879
|
+
(year < newDateFilterState.endDate.year)
|
|
604
880
|
// Current year but included month
|
|
605
881
|
|| (
|
|
606
882
|
year === newDateFilterState.endDate.year
|
|
607
883
|
&& month <= newDateFilterState.endDate.month
|
|
608
884
|
)
|
|
609
885
|
) {
|
|
610
|
-
// Add to list
|
|
611
|
-
|
|
612
|
-
year
|
|
613
|
-
month
|
|
614
|
-
|
|
886
|
+
// Add to list if not already loaded
|
|
887
|
+
if (
|
|
888
|
+
!logMap[year]
|
|
889
|
+
|| !logMap[year][month]
|
|
890
|
+
) {
|
|
891
|
+
toLoad.push({
|
|
892
|
+
year,
|
|
893
|
+
month,
|
|
894
|
+
});
|
|
895
|
+
}
|
|
615
896
|
|
|
616
897
|
// Increment
|
|
617
898
|
month += 1;
|
|
@@ -727,16 +1008,16 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
727
1008
|
|
|
728
1009
|
// Filter toggle
|
|
729
1010
|
const filterToggles = (
|
|
730
|
-
<div className="LogReviewer-filter-toggles
|
|
1011
|
+
<div className="LogReviewer-filter-toggles">
|
|
731
1012
|
<h3 className="m-0">
|
|
732
1013
|
Filters:
|
|
733
1014
|
</h3>
|
|
734
|
-
<div className="LogReviewer-filter-toggle-buttons">
|
|
1015
|
+
<div className="LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0">
|
|
735
1016
|
{/* Date */}
|
|
736
1017
|
<button
|
|
737
1018
|
type="button"
|
|
738
1019
|
id="LogReviewer-toggle-date-filter-drawer"
|
|
739
|
-
className={`btn btn-${FilterDrawer.Date === expandedFilterDrawer} me-2`}
|
|
1020
|
+
className={`btn btn-${FilterDrawer.Date === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
740
1021
|
aria-label="toggle date filter drawer"
|
|
741
1022
|
onClick={() => {
|
|
742
1023
|
dispatch({
|
|
@@ -755,7 +1036,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
755
1036
|
<button
|
|
756
1037
|
type="button"
|
|
757
1038
|
id="LogReviewer-toggle-context-filter-drawer"
|
|
758
|
-
className={`btn btn-${FilterDrawer.Context === expandedFilterDrawer} me-2`}
|
|
1039
|
+
className={`btn btn-${FilterDrawer.Context === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
759
1040
|
aria-label="toggle context filter drawer"
|
|
760
1041
|
onClick={() => {
|
|
761
1042
|
dispatch({
|
|
@@ -771,29 +1052,32 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
771
1052
|
Context
|
|
772
1053
|
</button>
|
|
773
1054
|
{/* Tag */}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
1055
|
+
{/* Skip if no tags are used */}
|
|
1056
|
+
{(LogMetadata.Tag && Object.keys(LogMetadata.Tag).length > 0) && (
|
|
1057
|
+
<button
|
|
1058
|
+
type="button"
|
|
1059
|
+
id="LogReviewer-toggle-tag-filter-drawer"
|
|
1060
|
+
className={`btn btn-${FilterDrawer.Tag === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
1061
|
+
aria-label="toggle tag filter drawer"
|
|
1062
|
+
onClick={() => {
|
|
1063
|
+
dispatch({
|
|
1064
|
+
type: ActionType.ToggleFilterDrawer,
|
|
1065
|
+
filterDrawer: FilterDrawer.Tag,
|
|
1066
|
+
});
|
|
1067
|
+
}}
|
|
1068
|
+
>
|
|
1069
|
+
<FontAwesomeIcon
|
|
1070
|
+
icon={faTag}
|
|
1071
|
+
className="me-2"
|
|
1072
|
+
/>
|
|
1073
|
+
Tag
|
|
1074
|
+
</button>
|
|
1075
|
+
)}
|
|
792
1076
|
{/* Action */}
|
|
793
1077
|
<button
|
|
794
1078
|
type="button"
|
|
795
1079
|
id="LogReviewer-toggle-action-filter-drawer"
|
|
796
|
-
className={`btn btn-${FilterDrawer.Action === expandedFilterDrawer} me-2`}
|
|
1080
|
+
className={`btn btn-${FilterDrawer.Action === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
797
1081
|
aria-label="toggle action and error filter drawer"
|
|
798
1082
|
onClick={() => {
|
|
799
1083
|
dispatch({
|
|
@@ -812,7 +1096,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
812
1096
|
<button
|
|
813
1097
|
type="button"
|
|
814
1098
|
id="LogReviewer-toggle-advanced-filter-drawer"
|
|
815
|
-
className={`btn btn-${FilterDrawer.Advanced === expandedFilterDrawer}`}
|
|
1099
|
+
className={`btn btn-${FilterDrawer.Advanced === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
816
1100
|
aria-label="toggle advanced filter drawer"
|
|
817
1101
|
onClick={() => {
|
|
818
1102
|
dispatch({
|
|
@@ -827,6 +1111,29 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
827
1111
|
/>
|
|
828
1112
|
Advanced
|
|
829
1113
|
</button>
|
|
1114
|
+
{/* Reset */}
|
|
1115
|
+
<button
|
|
1116
|
+
type="button"
|
|
1117
|
+
id="LogReviewer-reset-filters-button"
|
|
1118
|
+
className="btn btn-light"
|
|
1119
|
+
aria-label="reset filters"
|
|
1120
|
+
onClick={() => {
|
|
1121
|
+
dispatch({
|
|
1122
|
+
type: ActionType.ResetFilters,
|
|
1123
|
+
initActionErrorFilterState,
|
|
1124
|
+
initAdvancedFilterState,
|
|
1125
|
+
initContextFilterState,
|
|
1126
|
+
initDateFilterState,
|
|
1127
|
+
initTagFilterState,
|
|
1128
|
+
});
|
|
1129
|
+
}}
|
|
1130
|
+
>
|
|
1131
|
+
<FontAwesomeIcon
|
|
1132
|
+
icon={faTimes}
|
|
1133
|
+
/>
|
|
1134
|
+
{' '}
|
|
1135
|
+
Reset
|
|
1136
|
+
</button>
|
|
830
1137
|
</div>
|
|
831
1138
|
</div>
|
|
832
1139
|
);
|
|
@@ -843,14 +1150,11 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
843
1150
|
year={dateFilterState.startDate.year}
|
|
844
1151
|
month={dateFilterState.startDate.month}
|
|
845
1152
|
day={dateFilterState.startDate.day}
|
|
1153
|
+
chooseFromPast
|
|
1154
|
+
numMonthsToShow={36}
|
|
846
1155
|
onChange={(month, day, year) => {
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
dateFilterState: {
|
|
850
|
-
...dateFilterState,
|
|
851
|
-
startDate: { month, day, year },
|
|
852
|
-
},
|
|
853
|
-
});
|
|
1156
|
+
dateFilterState.startDate = { month, day, year };
|
|
1157
|
+
handleDateRangeUpdated(dateFilterState);
|
|
854
1158
|
}}
|
|
855
1159
|
/>
|
|
856
1160
|
{' '}
|
|
@@ -862,59 +1166,91 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
862
1166
|
year={dateFilterState.endDate.year}
|
|
863
1167
|
month={dateFilterState.endDate.month}
|
|
864
1168
|
day={dateFilterState.endDate.day}
|
|
1169
|
+
chooseFromPast
|
|
1170
|
+
numMonthsToShow={12}
|
|
865
1171
|
onChange={(month, day, year) => {
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1172
|
+
if (
|
|
1173
|
+
year < dateFilterState.startDate.year
|
|
1174
|
+
|| (
|
|
1175
|
+
year === dateFilterState.startDate.year
|
|
1176
|
+
&& month < dateFilterState.startDate.month
|
|
1177
|
+
)
|
|
1178
|
+
|| (
|
|
1179
|
+
year === dateFilterState.startDate.year
|
|
1180
|
+
&& month === dateFilterState.startDate.month
|
|
1181
|
+
&& day < dateFilterState.startDate.day
|
|
1182
|
+
)
|
|
1183
|
+
) {
|
|
1184
|
+
return alert(
|
|
1185
|
+
'Invalid Start Date',
|
|
1186
|
+
'The start date cannot be before the end date.',
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
dateFilterState.endDate = { month, day, year };
|
|
1190
|
+
handleDateRangeUpdated(dateFilterState);
|
|
873
1191
|
}}
|
|
874
1192
|
/>
|
|
875
1193
|
</TabBox>
|
|
876
1194
|
);
|
|
877
1195
|
} else if (expandedFilterDrawer === FilterDrawer.Context) {
|
|
878
1196
|
// Create item picker items
|
|
879
|
-
const
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
return item;
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
// Has subcategories
|
|
895
|
-
const children: PickableItem[] = (
|
|
896
|
-
Object.keys(value)
|
|
897
|
-
.filter((subcontext) => {
|
|
898
|
-
return subcontext !== '_'
|
|
899
|
-
})
|
|
900
|
-
.map((subcontext) => {
|
|
901
|
-
return {
|
|
902
|
-
id: `${context}-${subcontext}`,
|
|
903
|
-
name: genHumanReadableName(subcontext),
|
|
904
|
-
isGroup: false,
|
|
905
|
-
checked: !!value[subcontext],
|
|
906
|
-
};
|
|
907
|
-
})
|
|
908
|
-
);
|
|
1197
|
+
const builtInPickableItem: PickableItem = {
|
|
1198
|
+
id: '_',
|
|
1199
|
+
name: 'Auto-logged',
|
|
1200
|
+
isGroup: true,
|
|
1201
|
+
children: [],
|
|
1202
|
+
};
|
|
1203
|
+
const pickableItems: PickableItem[] = [];
|
|
1204
|
+
Object.keys(contextMap)
|
|
1205
|
+
.forEach((context) => {
|
|
1206
|
+
const value = (contextMap)[context];
|
|
1207
|
+
if (typeof value === 'string') {
|
|
1208
|
+
// No subcategories
|
|
909
1209
|
const item: PickableItem = {
|
|
910
1210
|
id: context,
|
|
911
|
-
name: context,
|
|
912
|
-
isGroup:
|
|
913
|
-
|
|
1211
|
+
name: genHumanReadableName(context),
|
|
1212
|
+
isGroup: false,
|
|
1213
|
+
checked: !!contextFilterState[context],
|
|
914
1214
|
};
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
1215
|
+
|
|
1216
|
+
// Add built-in items to its own folder
|
|
1217
|
+
const isBuiltIn = context in LogBuiltInMetadata.Context;
|
|
1218
|
+
if (isBuiltIn) {
|
|
1219
|
+
// Add to built-in pickable item
|
|
1220
|
+
builtInPickableItem.children.push(item);
|
|
1221
|
+
} else {
|
|
1222
|
+
// Add to pickable items list
|
|
1223
|
+
pickableItems.push(item);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
// Has subcategories
|
|
1228
|
+
const children: PickableItem[] = (
|
|
1229
|
+
Object.keys(value)
|
|
1230
|
+
// Remove parent name
|
|
1231
|
+
.filter((subcontext) => {
|
|
1232
|
+
return subcontext !== '_';
|
|
1233
|
+
})
|
|
1234
|
+
// Create child pickable items
|
|
1235
|
+
.map((subcontext) => {
|
|
1236
|
+
return {
|
|
1237
|
+
id: subcontext,
|
|
1238
|
+
name: genHumanReadableName(subcontext),
|
|
1239
|
+
isGroup: false,
|
|
1240
|
+
checked: (contextFilterState[context] as any)[subcontext],
|
|
1241
|
+
};
|
|
1242
|
+
})
|
|
1243
|
+
);
|
|
1244
|
+
const item: PickableItem = {
|
|
1245
|
+
id: context,
|
|
1246
|
+
name: genHumanReadableName(context),
|
|
1247
|
+
isGroup: true,
|
|
1248
|
+
children,
|
|
1249
|
+
};
|
|
1250
|
+
pickableItems.push(item);
|
|
1251
|
+
});
|
|
1252
|
+
// Add built-in contexts to end ofl ist
|
|
1253
|
+
pickableItems.push(builtInPickableItem);
|
|
918
1254
|
|
|
919
1255
|
// Create filter UI
|
|
920
1256
|
filterDrawer = (
|
|
@@ -926,11 +1262,30 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
926
1262
|
updatedItems.forEach((pickableItem) => {
|
|
927
1263
|
if (pickableItem.isGroup) {
|
|
928
1264
|
// Has subcontexts
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
1265
|
+
|
|
1266
|
+
|
|
1267
|
+
if (pickableItem.id === '_') {
|
|
1268
|
+
// Built-in
|
|
1269
|
+
|
|
1270
|
+
// Treat as if these were top-level contexts
|
|
1271
|
+
pickableItem.children.forEach((subcontextItem) => {
|
|
1272
|
+
contextFilterState[subcontextItem.id] = (
|
|
1273
|
+
'checked' in subcontextItem
|
|
1274
|
+
&& subcontextItem.checked
|
|
1275
|
+
);
|
|
1276
|
+
});
|
|
1277
|
+
} else {
|
|
1278
|
+
// Not built-in
|
|
1279
|
+
pickableItem.children.forEach((subcontextItem) => {
|
|
1280
|
+
if (!subcontextItem.isGroup) {
|
|
1281
|
+
(
|
|
1282
|
+
contextFilterState[pickableItem.id] as { [k: string]: boolean }
|
|
1283
|
+
)[subcontextItem.id] = (
|
|
1284
|
+
subcontextItem.checked
|
|
1285
|
+
);
|
|
1286
|
+
}
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
934
1289
|
} else {
|
|
935
1290
|
// No subcontexts
|
|
936
1291
|
(contextFilterState as any)[pickableItem.id] = (
|
|
@@ -949,18 +1304,28 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
949
1304
|
// Create filter UI
|
|
950
1305
|
filterDrawer = (
|
|
951
1306
|
<TabBox title="Tags">
|
|
1307
|
+
<div>
|
|
1308
|
+
If any are selected, logs must contain at least one
|
|
1309
|
+
but not necessarily all of the
|
|
1310
|
+
selected tags.
|
|
1311
|
+
</div>
|
|
952
1312
|
{
|
|
953
|
-
Object.keys(
|
|
1313
|
+
Object.keys(LogMetadata.Tag ?? {})
|
|
954
1314
|
.map((tag, i) => {
|
|
955
1315
|
const description = genHumanReadableName(tag);
|
|
956
1316
|
return (
|
|
957
1317
|
<CheckboxButton
|
|
958
1318
|
id={`LogReviewer-tag-${tag}-checkbox`}
|
|
959
1319
|
text={description}
|
|
960
|
-
ariaLabel={`
|
|
1320
|
+
ariaLabel={`require that logs be tagged with "${description}" or any other selected tag`}
|
|
961
1321
|
noMarginOnRight={i === Object.keys(tagFilterState).length - 1}
|
|
1322
|
+
checked={tagFilterState[tag]}
|
|
962
1323
|
onChanged={(checked) => {
|
|
963
1324
|
tagFilterState[tag] = checked;
|
|
1325
|
+
dispatch({
|
|
1326
|
+
type: ActionType.UpdateTagFilterState,
|
|
1327
|
+
tagFilterState,
|
|
1328
|
+
});
|
|
964
1329
|
}}
|
|
965
1330
|
/>
|
|
966
1331
|
);
|
|
@@ -1023,52 +1388,66 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1023
1388
|
) && (
|
|
1024
1389
|
<TabBox title="Action Log Details">
|
|
1025
1390
|
{/* Action */}
|
|
1026
|
-
<ButtonInputGroup
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1391
|
+
<ButtonInputGroup
|
|
1392
|
+
label="Action"
|
|
1393
|
+
className="mb-2"
|
|
1394
|
+
>
|
|
1395
|
+
<div className="d-flex gap-1 flex-wrap">
|
|
1396
|
+
{
|
|
1397
|
+
Object.keys(LogAction)
|
|
1398
|
+
.map((action) => {
|
|
1399
|
+
const description = genHumanReadableName(action);
|
|
1400
|
+
return (
|
|
1401
|
+
<CheckboxButton
|
|
1402
|
+
key={action}
|
|
1403
|
+
id={`LogReviewer-action-${action}-checkbox`}
|
|
1404
|
+
text={description}
|
|
1405
|
+
ariaLabel={`include logs with action type "${description}" in results`}
|
|
1406
|
+
noMarginOnRight
|
|
1407
|
+
checked={actionErrorFilterState.action[action]}
|
|
1408
|
+
onChanged={(checked) => {
|
|
1409
|
+
actionErrorFilterState.action[action] = checked;
|
|
1410
|
+
console.log(actionErrorFilterState, action, checked);
|
|
1411
|
+
dispatch({
|
|
1412
|
+
type: ActionType.UpdateActionErrorFilterState,
|
|
1413
|
+
actionErrorFilterState,
|
|
1414
|
+
});
|
|
1415
|
+
}}
|
|
1416
|
+
/>
|
|
1417
|
+
);
|
|
1418
|
+
})
|
|
1419
|
+
}
|
|
1420
|
+
</div>
|
|
1048
1421
|
</ButtonInputGroup>
|
|
1049
1422
|
{/* Target */}
|
|
1050
1423
|
<ButtonInputGroup label="Target">
|
|
1051
|
-
{
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1424
|
+
{/* List of targets */}
|
|
1425
|
+
<div className="d-flex gap-1 flex-wrap">
|
|
1426
|
+
{
|
|
1427
|
+
Object.keys(targetMap)
|
|
1428
|
+
.map((target) => {
|
|
1429
|
+
const description = genHumanReadableName(target);
|
|
1430
|
+
return (
|
|
1431
|
+
<CheckboxButton
|
|
1432
|
+
key={target}
|
|
1433
|
+
id={`LogReviewer-target-${target}-checkbox`}
|
|
1434
|
+
text={description}
|
|
1435
|
+
ariaLabel={`include logs with target "${description}" in results`}
|
|
1436
|
+
checked={actionErrorFilterState.target[target]}
|
|
1437
|
+
noMarginOnRight
|
|
1438
|
+
onChanged={(checked) => {
|
|
1439
|
+
actionErrorFilterState.target[target] = checked;
|
|
1440
|
+
console.log(actionErrorFilterState, target, checked);
|
|
1441
|
+
dispatch({
|
|
1442
|
+
type: ActionType.UpdateActionErrorFilterState,
|
|
1443
|
+
actionErrorFilterState,
|
|
1444
|
+
});
|
|
1445
|
+
}}
|
|
1446
|
+
/>
|
|
1447
|
+
);
|
|
1448
|
+
})
|
|
1449
|
+
}
|
|
1450
|
+
</div>
|
|
1072
1451
|
</ButtonInputGroup>
|
|
1073
1452
|
</TabBox>
|
|
1074
1453
|
)
|
|
@@ -1090,6 +1469,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1090
1469
|
className="form-control"
|
|
1091
1470
|
aria-label="query for error message"
|
|
1092
1471
|
value={actionErrorFilterState.errorMessage}
|
|
1472
|
+
placeholder="e.g. undefined is not a function"
|
|
1093
1473
|
onChange={(e) => {
|
|
1094
1474
|
actionErrorFilterState.errorMessage = e.target.value;
|
|
1095
1475
|
dispatch({
|
|
@@ -1108,7 +1488,8 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1108
1488
|
type="text"
|
|
1109
1489
|
className="form-control"
|
|
1110
1490
|
aria-label="query for error code"
|
|
1111
|
-
value={actionErrorFilterState.
|
|
1491
|
+
value={actionErrorFilterState.errorCode}
|
|
1492
|
+
placeholder="e.g. GC22"
|
|
1112
1493
|
onChange={(e) => {
|
|
1113
1494
|
actionErrorFilterState.errorCode = (
|
|
1114
1495
|
(e.target.value)
|
|
@@ -1143,6 +1524,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1143
1524
|
className="form-control"
|
|
1144
1525
|
aria-label="query for user first name"
|
|
1145
1526
|
value={advancedFilterState.userFirstName}
|
|
1527
|
+
placeholder="e.g. Divardo"
|
|
1146
1528
|
onChange={(e) => {
|
|
1147
1529
|
advancedFilterState.userFirstName = e.target.value;
|
|
1148
1530
|
dispatch({
|
|
@@ -1162,6 +1544,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1162
1544
|
className="form-control"
|
|
1163
1545
|
aria-label="query for user last name"
|
|
1164
1546
|
value={advancedFilterState.userLastName}
|
|
1547
|
+
placeholder="e.g. Calicci"
|
|
1165
1548
|
onChange={(e) => {
|
|
1166
1549
|
advancedFilterState.userLastName = e.target.value;
|
|
1167
1550
|
dispatch({
|
|
@@ -1181,6 +1564,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1181
1564
|
className="form-control"
|
|
1182
1565
|
aria-label="query for user email"
|
|
1183
1566
|
value={advancedFilterState.userEmail}
|
|
1567
|
+
placeholder="e.g. calicci@fas.harvard.edu"
|
|
1184
1568
|
onChange={(e) => {
|
|
1185
1569
|
advancedFilterState.userEmail = (
|
|
1186
1570
|
(e.target.value)
|
|
@@ -1203,6 +1587,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1203
1587
|
className="form-control"
|
|
1204
1588
|
aria-label="query for user canvas id"
|
|
1205
1589
|
value={advancedFilterState.userId}
|
|
1590
|
+
placeholder="e.g. 104985"
|
|
1206
1591
|
onChange={(e) => {
|
|
1207
1592
|
const { value } = e.target;
|
|
1208
1593
|
// Only update if value contains only numbers
|
|
@@ -1272,6 +1657,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1272
1657
|
className="form-control"
|
|
1273
1658
|
aria-label="query for course name"
|
|
1274
1659
|
value={advancedFilterState.courseName}
|
|
1660
|
+
placeholder="e.g. GLC 200"
|
|
1275
1661
|
onChange={(e) => {
|
|
1276
1662
|
advancedFilterState.courseName = e.target.value;
|
|
1277
1663
|
dispatch({
|
|
@@ -1291,6 +1677,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1291
1677
|
className="form-control"
|
|
1292
1678
|
aria-label="query for course canvas id"
|
|
1293
1679
|
value={advancedFilterState.courseId}
|
|
1680
|
+
placeholder="e.g. 15948"
|
|
1294
1681
|
onChange={(e) => {
|
|
1295
1682
|
const { value } = e.target;
|
|
1296
1683
|
// Only update if value contains only numbers
|
|
@@ -1394,53 +1781,58 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1394
1781
|
/>
|
|
1395
1782
|
</ButtonInputGroup>
|
|
1396
1783
|
|
|
1397
|
-
{/*
|
|
1398
|
-
|
|
1399
|
-
<
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1784
|
+
{/* Server filters */}
|
|
1785
|
+
{advancedFilterState.source !== LogSource.Client && (
|
|
1786
|
+
<div className="mt-2">
|
|
1787
|
+
{/* Route path */}
|
|
1788
|
+
<div className="input-group mb-2">
|
|
1789
|
+
<span className="input-group-text">
|
|
1790
|
+
Server Route Path
|
|
1791
|
+
</span>
|
|
1792
|
+
<input
|
|
1793
|
+
type="text"
|
|
1794
|
+
className="form-control"
|
|
1795
|
+
aria-label="query for server route path"
|
|
1796
|
+
value={advancedFilterState.routePath}
|
|
1797
|
+
placeholder="e.g. /api/ttm/courses/12345"
|
|
1798
|
+
onChange={(e) => {
|
|
1799
|
+
advancedFilterState.courseName = (
|
|
1800
|
+
(e.target.value)
|
|
1801
|
+
.trim()
|
|
1802
|
+
);
|
|
1803
|
+
dispatch({
|
|
1804
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1805
|
+
advancedFilterState,
|
|
1806
|
+
});
|
|
1807
|
+
}}
|
|
1808
|
+
/>
|
|
1809
|
+
</div>
|
|
1420
1810
|
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1811
|
+
{/* Route template */}
|
|
1812
|
+
<div className="input-group mb-2">
|
|
1813
|
+
<span className="input-group-text">
|
|
1814
|
+
Server Route Template
|
|
1815
|
+
</span>
|
|
1816
|
+
<input
|
|
1817
|
+
type="text"
|
|
1818
|
+
className="form-control"
|
|
1819
|
+
aria-label="query for server route template"
|
|
1820
|
+
value={advancedFilterState.routeTemplate}
|
|
1821
|
+
placeholder="e.g. /api/ttm/courses/:courseId"
|
|
1822
|
+
onChange={(e) => {
|
|
1823
|
+
advancedFilterState.courseName = (
|
|
1824
|
+
(e.target.value)
|
|
1825
|
+
.trim()
|
|
1826
|
+
);
|
|
1827
|
+
dispatch({
|
|
1828
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1829
|
+
advancedFilterState,
|
|
1830
|
+
});
|
|
1831
|
+
}}
|
|
1832
|
+
/>
|
|
1833
|
+
</div>
|
|
1834
|
+
</div>
|
|
1835
|
+
)}
|
|
1444
1836
|
</TabBox>
|
|
1445
1837
|
</>
|
|
1446
1838
|
);
|
|
@@ -1452,7 +1844,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1452
1844
|
<>
|
|
1453
1845
|
{filterToggles}
|
|
1454
1846
|
{filterDrawer && (
|
|
1455
|
-
<Drawer>
|
|
1847
|
+
<Drawer customBackgroundColor="#eee">
|
|
1456
1848
|
{filterDrawer}
|
|
1457
1849
|
</Drawer>
|
|
1458
1850
|
)}
|
|
@@ -1463,7 +1855,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1463
1855
|
// > Perform filters
|
|
1464
1856
|
const logs: Log[] = [];
|
|
1465
1857
|
Object.keys(logMap).forEach((year) => {
|
|
1466
|
-
Object.keys(logMap).forEach((month) => {
|
|
1858
|
+
Object.keys(logMap[year]).forEach((month) => {
|
|
1467
1859
|
logMap[year][month].forEach((log) => {
|
|
1468
1860
|
/* ----------- Date Filter ---------- */
|
|
1469
1861
|
|
|
@@ -1513,19 +1905,25 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1513
1905
|
contextFilterState[log.context] === false
|
|
1514
1906
|
// None of the subcontexts are selected
|
|
1515
1907
|
|| (
|
|
1516
|
-
|
|
1908
|
+
// Has subcontexts
|
|
1909
|
+
typeof contextFilterState[log.context] !== 'boolean'
|
|
1910
|
+
// None of the subcontexts are selected
|
|
1911
|
+
&& Object.values(contextFilterState[log.context] ?? {})
|
|
1517
1912
|
.every((isSelected) => {
|
|
1518
1913
|
return !isSelected;
|
|
1519
1914
|
})
|
|
1520
1915
|
)
|
|
1521
1916
|
) {
|
|
1917
|
+
console.log(log.context, contextFilterState);
|
|
1522
1918
|
return;
|
|
1523
1919
|
}
|
|
1524
1920
|
|
|
1525
1921
|
// Subcontext doesn't match
|
|
1526
1922
|
if (
|
|
1923
|
+
// Log context is not "uncategorized" (no point in further filters)
|
|
1924
|
+
log.context !== LogBuiltInMetadata.Context.Uncategorized
|
|
1527
1925
|
// Log has a subcontext
|
|
1528
|
-
log.subcontext
|
|
1926
|
+
&& log.subcontext
|
|
1529
1927
|
// Context has subcontexts
|
|
1530
1928
|
&& (
|
|
1531
1929
|
contextFilterState[log.context]
|
|
@@ -1542,14 +1940,16 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1542
1940
|
|
|
1543
1941
|
// No tags match
|
|
1544
1942
|
if (
|
|
1545
|
-
//
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
log.tags.every((tag) => {
|
|
1550
|
-
return !tagFilterState[tag];
|
|
1943
|
+
// At least one tag is required
|
|
1944
|
+
Object.values(tagFilterState)
|
|
1945
|
+
.filter((isSelected) => {
|
|
1946
|
+
return isSelected;
|
|
1551
1947
|
})
|
|
1552
|
-
|
|
1948
|
+
.length > 0
|
|
1949
|
+
// No tags match
|
|
1950
|
+
&& log.tags.every((tag) => {
|
|
1951
|
+
return !tagFilterState[tag];
|
|
1952
|
+
})
|
|
1553
1953
|
) {
|
|
1554
1954
|
return;
|
|
1555
1955
|
}
|
|
@@ -1731,7 +2131,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1731
2131
|
// Device info exists
|
|
1732
2132
|
&& log.device
|
|
1733
2133
|
// Mobile filter doesn't match
|
|
1734
|
-
&& (advancedFilterState.isMobile
|
|
2134
|
+
&& (advancedFilterState.isMobile !== log.device.isMobile)
|
|
1735
2135
|
) {
|
|
1736
2136
|
return;
|
|
1737
2137
|
}
|
|
@@ -1784,198 +2184,28 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1784
2184
|
/* Data */
|
|
1785
2185
|
/*----------------------------------------*/
|
|
1786
2186
|
|
|
1787
|
-
//
|
|
1788
|
-
const
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
},
|
|
1804
|
-
{
|
|
1805
|
-
title: 'Canvas Id',
|
|
1806
|
-
param: 'userId',
|
|
1807
|
-
type: ParamType.Int,
|
|
1808
|
-
},
|
|
1809
|
-
{
|
|
1810
|
-
title: 'Student',
|
|
1811
|
-
param: 'isLearner',
|
|
1812
|
-
type: ParamType.Boolean,
|
|
1813
|
-
},
|
|
1814
|
-
{
|
|
1815
|
-
title: 'Teaching Staff',
|
|
1816
|
-
param: 'isTTM',
|
|
1817
|
-
type: ParamType.Boolean,
|
|
1818
|
-
startsHidden: true,
|
|
1819
|
-
},
|
|
1820
|
-
{
|
|
1821
|
-
title: 'Admin',
|
|
1822
|
-
param: 'isAdmin',
|
|
1823
|
-
type: ParamType.Boolean,
|
|
1824
|
-
startsHidden: true,
|
|
1825
|
-
},
|
|
1826
|
-
{
|
|
1827
|
-
title: 'Course Canvas Id',
|
|
1828
|
-
param: 'courseId',
|
|
1829
|
-
type: ParamType.Int,
|
|
1830
|
-
startsHidden: true,
|
|
1831
|
-
},
|
|
1832
|
-
{
|
|
1833
|
-
title: 'Course Name',
|
|
1834
|
-
param: 'courseName',
|
|
1835
|
-
type: ParamType.String,
|
|
1836
|
-
},
|
|
1837
|
-
{
|
|
1838
|
-
title: 'Browser Name',
|
|
1839
|
-
param: 'browser.name',
|
|
1840
|
-
type: ParamType.String,
|
|
1841
|
-
startsHidden: true,
|
|
1842
|
-
},
|
|
1843
|
-
{
|
|
1844
|
-
title: 'Browser Version',
|
|
1845
|
-
param: 'browser.version',
|
|
1846
|
-
type: ParamType.String,
|
|
1847
|
-
startsHidden: true,
|
|
1848
|
-
},
|
|
1849
|
-
{
|
|
1850
|
-
title: 'OS',
|
|
1851
|
-
param: 'device.os',
|
|
1852
|
-
type: ParamType.String,
|
|
1853
|
-
startsHidden: true,
|
|
1854
|
-
},
|
|
1855
|
-
{
|
|
1856
|
-
title: 'Mobile',
|
|
1857
|
-
param: 'device.isMobile',
|
|
1858
|
-
type: ParamType.Boolean,
|
|
1859
|
-
startsHidden: true,
|
|
1860
|
-
},
|
|
1861
|
-
{
|
|
1862
|
-
title: 'Year',
|
|
1863
|
-
param: 'year',
|
|
1864
|
-
type: ParamType.Int,
|
|
1865
|
-
},
|
|
1866
|
-
{
|
|
1867
|
-
title: 'Month',
|
|
1868
|
-
param: 'month',
|
|
1869
|
-
type: ParamType.Int,
|
|
1870
|
-
},
|
|
1871
|
-
{
|
|
1872
|
-
title: 'Day',
|
|
1873
|
-
param: 'day',
|
|
1874
|
-
type: ParamType.Int,
|
|
1875
|
-
},
|
|
1876
|
-
{
|
|
1877
|
-
title: 'Hour',
|
|
1878
|
-
param: 'hour',
|
|
1879
|
-
type: ParamType.Int,
|
|
1880
|
-
},
|
|
1881
|
-
{
|
|
1882
|
-
title: 'Minute',
|
|
1883
|
-
param: 'minute',
|
|
1884
|
-
type: ParamType.Int,
|
|
1885
|
-
startsHidden: true,
|
|
1886
|
-
},
|
|
1887
|
-
{
|
|
1888
|
-
title: 'Timestamp',
|
|
1889
|
-
param: 'timestamp',
|
|
1890
|
-
type: ParamType.Int,
|
|
1891
|
-
startsHidden: true,
|
|
1892
|
-
},
|
|
1893
|
-
{
|
|
1894
|
-
title: 'Context',
|
|
1895
|
-
param: 'context',
|
|
1896
|
-
type: ParamType.String,
|
|
1897
|
-
},
|
|
1898
|
-
{
|
|
1899
|
-
title: 'Subcontext',
|
|
1900
|
-
param: 'subcontext',
|
|
1901
|
-
type: ParamType.String,
|
|
1902
|
-
},
|
|
1903
|
-
{
|
|
1904
|
-
title: 'Tags',
|
|
1905
|
-
param: 'tags',
|
|
1906
|
-
type: ParamType.JSON,
|
|
1907
|
-
startsHidden: true,
|
|
1908
|
-
},
|
|
1909
|
-
{
|
|
1910
|
-
title: 'Log Level',
|
|
1911
|
-
param: 'level',
|
|
1912
|
-
type: ParamType.String,
|
|
1913
|
-
startsHidden: true,
|
|
1914
|
-
},
|
|
1915
|
-
{
|
|
1916
|
-
title: 'Metadata',
|
|
1917
|
-
param: 'metadata',
|
|
1918
|
-
type: ParamType.JSON,
|
|
1919
|
-
startsHidden: true,
|
|
1920
|
-
},
|
|
1921
|
-
{
|
|
1922
|
-
title: 'Source',
|
|
1923
|
-
param: 'source',
|
|
1924
|
-
type: ParamType.String,
|
|
1925
|
-
},
|
|
1926
|
-
{
|
|
1927
|
-
title: 'Server Route Path',
|
|
1928
|
-
param: 'routePath',
|
|
1929
|
-
type: ParamType.String,
|
|
1930
|
-
startsHidden: true,
|
|
1931
|
-
},
|
|
1932
|
-
{
|
|
1933
|
-
title: 'Server Route Template',
|
|
1934
|
-
param: 'routeTemplate',
|
|
1935
|
-
type: ParamType.String,
|
|
1936
|
-
startsHidden: true,
|
|
1937
|
-
},
|
|
1938
|
-
{
|
|
1939
|
-
title: 'Type',
|
|
1940
|
-
param: 'type',
|
|
1941
|
-
type: ParamType.String,
|
|
1942
|
-
},
|
|
1943
|
-
{
|
|
1944
|
-
title: 'Error Message',
|
|
1945
|
-
param: 'errorMessage',
|
|
1946
|
-
type: ParamType.String,
|
|
1947
|
-
startsHidden: true,
|
|
1948
|
-
},
|
|
1949
|
-
{
|
|
1950
|
-
title: 'Error Code',
|
|
1951
|
-
param: 'errorCode',
|
|
1952
|
-
type: ParamType.String,
|
|
1953
|
-
startsHidden: true,
|
|
1954
|
-
},
|
|
1955
|
-
{
|
|
1956
|
-
title: 'Error Stack',
|
|
1957
|
-
param: 'errorStack',
|
|
1958
|
-
type: ParamType.String,
|
|
1959
|
-
startsHidden: true,
|
|
1960
|
-
},
|
|
1961
|
-
{
|
|
1962
|
-
title: 'Action Target',
|
|
1963
|
-
param: 'target',
|
|
1964
|
-
type: ParamType.String,
|
|
1965
|
-
startsHidden: true,
|
|
1966
|
-
},
|
|
1967
|
-
{
|
|
1968
|
-
title: 'Action Type',
|
|
1969
|
-
param: 'action',
|
|
1970
|
-
type: ParamType.String,
|
|
1971
|
-
startsHidden: true,
|
|
1972
|
-
},
|
|
1973
|
-
];
|
|
2187
|
+
// Nothing to show notice
|
|
2188
|
+
const noLogsNotice = (
|
|
2189
|
+
logs.length === 0
|
|
2190
|
+
? (
|
|
2191
|
+
<div className="alert alert-warning text-center mt-2">
|
|
2192
|
+
<h4 className="m-1">
|
|
2193
|
+
No Logs to Show
|
|
2194
|
+
</h4>
|
|
2195
|
+
<div>
|
|
2196
|
+
Either your filters are too strict or no matching logs have been
|
|
2197
|
+
created yet.
|
|
2198
|
+
</div>
|
|
2199
|
+
</div>
|
|
2200
|
+
)
|
|
2201
|
+
: undefined
|
|
2202
|
+
);
|
|
1974
2203
|
|
|
1975
2204
|
// Create intelliTable
|
|
1976
2205
|
const dataTable = (
|
|
1977
2206
|
<IntelliTable
|
|
1978
|
-
title="Matching Logs"
|
|
2207
|
+
title="Matching Logs:"
|
|
2208
|
+
csvName={`Logs from ${getHumanReadableDate()}`}
|
|
1979
2209
|
id="logs"
|
|
1980
2210
|
data={logs}
|
|
1981
2211
|
columns={columns}
|
|
@@ -1986,7 +2216,10 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1986
2216
|
body = (
|
|
1987
2217
|
<>
|
|
1988
2218
|
{filters}
|
|
1989
|
-
|
|
2219
|
+
<div className="mt-2">
|
|
2220
|
+
{dataTable}
|
|
2221
|
+
{noLogsNotice}
|
|
2222
|
+
</div>
|
|
1990
2223
|
</>
|
|
1991
2224
|
);
|
|
1992
2225
|
}
|
|
@@ -2001,26 +2234,22 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
2001
2234
|
<div className="LogReviewer-inner-container">
|
|
2002
2235
|
<div className="LogReviewer-header">
|
|
2003
2236
|
<div className="LogReviewer-header-title">
|
|
2004
|
-
<
|
|
2237
|
+
<h3 className="text-center m-0">
|
|
2005
2238
|
Log Review Dashboard
|
|
2006
|
-
</
|
|
2239
|
+
</h3>
|
|
2240
|
+
</div>
|
|
2241
|
+
<div style={{ width: 0 }}>
|
|
2242
|
+
<button
|
|
2243
|
+
type="button"
|
|
2244
|
+
className="LogReviewer-header-close-button btn btn-dark btn-lg pe-0"
|
|
2245
|
+
aria-label="close log reviewer panel"
|
|
2246
|
+
onClick={onClose}
|
|
2247
|
+
>
|
|
2248
|
+
<FontAwesomeIcon
|
|
2249
|
+
icon={faTimes}
|
|
2250
|
+
/>
|
|
2251
|
+
</button>
|
|
2007
2252
|
</div>
|
|
2008
|
-
<button
|
|
2009
|
-
type="button"
|
|
2010
|
-
className="LogReviewer-header-close-button btn btn-lg"
|
|
2011
|
-
aria-label="close log reviewer panel"
|
|
2012
|
-
onClick={onClose}
|
|
2013
|
-
style={{
|
|
2014
|
-
border: 0,
|
|
2015
|
-
backgroundColor: 'transparent',
|
|
2016
|
-
padding: 0,
|
|
2017
|
-
margin: 0,
|
|
2018
|
-
}}
|
|
2019
|
-
>
|
|
2020
|
-
<FontAwesomeIcon
|
|
2021
|
-
icon={faTimes}
|
|
2022
|
-
/>
|
|
2023
|
-
</button>
|
|
2024
2253
|
</div>
|
|
2025
2254
|
<div className="LogReviewer-contents">
|
|
2026
2255
|
{body}
|