dce-reactkit 3.2.2-beta.8 → 3.2.3
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 +743 -440
- package/dist/cjs/index.js.map +1 -1
- 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/dynamicConstants/DynamicWord.d.ts +13 -0
- package/dist/cjs/types/index.d.ts +2 -1
- 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 +744 -442
- package/dist/esm/index.js.map +1 -1
- 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/dynamicConstants/DynamicWord.d.ts +13 -0
- package/dist/esm/types/index.d.ts +2 -1
- 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 +64 -30
- package/package.json +1 -1
- package/src/components/ButtonInputGroup.tsx +1 -1
- package/src/components/CSVDownloadButton.tsx +2 -2
- package/src/components/Drawer.tsx +9 -1
- package/src/components/IntelliTable.tsx +178 -23
- package/src/components/ItemPicker/NestableItemList.tsx +32 -14
- package/src/components/ItemPicker/index.tsx +4 -0
- package/src/components/LogReviewer.tsx +588 -418
- package/src/components/SimpleDateChooser.tsx +30 -25
- package/src/dynamicConstants/DynamicWord.tsx +55 -0
- package/src/helpers/genCSV.ts +22 -4
- package/src/helpers/genRouteHandler.ts +2 -2
- package/src/helpers/logClientEvent.tsx +1 -1
- package/src/index.ts +5 -0
- 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)
|
|
@@ -243,14 +247,214 @@ const style = `
|
|
|
243
247
|
background-color: transparent !important;
|
|
244
248
|
padding-top: 0 !important;
|
|
245
249
|
padding-bottom: 0 !important;
|
|
250
|
+
padding-right: 1em !important;
|
|
246
251
|
margin: 0 !important;
|
|
247
|
-
color: #
|
|
252
|
+
color: #444 !important;
|
|
253
|
+
|
|
254
|
+
right: 0 !important;
|
|
255
|
+
position: absolute !important;
|
|
248
256
|
}
|
|
249
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;
|
|
250
263
|
color: #000 !important;
|
|
251
264
|
}
|
|
252
265
|
`;
|
|
253
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
|
+
|
|
254
458
|
/*------------------------------------------------------------------------*/
|
|
255
459
|
/* Static Functions */
|
|
256
460
|
/*------------------------------------------------------------------------*/
|
|
@@ -494,6 +698,36 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
494
698
|
onClose,
|
|
495
699
|
} = props;
|
|
496
700
|
|
|
701
|
+
// Create complete map of contexts
|
|
702
|
+
const contextMap: LogMetadataContextMap = {};
|
|
703
|
+
[
|
|
704
|
+
(LogMetadata.Context ?? {}),
|
|
705
|
+
LogBuiltInMetadata.Context,
|
|
706
|
+
].forEach((partialContextMap) => {
|
|
707
|
+
Object.keys(partialContextMap).forEach((context) => {
|
|
708
|
+
// Add context
|
|
709
|
+
contextMap[context] = partialContextMap[context];
|
|
710
|
+
|
|
711
|
+
// If context has children, add an "uncategorized" subcontext
|
|
712
|
+
if (typeof contextMap[context] !== 'string') {
|
|
713
|
+
(contextMap[context] as any)[LogBuiltInMetadata.Context.Uncategorized] = (
|
|
714
|
+
LogBuiltInMetadata.Context.Uncategorized
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
// Create complete map of targets
|
|
721
|
+
const targetMap: LogMetadataTargetMap = {};
|
|
722
|
+
[
|
|
723
|
+
(LogMetadata.Target ?? {}),
|
|
724
|
+
LogBuiltInMetadata.Target,
|
|
725
|
+
].forEach((partialTargetMap) => {
|
|
726
|
+
Object.keys(partialTargetMap).forEach((target) => {
|
|
727
|
+
targetMap[target] = partialTargetMap[target];
|
|
728
|
+
});
|
|
729
|
+
});
|
|
730
|
+
|
|
497
731
|
/* -------------- State ------------- */
|
|
498
732
|
|
|
499
733
|
// Create initial date filter state
|
|
@@ -515,25 +749,30 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
515
749
|
|
|
516
750
|
// Create initial context filter state
|
|
517
751
|
const initContextFilterState: ContextFilterState = {};
|
|
518
|
-
Object.keys(
|
|
519
|
-
const contextValue =
|
|
752
|
+
Object.keys(contextMap).forEach((context) => {
|
|
753
|
+
const contextValue = contextMap[context];
|
|
520
754
|
if (typeof contextValue === 'string') {
|
|
521
|
-
// Case: no subcontexts
|
|
755
|
+
// Case: no subcontexts, init as checked
|
|
522
756
|
initContextFilterState[contextValue] = true;
|
|
523
757
|
} else {
|
|
524
758
|
// Case: subcontexts exist
|
|
525
|
-
initContextFilterState[
|
|
526
|
-
Object.values(
|
|
527
|
-
|
|
528
|
-
(
|
|
759
|
+
initContextFilterState[context] = {};
|
|
760
|
+
Object.values(contextMap[context]).forEach((subcontext) => {
|
|
761
|
+
// Skip self ("_")
|
|
762
|
+
if (subcontext === '_') {
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Initialize as checked
|
|
767
|
+
(initContextFilterState[context] as any)[subcontext] = true;
|
|
529
768
|
});
|
|
530
769
|
}
|
|
531
770
|
});
|
|
532
771
|
|
|
533
772
|
// Create initial tag filter state
|
|
534
773
|
const initTagFilterState: TagFilterState = {};
|
|
535
|
-
Object.
|
|
536
|
-
initTagFilterState[
|
|
774
|
+
Object.keys(LogMetadata.Tag ?? {}).forEach((tag) => {
|
|
775
|
+
initTagFilterState[tag] = false;
|
|
537
776
|
});
|
|
538
777
|
|
|
539
778
|
// Create advanced filter state
|
|
@@ -561,7 +800,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
561
800
|
target: {},
|
|
562
801
|
action: {},
|
|
563
802
|
};
|
|
564
|
-
Object.values(
|
|
803
|
+
Object.values(targetMap).forEach((target) => {
|
|
565
804
|
initActionErrorFilterState.target[target] = true;
|
|
566
805
|
});
|
|
567
806
|
Object.values(LogAction).forEach((action) => {
|
|
@@ -617,18 +856,23 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
617
856
|
let month = newDateFilterState.startDate.month;
|
|
618
857
|
while (
|
|
619
858
|
// Earlier year
|
|
620
|
-
(year
|
|
859
|
+
(year < newDateFilterState.endDate.year)
|
|
621
860
|
// Current year but included month
|
|
622
861
|
|| (
|
|
623
862
|
year === newDateFilterState.endDate.year
|
|
624
863
|
&& month <= newDateFilterState.endDate.month
|
|
625
864
|
)
|
|
626
865
|
) {
|
|
627
|
-
// Add to list
|
|
628
|
-
|
|
629
|
-
year
|
|
630
|
-
month
|
|
631
|
-
|
|
866
|
+
// Add to list if not already loaded
|
|
867
|
+
if (
|
|
868
|
+
!logMap[year]
|
|
869
|
+
|| !logMap[year][month]
|
|
870
|
+
) {
|
|
871
|
+
toLoad.push({
|
|
872
|
+
year,
|
|
873
|
+
month,
|
|
874
|
+
});
|
|
875
|
+
}
|
|
632
876
|
|
|
633
877
|
// Increment
|
|
634
878
|
month += 1;
|
|
@@ -744,16 +988,16 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
744
988
|
|
|
745
989
|
// Filter toggle
|
|
746
990
|
const filterToggles = (
|
|
747
|
-
<div className="LogReviewer-filter-toggles
|
|
991
|
+
<div className="LogReviewer-filter-toggles">
|
|
748
992
|
<h3 className="m-0">
|
|
749
993
|
Filters:
|
|
750
994
|
</h3>
|
|
751
|
-
<div className="LogReviewer-filter-toggle-buttons">
|
|
995
|
+
<div className="LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0">
|
|
752
996
|
{/* Date */}
|
|
753
997
|
<button
|
|
754
998
|
type="button"
|
|
755
999
|
id="LogReviewer-toggle-date-filter-drawer"
|
|
756
|
-
className={`btn btn-${FilterDrawer.Date === expandedFilterDrawer ? 'warning' : '
|
|
1000
|
+
className={`btn btn-${FilterDrawer.Date === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
757
1001
|
aria-label="toggle date filter drawer"
|
|
758
1002
|
onClick={() => {
|
|
759
1003
|
dispatch({
|
|
@@ -772,7 +1016,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
772
1016
|
<button
|
|
773
1017
|
type="button"
|
|
774
1018
|
id="LogReviewer-toggle-context-filter-drawer"
|
|
775
|
-
className={`btn btn-${FilterDrawer.Context === expandedFilterDrawer ? 'warning' : '
|
|
1019
|
+
className={`btn btn-${FilterDrawer.Context === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
776
1020
|
aria-label="toggle context filter drawer"
|
|
777
1021
|
onClick={() => {
|
|
778
1022
|
dispatch({
|
|
@@ -793,7 +1037,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
793
1037
|
<button
|
|
794
1038
|
type="button"
|
|
795
1039
|
id="LogReviewer-toggle-tag-filter-drawer"
|
|
796
|
-
className={`btn btn-${FilterDrawer.Tag === expandedFilterDrawer ? 'warning' : '
|
|
1040
|
+
className={`btn btn-${FilterDrawer.Tag === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
797
1041
|
aria-label="toggle tag filter drawer"
|
|
798
1042
|
onClick={() => {
|
|
799
1043
|
dispatch({
|
|
@@ -813,7 +1057,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
813
1057
|
<button
|
|
814
1058
|
type="button"
|
|
815
1059
|
id="LogReviewer-toggle-action-filter-drawer"
|
|
816
|
-
className={`btn btn-${FilterDrawer.Action === expandedFilterDrawer ? 'warning' : '
|
|
1060
|
+
className={`btn btn-${FilterDrawer.Action === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
817
1061
|
aria-label="toggle action and error filter drawer"
|
|
818
1062
|
onClick={() => {
|
|
819
1063
|
dispatch({
|
|
@@ -832,7 +1076,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
832
1076
|
<button
|
|
833
1077
|
type="button"
|
|
834
1078
|
id="LogReviewer-toggle-advanced-filter-drawer"
|
|
835
|
-
className={`btn btn-${FilterDrawer.Advanced === expandedFilterDrawer ? 'warning' : '
|
|
1079
|
+
className={`btn btn-${FilterDrawer.Advanced === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
836
1080
|
aria-label="toggle advanced filter drawer"
|
|
837
1081
|
onClick={() => {
|
|
838
1082
|
dispatch({
|
|
@@ -847,6 +1091,29 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
847
1091
|
/>
|
|
848
1092
|
Advanced
|
|
849
1093
|
</button>
|
|
1094
|
+
{/* Reset */}
|
|
1095
|
+
<button
|
|
1096
|
+
type="button"
|
|
1097
|
+
id="LogReviewer-reset-filters-button"
|
|
1098
|
+
className="btn btn-light"
|
|
1099
|
+
aria-label="reset filters"
|
|
1100
|
+
onClick={() => {
|
|
1101
|
+
dispatch({
|
|
1102
|
+
type: ActionType.ResetFilters,
|
|
1103
|
+
initActionErrorFilterState,
|
|
1104
|
+
initAdvancedFilterState,
|
|
1105
|
+
initContextFilterState,
|
|
1106
|
+
initDateFilterState,
|
|
1107
|
+
initTagFilterState,
|
|
1108
|
+
});
|
|
1109
|
+
}}
|
|
1110
|
+
>
|
|
1111
|
+
<FontAwesomeIcon
|
|
1112
|
+
icon={faTimes}
|
|
1113
|
+
/>
|
|
1114
|
+
{' '}
|
|
1115
|
+
Reset
|
|
1116
|
+
</button>
|
|
850
1117
|
</div>
|
|
851
1118
|
</div>
|
|
852
1119
|
);
|
|
@@ -863,14 +1130,11 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
863
1130
|
year={dateFilterState.startDate.year}
|
|
864
1131
|
month={dateFilterState.startDate.month}
|
|
865
1132
|
day={dateFilterState.startDate.day}
|
|
1133
|
+
chooseFromPast
|
|
1134
|
+
numMonthsToShow={36}
|
|
866
1135
|
onChange={(month, day, year) => {
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
dateFilterState: {
|
|
870
|
-
...dateFilterState,
|
|
871
|
-
startDate: { month, day, year },
|
|
872
|
-
},
|
|
873
|
-
});
|
|
1136
|
+
dateFilterState.startDate = { month, day, year };
|
|
1137
|
+
handleDateRangeUpdated(dateFilterState);
|
|
874
1138
|
}}
|
|
875
1139
|
/>
|
|
876
1140
|
{' '}
|
|
@@ -882,59 +1146,92 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
882
1146
|
year={dateFilterState.endDate.year}
|
|
883
1147
|
month={dateFilterState.endDate.month}
|
|
884
1148
|
day={dateFilterState.endDate.day}
|
|
1149
|
+
chooseFromPast
|
|
1150
|
+
numMonthsToShow={12}
|
|
885
1151
|
onChange={(month, day, year) => {
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
1152
|
+
if (
|
|
1153
|
+
year < dateFilterState.startDate.year
|
|
1154
|
+
|| (
|
|
1155
|
+
year === dateFilterState.startDate.year
|
|
1156
|
+
&& month < dateFilterState.startDate.month
|
|
1157
|
+
)
|
|
1158
|
+
|| (
|
|
1159
|
+
year === dateFilterState.startDate.year
|
|
1160
|
+
&& month === dateFilterState.startDate.month
|
|
1161
|
+
&& day < dateFilterState.startDate.day
|
|
1162
|
+
)
|
|
1163
|
+
) {
|
|
1164
|
+
return alert(
|
|
1165
|
+
'Invalid Start Date',
|
|
1166
|
+
'The start date cannot be before the end date.',
|
|
1167
|
+
);
|
|
1168
|
+
}
|
|
1169
|
+
dateFilterState.endDate = { month, day, year };
|
|
1170
|
+
handleDateRangeUpdated(dateFilterState);
|
|
893
1171
|
}}
|
|
894
1172
|
/>
|
|
895
1173
|
</TabBox>
|
|
896
1174
|
);
|
|
897
1175
|
} else if (expandedFilterDrawer === FilterDrawer.Context) {
|
|
898
1176
|
// Create item picker items
|
|
899
|
-
const
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
return item;
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
// Has subcategories
|
|
915
|
-
const children: PickableItem[] = (
|
|
916
|
-
Object.keys(value)
|
|
917
|
-
.filter((subcontext) => {
|
|
918
|
-
return subcontext !== '_'
|
|
919
|
-
})
|
|
920
|
-
.map((subcontext) => {
|
|
921
|
-
return {
|
|
922
|
-
id: `${context}-${subcontext}`,
|
|
923
|
-
name: genHumanReadableName(subcontext),
|
|
924
|
-
isGroup: false,
|
|
925
|
-
checked: !!value[subcontext],
|
|
926
|
-
};
|
|
927
|
-
})
|
|
928
|
-
);
|
|
1177
|
+
const builtInPickableItem: PickableItem = {
|
|
1178
|
+
id: 'built-in-contexts',
|
|
1179
|
+
name: 'Auto-logged',
|
|
1180
|
+
isGroup: true,
|
|
1181
|
+
children: [],
|
|
1182
|
+
};
|
|
1183
|
+
const pickableItems: PickableItem[] = [];
|
|
1184
|
+
Object.keys(contextMap)
|
|
1185
|
+
.forEach((context) => {
|
|
1186
|
+
const value = contextMap[context];
|
|
1187
|
+
if (typeof value === 'string') {
|
|
1188
|
+
// No subcategories
|
|
929
1189
|
const item: PickableItem = {
|
|
930
1190
|
id: context,
|
|
931
|
-
name: context,
|
|
932
|
-
isGroup:
|
|
933
|
-
|
|
1191
|
+
name: genHumanReadableName(context),
|
|
1192
|
+
isGroup: false,
|
|
1193
|
+
checked: !!contextFilterState[context],
|
|
934
1194
|
};
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
1195
|
+
|
|
1196
|
+
// Add built-in items to its own folder
|
|
1197
|
+
const isBuiltIn = context in LogBuiltInMetadata.Context;
|
|
1198
|
+
if (isBuiltIn) {
|
|
1199
|
+
// Add to built-in pickable item
|
|
1200
|
+
builtInPickableItem.children.push(item);
|
|
1201
|
+
} else {
|
|
1202
|
+
// Add to pickable items list
|
|
1203
|
+
pickableItems.push(item);
|
|
1204
|
+
}
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// Has subcategories
|
|
1209
|
+
const children: PickableItem[] = (
|
|
1210
|
+
Object.keys(value)
|
|
1211
|
+
// Remove parent name
|
|
1212
|
+
.filter((subcontext) => {
|
|
1213
|
+
return subcontext !== '_';
|
|
1214
|
+
})
|
|
1215
|
+
// Create child pickable items
|
|
1216
|
+
.map((subcontext) => {
|
|
1217
|
+
return {
|
|
1218
|
+
id: subcontext,
|
|
1219
|
+
name: genHumanReadableName(subcontext),
|
|
1220
|
+
isGroup: false,
|
|
1221
|
+
checked: (contextFilterState[context] as any)[subcontext],
|
|
1222
|
+
};
|
|
1223
|
+
})
|
|
1224
|
+
);
|
|
1225
|
+
const item: PickableItem = {
|
|
1226
|
+
id: context,
|
|
1227
|
+
name: genHumanReadableName(context),
|
|
1228
|
+
isGroup: true,
|
|
1229
|
+
children,
|
|
1230
|
+
};
|
|
1231
|
+
pickableItems.push(item);
|
|
1232
|
+
});
|
|
1233
|
+
// Add built-in contexts to end ofl ist
|
|
1234
|
+
pickableItems.push(builtInPickableItem);
|
|
938
1235
|
|
|
939
1236
|
// Create filter UI
|
|
940
1237
|
filterDrawer = (
|
|
@@ -946,11 +1243,29 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
946
1243
|
updatedItems.forEach((pickableItem) => {
|
|
947
1244
|
if (pickableItem.isGroup) {
|
|
948
1245
|
// Has subcontexts
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1246
|
+
|
|
1247
|
+
if (pickableItem.id === 'built-in-contexts') {
|
|
1248
|
+
// Built-in
|
|
1249
|
+
|
|
1250
|
+
// Treat as if these were top-level contexts
|
|
1251
|
+
pickableItem.children.forEach((subcontextItem) => {
|
|
1252
|
+
contextFilterState[subcontextItem.id] = (
|
|
1253
|
+
'checked' in subcontextItem
|
|
1254
|
+
&& subcontextItem.checked
|
|
1255
|
+
);
|
|
1256
|
+
});
|
|
1257
|
+
} else {
|
|
1258
|
+
// Not built-in
|
|
1259
|
+
pickableItem.children.forEach((subcontextItem) => {
|
|
1260
|
+
if (!subcontextItem.isGroup) {
|
|
1261
|
+
(
|
|
1262
|
+
contextFilterState[pickableItem.id] as { [k: string]: boolean }
|
|
1263
|
+
)[subcontextItem.id] = (
|
|
1264
|
+
subcontextItem.checked
|
|
1265
|
+
);
|
|
1266
|
+
}
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
954
1269
|
} else {
|
|
955
1270
|
// No subcontexts
|
|
956
1271
|
(contextFilterState as any)[pickableItem.id] = (
|
|
@@ -969,23 +1284,35 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
969
1284
|
// Create filter UI
|
|
970
1285
|
filterDrawer = (
|
|
971
1286
|
<TabBox title="Tags">
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1287
|
+
<div>
|
|
1288
|
+
If any tags are selected, logs must contain at least one
|
|
1289
|
+
(but not necessarily all) of the
|
|
1290
|
+
selected tags.
|
|
1291
|
+
</div>
|
|
1292
|
+
<div className="d-flex gap-1 flex-wrap">
|
|
1293
|
+
{
|
|
1294
|
+
Object.keys(LogMetadata.Tag ?? {})
|
|
1295
|
+
.map((tag) => {
|
|
1296
|
+
const description = genHumanReadableName(tag);
|
|
1297
|
+
return (
|
|
1298
|
+
<CheckboxButton
|
|
1299
|
+
key={tag}
|
|
1300
|
+
id={`LogReviewer-tag-${tag}-checkbox`}
|
|
1301
|
+
text={description}
|
|
1302
|
+
ariaLabel={`require that logs be tagged with "${description}" or any other selected tag`}
|
|
1303
|
+
checked={tagFilterState[tag]}
|
|
1304
|
+
onChanged={(checked) => {
|
|
1305
|
+
tagFilterState[tag] = checked;
|
|
1306
|
+
dispatch({
|
|
1307
|
+
type: ActionType.UpdateTagFilterState,
|
|
1308
|
+
tagFilterState,
|
|
1309
|
+
});
|
|
1310
|
+
}}
|
|
1311
|
+
/>
|
|
1312
|
+
);
|
|
1313
|
+
})
|
|
1314
|
+
}
|
|
1315
|
+
</div>
|
|
989
1316
|
</TabBox>
|
|
990
1317
|
);
|
|
991
1318
|
} else if (expandedFilterDrawer === FilterDrawer.Action) {
|
|
@@ -1047,58 +1374,60 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1047
1374
|
label="Action"
|
|
1048
1375
|
className="mb-2"
|
|
1049
1376
|
>
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
.
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
actionErrorFilterState
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1377
|
+
<div className="d-flex gap-1 flex-wrap">
|
|
1378
|
+
{
|
|
1379
|
+
Object.keys(LogAction)
|
|
1380
|
+
.map((action) => {
|
|
1381
|
+
const description = genHumanReadableName(action);
|
|
1382
|
+
return (
|
|
1383
|
+
<CheckboxButton
|
|
1384
|
+
key={action}
|
|
1385
|
+
id={`LogReviewer-action-${action}-checkbox`}
|
|
1386
|
+
text={description}
|
|
1387
|
+
ariaLabel={`include logs with action type "${description}" in results`}
|
|
1388
|
+
noMarginOnRight
|
|
1389
|
+
checked={actionErrorFilterState.action[action]}
|
|
1390
|
+
onChanged={(checked) => {
|
|
1391
|
+
actionErrorFilterState.action[action] = checked;
|
|
1392
|
+
dispatch({
|
|
1393
|
+
type: ActionType.UpdateActionErrorFilterState,
|
|
1394
|
+
actionErrorFilterState,
|
|
1395
|
+
});
|
|
1396
|
+
}}
|
|
1397
|
+
/>
|
|
1398
|
+
);
|
|
1399
|
+
})
|
|
1400
|
+
}
|
|
1401
|
+
</div>
|
|
1071
1402
|
</ButtonInputGroup>
|
|
1072
1403
|
{/* Target */}
|
|
1073
1404
|
<ButtonInputGroup label="Target">
|
|
1074
|
-
{/* Nothing here */}
|
|
1075
|
-
{(Object.keys(LogMetadata.Target ?? {})) && (
|
|
1076
|
-
<div>
|
|
1077
|
-
This app does not have any targets yet.
|
|
1078
|
-
</div>
|
|
1079
|
-
)}
|
|
1080
1405
|
{/* List of targets */}
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
.
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1406
|
+
<div className="d-flex gap-1 flex-wrap">
|
|
1407
|
+
{
|
|
1408
|
+
Object.keys(targetMap)
|
|
1409
|
+
.map((target) => {
|
|
1410
|
+
const description = genHumanReadableName(target);
|
|
1411
|
+
return (
|
|
1412
|
+
<CheckboxButton
|
|
1413
|
+
key={target}
|
|
1414
|
+
id={`LogReviewer-target-${target}-checkbox`}
|
|
1415
|
+
text={description}
|
|
1416
|
+
ariaLabel={`include logs with target "${description}" in results`}
|
|
1417
|
+
checked={actionErrorFilterState.target[target]}
|
|
1418
|
+
noMarginOnRight
|
|
1419
|
+
onChanged={(checked) => {
|
|
1420
|
+
actionErrorFilterState.target[target] = checked;
|
|
1421
|
+
dispatch({
|
|
1422
|
+
type: ActionType.UpdateActionErrorFilterState,
|
|
1423
|
+
actionErrorFilterState,
|
|
1424
|
+
});
|
|
1425
|
+
}}
|
|
1426
|
+
/>
|
|
1427
|
+
);
|
|
1428
|
+
})
|
|
1429
|
+
}
|
|
1430
|
+
</div>
|
|
1102
1431
|
</ButtonInputGroup>
|
|
1103
1432
|
</TabBox>
|
|
1104
1433
|
)
|
|
@@ -1120,6 +1449,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1120
1449
|
className="form-control"
|
|
1121
1450
|
aria-label="query for error message"
|
|
1122
1451
|
value={actionErrorFilterState.errorMessage}
|
|
1452
|
+
placeholder="e.g. undefined is not a function"
|
|
1123
1453
|
onChange={(e) => {
|
|
1124
1454
|
actionErrorFilterState.errorMessage = e.target.value;
|
|
1125
1455
|
dispatch({
|
|
@@ -1138,7 +1468,8 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1138
1468
|
type="text"
|
|
1139
1469
|
className="form-control"
|
|
1140
1470
|
aria-label="query for error code"
|
|
1141
|
-
value={actionErrorFilterState.
|
|
1471
|
+
value={actionErrorFilterState.errorCode}
|
|
1472
|
+
placeholder="e.g. GC22"
|
|
1142
1473
|
onChange={(e) => {
|
|
1143
1474
|
actionErrorFilterState.errorCode = (
|
|
1144
1475
|
(e.target.value)
|
|
@@ -1162,7 +1493,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1162
1493
|
filterDrawer = (
|
|
1163
1494
|
<>
|
|
1164
1495
|
{/* User Info */}
|
|
1165
|
-
<TabBox title="User
|
|
1496
|
+
<TabBox title="User">
|
|
1166
1497
|
{/* First Name */}
|
|
1167
1498
|
<div className="input-group mb-2">
|
|
1168
1499
|
<span className="input-group-text">
|
|
@@ -1173,6 +1504,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1173
1504
|
className="form-control"
|
|
1174
1505
|
aria-label="query for user first name"
|
|
1175
1506
|
value={advancedFilterState.userFirstName}
|
|
1507
|
+
placeholder="e.g. Divardo"
|
|
1176
1508
|
onChange={(e) => {
|
|
1177
1509
|
advancedFilterState.userFirstName = e.target.value;
|
|
1178
1510
|
dispatch({
|
|
@@ -1192,6 +1524,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1192
1524
|
className="form-control"
|
|
1193
1525
|
aria-label="query for user last name"
|
|
1194
1526
|
value={advancedFilterState.userLastName}
|
|
1527
|
+
placeholder="e.g. Calicci"
|
|
1195
1528
|
onChange={(e) => {
|
|
1196
1529
|
advancedFilterState.userLastName = e.target.value;
|
|
1197
1530
|
dispatch({
|
|
@@ -1211,6 +1544,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1211
1544
|
className="form-control"
|
|
1212
1545
|
aria-label="query for user email"
|
|
1213
1546
|
value={advancedFilterState.userEmail}
|
|
1547
|
+
placeholder="e.g. calicci@fas.harvard.edu"
|
|
1214
1548
|
onChange={(e) => {
|
|
1215
1549
|
advancedFilterState.userEmail = (
|
|
1216
1550
|
(e.target.value)
|
|
@@ -1233,6 +1567,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1233
1567
|
className="form-control"
|
|
1234
1568
|
aria-label="query for user canvas id"
|
|
1235
1569
|
value={advancedFilterState.userId}
|
|
1570
|
+
placeholder="e.g. 104985"
|
|
1236
1571
|
onChange={(e) => {
|
|
1237
1572
|
const { value } = e.target;
|
|
1238
1573
|
// Only update if value contains only numbers
|
|
@@ -1291,7 +1626,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1291
1626
|
</TabBox>
|
|
1292
1627
|
|
|
1293
1628
|
{/* Course Info */}
|
|
1294
|
-
<TabBox title="Course
|
|
1629
|
+
<TabBox title="Course">
|
|
1295
1630
|
{/* Name */}
|
|
1296
1631
|
<div className="input-group mb-2">
|
|
1297
1632
|
<span className="input-group-text">
|
|
@@ -1302,6 +1637,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1302
1637
|
className="form-control"
|
|
1303
1638
|
aria-label="query for course name"
|
|
1304
1639
|
value={advancedFilterState.courseName}
|
|
1640
|
+
placeholder="e.g. GLC 200"
|
|
1305
1641
|
onChange={(e) => {
|
|
1306
1642
|
advancedFilterState.courseName = e.target.value;
|
|
1307
1643
|
dispatch({
|
|
@@ -1321,6 +1657,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1321
1657
|
className="form-control"
|
|
1322
1658
|
aria-label="query for course canvas id"
|
|
1323
1659
|
value={advancedFilterState.courseId}
|
|
1660
|
+
placeholder="e.g. 15948"
|
|
1324
1661
|
onChange={(e) => {
|
|
1325
1662
|
const { value } = e.target;
|
|
1326
1663
|
// Only update if value contains only numbers
|
|
@@ -1340,7 +1677,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1340
1677
|
</TabBox>
|
|
1341
1678
|
|
|
1342
1679
|
{/* Device Info */}
|
|
1343
|
-
<TabBox title="Device
|
|
1680
|
+
<TabBox title="Device">
|
|
1344
1681
|
<ButtonInputGroup label="Device Type">
|
|
1345
1682
|
<RadioButton
|
|
1346
1683
|
text="All Devices"
|
|
@@ -1424,53 +1761,58 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1424
1761
|
/>
|
|
1425
1762
|
</ButtonInputGroup>
|
|
1426
1763
|
|
|
1427
|
-
{/*
|
|
1428
|
-
|
|
1429
|
-
<
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1764
|
+
{/* Server filters */}
|
|
1765
|
+
{advancedFilterState.source !== LogSource.Client && (
|
|
1766
|
+
<div className="mt-2">
|
|
1767
|
+
{/* Route path */}
|
|
1768
|
+
<div className="input-group mb-2">
|
|
1769
|
+
<span className="input-group-text">
|
|
1770
|
+
Server Route Path
|
|
1771
|
+
</span>
|
|
1772
|
+
<input
|
|
1773
|
+
type="text"
|
|
1774
|
+
className="form-control"
|
|
1775
|
+
aria-label="query for server route path"
|
|
1776
|
+
value={advancedFilterState.routePath}
|
|
1777
|
+
placeholder="e.g. /api/ttm/courses/12345"
|
|
1778
|
+
onChange={(e) => {
|
|
1779
|
+
advancedFilterState.courseName = (
|
|
1780
|
+
(e.target.value)
|
|
1781
|
+
.trim()
|
|
1782
|
+
);
|
|
1783
|
+
dispatch({
|
|
1784
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1785
|
+
advancedFilterState,
|
|
1786
|
+
});
|
|
1787
|
+
}}
|
|
1788
|
+
/>
|
|
1789
|
+
</div>
|
|
1450
1790
|
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1791
|
+
{/* Route template */}
|
|
1792
|
+
<div className="input-group mb-2">
|
|
1793
|
+
<span className="input-group-text">
|
|
1794
|
+
Server Route Template
|
|
1795
|
+
</span>
|
|
1796
|
+
<input
|
|
1797
|
+
type="text"
|
|
1798
|
+
className="form-control"
|
|
1799
|
+
aria-label="query for server route template"
|
|
1800
|
+
value={advancedFilterState.routeTemplate}
|
|
1801
|
+
placeholder="e.g. /api/ttm/courses/:courseId"
|
|
1802
|
+
onChange={(e) => {
|
|
1803
|
+
advancedFilterState.courseName = (
|
|
1804
|
+
(e.target.value)
|
|
1805
|
+
.trim()
|
|
1806
|
+
);
|
|
1807
|
+
dispatch({
|
|
1808
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1809
|
+
advancedFilterState,
|
|
1810
|
+
});
|
|
1811
|
+
}}
|
|
1812
|
+
/>
|
|
1813
|
+
</div>
|
|
1814
|
+
</div>
|
|
1815
|
+
)}
|
|
1474
1816
|
</TabBox>
|
|
1475
1817
|
</>
|
|
1476
1818
|
);
|
|
@@ -1482,7 +1824,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1482
1824
|
<>
|
|
1483
1825
|
{filterToggles}
|
|
1484
1826
|
{filterDrawer && (
|
|
1485
|
-
<Drawer>
|
|
1827
|
+
<Drawer customBackgroundColor="#eee">
|
|
1486
1828
|
{filterDrawer}
|
|
1487
1829
|
</Drawer>
|
|
1488
1830
|
)}
|
|
@@ -1543,7 +1885,10 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1543
1885
|
contextFilterState[log.context] === false
|
|
1544
1886
|
// None of the subcontexts are selected
|
|
1545
1887
|
|| (
|
|
1546
|
-
|
|
1888
|
+
// Has subcontexts
|
|
1889
|
+
typeof contextFilterState[log.context] !== 'boolean'
|
|
1890
|
+
// None of the subcontexts are selected
|
|
1891
|
+
&& Object.values(contextFilterState[log.context] ?? {})
|
|
1547
1892
|
.every((isSelected) => {
|
|
1548
1893
|
return !isSelected;
|
|
1549
1894
|
})
|
|
@@ -1554,8 +1899,10 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1554
1899
|
|
|
1555
1900
|
// Subcontext doesn't match
|
|
1556
1901
|
if (
|
|
1902
|
+
// Log context is not "uncategorized" (no point in further filters)
|
|
1903
|
+
log.context !== LogBuiltInMetadata.Context.Uncategorized
|
|
1557
1904
|
// Log has a subcontext
|
|
1558
|
-
log.subcontext
|
|
1905
|
+
&& log.subcontext
|
|
1559
1906
|
// Context has subcontexts
|
|
1560
1907
|
&& (
|
|
1561
1908
|
contextFilterState[log.context]
|
|
@@ -1572,14 +1919,16 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1572
1919
|
|
|
1573
1920
|
// No tags match
|
|
1574
1921
|
if (
|
|
1575
|
-
//
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
log.tags.every((tag) => {
|
|
1580
|
-
return !tagFilterState[tag];
|
|
1922
|
+
// At least one tag is required
|
|
1923
|
+
Object.values(tagFilterState)
|
|
1924
|
+
.filter((isSelected) => {
|
|
1925
|
+
return isSelected;
|
|
1581
1926
|
})
|
|
1582
|
-
|
|
1927
|
+
.length > 0
|
|
1928
|
+
// No tags match
|
|
1929
|
+
&& log.tags.every((tag) => {
|
|
1930
|
+
return !tagFilterState[tag];
|
|
1931
|
+
})
|
|
1583
1932
|
) {
|
|
1584
1933
|
return;
|
|
1585
1934
|
}
|
|
@@ -1761,7 +2110,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1761
2110
|
// Device info exists
|
|
1762
2111
|
&& log.device
|
|
1763
2112
|
// Mobile filter doesn't match
|
|
1764
|
-
&& (advancedFilterState.isMobile
|
|
2113
|
+
&& (advancedFilterState.isMobile !== log.device.isMobile)
|
|
1765
2114
|
) {
|
|
1766
2115
|
return;
|
|
1767
2116
|
}
|
|
@@ -1814,199 +2163,11 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1814
2163
|
/* Data */
|
|
1815
2164
|
/*----------------------------------------*/
|
|
1816
2165
|
|
|
1817
|
-
//
|
|
1818
|
-
const
|
|
1819
|
-
{
|
|
1820
|
-
title: 'First Name',
|
|
1821
|
-
param: 'userFirstName',
|
|
1822
|
-
type: ParamType.String,
|
|
1823
|
-
},
|
|
1824
|
-
{
|
|
1825
|
-
title: 'Last Name',
|
|
1826
|
-
param: 'userLastName',
|
|
1827
|
-
type: ParamType.String,
|
|
1828
|
-
},
|
|
1829
|
-
{
|
|
1830
|
-
title: 'Email',
|
|
1831
|
-
param: 'userEmail',
|
|
1832
|
-
type: ParamType.String,
|
|
1833
|
-
},
|
|
1834
|
-
{
|
|
1835
|
-
title: 'Canvas Id',
|
|
1836
|
-
param: 'userId',
|
|
1837
|
-
type: ParamType.Int,
|
|
1838
|
-
},
|
|
1839
|
-
{
|
|
1840
|
-
title: 'Student',
|
|
1841
|
-
param: 'isLearner',
|
|
1842
|
-
type: ParamType.Boolean,
|
|
1843
|
-
},
|
|
1844
|
-
{
|
|
1845
|
-
title: 'Teaching Staff',
|
|
1846
|
-
param: 'isTTM',
|
|
1847
|
-
type: ParamType.Boolean,
|
|
1848
|
-
startsHidden: true,
|
|
1849
|
-
},
|
|
1850
|
-
{
|
|
1851
|
-
title: 'Admin',
|
|
1852
|
-
param: 'isAdmin',
|
|
1853
|
-
type: ParamType.Boolean,
|
|
1854
|
-
startsHidden: true,
|
|
1855
|
-
},
|
|
1856
|
-
{
|
|
1857
|
-
title: 'Course Canvas Id',
|
|
1858
|
-
param: 'courseId',
|
|
1859
|
-
type: ParamType.Int,
|
|
1860
|
-
startsHidden: true,
|
|
1861
|
-
},
|
|
1862
|
-
{
|
|
1863
|
-
title: 'Course Name',
|
|
1864
|
-
param: 'courseName',
|
|
1865
|
-
type: ParamType.String,
|
|
1866
|
-
},
|
|
1867
|
-
{
|
|
1868
|
-
title: 'Browser Name',
|
|
1869
|
-
param: 'browser.name',
|
|
1870
|
-
type: ParamType.String,
|
|
1871
|
-
startsHidden: true,
|
|
1872
|
-
},
|
|
1873
|
-
{
|
|
1874
|
-
title: 'Browser Version',
|
|
1875
|
-
param: 'browser.version',
|
|
1876
|
-
type: ParamType.String,
|
|
1877
|
-
startsHidden: true,
|
|
1878
|
-
},
|
|
1879
|
-
{
|
|
1880
|
-
title: 'OS',
|
|
1881
|
-
param: 'device.os',
|
|
1882
|
-
type: ParamType.String,
|
|
1883
|
-
startsHidden: true,
|
|
1884
|
-
},
|
|
1885
|
-
{
|
|
1886
|
-
title: 'Mobile',
|
|
1887
|
-
param: 'device.isMobile',
|
|
1888
|
-
type: ParamType.Boolean,
|
|
1889
|
-
startsHidden: true,
|
|
1890
|
-
},
|
|
1891
|
-
{
|
|
1892
|
-
title: 'Year',
|
|
1893
|
-
param: 'year',
|
|
1894
|
-
type: ParamType.Int,
|
|
1895
|
-
},
|
|
1896
|
-
{
|
|
1897
|
-
title: 'Month',
|
|
1898
|
-
param: 'month',
|
|
1899
|
-
type: ParamType.Int,
|
|
1900
|
-
},
|
|
1901
|
-
{
|
|
1902
|
-
title: 'Day',
|
|
1903
|
-
param: 'day',
|
|
1904
|
-
type: ParamType.Int,
|
|
1905
|
-
},
|
|
1906
|
-
{
|
|
1907
|
-
title: 'Hour',
|
|
1908
|
-
param: 'hour',
|
|
1909
|
-
type: ParamType.Int,
|
|
1910
|
-
},
|
|
1911
|
-
{
|
|
1912
|
-
title: 'Minute',
|
|
1913
|
-
param: 'minute',
|
|
1914
|
-
type: ParamType.Int,
|
|
1915
|
-
startsHidden: true,
|
|
1916
|
-
},
|
|
1917
|
-
{
|
|
1918
|
-
title: 'Timestamp',
|
|
1919
|
-
param: 'timestamp',
|
|
1920
|
-
type: ParamType.Int,
|
|
1921
|
-
startsHidden: true,
|
|
1922
|
-
},
|
|
1923
|
-
{
|
|
1924
|
-
title: 'Context',
|
|
1925
|
-
param: 'context',
|
|
1926
|
-
type: ParamType.String,
|
|
1927
|
-
},
|
|
1928
|
-
{
|
|
1929
|
-
title: 'Subcontext',
|
|
1930
|
-
param: 'subcontext',
|
|
1931
|
-
type: ParamType.String,
|
|
1932
|
-
},
|
|
1933
|
-
{
|
|
1934
|
-
title: 'Tags',
|
|
1935
|
-
param: 'tags',
|
|
1936
|
-
type: ParamType.JSON,
|
|
1937
|
-
startsHidden: true,
|
|
1938
|
-
},
|
|
1939
|
-
{
|
|
1940
|
-
title: 'Log Level',
|
|
1941
|
-
param: 'level',
|
|
1942
|
-
type: ParamType.String,
|
|
1943
|
-
startsHidden: true,
|
|
1944
|
-
},
|
|
1945
|
-
{
|
|
1946
|
-
title: 'Metadata',
|
|
1947
|
-
param: 'metadata',
|
|
1948
|
-
type: ParamType.JSON,
|
|
1949
|
-
startsHidden: true,
|
|
1950
|
-
},
|
|
1951
|
-
{
|
|
1952
|
-
title: 'Source',
|
|
1953
|
-
param: 'source',
|
|
1954
|
-
type: ParamType.String,
|
|
1955
|
-
},
|
|
1956
|
-
{
|
|
1957
|
-
title: 'Server Route Path',
|
|
1958
|
-
param: 'routePath',
|
|
1959
|
-
type: ParamType.String,
|
|
1960
|
-
startsHidden: true,
|
|
1961
|
-
},
|
|
1962
|
-
{
|
|
1963
|
-
title: 'Server Route Template',
|
|
1964
|
-
param: 'routeTemplate',
|
|
1965
|
-
type: ParamType.String,
|
|
1966
|
-
startsHidden: true,
|
|
1967
|
-
},
|
|
1968
|
-
{
|
|
1969
|
-
title: 'Type',
|
|
1970
|
-
param: 'type',
|
|
1971
|
-
type: ParamType.String,
|
|
1972
|
-
},
|
|
1973
|
-
{
|
|
1974
|
-
title: 'Error Message',
|
|
1975
|
-
param: 'errorMessage',
|
|
1976
|
-
type: ParamType.String,
|
|
1977
|
-
startsHidden: true,
|
|
1978
|
-
},
|
|
1979
|
-
{
|
|
1980
|
-
title: 'Error Code',
|
|
1981
|
-
param: 'errorCode',
|
|
1982
|
-
type: ParamType.String,
|
|
1983
|
-
startsHidden: true,
|
|
1984
|
-
},
|
|
1985
|
-
{
|
|
1986
|
-
title: 'Error Stack',
|
|
1987
|
-
param: 'errorStack',
|
|
1988
|
-
type: ParamType.String,
|
|
1989
|
-
startsHidden: true,
|
|
1990
|
-
},
|
|
1991
|
-
{
|
|
1992
|
-
title: 'Action Target',
|
|
1993
|
-
param: 'target',
|
|
1994
|
-
type: ParamType.String,
|
|
1995
|
-
startsHidden: true,
|
|
1996
|
-
},
|
|
1997
|
-
{
|
|
1998
|
-
title: 'Action Type',
|
|
1999
|
-
param: 'action',
|
|
2000
|
-
type: ParamType.String,
|
|
2001
|
-
startsHidden: true,
|
|
2002
|
-
},
|
|
2003
|
-
];
|
|
2004
|
-
|
|
2005
|
-
// Create intelliTable
|
|
2006
|
-
const dataTable = (
|
|
2166
|
+
// Nothing to show notice
|
|
2167
|
+
const noLogsNotice = (
|
|
2007
2168
|
logs.length === 0
|
|
2008
2169
|
? (
|
|
2009
|
-
<div className="alert alert-warning">
|
|
2170
|
+
<div className="alert alert-warning text-center mt-2">
|
|
2010
2171
|
<h4 className="m-1">
|
|
2011
2172
|
No Logs to Show
|
|
2012
2173
|
</h4>
|
|
@@ -2016,21 +2177,28 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
2016
2177
|
</div>
|
|
2017
2178
|
</div>
|
|
2018
2179
|
)
|
|
2019
|
-
:
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
)
|
|
2180
|
+
: undefined
|
|
2181
|
+
);
|
|
2182
|
+
|
|
2183
|
+
// Create intelliTable
|
|
2184
|
+
const dataTable = (
|
|
2185
|
+
<IntelliTable
|
|
2186
|
+
title="Matching Logs:"
|
|
2187
|
+
csvName={`Logs from ${getHumanReadableDate()}`}
|
|
2188
|
+
id="logs"
|
|
2189
|
+
data={logs}
|
|
2190
|
+
columns={columns}
|
|
2191
|
+
/>
|
|
2027
2192
|
);
|
|
2028
2193
|
|
|
2029
2194
|
// Main body
|
|
2030
2195
|
body = (
|
|
2031
2196
|
<>
|
|
2032
2197
|
{filters}
|
|
2033
|
-
|
|
2198
|
+
<div className="mt-2">
|
|
2199
|
+
{dataTable}
|
|
2200
|
+
{noLogsNotice}
|
|
2201
|
+
</div>
|
|
2034
2202
|
</>
|
|
2035
2203
|
);
|
|
2036
2204
|
}
|
|
@@ -2045,20 +2213,22 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
2045
2213
|
<div className="LogReviewer-inner-container">
|
|
2046
2214
|
<div className="LogReviewer-header">
|
|
2047
2215
|
<div className="LogReviewer-header-title">
|
|
2048
|
-
<h3 className="m-0">
|
|
2216
|
+
<h3 className="text-center m-0">
|
|
2049
2217
|
Log Review Dashboard
|
|
2050
2218
|
</h3>
|
|
2051
2219
|
</div>
|
|
2052
|
-
<
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2220
|
+
<div style={{ width: 0 }}>
|
|
2221
|
+
<button
|
|
2222
|
+
type="button"
|
|
2223
|
+
className="LogReviewer-header-close-button btn btn-dark btn-lg pe-0"
|
|
2224
|
+
aria-label="close log reviewer panel"
|
|
2225
|
+
onClick={onClose}
|
|
2226
|
+
>
|
|
2227
|
+
<FontAwesomeIcon
|
|
2228
|
+
icon={faTimes}
|
|
2229
|
+
/>
|
|
2230
|
+
</button>
|
|
2231
|
+
</div>
|
|
2062
2232
|
</div>
|
|
2063
2233
|
<div className="LogReviewer-contents">
|
|
2064
2234
|
{body}
|