dce-reactkit 3.2.2-beta.9 → 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 +731 -435
- 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/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 +732 -437
- 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/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 +63 -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/LogReviewer.tsx +584 -424
- 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,8 +247,12 @@ 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 {
|
|
250
258
|
border: 0 !important;
|
|
@@ -256,6 +264,197 @@ const style = `
|
|
|
256
264
|
}
|
|
257
265
|
`;
|
|
258
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
|
+
|
|
259
458
|
/*------------------------------------------------------------------------*/
|
|
260
459
|
/* Static Functions */
|
|
261
460
|
/*------------------------------------------------------------------------*/
|
|
@@ -499,6 +698,36 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
499
698
|
onClose,
|
|
500
699
|
} = props;
|
|
501
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
|
+
|
|
502
731
|
/* -------------- State ------------- */
|
|
503
732
|
|
|
504
733
|
// Create initial date filter state
|
|
@@ -520,25 +749,30 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
520
749
|
|
|
521
750
|
// Create initial context filter state
|
|
522
751
|
const initContextFilterState: ContextFilterState = {};
|
|
523
|
-
Object.keys(
|
|
524
|
-
const contextValue =
|
|
752
|
+
Object.keys(contextMap).forEach((context) => {
|
|
753
|
+
const contextValue = contextMap[context];
|
|
525
754
|
if (typeof contextValue === 'string') {
|
|
526
|
-
// Case: no subcontexts
|
|
755
|
+
// Case: no subcontexts, init as checked
|
|
527
756
|
initContextFilterState[contextValue] = true;
|
|
528
757
|
} else {
|
|
529
758
|
// Case: subcontexts exist
|
|
530
|
-
initContextFilterState[
|
|
531
|
-
Object.values(
|
|
532
|
-
|
|
533
|
-
(
|
|
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;
|
|
534
768
|
});
|
|
535
769
|
}
|
|
536
770
|
});
|
|
537
771
|
|
|
538
772
|
// Create initial tag filter state
|
|
539
773
|
const initTagFilterState: TagFilterState = {};
|
|
540
|
-
Object.
|
|
541
|
-
initTagFilterState[
|
|
774
|
+
Object.keys(LogMetadata.Tag ?? {}).forEach((tag) => {
|
|
775
|
+
initTagFilterState[tag] = false;
|
|
542
776
|
});
|
|
543
777
|
|
|
544
778
|
// Create advanced filter state
|
|
@@ -566,7 +800,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
566
800
|
target: {},
|
|
567
801
|
action: {},
|
|
568
802
|
};
|
|
569
|
-
Object.values(
|
|
803
|
+
Object.values(targetMap).forEach((target) => {
|
|
570
804
|
initActionErrorFilterState.target[target] = true;
|
|
571
805
|
});
|
|
572
806
|
Object.values(LogAction).forEach((action) => {
|
|
@@ -622,18 +856,23 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
622
856
|
let month = newDateFilterState.startDate.month;
|
|
623
857
|
while (
|
|
624
858
|
// Earlier year
|
|
625
|
-
(year
|
|
859
|
+
(year < newDateFilterState.endDate.year)
|
|
626
860
|
// Current year but included month
|
|
627
861
|
|| (
|
|
628
862
|
year === newDateFilterState.endDate.year
|
|
629
863
|
&& month <= newDateFilterState.endDate.month
|
|
630
864
|
)
|
|
631
865
|
) {
|
|
632
|
-
// Add to list
|
|
633
|
-
|
|
634
|
-
year
|
|
635
|
-
month
|
|
636
|
-
|
|
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
|
+
}
|
|
637
876
|
|
|
638
877
|
// Increment
|
|
639
878
|
month += 1;
|
|
@@ -749,7 +988,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
749
988
|
|
|
750
989
|
// Filter toggle
|
|
751
990
|
const filterToggles = (
|
|
752
|
-
<div className="LogReviewer-filter-toggles
|
|
991
|
+
<div className="LogReviewer-filter-toggles">
|
|
753
992
|
<h3 className="m-0">
|
|
754
993
|
Filters:
|
|
755
994
|
</h3>
|
|
@@ -837,7 +1076,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
837
1076
|
<button
|
|
838
1077
|
type="button"
|
|
839
1078
|
id="LogReviewer-toggle-advanced-filter-drawer"
|
|
840
|
-
className={`btn btn-${FilterDrawer.Advanced === expandedFilterDrawer ? 'warning' : 'light'}`}
|
|
1079
|
+
className={`btn btn-${FilterDrawer.Advanced === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
841
1080
|
aria-label="toggle advanced filter drawer"
|
|
842
1081
|
onClick={() => {
|
|
843
1082
|
dispatch({
|
|
@@ -852,6 +1091,29 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
852
1091
|
/>
|
|
853
1092
|
Advanced
|
|
854
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>
|
|
855
1117
|
</div>
|
|
856
1118
|
</div>
|
|
857
1119
|
);
|
|
@@ -868,14 +1130,11 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
868
1130
|
year={dateFilterState.startDate.year}
|
|
869
1131
|
month={dateFilterState.startDate.month}
|
|
870
1132
|
day={dateFilterState.startDate.day}
|
|
1133
|
+
chooseFromPast
|
|
1134
|
+
numMonthsToShow={36}
|
|
871
1135
|
onChange={(month, day, year) => {
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
dateFilterState: {
|
|
875
|
-
...dateFilterState,
|
|
876
|
-
startDate: { month, day, year },
|
|
877
|
-
},
|
|
878
|
-
});
|
|
1136
|
+
dateFilterState.startDate = { month, day, year };
|
|
1137
|
+
handleDateRangeUpdated(dateFilterState);
|
|
879
1138
|
}}
|
|
880
1139
|
/>
|
|
881
1140
|
{' '}
|
|
@@ -887,59 +1146,92 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
887
1146
|
year={dateFilterState.endDate.year}
|
|
888
1147
|
month={dateFilterState.endDate.month}
|
|
889
1148
|
day={dateFilterState.endDate.day}
|
|
1149
|
+
chooseFromPast
|
|
1150
|
+
numMonthsToShow={12}
|
|
890
1151
|
onChange={(month, day, year) => {
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
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);
|
|
898
1171
|
}}
|
|
899
1172
|
/>
|
|
900
1173
|
</TabBox>
|
|
901
1174
|
);
|
|
902
1175
|
} else if (expandedFilterDrawer === FilterDrawer.Context) {
|
|
903
1176
|
// Create item picker items
|
|
904
|
-
const
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
return item;
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
// Has subcategories
|
|
920
|
-
const children: PickableItem[] = (
|
|
921
|
-
Object.keys(value)
|
|
922
|
-
.filter((subcontext) => {
|
|
923
|
-
return subcontext !== '_'
|
|
924
|
-
})
|
|
925
|
-
.map((subcontext) => {
|
|
926
|
-
return {
|
|
927
|
-
id: `${context}-${subcontext}`,
|
|
928
|
-
name: genHumanReadableName(subcontext),
|
|
929
|
-
isGroup: false,
|
|
930
|
-
checked: !!value[subcontext],
|
|
931
|
-
};
|
|
932
|
-
})
|
|
933
|
-
);
|
|
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
|
|
934
1189
|
const item: PickableItem = {
|
|
935
1190
|
id: context,
|
|
936
|
-
name: context,
|
|
937
|
-
isGroup:
|
|
938
|
-
|
|
1191
|
+
name: genHumanReadableName(context),
|
|
1192
|
+
isGroup: false,
|
|
1193
|
+
checked: !!contextFilterState[context],
|
|
939
1194
|
};
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
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);
|
|
943
1235
|
|
|
944
1236
|
// Create filter UI
|
|
945
1237
|
filterDrawer = (
|
|
@@ -951,11 +1243,29 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
951
1243
|
updatedItems.forEach((pickableItem) => {
|
|
952
1244
|
if (pickableItem.isGroup) {
|
|
953
1245
|
// Has subcontexts
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
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
|
+
}
|
|
959
1269
|
} else {
|
|
960
1270
|
// No subcontexts
|
|
961
1271
|
(contextFilterState as any)[pickableItem.id] = (
|
|
@@ -974,23 +1284,35 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
974
1284
|
// Create filter UI
|
|
975
1285
|
filterDrawer = (
|
|
976
1286
|
<TabBox title="Tags">
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
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>
|
|
994
1316
|
</TabBox>
|
|
995
1317
|
);
|
|
996
1318
|
} else if (expandedFilterDrawer === FilterDrawer.Action) {
|
|
@@ -1052,58 +1374,60 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1052
1374
|
label="Action"
|
|
1053
1375
|
className="mb-2"
|
|
1054
1376
|
>
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
.
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
actionErrorFilterState
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
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>
|
|
1076
1402
|
</ButtonInputGroup>
|
|
1077
1403
|
{/* Target */}
|
|
1078
1404
|
<ButtonInputGroup label="Target">
|
|
1079
|
-
{/* Nothing here */}
|
|
1080
|
-
{(Object.keys(LogMetadata.Target ?? {})) && (
|
|
1081
|
-
<div>
|
|
1082
|
-
This app does not have any targets yet.
|
|
1083
|
-
</div>
|
|
1084
|
-
)}
|
|
1085
1405
|
{/* List of targets */}
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
.
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
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>
|
|
1107
1431
|
</ButtonInputGroup>
|
|
1108
1432
|
</TabBox>
|
|
1109
1433
|
)
|
|
@@ -1125,6 +1449,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1125
1449
|
className="form-control"
|
|
1126
1450
|
aria-label="query for error message"
|
|
1127
1451
|
value={actionErrorFilterState.errorMessage}
|
|
1452
|
+
placeholder="e.g. undefined is not a function"
|
|
1128
1453
|
onChange={(e) => {
|
|
1129
1454
|
actionErrorFilterState.errorMessage = e.target.value;
|
|
1130
1455
|
dispatch({
|
|
@@ -1143,7 +1468,8 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1143
1468
|
type="text"
|
|
1144
1469
|
className="form-control"
|
|
1145
1470
|
aria-label="query for error code"
|
|
1146
|
-
value={actionErrorFilterState.
|
|
1471
|
+
value={actionErrorFilterState.errorCode}
|
|
1472
|
+
placeholder="e.g. GC22"
|
|
1147
1473
|
onChange={(e) => {
|
|
1148
1474
|
actionErrorFilterState.errorCode = (
|
|
1149
1475
|
(e.target.value)
|
|
@@ -1167,7 +1493,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1167
1493
|
filterDrawer = (
|
|
1168
1494
|
<>
|
|
1169
1495
|
{/* User Info */}
|
|
1170
|
-
<TabBox title="User
|
|
1496
|
+
<TabBox title="User">
|
|
1171
1497
|
{/* First Name */}
|
|
1172
1498
|
<div className="input-group mb-2">
|
|
1173
1499
|
<span className="input-group-text">
|
|
@@ -1178,6 +1504,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1178
1504
|
className="form-control"
|
|
1179
1505
|
aria-label="query for user first name"
|
|
1180
1506
|
value={advancedFilterState.userFirstName}
|
|
1507
|
+
placeholder="e.g. Divardo"
|
|
1181
1508
|
onChange={(e) => {
|
|
1182
1509
|
advancedFilterState.userFirstName = e.target.value;
|
|
1183
1510
|
dispatch({
|
|
@@ -1197,6 +1524,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1197
1524
|
className="form-control"
|
|
1198
1525
|
aria-label="query for user last name"
|
|
1199
1526
|
value={advancedFilterState.userLastName}
|
|
1527
|
+
placeholder="e.g. Calicci"
|
|
1200
1528
|
onChange={(e) => {
|
|
1201
1529
|
advancedFilterState.userLastName = e.target.value;
|
|
1202
1530
|
dispatch({
|
|
@@ -1216,6 +1544,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1216
1544
|
className="form-control"
|
|
1217
1545
|
aria-label="query for user email"
|
|
1218
1546
|
value={advancedFilterState.userEmail}
|
|
1547
|
+
placeholder="e.g. calicci@fas.harvard.edu"
|
|
1219
1548
|
onChange={(e) => {
|
|
1220
1549
|
advancedFilterState.userEmail = (
|
|
1221
1550
|
(e.target.value)
|
|
@@ -1238,6 +1567,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1238
1567
|
className="form-control"
|
|
1239
1568
|
aria-label="query for user canvas id"
|
|
1240
1569
|
value={advancedFilterState.userId}
|
|
1570
|
+
placeholder="e.g. 104985"
|
|
1241
1571
|
onChange={(e) => {
|
|
1242
1572
|
const { value } = e.target;
|
|
1243
1573
|
// Only update if value contains only numbers
|
|
@@ -1296,7 +1626,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1296
1626
|
</TabBox>
|
|
1297
1627
|
|
|
1298
1628
|
{/* Course Info */}
|
|
1299
|
-
<TabBox title="Course
|
|
1629
|
+
<TabBox title="Course">
|
|
1300
1630
|
{/* Name */}
|
|
1301
1631
|
<div className="input-group mb-2">
|
|
1302
1632
|
<span className="input-group-text">
|
|
@@ -1307,6 +1637,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1307
1637
|
className="form-control"
|
|
1308
1638
|
aria-label="query for course name"
|
|
1309
1639
|
value={advancedFilterState.courseName}
|
|
1640
|
+
placeholder="e.g. GLC 200"
|
|
1310
1641
|
onChange={(e) => {
|
|
1311
1642
|
advancedFilterState.courseName = e.target.value;
|
|
1312
1643
|
dispatch({
|
|
@@ -1326,6 +1657,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1326
1657
|
className="form-control"
|
|
1327
1658
|
aria-label="query for course canvas id"
|
|
1328
1659
|
value={advancedFilterState.courseId}
|
|
1660
|
+
placeholder="e.g. 15948"
|
|
1329
1661
|
onChange={(e) => {
|
|
1330
1662
|
const { value } = e.target;
|
|
1331
1663
|
// Only update if value contains only numbers
|
|
@@ -1345,7 +1677,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1345
1677
|
</TabBox>
|
|
1346
1678
|
|
|
1347
1679
|
{/* Device Info */}
|
|
1348
|
-
<TabBox title="Device
|
|
1680
|
+
<TabBox title="Device">
|
|
1349
1681
|
<ButtonInputGroup label="Device Type">
|
|
1350
1682
|
<RadioButton
|
|
1351
1683
|
text="All Devices"
|
|
@@ -1429,53 +1761,58 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1429
1761
|
/>
|
|
1430
1762
|
</ButtonInputGroup>
|
|
1431
1763
|
|
|
1432
|
-
{/*
|
|
1433
|
-
|
|
1434
|
-
<
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
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>
|
|
1455
1790
|
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
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
|
+
)}
|
|
1479
1816
|
</TabBox>
|
|
1480
1817
|
</>
|
|
1481
1818
|
);
|
|
@@ -1487,7 +1824,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1487
1824
|
<>
|
|
1488
1825
|
{filterToggles}
|
|
1489
1826
|
{filterDrawer && (
|
|
1490
|
-
<Drawer>
|
|
1827
|
+
<Drawer customBackgroundColor="#eee">
|
|
1491
1828
|
{filterDrawer}
|
|
1492
1829
|
</Drawer>
|
|
1493
1830
|
)}
|
|
@@ -1548,7 +1885,10 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1548
1885
|
contextFilterState[log.context] === false
|
|
1549
1886
|
// None of the subcontexts are selected
|
|
1550
1887
|
|| (
|
|
1551
|
-
|
|
1888
|
+
// Has subcontexts
|
|
1889
|
+
typeof contextFilterState[log.context] !== 'boolean'
|
|
1890
|
+
// None of the subcontexts are selected
|
|
1891
|
+
&& Object.values(contextFilterState[log.context] ?? {})
|
|
1552
1892
|
.every((isSelected) => {
|
|
1553
1893
|
return !isSelected;
|
|
1554
1894
|
})
|
|
@@ -1559,8 +1899,10 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1559
1899
|
|
|
1560
1900
|
// Subcontext doesn't match
|
|
1561
1901
|
if (
|
|
1902
|
+
// Log context is not "uncategorized" (no point in further filters)
|
|
1903
|
+
log.context !== LogBuiltInMetadata.Context.Uncategorized
|
|
1562
1904
|
// Log has a subcontext
|
|
1563
|
-
log.subcontext
|
|
1905
|
+
&& log.subcontext
|
|
1564
1906
|
// Context has subcontexts
|
|
1565
1907
|
&& (
|
|
1566
1908
|
contextFilterState[log.context]
|
|
@@ -1577,14 +1919,16 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1577
1919
|
|
|
1578
1920
|
// No tags match
|
|
1579
1921
|
if (
|
|
1580
|
-
//
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
log.tags.every((tag) => {
|
|
1585
|
-
return !tagFilterState[tag];
|
|
1922
|
+
// At least one tag is required
|
|
1923
|
+
Object.values(tagFilterState)
|
|
1924
|
+
.filter((isSelected) => {
|
|
1925
|
+
return isSelected;
|
|
1586
1926
|
})
|
|
1587
|
-
|
|
1927
|
+
.length > 0
|
|
1928
|
+
// No tags match
|
|
1929
|
+
&& log.tags.every((tag) => {
|
|
1930
|
+
return !tagFilterState[tag];
|
|
1931
|
+
})
|
|
1588
1932
|
) {
|
|
1589
1933
|
return;
|
|
1590
1934
|
}
|
|
@@ -1766,7 +2110,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1766
2110
|
// Device info exists
|
|
1767
2111
|
&& log.device
|
|
1768
2112
|
// Mobile filter doesn't match
|
|
1769
|
-
&& (advancedFilterState.isMobile
|
|
2113
|
+
&& (advancedFilterState.isMobile !== log.device.isMobile)
|
|
1770
2114
|
) {
|
|
1771
2115
|
return;
|
|
1772
2116
|
}
|
|
@@ -1819,228 +2163,42 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1819
2163
|
/* Data */
|
|
1820
2164
|
/*----------------------------------------*/
|
|
1821
2165
|
|
|
1822
|
-
//
|
|
1823
|
-
const
|
|
1824
|
-
{
|
|
1825
|
-
title: 'First Name',
|
|
1826
|
-
param: 'userFirstName',
|
|
1827
|
-
type: ParamType.String,
|
|
1828
|
-
},
|
|
1829
|
-
{
|
|
1830
|
-
title: 'Last Name',
|
|
1831
|
-
param: 'userLastName',
|
|
1832
|
-
type: ParamType.String,
|
|
1833
|
-
},
|
|
1834
|
-
{
|
|
1835
|
-
title: 'Email',
|
|
1836
|
-
param: 'userEmail',
|
|
1837
|
-
type: ParamType.String,
|
|
1838
|
-
},
|
|
1839
|
-
{
|
|
1840
|
-
title: 'Canvas Id',
|
|
1841
|
-
param: 'userId',
|
|
1842
|
-
type: ParamType.Int,
|
|
1843
|
-
},
|
|
1844
|
-
{
|
|
1845
|
-
title: 'Student',
|
|
1846
|
-
param: 'isLearner',
|
|
1847
|
-
type: ParamType.Boolean,
|
|
1848
|
-
},
|
|
1849
|
-
{
|
|
1850
|
-
title: 'Teaching Staff',
|
|
1851
|
-
param: 'isTTM',
|
|
1852
|
-
type: ParamType.Boolean,
|
|
1853
|
-
startsHidden: true,
|
|
1854
|
-
},
|
|
1855
|
-
{
|
|
1856
|
-
title: 'Admin',
|
|
1857
|
-
param: 'isAdmin',
|
|
1858
|
-
type: ParamType.Boolean,
|
|
1859
|
-
startsHidden: true,
|
|
1860
|
-
},
|
|
1861
|
-
{
|
|
1862
|
-
title: 'Course Canvas Id',
|
|
1863
|
-
param: 'courseId',
|
|
1864
|
-
type: ParamType.Int,
|
|
1865
|
-
startsHidden: true,
|
|
1866
|
-
},
|
|
1867
|
-
{
|
|
1868
|
-
title: 'Course Name',
|
|
1869
|
-
param: 'courseName',
|
|
1870
|
-
type: ParamType.String,
|
|
1871
|
-
},
|
|
1872
|
-
{
|
|
1873
|
-
title: 'Browser Name',
|
|
1874
|
-
param: 'browser.name',
|
|
1875
|
-
type: ParamType.String,
|
|
1876
|
-
startsHidden: true,
|
|
1877
|
-
},
|
|
1878
|
-
{
|
|
1879
|
-
title: 'Browser Version',
|
|
1880
|
-
param: 'browser.version',
|
|
1881
|
-
type: ParamType.String,
|
|
1882
|
-
startsHidden: true,
|
|
1883
|
-
},
|
|
1884
|
-
{
|
|
1885
|
-
title: 'OS',
|
|
1886
|
-
param: 'device.os',
|
|
1887
|
-
type: ParamType.String,
|
|
1888
|
-
startsHidden: true,
|
|
1889
|
-
},
|
|
1890
|
-
{
|
|
1891
|
-
title: 'Mobile',
|
|
1892
|
-
param: 'device.isMobile',
|
|
1893
|
-
type: ParamType.Boolean,
|
|
1894
|
-
startsHidden: true,
|
|
1895
|
-
},
|
|
1896
|
-
{
|
|
1897
|
-
title: 'Year',
|
|
1898
|
-
param: 'year',
|
|
1899
|
-
type: ParamType.Int,
|
|
1900
|
-
},
|
|
1901
|
-
{
|
|
1902
|
-
title: 'Month',
|
|
1903
|
-
param: 'month',
|
|
1904
|
-
type: ParamType.Int,
|
|
1905
|
-
},
|
|
1906
|
-
{
|
|
1907
|
-
title: 'Day',
|
|
1908
|
-
param: 'day',
|
|
1909
|
-
type: ParamType.Int,
|
|
1910
|
-
},
|
|
1911
|
-
{
|
|
1912
|
-
title: 'Hour',
|
|
1913
|
-
param: 'hour',
|
|
1914
|
-
type: ParamType.Int,
|
|
1915
|
-
},
|
|
1916
|
-
{
|
|
1917
|
-
title: 'Minute',
|
|
1918
|
-
param: 'minute',
|
|
1919
|
-
type: ParamType.Int,
|
|
1920
|
-
startsHidden: true,
|
|
1921
|
-
},
|
|
1922
|
-
{
|
|
1923
|
-
title: 'Timestamp',
|
|
1924
|
-
param: 'timestamp',
|
|
1925
|
-
type: ParamType.Int,
|
|
1926
|
-
startsHidden: true,
|
|
1927
|
-
},
|
|
1928
|
-
{
|
|
1929
|
-
title: 'Context',
|
|
1930
|
-
param: 'context',
|
|
1931
|
-
type: ParamType.String,
|
|
1932
|
-
},
|
|
1933
|
-
{
|
|
1934
|
-
title: 'Subcontext',
|
|
1935
|
-
param: 'subcontext',
|
|
1936
|
-
type: ParamType.String,
|
|
1937
|
-
},
|
|
1938
|
-
{
|
|
1939
|
-
title: 'Tags',
|
|
1940
|
-
param: 'tags',
|
|
1941
|
-
type: ParamType.JSON,
|
|
1942
|
-
startsHidden: true,
|
|
1943
|
-
},
|
|
1944
|
-
{
|
|
1945
|
-
title: 'Log Level',
|
|
1946
|
-
param: 'level',
|
|
1947
|
-
type: ParamType.String,
|
|
1948
|
-
startsHidden: true,
|
|
1949
|
-
},
|
|
1950
|
-
{
|
|
1951
|
-
title: 'Metadata',
|
|
1952
|
-
param: 'metadata',
|
|
1953
|
-
type: ParamType.JSON,
|
|
1954
|
-
startsHidden: true,
|
|
1955
|
-
},
|
|
1956
|
-
{
|
|
1957
|
-
title: 'Source',
|
|
1958
|
-
param: 'source',
|
|
1959
|
-
type: ParamType.String,
|
|
1960
|
-
},
|
|
1961
|
-
{
|
|
1962
|
-
title: 'Server Route Path',
|
|
1963
|
-
param: 'routePath',
|
|
1964
|
-
type: ParamType.String,
|
|
1965
|
-
startsHidden: true,
|
|
1966
|
-
},
|
|
1967
|
-
{
|
|
1968
|
-
title: 'Server Route Template',
|
|
1969
|
-
param: 'routeTemplate',
|
|
1970
|
-
type: ParamType.String,
|
|
1971
|
-
startsHidden: true,
|
|
1972
|
-
},
|
|
1973
|
-
{
|
|
1974
|
-
title: 'Type',
|
|
1975
|
-
param: 'type',
|
|
1976
|
-
type: ParamType.String,
|
|
1977
|
-
},
|
|
1978
|
-
{
|
|
1979
|
-
title: 'Error Message',
|
|
1980
|
-
param: 'errorMessage',
|
|
1981
|
-
type: ParamType.String,
|
|
1982
|
-
startsHidden: true,
|
|
1983
|
-
},
|
|
1984
|
-
{
|
|
1985
|
-
title: 'Error Code',
|
|
1986
|
-
param: 'errorCode',
|
|
1987
|
-
type: ParamType.String,
|
|
1988
|
-
startsHidden: true,
|
|
1989
|
-
},
|
|
1990
|
-
{
|
|
1991
|
-
title: 'Error Stack',
|
|
1992
|
-
param: 'errorStack',
|
|
1993
|
-
type: ParamType.String,
|
|
1994
|
-
startsHidden: true,
|
|
1995
|
-
},
|
|
1996
|
-
{
|
|
1997
|
-
title: 'Action Target',
|
|
1998
|
-
param: 'target',
|
|
1999
|
-
type: ParamType.String,
|
|
2000
|
-
startsHidden: true,
|
|
2001
|
-
},
|
|
2002
|
-
{
|
|
2003
|
-
title: 'Action Type',
|
|
2004
|
-
param: 'action',
|
|
2005
|
-
type: ParamType.String,
|
|
2006
|
-
startsHidden: true,
|
|
2007
|
-
},
|
|
2008
|
-
];
|
|
2009
|
-
|
|
2010
|
-
// Create intelliTable
|
|
2011
|
-
const dataTable = (
|
|
2166
|
+
// Nothing to show notice
|
|
2167
|
+
const noLogsNotice = (
|
|
2012
2168
|
logs.length === 0
|
|
2013
2169
|
? (
|
|
2014
|
-
|
|
2015
|
-
<
|
|
2016
|
-
|
|
2017
|
-
</
|
|
2018
|
-
<div
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
</h4>
|
|
2022
|
-
<div>
|
|
2023
|
-
Either your filters are too strict or no matching logs have been
|
|
2024
|
-
created yet.
|
|
2025
|
-
</div>
|
|
2170
|
+
<div className="alert alert-warning text-center mt-2">
|
|
2171
|
+
<h4 className="m-1">
|
|
2172
|
+
No Logs to Show
|
|
2173
|
+
</h4>
|
|
2174
|
+
<div>
|
|
2175
|
+
Either your filters are too strict or no matching logs have been
|
|
2176
|
+
created yet.
|
|
2026
2177
|
</div>
|
|
2027
|
-
|
|
2028
|
-
)
|
|
2029
|
-
: (
|
|
2030
|
-
<IntelliTable
|
|
2031
|
-
title="Matching Logs:"
|
|
2032
|
-
id="logs"
|
|
2033
|
-
data={logs}
|
|
2034
|
-
columns={columns}
|
|
2035
|
-
/>
|
|
2178
|
+
</div>
|
|
2036
2179
|
)
|
|
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
|
+
/>
|
|
2037
2192
|
);
|
|
2038
2193
|
|
|
2039
2194
|
// Main body
|
|
2040
2195
|
body = (
|
|
2041
2196
|
<>
|
|
2042
2197
|
{filters}
|
|
2043
|
-
|
|
2198
|
+
<div className="mt-2">
|
|
2199
|
+
{dataTable}
|
|
2200
|
+
{noLogsNotice}
|
|
2201
|
+
</div>
|
|
2044
2202
|
</>
|
|
2045
2203
|
);
|
|
2046
2204
|
}
|
|
@@ -2059,16 +2217,18 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
2059
2217
|
Log Review Dashboard
|
|
2060
2218
|
</h3>
|
|
2061
2219
|
</div>
|
|
2062
|
-
<
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
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>
|
|
2072
2232
|
</div>
|
|
2073
2233
|
<div className="LogReviewer-contents">
|
|
2074
2234
|
{body}
|