dce-reactkit 4.0.1 → 4.1.0
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 +662 -771
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/Pagination.d.ts +13 -0
- package/dist/cjs/types/constants/LOG_REVIEW_GET_LOGS_ROUTE.d.ts +6 -0
- package/dist/cjs/types/helpers/cloneDeep.d.ts +8 -0
- package/dist/cjs/types/index.d.ts +4 -1
- package/dist/cjs/types/types/LogReviewerFilterState/ActionErrorFilterState.d.ts +17 -0
- package/dist/cjs/types/types/LogReviewerFilterState/AdvancedFilterState.d.ts +21 -0
- package/dist/cjs/types/types/LogReviewerFilterState/ContextFilterState.d.ts +10 -0
- package/dist/cjs/types/types/LogReviewerFilterState/DateFilterState.d.ts +17 -0
- package/dist/cjs/types/types/LogReviewerFilterState/TagFilterState.d.ts +8 -0
- package/dist/cjs/types/types/LogReviewerFilterState/index.d.ts +17 -0
- package/dist/esm/index.js +661 -773
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/Pagination.d.ts +13 -0
- package/dist/esm/types/constants/LOG_REVIEW_GET_LOGS_ROUTE.d.ts +6 -0
- package/dist/esm/types/helpers/cloneDeep.d.ts +8 -0
- package/dist/esm/types/index.d.ts +4 -1
- package/dist/esm/types/types/LogReviewerFilterState/ActionErrorFilterState.d.ts +17 -0
- package/dist/esm/types/types/LogReviewerFilterState/AdvancedFilterState.d.ts +21 -0
- package/dist/esm/types/types/LogReviewerFilterState/ContextFilterState.d.ts +10 -0
- package/dist/esm/types/types/LogReviewerFilterState/DateFilterState.d.ts +17 -0
- package/dist/esm/types/types/LogReviewerFilterState/TagFilterState.d.ts +8 -0
- package/dist/esm/types/types/LogReviewerFilterState/index.d.ts +17 -0
- package/dist/index.d.ts +98 -1
- package/package.json +2 -1
- package/src/components/LogReviewer.tsx +1133 -1307
- package/src/components/Pagination.tsx +172 -0
- package/src/constants/LOG_REVIEW_GET_LOGS_ROUTE.ts +9 -0
- package/src/helpers/cloneDeep.ts +11 -0
- package/src/index.ts +6 -0
- package/src/types/LogReviewerFilterState/ActionErrorFilterState.ts +24 -0
- package/src/types/LogReviewerFilterState/AdvancedFilterState.ts +36 -0
- package/src/types/LogReviewerFilterState/ContextFilterState.ts +16 -0
- package/src/types/LogReviewerFilterState/DateFilterState.ts +26 -0
- package/src/types/LogReviewerFilterState/TagFilterState.ts +9 -0
- package/src/types/LogReviewerFilterState/index.ts +24 -0
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
// Import React
|
|
8
|
-
import React, { useReducer,
|
|
8
|
+
import React, { useEffect, useReducer, useRef } from 'react';
|
|
9
9
|
|
|
10
10
|
// Import FontAwesome
|
|
11
11
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
faList,
|
|
17
17
|
faTag,
|
|
18
18
|
faTimes,
|
|
19
|
+
faSearch,
|
|
19
20
|
} from '@fortawesome/free-solid-svg-icons';
|
|
20
21
|
|
|
21
22
|
// Import shared helpers
|
|
@@ -25,7 +26,7 @@ import { alert, showFatalError } from './AppWrapper';
|
|
|
25
26
|
import getHumanReadableDate from '../helpers/getHumanReadableDate';
|
|
26
27
|
|
|
27
28
|
// Import shared constants
|
|
28
|
-
import
|
|
29
|
+
import LOG_REVIEW_GET_LOGS_ROUTE from '../constants/LOG_REVIEW_GET_LOGS_ROUTE';
|
|
29
30
|
|
|
30
31
|
// Import shared types
|
|
31
32
|
import Log from '../types/Log';
|
|
@@ -38,6 +39,13 @@ import IntelliTableColumn from '../types/IntelliTableColumn';
|
|
|
38
39
|
import LogBuiltInMetadata from '../types/LogBuiltInMetadata';
|
|
39
40
|
import LogMetadataContextMap from '../types/LogMetadataContextMap';
|
|
40
41
|
import LogMetadataTargetMap from '../types/LogMetadataTargetMap';
|
|
42
|
+
import LogReviewerFilterState from '../types/LogReviewerFilterState';
|
|
43
|
+
import DateFilterState from '../types/LogReviewerFilterState/DateFilterState';
|
|
44
|
+
import ContextFilterState from '../types/LogReviewerFilterState/ContextFilterState';
|
|
45
|
+
import TagFilterState from '../types/LogReviewerFilterState/TagFilterState';
|
|
46
|
+
import ActionErrorFilterState from '../types/LogReviewerFilterState/ActionErrorFilterState';
|
|
47
|
+
import AdvancedFilterState from '../types/LogReviewerFilterState/AdvancedFilterState';
|
|
48
|
+
import Variant from '../types/Variant';
|
|
41
49
|
|
|
42
50
|
// Import shared components
|
|
43
51
|
import SimpleDateChooser from './SimpleDateChooser';
|
|
@@ -50,6 +58,8 @@ import TabBox from './TabBox';
|
|
|
50
58
|
import RadioButton from './RadioButton';
|
|
51
59
|
import ButtonInputGroup from './ButtonInputGroup';
|
|
52
60
|
import IntelliTable from './IntelliTable';
|
|
61
|
+
import Pagination from './Pagination';
|
|
62
|
+
import cloneDeep from '../helpers/cloneDeep';
|
|
53
63
|
|
|
54
64
|
/*------------------------------------------------------------------------*/
|
|
55
65
|
/* -------------------------------- Types ------------------------------- */
|
|
@@ -63,13 +73,6 @@ type Props = {
|
|
|
63
73
|
onClose: () => void,
|
|
64
74
|
};
|
|
65
75
|
|
|
66
|
-
// Map of loaded logs (year => month => Log[])
|
|
67
|
-
type LogMap = {
|
|
68
|
-
[k: string]: {
|
|
69
|
-
[k: string]: Log[]
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
73
76
|
// Triple of year/month/day
|
|
74
77
|
type DateTriple = {
|
|
75
78
|
// Full year
|
|
@@ -89,93 +92,6 @@ enum FilterDrawer {
|
|
|
89
92
|
Advanced = 'advanced',
|
|
90
93
|
}
|
|
91
94
|
|
|
92
|
-
// Date filter state
|
|
93
|
-
type DateFilterState = {
|
|
94
|
-
// Current start date
|
|
95
|
-
startDate: {
|
|
96
|
-
// Full year
|
|
97
|
-
year: number,
|
|
98
|
-
// 1-indexed month
|
|
99
|
-
month: number,
|
|
100
|
-
// 1-indexed day
|
|
101
|
-
day: number,
|
|
102
|
-
},
|
|
103
|
-
// Current end date
|
|
104
|
-
endDate: {
|
|
105
|
-
// Full year
|
|
106
|
-
year: number,
|
|
107
|
-
// 1-indexed month
|
|
108
|
-
month: number,
|
|
109
|
-
// 1-indexed day
|
|
110
|
-
day: number,
|
|
111
|
-
},
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
// Context filter state
|
|
115
|
-
type ContextFilterState = {
|
|
116
|
-
[k: string]: (
|
|
117
|
-
// No subcontexts
|
|
118
|
-
| boolean // True if selected
|
|
119
|
-
// Includes subcontexts
|
|
120
|
-
| {
|
|
121
|
-
[k: string]: boolean // True if selected
|
|
122
|
-
}
|
|
123
|
-
)
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
// Tag filter state
|
|
127
|
-
type TagFilterState = {
|
|
128
|
-
[k: string]: boolean // tag => true if in the list of tags to show
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
// Action filter state (only relevant for action logs)
|
|
132
|
-
type ActionErrorFilterState = {
|
|
133
|
-
// Required type of log
|
|
134
|
-
type: LogType | undefined, // If undefined, no filter applied
|
|
135
|
-
// Query for error message (only relevant if type is error)
|
|
136
|
-
errorMessage: string, // If empty, no filter applied
|
|
137
|
-
// Query for error code (only relevant if type is error)
|
|
138
|
-
errorCode: string, // If empty, no filter applied
|
|
139
|
-
// Action targets to include (only relevant if type is action)
|
|
140
|
-
target: {
|
|
141
|
-
[k: string]: boolean
|
|
142
|
-
},
|
|
143
|
-
// Action types to include (only relevant if type is action)
|
|
144
|
-
action: {
|
|
145
|
-
[k: string]: boolean
|
|
146
|
-
},
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
// Advanced filter state
|
|
150
|
-
type AdvancedFilterState = {
|
|
151
|
-
// Query for user first name (case insensitive)
|
|
152
|
-
userFirstName: string, // If empty, no filter applied
|
|
153
|
-
// Query for user last name (case insensitive)
|
|
154
|
-
userLastName: string, // If empty, no filter applied
|
|
155
|
-
// Query for user email (case insensitive)
|
|
156
|
-
userEmail: string, // If empty, no filter applied
|
|
157
|
-
// Match for userId (numerical)
|
|
158
|
-
userId: string, // If empty, no filter applied
|
|
159
|
-
// If true, include students
|
|
160
|
-
includeLearners: boolean,
|
|
161
|
-
// If true, include ttms
|
|
162
|
-
includeTTMs: boolean,
|
|
163
|
-
// If true, include admins
|
|
164
|
-
includeAdmins: boolean,
|
|
165
|
-
// Match for courseId (numerical)
|
|
166
|
-
courseId: string, // If empty, no filter applied
|
|
167
|
-
// Query for course name (case insensitive)
|
|
168
|
-
courseName: string, // If empty, no filter applied
|
|
169
|
-
// Required isMobile value
|
|
170
|
-
isMobile: (true | false | undefined), // If undefined, no filter applied
|
|
171
|
-
// Required log source value
|
|
172
|
-
source: LogSource | undefined, // If undefined, no filter applied
|
|
173
|
-
// Query for route path (only relevant if source is server)
|
|
174
|
-
routePath: string, // If empty, no filter applied
|
|
175
|
-
// Query for route template (only relevant if source is server)
|
|
176
|
-
routeTemplate: string, // If empty, no filter applied
|
|
177
|
-
};
|
|
178
|
-
|
|
179
95
|
/*------------------------------------------------------------------------*/
|
|
180
96
|
/* -------------------------------- Style ------------------------------- */
|
|
181
97
|
/*------------------------------------------------------------------------*/
|
|
@@ -530,23 +446,31 @@ const genHumanReadableName = (machineReadableName: string) => {
|
|
|
530
446
|
|
|
531
447
|
type State = {
|
|
532
448
|
/* -------------- Logs -------------- */
|
|
533
|
-
// True if currently loading
|
|
449
|
+
// True if currently loading new logs
|
|
534
450
|
loading: boolean,
|
|
535
|
-
// Loaded logs
|
|
536
|
-
|
|
451
|
+
// Loaded logs
|
|
452
|
+
logs: Log[],
|
|
537
453
|
/* ------------- Filters ------------ */
|
|
538
454
|
// Current expanded filter drawer
|
|
539
455
|
expandedFilterDrawer: FilterDrawer | undefined,
|
|
540
456
|
// State of date filters
|
|
541
|
-
|
|
457
|
+
pendingDateFilterState: DateFilterState,
|
|
542
458
|
// State of context filters
|
|
543
|
-
|
|
459
|
+
pendingContextFilterState: ContextFilterState,
|
|
544
460
|
// State of tag filters
|
|
545
|
-
|
|
461
|
+
pendingTagFilterState: TagFilterState,
|
|
546
462
|
// State of the action and error filter
|
|
547
|
-
|
|
463
|
+
pendingActionErrorFilterState: ActionErrorFilterState,
|
|
548
464
|
// State of the advanced filter
|
|
549
|
-
|
|
465
|
+
pendingAdvancedFilterState: AdvancedFilterState,
|
|
466
|
+
// Current page number
|
|
467
|
+
pageNumber: number,
|
|
468
|
+
// If true, there is another page to load
|
|
469
|
+
hasAnotherPage: boolean,
|
|
470
|
+
// Total number of pages
|
|
471
|
+
numPages: number,
|
|
472
|
+
// If true, filters have changed
|
|
473
|
+
userMadeFilterChange: boolean,
|
|
550
474
|
};
|
|
551
475
|
|
|
552
476
|
/* ------------- Actions ------------ */
|
|
@@ -555,7 +479,7 @@ type State = {
|
|
|
555
479
|
enum ActionType {
|
|
556
480
|
// Show the loading bar
|
|
557
481
|
StartLoading = 'start-loading',
|
|
558
|
-
// Finish loading
|
|
482
|
+
// Finish loading logs
|
|
559
483
|
FinishLoading = 'finish-loading',
|
|
560
484
|
// Reset filters to initial values
|
|
561
485
|
ResetFilters = 'reset-filters',
|
|
@@ -573,6 +497,14 @@ enum ActionType {
|
|
|
573
497
|
UpdateActionErrorFilterState = 'update-action-error-filter-state',
|
|
574
498
|
// Update the advanced filter state
|
|
575
499
|
UpdateAdvancedFilterState = 'update-advanced-filter-state',
|
|
500
|
+
// Set has another page
|
|
501
|
+
SetHasAnotherPage = 'set-has-another-page',
|
|
502
|
+
// Set the number of pages
|
|
503
|
+
SetNumPages = 'set-num-pages',
|
|
504
|
+
// Set page number
|
|
505
|
+
SetPageNumber = 'set-page-number',
|
|
506
|
+
// Reset user made filter change indicator
|
|
507
|
+
ResetUserMadeFilterChange = 'reset-user-made-filter-change',
|
|
576
508
|
}
|
|
577
509
|
|
|
578
510
|
// Action definitions
|
|
@@ -580,8 +512,8 @@ type Action = (
|
|
|
580
512
|
| {
|
|
581
513
|
// Action type
|
|
582
514
|
type: ActionType.FinishLoading,
|
|
583
|
-
// Updated
|
|
584
|
-
|
|
515
|
+
// Updated logs
|
|
516
|
+
logs: Log[],
|
|
585
517
|
}
|
|
586
518
|
| {
|
|
587
519
|
// Action type
|
|
@@ -629,11 +561,26 @@ type Action = (
|
|
|
629
561
|
// New advanced filter state
|
|
630
562
|
advancedFilterState: AdvancedFilterState,
|
|
631
563
|
}
|
|
564
|
+
| {
|
|
565
|
+
type: ActionType.SetHasAnotherPage,
|
|
566
|
+
hasAnotherPage: boolean,
|
|
567
|
+
}
|
|
568
|
+
| {
|
|
569
|
+
type: ActionType.SetNumPages,
|
|
570
|
+
numPages: number,
|
|
571
|
+
}
|
|
572
|
+
| {
|
|
573
|
+
type: ActionType.SetPageNumber,
|
|
574
|
+
pageNumber: number,
|
|
575
|
+
}
|
|
576
|
+
| {
|
|
577
|
+
type: ActionType.StartLoading,
|
|
578
|
+
}
|
|
632
579
|
| {
|
|
633
580
|
// Action type
|
|
634
581
|
type: (
|
|
635
|
-
| ActionType.StartLoading
|
|
636
582
|
| ActionType.HideFilterDrawer
|
|
583
|
+
| ActionType.ResetUserMadeFilterChange
|
|
637
584
|
),
|
|
638
585
|
}
|
|
639
586
|
);
|
|
@@ -656,7 +603,7 @@ const reducer = (state: State, action: Action): State => {
|
|
|
656
603
|
return {
|
|
657
604
|
...state,
|
|
658
605
|
loading: false,
|
|
659
|
-
|
|
606
|
+
logs: action.logs,
|
|
660
607
|
};
|
|
661
608
|
}
|
|
662
609
|
case ActionType.ToggleFilterDrawer: {
|
|
@@ -678,41 +625,71 @@ const reducer = (state: State, action: Action): State => {
|
|
|
678
625
|
case ActionType.ResetFilters: {
|
|
679
626
|
return {
|
|
680
627
|
...state,
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
628
|
+
pendingDateFilterState: action.initDateFilterState,
|
|
629
|
+
pendingContextFilterState: action.initContextFilterState,
|
|
630
|
+
pendingTagFilterState: action.initTagFilterState,
|
|
631
|
+
pendingActionErrorFilterState: action.initActionErrorFilterState,
|
|
632
|
+
pendingAdvancedFilterState: action.initAdvancedFilterState,
|
|
633
|
+
pageNumber: 1,
|
|
686
634
|
};
|
|
687
635
|
}
|
|
688
636
|
case ActionType.UpdateDateFilterState: {
|
|
689
637
|
return {
|
|
690
638
|
...state,
|
|
691
|
-
|
|
639
|
+
pendingDateFilterState: action.dateFilterState,
|
|
640
|
+
userMadeFilterChange: true,
|
|
692
641
|
};
|
|
693
642
|
}
|
|
694
643
|
case ActionType.UpdateContextFilterState: {
|
|
695
644
|
return {
|
|
696
645
|
...state,
|
|
697
|
-
|
|
646
|
+
pendingContextFilterState: action.contextFilterState,
|
|
647
|
+
userMadeFilterChange: true,
|
|
698
648
|
};
|
|
699
649
|
}
|
|
700
650
|
case ActionType.UpdateTagFilterState: {
|
|
701
651
|
return {
|
|
702
652
|
...state,
|
|
703
|
-
|
|
653
|
+
pendingTagFilterState: action.tagFilterState,
|
|
654
|
+
userMadeFilterChange: true,
|
|
704
655
|
};
|
|
705
656
|
}
|
|
706
657
|
case ActionType.UpdateActionErrorFilterState: {
|
|
707
658
|
return {
|
|
708
659
|
...state,
|
|
709
|
-
|
|
660
|
+
pendingActionErrorFilterState: action.actionErrorFilterState,
|
|
661
|
+
userMadeFilterChange: true,
|
|
710
662
|
};
|
|
711
663
|
}
|
|
712
664
|
case ActionType.UpdateAdvancedFilterState: {
|
|
713
665
|
return {
|
|
714
666
|
...state,
|
|
715
|
-
|
|
667
|
+
pendingAdvancedFilterState: action.advancedFilterState,
|
|
668
|
+
userMadeFilterChange: true,
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
case ActionType.SetHasAnotherPage: {
|
|
672
|
+
return {
|
|
673
|
+
...state,
|
|
674
|
+
hasAnotherPage: action.hasAnotherPage,
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
case ActionType.SetNumPages: {
|
|
678
|
+
return {
|
|
679
|
+
...state,
|
|
680
|
+
numPages: action.numPages,
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
case ActionType.SetPageNumber: {
|
|
684
|
+
return {
|
|
685
|
+
...state,
|
|
686
|
+
pageNumber: action.pageNumber,
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
case ActionType.ResetUserMadeFilterChange: {
|
|
690
|
+
return {
|
|
691
|
+
...state,
|
|
692
|
+
userMadeFilterChange: false,
|
|
716
693
|
};
|
|
717
694
|
}
|
|
718
695
|
default: {
|
|
@@ -750,7 +727,9 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
750
727
|
|
|
751
728
|
// If context has children, add an "uncategorized" subcontext
|
|
752
729
|
if (typeof contextMap[context] !== 'string') {
|
|
753
|
-
(contextMap[context] as any)[
|
|
730
|
+
(contextMap[context] as any)[
|
|
731
|
+
LogBuiltInMetadata.Context.Uncategorized
|
|
732
|
+
] = (
|
|
754
733
|
LogBuiltInMetadata.Context.Uncategorized
|
|
755
734
|
);
|
|
756
735
|
}
|
|
@@ -797,7 +776,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
797
776
|
} else {
|
|
798
777
|
// Case: subcontexts exist
|
|
799
778
|
initContextFilterState[context] = {};
|
|
800
|
-
Object.
|
|
779
|
+
Object.keys(contextMap[context]).forEach((subcontext) => {
|
|
801
780
|
// Skip self ("_")
|
|
802
781
|
if (subcontext === '_') {
|
|
803
782
|
return;
|
|
@@ -850,13 +829,17 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
850
829
|
// Initial state
|
|
851
830
|
const initialState: State = {
|
|
852
831
|
loading: true,
|
|
853
|
-
|
|
832
|
+
logs: [],
|
|
854
833
|
expandedFilterDrawer: undefined,
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
834
|
+
pendingDateFilterState: initDateFilterState,
|
|
835
|
+
pendingContextFilterState: initContextFilterState,
|
|
836
|
+
pendingTagFilterState: initTagFilterState,
|
|
837
|
+
pendingActionErrorFilterState: initActionErrorFilterState,
|
|
838
|
+
pendingAdvancedFilterState: initAdvancedFilterState,
|
|
839
|
+
pageNumber: 1,
|
|
840
|
+
hasAnotherPage: false,
|
|
841
|
+
numPages: 1,
|
|
842
|
+
userMadeFilterChange: false,
|
|
860
843
|
};
|
|
861
844
|
|
|
862
845
|
// Initialize state
|
|
@@ -865,133 +848,100 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
865
848
|
// Destructure common state
|
|
866
849
|
const {
|
|
867
850
|
loading,
|
|
868
|
-
|
|
851
|
+
logs,
|
|
869
852
|
expandedFilterDrawer,
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
853
|
+
pendingDateFilterState,
|
|
854
|
+
pendingContextFilterState,
|
|
855
|
+
pendingTagFilterState,
|
|
856
|
+
pendingActionErrorFilterState,
|
|
857
|
+
pendingAdvancedFilterState,
|
|
858
|
+
pageNumber,
|
|
859
|
+
numPages,
|
|
860
|
+
userMadeFilterChange,
|
|
875
861
|
} = state;
|
|
876
862
|
|
|
863
|
+
/* -------------- Refs -------------- */
|
|
864
|
+
|
|
865
|
+
// Initialize refs
|
|
866
|
+
const activeFiltersRef = useRef({
|
|
867
|
+
dateFilterState: JSON.parse(JSON.stringify(pendingDateFilterState)),
|
|
868
|
+
contextFilterState: JSON.parse(JSON.stringify(pendingContextFilterState)),
|
|
869
|
+
tagFilterState: JSON.parse(JSON.stringify(pendingTagFilterState)),
|
|
870
|
+
actionErrorFilterState: JSON.parse(JSON.stringify(pendingActionErrorFilterState)),
|
|
871
|
+
advancedFilterState: JSON.parse(JSON.stringify(pendingAdvancedFilterState)),
|
|
872
|
+
});
|
|
873
|
+
|
|
877
874
|
/*------------------------------------------------------------------------*/
|
|
878
875
|
/* ------------------------- Component Functions ------------------------ */
|
|
879
876
|
/*------------------------------------------------------------------------*/
|
|
880
877
|
|
|
881
878
|
/**
|
|
882
|
-
*
|
|
883
|
-
*
|
|
884
|
-
* @
|
|
885
|
-
* @param
|
|
886
|
-
* @
|
|
887
|
-
|
|
888
|
-
const listMonthsToLoad = (
|
|
889
|
-
newDateFilterState: DateFilterState,
|
|
890
|
-
): { year: number, month: number }[] => {
|
|
891
|
-
// List of year/month combos that need to be loaded
|
|
892
|
-
const toLoad: { year: number, month: number }[] = [];
|
|
893
|
-
|
|
894
|
-
// Loop through dates
|
|
895
|
-
let { year, month } = newDateFilterState.startDate;
|
|
896
|
-
while (
|
|
897
|
-
// Earlier year
|
|
898
|
-
(year < newDateFilterState.endDate.year)
|
|
899
|
-
// Current year but included month
|
|
900
|
-
|| (
|
|
901
|
-
year === newDateFilterState.endDate.year
|
|
902
|
-
&& month <= newDateFilterState.endDate.month
|
|
903
|
-
)
|
|
904
|
-
) {
|
|
905
|
-
// Add to list if not already loaded
|
|
906
|
-
if (
|
|
907
|
-
!logMap[year]
|
|
908
|
-
|| !logMap[year][month]
|
|
909
|
-
) {
|
|
910
|
-
toLoad.push({
|
|
911
|
-
year,
|
|
912
|
-
month,
|
|
913
|
-
});
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
// Increment
|
|
917
|
-
month += 1;
|
|
918
|
-
if (month > 12) {
|
|
919
|
-
month -= 12;
|
|
920
|
-
year += 1;
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
// Return
|
|
925
|
-
return toLoad;
|
|
926
|
-
};
|
|
927
|
-
|
|
928
|
-
/**
|
|
929
|
-
* Handle updated start/end dates (updates state, loads if necessary)
|
|
930
|
-
* @author Gabe Abrams
|
|
931
|
-
* @param newDateFilterState the new date filter state
|
|
879
|
+
* Fetch logs from the server based on current filters
|
|
880
|
+
* @author Yuen Ler Chow
|
|
881
|
+
* @param opts object containing all arguments
|
|
882
|
+
* @param opts.filters the filters to apply
|
|
883
|
+
* @param opts.pageNum the page number to fetch
|
|
884
|
+
* @param opts.filtersChanged if true, the filters have changed
|
|
932
885
|
*/
|
|
933
|
-
const
|
|
934
|
-
|
|
886
|
+
const fetchLogs = async (
|
|
887
|
+
opts: {
|
|
888
|
+
filters: LogReviewerFilterState,
|
|
889
|
+
pageNum: number,
|
|
890
|
+
filtersChanged: boolean,
|
|
891
|
+
},
|
|
935
892
|
) => {
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
}
|
|
893
|
+
const {
|
|
894
|
+
filters,
|
|
895
|
+
pageNum,
|
|
896
|
+
filtersChanged,
|
|
897
|
+
} = opts;
|
|
941
898
|
|
|
942
|
-
// Check which year/month combos we need to load
|
|
943
|
-
const toLoad = listMonthsToLoad(newDateFilterState);
|
|
944
|
-
|
|
945
|
-
// If nothing to load, finished
|
|
946
|
-
if (toLoad.length === 0) {
|
|
947
|
-
return;
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
// Start loading
|
|
951
899
|
dispatch({
|
|
952
900
|
type: ActionType.StartLoading,
|
|
953
901
|
});
|
|
954
902
|
|
|
955
|
-
// Load required months
|
|
956
903
|
try {
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
params: {
|
|
971
|
-
pageNumber,
|
|
972
|
-
},
|
|
973
|
-
});
|
|
974
|
-
|
|
975
|
-
logs = logs.concat(response.items);
|
|
976
|
-
hasAnotherPage = response.hasAnotherPage;
|
|
977
|
-
pageNumber += 1;
|
|
978
|
-
}
|
|
904
|
+
// Send filters to the server
|
|
905
|
+
let fetchedLogs: Log[] = [];
|
|
906
|
+
|
|
907
|
+
// Get logs from server
|
|
908
|
+
const response = await visitServerEndpoint({
|
|
909
|
+
path: LOG_REVIEW_GET_LOGS_ROUTE,
|
|
910
|
+
method: 'GET',
|
|
911
|
+
params: {
|
|
912
|
+
pageNumber: pageNum,
|
|
913
|
+
filters,
|
|
914
|
+
countDocuments: filtersChanged,
|
|
915
|
+
},
|
|
916
|
+
});
|
|
979
917
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
918
|
+
fetchedLogs = fetchedLogs.concat(response.items);
|
|
919
|
+
dispatch({
|
|
920
|
+
type: ActionType.SetHasAnotherPage,
|
|
921
|
+
hasAnotherPage: response.hasAnotherPage,
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
if (filtersChanged && response.numPages !== undefined) {
|
|
925
|
+
dispatch({
|
|
926
|
+
type: ActionType.SetNumPages,
|
|
927
|
+
numPages: response.numPages,
|
|
928
|
+
});
|
|
985
929
|
}
|
|
930
|
+
|
|
931
|
+
// Update logs in state
|
|
932
|
+
dispatch({
|
|
933
|
+
type: ActionType.FinishLoading,
|
|
934
|
+
logs: fetchedLogs,
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
// Update page number
|
|
938
|
+
dispatch({
|
|
939
|
+
type: ActionType.SetPageNumber,
|
|
940
|
+
pageNumber: pageNum,
|
|
941
|
+
});
|
|
986
942
|
} catch (err) {
|
|
987
943
|
return showFatalError(err);
|
|
988
944
|
}
|
|
989
|
-
|
|
990
|
-
// Finish loading
|
|
991
|
-
dispatch({
|
|
992
|
-
type: ActionType.FinishLoading,
|
|
993
|
-
logMap,
|
|
994
|
-
});
|
|
995
945
|
};
|
|
996
946
|
|
|
997
947
|
/*------------------------------------------------------------------------*/
|
|
@@ -999,350 +949,454 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
999
949
|
/*------------------------------------------------------------------------*/
|
|
1000
950
|
|
|
1001
951
|
/**
|
|
1002
|
-
*
|
|
1003
|
-
* @author
|
|
952
|
+
* Fetch logs on mount
|
|
953
|
+
* @author Yuen Ler Chow
|
|
1004
954
|
*/
|
|
1005
|
-
useEffect(
|
|
1006
|
-
(
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
955
|
+
useEffect(() => {
|
|
956
|
+
fetchLogs({
|
|
957
|
+
filters: {
|
|
958
|
+
dateFilterState: pendingDateFilterState,
|
|
959
|
+
contextFilterState: pendingContextFilterState,
|
|
960
|
+
tagFilterState: pendingTagFilterState,
|
|
961
|
+
actionErrorFilterState: pendingActionErrorFilterState,
|
|
962
|
+
advancedFilterState: pendingAdvancedFilterState,
|
|
963
|
+
},
|
|
964
|
+
pageNum: 1,
|
|
965
|
+
filtersChanged: true,
|
|
966
|
+
});
|
|
967
|
+
}, []);
|
|
1012
968
|
|
|
1013
969
|
/*------------------------------------------------------------------------*/
|
|
1014
970
|
/* ------------------------------- Render ------------------------------- */
|
|
1015
971
|
/*------------------------------------------------------------------------*/
|
|
1016
|
-
|
|
1017
972
|
/*----------------------------------------*/
|
|
1018
973
|
/* --------------- Main UI -------------- */
|
|
1019
974
|
/*----------------------------------------*/
|
|
1020
975
|
|
|
1021
|
-
|
|
1022
|
-
|
|
976
|
+
/*----------------------------------------*/
|
|
977
|
+
/* ------------ Pagination -------------- */
|
|
978
|
+
/*----------------------------------------*/
|
|
979
|
+
|
|
980
|
+
const paginationControls = logs.length > 0 && (
|
|
981
|
+
<Pagination
|
|
982
|
+
currentPage={pageNumber}
|
|
983
|
+
numPages={numPages}
|
|
984
|
+
loading={loading}
|
|
985
|
+
onPageChanged={(targetPage) => {
|
|
986
|
+
const { current: activeFilters } = activeFiltersRef;
|
|
987
|
+
fetchLogs({
|
|
988
|
+
filters: {
|
|
989
|
+
dateFilterState: activeFilters.dateFilterState,
|
|
990
|
+
contextFilterState: activeFilters.contextFilterState,
|
|
991
|
+
tagFilterState: activeFilters.tagFilterState,
|
|
992
|
+
actionErrorFilterState: activeFilters.actionErrorFilterState,
|
|
993
|
+
advancedFilterState: activeFilters.advancedFilterState,
|
|
994
|
+
},
|
|
995
|
+
pageNum: targetPage,
|
|
996
|
+
filtersChanged: false,
|
|
997
|
+
});
|
|
998
|
+
}}
|
|
999
|
+
/>
|
|
1000
|
+
);
|
|
1023
1001
|
|
|
1024
|
-
|
|
1002
|
+
/*----------------------------------------*/
|
|
1003
|
+
/* --------------- Filters -------------- */
|
|
1004
|
+
/*----------------------------------------*/
|
|
1025
1005
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1006
|
+
// Filter toggle
|
|
1007
|
+
const filterToggles = (
|
|
1008
|
+
<div className="LogReviewer-filter-toggles">
|
|
1009
|
+
<div className="LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0">
|
|
1010
|
+
{/* Date */}
|
|
1011
|
+
<button
|
|
1012
|
+
type="button"
|
|
1013
|
+
id="LogReviewer-toggle-date-filter-drawer"
|
|
1014
|
+
className={`btn btn-${FilterDrawer.Date === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
1015
|
+
aria-label="toggle date filter drawer"
|
|
1016
|
+
onClick={() => {
|
|
1017
|
+
dispatch({
|
|
1018
|
+
type: ActionType.ToggleFilterDrawer,
|
|
1019
|
+
filterDrawer: FilterDrawer.Date,
|
|
1020
|
+
});
|
|
1021
|
+
}}
|
|
1022
|
+
>
|
|
1023
|
+
<FontAwesomeIcon
|
|
1024
|
+
icon={faCalendar}
|
|
1025
|
+
className="me-2"
|
|
1026
|
+
/>
|
|
1027
|
+
Date
|
|
1028
|
+
</button>
|
|
1029
|
+
{/* Context */}
|
|
1030
|
+
<button
|
|
1031
|
+
type="button"
|
|
1032
|
+
id="LogReviewer-toggle-context-filter-drawer"
|
|
1033
|
+
className={`btn btn-${FilterDrawer.Context === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
1034
|
+
aria-label="toggle context filter drawer"
|
|
1035
|
+
onClick={() => {
|
|
1036
|
+
dispatch({
|
|
1037
|
+
type: ActionType.ToggleFilterDrawer,
|
|
1038
|
+
filterDrawer: FilterDrawer.Context,
|
|
1039
|
+
});
|
|
1040
|
+
}}
|
|
1041
|
+
>
|
|
1042
|
+
<FontAwesomeIcon
|
|
1043
|
+
icon={faCircle}
|
|
1044
|
+
className="me-2"
|
|
1045
|
+
/>
|
|
1046
|
+
Context
|
|
1047
|
+
</button>
|
|
1048
|
+
{/* Tag */}
|
|
1049
|
+
{(LogMetadata.Tag && Object.keys(LogMetadata.Tag).length > 0) && (
|
|
1050
|
+
<button
|
|
1051
|
+
type="button"
|
|
1052
|
+
id="LogReviewer-toggle-tag-filter-drawer"
|
|
1053
|
+
className={`btn btn-${FilterDrawer.Tag === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
1054
|
+
aria-label="toggle tag filter drawer"
|
|
1055
|
+
onClick={() => {
|
|
1056
|
+
dispatch({
|
|
1057
|
+
type: ActionType.ToggleFilterDrawer,
|
|
1058
|
+
filterDrawer: FilterDrawer.Tag,
|
|
1059
|
+
});
|
|
1060
|
+
}}
|
|
1061
|
+
>
|
|
1062
|
+
<FontAwesomeIcon
|
|
1063
|
+
icon={faTag}
|
|
1064
|
+
className="me-2"
|
|
1065
|
+
/>
|
|
1066
|
+
Tag
|
|
1067
|
+
</button>
|
|
1068
|
+
)}
|
|
1069
|
+
{/* Action */}
|
|
1070
|
+
<button
|
|
1071
|
+
type="button"
|
|
1072
|
+
id="LogReviewer-toggle-action-filter-drawer"
|
|
1073
|
+
className={`btn btn-${FilterDrawer.Action === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
1074
|
+
aria-label="toggle action and error filter drawer"
|
|
1075
|
+
onClick={() => {
|
|
1076
|
+
dispatch({
|
|
1077
|
+
type: ActionType.ToggleFilterDrawer,
|
|
1078
|
+
filterDrawer: FilterDrawer.Action,
|
|
1079
|
+
});
|
|
1080
|
+
}}
|
|
1081
|
+
>
|
|
1082
|
+
<FontAwesomeIcon
|
|
1083
|
+
icon={faHammer}
|
|
1084
|
+
className="me-2"
|
|
1085
|
+
/>
|
|
1086
|
+
Action
|
|
1087
|
+
</button>
|
|
1088
|
+
{/* Advanced */}
|
|
1089
|
+
<button
|
|
1090
|
+
type="button"
|
|
1091
|
+
id="LogReviewer-toggle-advanced-filter-drawer"
|
|
1092
|
+
className={`btn btn-${FilterDrawer.Advanced === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
1093
|
+
aria-label="toggle advanced filter drawer"
|
|
1094
|
+
onClick={() => {
|
|
1095
|
+
dispatch({
|
|
1096
|
+
type: ActionType.ToggleFilterDrawer,
|
|
1097
|
+
filterDrawer: FilterDrawer.Advanced,
|
|
1098
|
+
});
|
|
1099
|
+
}}
|
|
1100
|
+
>
|
|
1101
|
+
<FontAwesomeIcon
|
|
1102
|
+
icon={faList}
|
|
1103
|
+
className="me-2"
|
|
1104
|
+
/>
|
|
1105
|
+
Advanced
|
|
1106
|
+
</button>
|
|
1107
|
+
{/* Reset */}
|
|
1108
|
+
<button
|
|
1109
|
+
type="button"
|
|
1110
|
+
id="LogReviewer-reset-filters-button"
|
|
1111
|
+
className="btn btn-light"
|
|
1112
|
+
aria-label="reset filters"
|
|
1113
|
+
onClick={() => {
|
|
1114
|
+
// Save active filters
|
|
1115
|
+
// Deep clone the initial filter states to avoid any reference issues
|
|
1116
|
+
// if the filter states are modified later
|
|
1117
|
+
activeFiltersRef.current = cloneDeep({
|
|
1118
|
+
dateFilterState: initDateFilterState,
|
|
1119
|
+
contextFilterState: initContextFilterState,
|
|
1120
|
+
tagFilterState: initTagFilterState,
|
|
1121
|
+
actionErrorFilterState: initActionErrorFilterState,
|
|
1122
|
+
advancedFilterState: initAdvancedFilterState,
|
|
1123
|
+
});
|
|
1124
|
+
fetchLogs(
|
|
1125
|
+
{
|
|
1126
|
+
filters: {
|
|
1127
|
+
dateFilterState: initDateFilterState,
|
|
1128
|
+
contextFilterState: initContextFilterState,
|
|
1129
|
+
tagFilterState: initTagFilterState,
|
|
1130
|
+
actionErrorFilterState: initActionErrorFilterState,
|
|
1131
|
+
advancedFilterState: initAdvancedFilterState,
|
|
1132
|
+
},
|
|
1133
|
+
pageNum: 1,
|
|
1134
|
+
filtersChanged: true,
|
|
1135
|
+
},
|
|
1136
|
+
);
|
|
1137
|
+
dispatch({
|
|
1138
|
+
type: ActionType.ResetFilters,
|
|
1139
|
+
initActionErrorFilterState,
|
|
1140
|
+
initAdvancedFilterState,
|
|
1141
|
+
initContextFilterState,
|
|
1142
|
+
initDateFilterState,
|
|
1143
|
+
initTagFilterState,
|
|
1144
|
+
});
|
|
1145
|
+
dispatch({
|
|
1146
|
+
type: ActionType.HideFilterDrawer,
|
|
1147
|
+
});
|
|
1148
|
+
}}
|
|
1149
|
+
>
|
|
1150
|
+
<FontAwesomeIcon
|
|
1151
|
+
icon={faTimes}
|
|
1152
|
+
/>
|
|
1153
|
+
{' '}
|
|
1154
|
+
Reset
|
|
1155
|
+
</button>
|
|
1156
|
+
|
|
1157
|
+
{/* Submit filter changes */}
|
|
1158
|
+
{ userMadeFilterChange && (
|
|
1159
|
+
<button
|
|
1160
|
+
type="button"
|
|
1161
|
+
id="LogReviewer-submit-filters-button"
|
|
1162
|
+
className="btn btn-primary ms-2"
|
|
1163
|
+
aria-label="submit filters"
|
|
1164
|
+
onClick={() => {
|
|
1165
|
+
dispatch({
|
|
1166
|
+
type: ActionType.HideFilterDrawer,
|
|
1167
|
+
});
|
|
1168
|
+
|
|
1169
|
+
// Reset user made filter change indicator
|
|
1170
|
+
dispatch({
|
|
1171
|
+
type: ActionType.ResetUserMadeFilterChange,
|
|
1172
|
+
});
|
|
1173
|
+
|
|
1174
|
+
// Save active filters
|
|
1175
|
+
// Deep clone the pending filter states to avoid any reference issues
|
|
1176
|
+
// if the filter states are modified later
|
|
1177
|
+
activeFiltersRef.current = cloneDeep({
|
|
1178
|
+
dateFilterState: pendingDateFilterState,
|
|
1179
|
+
contextFilterState: pendingContextFilterState,
|
|
1180
|
+
tagFilterState: pendingTagFilterState,
|
|
1181
|
+
actionErrorFilterState: pendingActionErrorFilterState,
|
|
1182
|
+
advancedFilterState: pendingAdvancedFilterState,
|
|
1183
|
+
});
|
|
1184
|
+
|
|
1185
|
+
fetchLogs(
|
|
1186
|
+
{
|
|
1187
|
+
filters: {
|
|
1188
|
+
dateFilterState: pendingDateFilterState,
|
|
1189
|
+
contextFilterState: pendingContextFilterState,
|
|
1190
|
+
tagFilterState: pendingTagFilterState,
|
|
1191
|
+
actionErrorFilterState: pendingActionErrorFilterState,
|
|
1192
|
+
advancedFilterState: pendingAdvancedFilterState,
|
|
1193
|
+
},
|
|
1194
|
+
pageNum: 1,
|
|
1195
|
+
filtersChanged: true,
|
|
1196
|
+
},
|
|
1197
|
+
);
|
|
1198
|
+
}}
|
|
1199
|
+
>
|
|
1200
|
+
<FontAwesomeIcon
|
|
1201
|
+
icon={faSearch}
|
|
1202
|
+
/>
|
|
1203
|
+
{' '}
|
|
1204
|
+
Apply Filters
|
|
1205
|
+
</button>
|
|
1206
|
+
)}
|
|
1030
1207
|
</div>
|
|
1031
|
-
|
|
1032
|
-
|
|
1208
|
+
</div>
|
|
1209
|
+
);
|
|
1033
1210
|
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
if (
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
aria-label="toggle date filter drawer"
|
|
1054
|
-
onClick={() => {
|
|
1055
|
-
dispatch({
|
|
1056
|
-
type: ActionType.ToggleFilterDrawer,
|
|
1057
|
-
filterDrawer: FilterDrawer.Date,
|
|
1058
|
-
});
|
|
1059
|
-
}}
|
|
1060
|
-
>
|
|
1061
|
-
<FontAwesomeIcon
|
|
1062
|
-
icon={faCalendar}
|
|
1063
|
-
className="me-2"
|
|
1064
|
-
/>
|
|
1065
|
-
Date
|
|
1066
|
-
</button>
|
|
1067
|
-
{/* Context */}
|
|
1068
|
-
<button
|
|
1069
|
-
type="button"
|
|
1070
|
-
id="LogReviewer-toggle-context-filter-drawer"
|
|
1071
|
-
className={`btn btn-${FilterDrawer.Context === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
1072
|
-
aria-label="toggle context filter drawer"
|
|
1073
|
-
onClick={() => {
|
|
1074
|
-
dispatch({
|
|
1075
|
-
type: ActionType.ToggleFilterDrawer,
|
|
1076
|
-
filterDrawer: FilterDrawer.Context,
|
|
1077
|
-
});
|
|
1078
|
-
}}
|
|
1079
|
-
>
|
|
1080
|
-
<FontAwesomeIcon
|
|
1081
|
-
icon={faCircle}
|
|
1082
|
-
className="me-2"
|
|
1083
|
-
/>
|
|
1084
|
-
Context
|
|
1085
|
-
</button>
|
|
1086
|
-
{/* Tag */}
|
|
1087
|
-
{/* Skip if no tags are used */}
|
|
1088
|
-
{(LogMetadata.Tag && Object.keys(LogMetadata.Tag).length > 0) && (
|
|
1089
|
-
<button
|
|
1090
|
-
type="button"
|
|
1091
|
-
id="LogReviewer-toggle-tag-filter-drawer"
|
|
1092
|
-
className={`btn btn-${FilterDrawer.Tag === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
1093
|
-
aria-label="toggle tag filter drawer"
|
|
1094
|
-
onClick={() => {
|
|
1095
|
-
dispatch({
|
|
1096
|
-
type: ActionType.ToggleFilterDrawer,
|
|
1097
|
-
filterDrawer: FilterDrawer.Tag,
|
|
1098
|
-
});
|
|
1099
|
-
}}
|
|
1100
|
-
>
|
|
1101
|
-
<FontAwesomeIcon
|
|
1102
|
-
icon={faTag}
|
|
1103
|
-
className="me-2"
|
|
1104
|
-
/>
|
|
1105
|
-
Tag
|
|
1106
|
-
</button>
|
|
1107
|
-
)}
|
|
1108
|
-
{/* Action */}
|
|
1109
|
-
<button
|
|
1110
|
-
type="button"
|
|
1111
|
-
id="LogReviewer-toggle-action-filter-drawer"
|
|
1112
|
-
className={`btn btn-${FilterDrawer.Action === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
1113
|
-
aria-label="toggle action and error filter drawer"
|
|
1114
|
-
onClick={() => {
|
|
1115
|
-
dispatch({
|
|
1116
|
-
type: ActionType.ToggleFilterDrawer,
|
|
1117
|
-
filterDrawer: FilterDrawer.Action,
|
|
1118
|
-
});
|
|
1119
|
-
}}
|
|
1120
|
-
>
|
|
1121
|
-
<FontAwesomeIcon
|
|
1122
|
-
icon={faHammer}
|
|
1123
|
-
className="me-2"
|
|
1124
|
-
/>
|
|
1125
|
-
Action
|
|
1126
|
-
</button>
|
|
1127
|
-
{/* Advanced */}
|
|
1128
|
-
<button
|
|
1129
|
-
type="button"
|
|
1130
|
-
id="LogReviewer-toggle-advanced-filter-drawer"
|
|
1131
|
-
className={`btn btn-${FilterDrawer.Advanced === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
1132
|
-
aria-label="toggle advanced filter drawer"
|
|
1133
|
-
onClick={() => {
|
|
1134
|
-
dispatch({
|
|
1135
|
-
type: ActionType.ToggleFilterDrawer,
|
|
1136
|
-
filterDrawer: FilterDrawer.Advanced,
|
|
1137
|
-
});
|
|
1138
|
-
}}
|
|
1139
|
-
>
|
|
1140
|
-
<FontAwesomeIcon
|
|
1141
|
-
icon={faList}
|
|
1142
|
-
className="me-2"
|
|
1143
|
-
/>
|
|
1144
|
-
Advanced
|
|
1145
|
-
</button>
|
|
1146
|
-
{/* Reset */}
|
|
1147
|
-
<button
|
|
1148
|
-
type="button"
|
|
1149
|
-
id="LogReviewer-reset-filters-button"
|
|
1150
|
-
className="btn btn-light"
|
|
1151
|
-
aria-label="reset filters"
|
|
1152
|
-
onClick={() => {
|
|
1211
|
+
// Filter drawer
|
|
1212
|
+
let filterDrawer: React.ReactNode;
|
|
1213
|
+
if (expandedFilterDrawer) {
|
|
1214
|
+
if (expandedFilterDrawer === FilterDrawer.Date) {
|
|
1215
|
+
filterDrawer = (
|
|
1216
|
+
<TabBox title="Date">
|
|
1217
|
+
<SimpleDateChooser
|
|
1218
|
+
ariaLabel="filter start date"
|
|
1219
|
+
name="filter-start-date"
|
|
1220
|
+
year={pendingDateFilterState.startDate.year}
|
|
1221
|
+
month={pendingDateFilterState.startDate.month}
|
|
1222
|
+
day={pendingDateFilterState.startDate.day}
|
|
1223
|
+
dontAllowFuture
|
|
1224
|
+
numMonthsToShow={36}
|
|
1225
|
+
onChange={(month, day, year) => {
|
|
1226
|
+
const newDateFilterState = {
|
|
1227
|
+
...pendingDateFilterState,
|
|
1228
|
+
startDate: { month, day, year },
|
|
1229
|
+
};
|
|
1153
1230
|
dispatch({
|
|
1154
|
-
type: ActionType.
|
|
1155
|
-
|
|
1156
|
-
initAdvancedFilterState,
|
|
1157
|
-
initContextFilterState,
|
|
1158
|
-
initDateFilterState,
|
|
1159
|
-
initTagFilterState,
|
|
1231
|
+
type: ActionType.UpdateDateFilterState,
|
|
1232
|
+
dateFilterState: newDateFilterState,
|
|
1160
1233
|
});
|
|
1161
1234
|
}}
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
filterDrawer = (
|
|
1178
|
-
<TabBox title="Date">
|
|
1179
|
-
<SimpleDateChooser
|
|
1180
|
-
ariaLabel="filter start date"
|
|
1181
|
-
name="filter-start-date"
|
|
1182
|
-
year={dateFilterState.startDate.year}
|
|
1183
|
-
month={dateFilterState.startDate.month}
|
|
1184
|
-
day={dateFilterState.startDate.day}
|
|
1185
|
-
dontAllowFuture
|
|
1186
|
-
numMonthsToShow={36}
|
|
1187
|
-
onChange={(month, day, year) => {
|
|
1188
|
-
dateFilterState.startDate = { month, day, year };
|
|
1189
|
-
handleDateRangeUpdated(dateFilterState);
|
|
1190
|
-
}}
|
|
1191
|
-
/>
|
|
1192
|
-
{' '}
|
|
1193
|
-
to
|
|
1194
|
-
{' '}
|
|
1195
|
-
<SimpleDateChooser
|
|
1196
|
-
ariaLabel="filter end date"
|
|
1197
|
-
name="filter-end-date"
|
|
1198
|
-
year={dateFilterState.endDate.year}
|
|
1199
|
-
month={dateFilterState.endDate.month}
|
|
1200
|
-
day={dateFilterState.endDate.day}
|
|
1201
|
-
dontAllowFuture
|
|
1202
|
-
numMonthsToShow={12}
|
|
1203
|
-
onChange={(month, day, year) => {
|
|
1204
|
-
if (
|
|
1205
|
-
year < dateFilterState.startDate.year
|
|
1235
|
+
/>
|
|
1236
|
+
{' '}
|
|
1237
|
+
to
|
|
1238
|
+
{' '}
|
|
1239
|
+
<SimpleDateChooser
|
|
1240
|
+
ariaLabel="filter end date"
|
|
1241
|
+
name="filter-end-date"
|
|
1242
|
+
year={pendingDateFilterState.endDate.year}
|
|
1243
|
+
month={pendingDateFilterState.endDate.month}
|
|
1244
|
+
day={pendingDateFilterState.endDate.day}
|
|
1245
|
+
dontAllowFuture
|
|
1246
|
+
numMonthsToShow={12}
|
|
1247
|
+
onChange={(month, day, year) => {
|
|
1248
|
+
if (
|
|
1249
|
+
year < pendingDateFilterState.startDate.year
|
|
1206
1250
|
|| (
|
|
1207
|
-
year ===
|
|
1208
|
-
&& month <
|
|
1251
|
+
year === pendingDateFilterState.startDate.year
|
|
1252
|
+
&& month < pendingDateFilterState.startDate.month
|
|
1209
1253
|
)
|
|
1210
1254
|
|| (
|
|
1211
|
-
year ===
|
|
1212
|
-
&& month ===
|
|
1213
|
-
&& day <
|
|
1255
|
+
year === pendingDateFilterState.startDate.year
|
|
1256
|
+
&& month === pendingDateFilterState.startDate.month
|
|
1257
|
+
&& day < pendingDateFilterState.startDate.day
|
|
1214
1258
|
)
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
}
|
|
1221
|
-
dateFilterState.endDate = { month, day, year };
|
|
1222
|
-
handleDateRangeUpdated(dateFilterState);
|
|
1223
|
-
}}
|
|
1224
|
-
/>
|
|
1225
|
-
</TabBox>
|
|
1226
|
-
);
|
|
1227
|
-
} else if (expandedFilterDrawer === FilterDrawer.Context) {
|
|
1228
|
-
// Create item picker items
|
|
1229
|
-
const builtInPickableItem: PickableItem = {
|
|
1230
|
-
id: 'built-in-contexts',
|
|
1231
|
-
name: 'Auto-logged',
|
|
1232
|
-
isGroup: true,
|
|
1233
|
-
children: [],
|
|
1234
|
-
};
|
|
1235
|
-
const pickableItems: PickableItem[] = [];
|
|
1236
|
-
Object.keys(contextMap)
|
|
1237
|
-
.forEach((context) => {
|
|
1238
|
-
const value = contextMap[context];
|
|
1239
|
-
if (typeof value === 'string') {
|
|
1240
|
-
// No subcategories
|
|
1241
|
-
const item: PickableItem = {
|
|
1242
|
-
id: context,
|
|
1243
|
-
name: genHumanReadableName(context),
|
|
1244
|
-
isGroup: false,
|
|
1245
|
-
checked: !!contextFilterState[context],
|
|
1246
|
-
};
|
|
1247
|
-
|
|
1248
|
-
// Add built-in items to its own folder
|
|
1249
|
-
const isBuiltIn = context in LogBuiltInMetadata.Context;
|
|
1250
|
-
if (isBuiltIn) {
|
|
1251
|
-
// Add to built-in pickable item
|
|
1252
|
-
builtInPickableItem.children.push(item);
|
|
1253
|
-
} else {
|
|
1254
|
-
// Add to pickable items list
|
|
1255
|
-
pickableItems.push(item);
|
|
1259
|
+
) {
|
|
1260
|
+
return alert(
|
|
1261
|
+
'Invalid End Date',
|
|
1262
|
+
'The end date cannot be before the start date.',
|
|
1263
|
+
);
|
|
1256
1264
|
}
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1265
|
+
const newDateFilterState = {
|
|
1266
|
+
...pendingDateFilterState,
|
|
1267
|
+
endDate: { month, day, year },
|
|
1268
|
+
};
|
|
1269
|
+
dispatch({
|
|
1270
|
+
type: ActionType.UpdateDateFilterState,
|
|
1271
|
+
dateFilterState: newDateFilterState,
|
|
1272
|
+
});
|
|
1273
|
+
}}
|
|
1274
|
+
/>
|
|
1275
|
+
</TabBox>
|
|
1276
|
+
);
|
|
1277
|
+
} else if (expandedFilterDrawer === FilterDrawer.Context) {
|
|
1278
|
+
// Create item picker items
|
|
1279
|
+
const builtInPickableItem: PickableItem = {
|
|
1280
|
+
id: 'built-in-contexts',
|
|
1281
|
+
name: 'Auto-logged',
|
|
1282
|
+
isGroup: true,
|
|
1283
|
+
children: [],
|
|
1284
|
+
};
|
|
1285
|
+
const pickableItems: PickableItem[] = [];
|
|
1286
|
+
Object.keys(contextMap)
|
|
1287
|
+
.forEach((context) => {
|
|
1288
|
+
const value = contextMap[context];
|
|
1289
|
+
if (typeof value === 'string') {
|
|
1290
|
+
// No subcategories
|
|
1277
1291
|
const item: PickableItem = {
|
|
1278
1292
|
id: context,
|
|
1279
1293
|
name: genHumanReadableName(context),
|
|
1280
|
-
isGroup:
|
|
1281
|
-
|
|
1294
|
+
isGroup: false,
|
|
1295
|
+
checked: !!pendingContextFilterState[context],
|
|
1282
1296
|
};
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1297
|
+
|
|
1298
|
+
// Add built-in items to its own folder
|
|
1299
|
+
const isBuiltIn = context in LogBuiltInMetadata.Context;
|
|
1300
|
+
if (isBuiltIn) {
|
|
1301
|
+
// Add to built-in pickable item
|
|
1302
|
+
builtInPickableItem.children.push(item);
|
|
1303
|
+
} else {
|
|
1304
|
+
// Add to pickable items list
|
|
1305
|
+
pickableItems.push(item);
|
|
1306
|
+
}
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
// Has subcategories
|
|
1311
|
+
const children: PickableItem[] = (
|
|
1312
|
+
Object.keys(value)
|
|
1313
|
+
// Remove parent name
|
|
1314
|
+
.filter((subcontext) => {
|
|
1315
|
+
return (subcontext !== '_');
|
|
1316
|
+
})
|
|
1317
|
+
// Create child pickable items
|
|
1318
|
+
.map((subcontext) => {
|
|
1319
|
+
return {
|
|
1320
|
+
id: subcontext,
|
|
1321
|
+
name: genHumanReadableName(subcontext),
|
|
1322
|
+
isGroup: false,
|
|
1323
|
+
checked: (pendingContextFilterState[context] as any)[subcontext],
|
|
1324
|
+
};
|
|
1325
|
+
})
|
|
1326
|
+
);
|
|
1327
|
+
const item: PickableItem = {
|
|
1328
|
+
id: context,
|
|
1329
|
+
name: genHumanReadableName(context),
|
|
1330
|
+
isGroup: true,
|
|
1331
|
+
children,
|
|
1332
|
+
};
|
|
1333
|
+
pickableItems.push(item);
|
|
1334
|
+
});
|
|
1335
|
+
// Add built-in contexts to end of list
|
|
1336
|
+
pickableItems.push(builtInPickableItem);
|
|
1337
|
+
|
|
1338
|
+
// Create filter UI
|
|
1339
|
+
filterDrawer = (
|
|
1340
|
+
<ItemPicker
|
|
1341
|
+
title="Context"
|
|
1342
|
+
items={pickableItems}
|
|
1343
|
+
onChanged={(updatedItems) => {
|
|
1344
|
+
// Update our state
|
|
1345
|
+
const newContextFilterState = { ...pendingContextFilterState };
|
|
1346
|
+
updatedItems.forEach((pickableItem) => {
|
|
1347
|
+
if (pickableItem.isGroup) {
|
|
1348
|
+
// Has subcontexts
|
|
1349
|
+
|
|
1350
|
+
if (pickableItem.id === 'built-in-contexts') {
|
|
1351
|
+
// Built-in
|
|
1352
|
+
|
|
1353
|
+
// Treat as if these were top-level contexts
|
|
1354
|
+
pickableItem.children.forEach((subcontextItem) => {
|
|
1355
|
+
newContextFilterState[subcontextItem.id] = (
|
|
1356
|
+
'checked' in subcontextItem
|
|
1306
1357
|
&& subcontextItem.checked
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
} else {
|
|
1310
|
-
// Not built-in
|
|
1311
|
-
pickableItem.children.forEach((subcontextItem) => {
|
|
1312
|
-
if (!subcontextItem.isGroup) {
|
|
1313
|
-
(
|
|
1314
|
-
contextFilterState[pickableItem.id] as { [k: string]: boolean }
|
|
1315
|
-
)[subcontextItem.id] = (
|
|
1316
|
-
subcontextItem.checked
|
|
1317
|
-
);
|
|
1318
|
-
}
|
|
1319
|
-
});
|
|
1320
|
-
}
|
|
1358
|
+
);
|
|
1359
|
+
});
|
|
1321
1360
|
} else {
|
|
1322
|
-
//
|
|
1323
|
-
(
|
|
1324
|
-
|
|
1325
|
-
|
|
1361
|
+
// Not built-in
|
|
1362
|
+
pickableItem.children.forEach((subcontextItem) => {
|
|
1363
|
+
if (!subcontextItem.isGroup) {
|
|
1364
|
+
(
|
|
1365
|
+
newContextFilterState[pickableItem.id] as {
|
|
1366
|
+
[k: string]: boolean
|
|
1367
|
+
}
|
|
1368
|
+
)[subcontextItem.id] = (
|
|
1369
|
+
subcontextItem.checked
|
|
1370
|
+
);
|
|
1371
|
+
}
|
|
1372
|
+
});
|
|
1326
1373
|
}
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1374
|
+
} else {
|
|
1375
|
+
// No subcontexts
|
|
1376
|
+
(newContextFilterState as any)[pickableItem.id] = (
|
|
1377
|
+
pickableItem.checked
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1380
|
+
});
|
|
1381
|
+
|
|
1382
|
+
// Update state
|
|
1383
|
+
dispatch({
|
|
1384
|
+
type: ActionType.UpdateContextFilterState,
|
|
1385
|
+
contextFilterState: newContextFilterState,
|
|
1386
|
+
});
|
|
1387
|
+
}}
|
|
1388
|
+
/>
|
|
1389
|
+
);
|
|
1390
|
+
} else if (expandedFilterDrawer === FilterDrawer.Tag) {
|
|
1391
|
+
// Create filter UI
|
|
1392
|
+
filterDrawer = (
|
|
1393
|
+
<TabBox title="Tags">
|
|
1394
|
+
<div>
|
|
1395
|
+
If any tags are selected, logs must contain at least one
|
|
1396
|
+
(but not necessarily all) of the selected tags.
|
|
1397
|
+
</div>
|
|
1398
|
+
<div className="d-flex gap-1 flex-wrap">
|
|
1399
|
+
{
|
|
1346
1400
|
Object.keys(LogMetadata.Tag ?? {})
|
|
1347
1401
|
.map((tag) => {
|
|
1348
1402
|
const description = genHumanReadableName(tag);
|
|
@@ -1352,73 +1406,91 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1352
1406
|
id={`LogReviewer-tag-${tag}-checkbox`}
|
|
1353
1407
|
text={description}
|
|
1354
1408
|
ariaLabel={`require that logs be tagged with "${description}" or any other selected tag`}
|
|
1355
|
-
checked={
|
|
1409
|
+
checked={pendingTagFilterState[tag]}
|
|
1356
1410
|
onChanged={(checked) => {
|
|
1357
|
-
|
|
1411
|
+
const newTagFilterState = {
|
|
1412
|
+
...pendingTagFilterState,
|
|
1413
|
+
[tag]: checked,
|
|
1414
|
+
};
|
|
1358
1415
|
dispatch({
|
|
1359
1416
|
type: ActionType.UpdateTagFilterState,
|
|
1360
|
-
tagFilterState,
|
|
1417
|
+
tagFilterState: newTagFilterState,
|
|
1361
1418
|
});
|
|
1362
1419
|
}}
|
|
1420
|
+
checkedVariant={Variant.Light}
|
|
1421
|
+
uncheckedVariant={Variant.Light}
|
|
1363
1422
|
/>
|
|
1364
1423
|
);
|
|
1365
1424
|
})
|
|
1366
1425
|
}
|
|
1367
|
-
|
|
1426
|
+
</div>
|
|
1427
|
+
</TabBox>
|
|
1428
|
+
);
|
|
1429
|
+
} else if (expandedFilterDrawer === FilterDrawer.Action) {
|
|
1430
|
+
// Create filter UI
|
|
1431
|
+
filterDrawer = (
|
|
1432
|
+
<>
|
|
1433
|
+
{/* Log Type */}
|
|
1434
|
+
<TabBox title="Log Type">
|
|
1435
|
+
<RadioButton
|
|
1436
|
+
id="LogReviewer-type-all"
|
|
1437
|
+
text="All Logs"
|
|
1438
|
+
onSelected={() => {
|
|
1439
|
+
const newActionErrorFilterState = {
|
|
1440
|
+
...pendingActionErrorFilterState,
|
|
1441
|
+
type: undefined,
|
|
1442
|
+
};
|
|
1443
|
+
dispatch({
|
|
1444
|
+
type: ActionType.UpdateActionErrorFilterState,
|
|
1445
|
+
actionErrorFilterState: newActionErrorFilterState,
|
|
1446
|
+
});
|
|
1447
|
+
}}
|
|
1448
|
+
ariaLabel="show logs of all types"
|
|
1449
|
+
selected={pendingActionErrorFilterState.type === undefined}
|
|
1450
|
+
unselectedVariant={Variant.Light}
|
|
1451
|
+
/>
|
|
1452
|
+
<RadioButton
|
|
1453
|
+
id="LogReviewer-type-action-only"
|
|
1454
|
+
text="Action Logs Only"
|
|
1455
|
+
onSelected={() => {
|
|
1456
|
+
const newActionErrorFilterState = {
|
|
1457
|
+
...pendingActionErrorFilterState,
|
|
1458
|
+
type: LogType.Action,
|
|
1459
|
+
};
|
|
1460
|
+
dispatch({
|
|
1461
|
+
type: ActionType.UpdateActionErrorFilterState,
|
|
1462
|
+
actionErrorFilterState: newActionErrorFilterState,
|
|
1463
|
+
});
|
|
1464
|
+
}}
|
|
1465
|
+
ariaLabel="only show action logs"
|
|
1466
|
+
selected={pendingActionErrorFilterState.type === LogType.Action}
|
|
1467
|
+
unselectedVariant={Variant.Light}
|
|
1468
|
+
/>
|
|
1469
|
+
<RadioButton
|
|
1470
|
+
id="LogReviewer-type-error-only"
|
|
1471
|
+
text="Action Error Only"
|
|
1472
|
+
onSelected={() => {
|
|
1473
|
+
const newActionErrorFilterState = {
|
|
1474
|
+
...pendingActionErrorFilterState,
|
|
1475
|
+
type: LogType.Error,
|
|
1476
|
+
};
|
|
1477
|
+
dispatch({
|
|
1478
|
+
type: ActionType.UpdateActionErrorFilterState,
|
|
1479
|
+
actionErrorFilterState: newActionErrorFilterState,
|
|
1480
|
+
});
|
|
1481
|
+
}}
|
|
1482
|
+
ariaLabel="only show error logs"
|
|
1483
|
+
selected={pendingActionErrorFilterState.type === LogType.Error}
|
|
1484
|
+
noMarginOnRight
|
|
1485
|
+
selectedVariant={Variant.Light}
|
|
1486
|
+
unselectedVariant={Variant.Light}
|
|
1487
|
+
/>
|
|
1368
1488
|
</TabBox>
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
// Create filter UI
|
|
1372
|
-
filterDrawer = (
|
|
1373
|
-
<>
|
|
1374
|
-
{/* Log Type */}
|
|
1375
|
-
<TabBox title="Log Type">
|
|
1376
|
-
<RadioButton
|
|
1377
|
-
id="LogReviewer-type-all"
|
|
1378
|
-
text="All Logs"
|
|
1379
|
-
onSelected={() => {
|
|
1380
|
-
actionErrorFilterState.type = undefined;
|
|
1381
|
-
dispatch({
|
|
1382
|
-
type: ActionType.UpdateActionErrorFilterState,
|
|
1383
|
-
actionErrorFilterState,
|
|
1384
|
-
});
|
|
1385
|
-
}}
|
|
1386
|
-
ariaLabel="show logs of all types"
|
|
1387
|
-
selected={actionErrorFilterState.type === undefined}
|
|
1388
|
-
/>
|
|
1389
|
-
<RadioButton
|
|
1390
|
-
id="LogReviewer-type-action-only"
|
|
1391
|
-
text="Action Logs Only"
|
|
1392
|
-
onSelected={() => {
|
|
1393
|
-
actionErrorFilterState.type = LogType.Action;
|
|
1394
|
-
dispatch({
|
|
1395
|
-
type: ActionType.UpdateActionErrorFilterState,
|
|
1396
|
-
actionErrorFilterState,
|
|
1397
|
-
});
|
|
1398
|
-
}}
|
|
1399
|
-
ariaLabel="only show action logs"
|
|
1400
|
-
selected={actionErrorFilterState.type === LogType.Action}
|
|
1401
|
-
/>
|
|
1402
|
-
<RadioButton
|
|
1403
|
-
id="LogReviewer-type-error-only"
|
|
1404
|
-
text="Action Error Only"
|
|
1405
|
-
onSelected={() => {
|
|
1406
|
-
actionErrorFilterState.type = LogType.Error;
|
|
1407
|
-
dispatch({
|
|
1408
|
-
type: ActionType.UpdateActionErrorFilterState,
|
|
1409
|
-
actionErrorFilterState,
|
|
1410
|
-
});
|
|
1411
|
-
}}
|
|
1412
|
-
ariaLabel="only show error logs"
|
|
1413
|
-
selected={actionErrorFilterState.type === LogType.Error}
|
|
1414
|
-
noMarginOnRight
|
|
1415
|
-
/>
|
|
1416
|
-
</TabBox>
|
|
1417
|
-
{/* Actions */}
|
|
1418
|
-
{
|
|
1489
|
+
{/* Actions */}
|
|
1490
|
+
{
|
|
1419
1491
|
(
|
|
1420
|
-
|
|
1421
|
-
||
|
|
1492
|
+
pendingActionErrorFilterState.type === undefined
|
|
1493
|
+
|| pendingActionErrorFilterState.type === LogType.Action
|
|
1422
1494
|
) && (
|
|
1423
1495
|
<TabBox title="Action Log Details">
|
|
1424
1496
|
{/* Action */}
|
|
@@ -1438,14 +1510,22 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1438
1510
|
text={description}
|
|
1439
1511
|
ariaLabel={`include logs with action type "${description}" in results`}
|
|
1440
1512
|
noMarginOnRight
|
|
1441
|
-
checked={
|
|
1513
|
+
checked={pendingActionErrorFilterState.action[action]}
|
|
1442
1514
|
onChanged={(checked) => {
|
|
1443
|
-
|
|
1515
|
+
const newActionErrorFilterState = {
|
|
1516
|
+
...pendingActionErrorFilterState,
|
|
1517
|
+
action: {
|
|
1518
|
+
...pendingActionErrorFilterState.action,
|
|
1519
|
+
[action]: checked,
|
|
1520
|
+
},
|
|
1521
|
+
};
|
|
1444
1522
|
dispatch({
|
|
1445
1523
|
type: ActionType.UpdateActionErrorFilterState,
|
|
1446
|
-
actionErrorFilterState,
|
|
1524
|
+
actionErrorFilterState: newActionErrorFilterState,
|
|
1447
1525
|
});
|
|
1448
1526
|
}}
|
|
1527
|
+
checkedVariant={Variant.Light}
|
|
1528
|
+
uncheckedVariant={Variant.Light}
|
|
1449
1529
|
/>
|
|
1450
1530
|
);
|
|
1451
1531
|
})
|
|
@@ -1467,15 +1547,23 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1467
1547
|
id={`LogReviewer-target-${target}-checkbox`}
|
|
1468
1548
|
text={description}
|
|
1469
1549
|
ariaLabel={`include logs with target "${description}" in results`}
|
|
1470
|
-
checked={
|
|
1550
|
+
checked={pendingActionErrorFilterState.target[target]}
|
|
1471
1551
|
noMarginOnRight
|
|
1472
1552
|
onChanged={(checked) => {
|
|
1473
|
-
|
|
1553
|
+
const newActionErrorFilterState = {
|
|
1554
|
+
...pendingActionErrorFilterState,
|
|
1555
|
+
target: {
|
|
1556
|
+
...pendingActionErrorFilterState.target,
|
|
1557
|
+
[target]: checked,
|
|
1558
|
+
},
|
|
1559
|
+
};
|
|
1474
1560
|
dispatch({
|
|
1475
1561
|
type: ActionType.UpdateActionErrorFilterState,
|
|
1476
|
-
actionErrorFilterState,
|
|
1562
|
+
actionErrorFilterState: newActionErrorFilterState,
|
|
1477
1563
|
});
|
|
1478
1564
|
}}
|
|
1565
|
+
checkedVariant={Variant.Light}
|
|
1566
|
+
uncheckedVariant={Variant.Light}
|
|
1479
1567
|
/>
|
|
1480
1568
|
);
|
|
1481
1569
|
})
|
|
@@ -1484,11 +1572,11 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1484
1572
|
</TabBox>
|
|
1485
1573
|
)
|
|
1486
1574
|
}
|
|
1487
|
-
|
|
1488
|
-
|
|
1575
|
+
{/* Errors */}
|
|
1576
|
+
{
|
|
1489
1577
|
(
|
|
1490
|
-
|
|
1491
|
-
||
|
|
1578
|
+
pendingActionErrorFilterState.type === undefined
|
|
1579
|
+
|| pendingActionErrorFilterState.type === LogType.Error
|
|
1492
1580
|
) && (
|
|
1493
1581
|
<TabBox title="Error Log Details">
|
|
1494
1582
|
{/* Message */}
|
|
@@ -1500,13 +1588,16 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1500
1588
|
type="text"
|
|
1501
1589
|
className="form-control"
|
|
1502
1590
|
aria-label="query for error message"
|
|
1503
|
-
value={
|
|
1591
|
+
value={pendingActionErrorFilterState.errorMessage}
|
|
1504
1592
|
placeholder="e.g. undefined is not a function"
|
|
1505
1593
|
onChange={(e) => {
|
|
1506
|
-
|
|
1594
|
+
const newActionErrorFilterState = {
|
|
1595
|
+
...pendingActionErrorFilterState,
|
|
1596
|
+
errorMessage: e.target.value,
|
|
1597
|
+
};
|
|
1507
1598
|
dispatch({
|
|
1508
1599
|
type: ActionType.UpdateActionErrorFilterState,
|
|
1509
|
-
actionErrorFilterState,
|
|
1600
|
+
actionErrorFilterState: newActionErrorFilterState,
|
|
1510
1601
|
});
|
|
1511
1602
|
}}
|
|
1512
1603
|
/>
|
|
@@ -1520,17 +1611,20 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1520
1611
|
type="text"
|
|
1521
1612
|
className="form-control"
|
|
1522
1613
|
aria-label="query for error code"
|
|
1523
|
-
value={
|
|
1614
|
+
value={pendingActionErrorFilterState.errorCode}
|
|
1524
1615
|
placeholder="e.g. GC22"
|
|
1525
1616
|
onChange={(e) => {
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
.
|
|
1530
|
-
|
|
1617
|
+
const newActionErrorFilterState = {
|
|
1618
|
+
...pendingActionErrorFilterState,
|
|
1619
|
+
errorCode: (
|
|
1620
|
+
(e.target.value)
|
|
1621
|
+
.trim()
|
|
1622
|
+
.toUpperCase()
|
|
1623
|
+
),
|
|
1624
|
+
};
|
|
1531
1625
|
dispatch({
|
|
1532
1626
|
type: ActionType.UpdateActionErrorFilterState,
|
|
1533
|
-
actionErrorFilterState,
|
|
1627
|
+
actionErrorFilterState: newActionErrorFilterState,
|
|
1534
1628
|
});
|
|
1535
1629
|
}}
|
|
1536
1630
|
/>
|
|
@@ -1538,722 +1632,460 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1538
1632
|
</TabBox>
|
|
1539
1633
|
)
|
|
1540
1634
|
}
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1635
|
+
</>
|
|
1636
|
+
);
|
|
1637
|
+
} else if (expandedFilterDrawer === FilterDrawer.Advanced) {
|
|
1638
|
+
// Create advanced filter ui
|
|
1639
|
+
filterDrawer = (
|
|
1640
|
+
<>
|
|
1641
|
+
{/* User Info */}
|
|
1642
|
+
<TabBox title="User">
|
|
1643
|
+
{/* First Name */}
|
|
1644
|
+
<div className="input-group mb-2">
|
|
1645
|
+
<span className="input-group-text">
|
|
1646
|
+
User First Name
|
|
1647
|
+
</span>
|
|
1648
|
+
<input
|
|
1649
|
+
type="text"
|
|
1650
|
+
className="form-control"
|
|
1651
|
+
aria-label="query for user first name"
|
|
1652
|
+
value={pendingAdvancedFilterState.userFirstName}
|
|
1653
|
+
placeholder="e.g. Divardo"
|
|
1654
|
+
onChange={(e) => {
|
|
1655
|
+
const newAdvancedFilterState = {
|
|
1656
|
+
...pendingAdvancedFilterState,
|
|
1657
|
+
userFirstName: e.target.value,
|
|
1658
|
+
};
|
|
1659
|
+
dispatch({
|
|
1660
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1661
|
+
advancedFilterState: newAdvancedFilterState,
|
|
1662
|
+
});
|
|
1663
|
+
}}
|
|
1664
|
+
/>
|
|
1665
|
+
</div>
|
|
1666
|
+
{/* Last Name */}
|
|
1667
|
+
<div className="input-group mb-2">
|
|
1668
|
+
<span className="input-group-text">
|
|
1669
|
+
User Last Name
|
|
1670
|
+
</span>
|
|
1671
|
+
<input
|
|
1672
|
+
type="text"
|
|
1673
|
+
className="form-control"
|
|
1674
|
+
aria-label="query for user last name"
|
|
1675
|
+
value={pendingAdvancedFilterState.userLastName}
|
|
1676
|
+
placeholder="e.g. Calicci"
|
|
1677
|
+
onChange={(e) => {
|
|
1678
|
+
const newAdvancedFilterState = {
|
|
1679
|
+
...pendingAdvancedFilterState,
|
|
1680
|
+
userLastName: e.target.value,
|
|
1681
|
+
};
|
|
1682
|
+
dispatch({
|
|
1683
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1684
|
+
advancedFilterState: newAdvancedFilterState,
|
|
1685
|
+
});
|
|
1686
|
+
}}
|
|
1687
|
+
/>
|
|
1688
|
+
</div>
|
|
1689
|
+
{/* Email */}
|
|
1690
|
+
<div className="input-group mb-2">
|
|
1691
|
+
<span className="input-group-text">
|
|
1692
|
+
User Email
|
|
1693
|
+
</span>
|
|
1694
|
+
<input
|
|
1695
|
+
type="text"
|
|
1696
|
+
className="form-control"
|
|
1697
|
+
aria-label="query for user email"
|
|
1698
|
+
value={pendingAdvancedFilterState.userEmail}
|
|
1699
|
+
placeholder="e.g. calicci@fas.harvard.edu"
|
|
1700
|
+
onChange={(e) => {
|
|
1701
|
+
const newAdvancedFilterState = {
|
|
1702
|
+
...pendingAdvancedFilterState,
|
|
1703
|
+
userEmail: (
|
|
1704
|
+
(e.target.value)
|
|
1705
|
+
.trim()
|
|
1706
|
+
),
|
|
1707
|
+
};
|
|
1708
|
+
dispatch({
|
|
1709
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1710
|
+
advancedFilterState: newAdvancedFilterState,
|
|
1711
|
+
});
|
|
1712
|
+
}}
|
|
1713
|
+
/>
|
|
1714
|
+
</div>
|
|
1715
|
+
{/* Canvas Id */}
|
|
1716
|
+
<div className="input-group mb-2">
|
|
1717
|
+
<span className="input-group-text">
|
|
1718
|
+
User Canvas Id
|
|
1719
|
+
</span>
|
|
1720
|
+
<input
|
|
1721
|
+
type="text"
|
|
1722
|
+
className="form-control"
|
|
1723
|
+
aria-label="query for user canvas id"
|
|
1724
|
+
value={pendingAdvancedFilterState.userId}
|
|
1725
|
+
placeholder="e.g. 104985"
|
|
1726
|
+
onChange={(e) => {
|
|
1727
|
+
const { value } = e.target;
|
|
1728
|
+
// Only update if value contains only numbers
|
|
1729
|
+
if (/^\d+$/.test(value) || value === '') {
|
|
1730
|
+
const newAdvancedFilterState = {
|
|
1731
|
+
...pendingAdvancedFilterState,
|
|
1732
|
+
userId: (
|
|
1733
|
+
(e.target.value)
|
|
1734
|
+
.trim()
|
|
1735
|
+
),
|
|
1736
|
+
};
|
|
1562
1737
|
dispatch({
|
|
1563
1738
|
type: ActionType.UpdateAdvancedFilterState,
|
|
1564
|
-
advancedFilterState,
|
|
1739
|
+
advancedFilterState: newAdvancedFilterState,
|
|
1565
1740
|
});
|
|
1566
|
-
}
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
advancedFilterState
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1741
|
+
}
|
|
1742
|
+
}}
|
|
1743
|
+
/>
|
|
1744
|
+
</div>
|
|
1745
|
+
{/* Role */}
|
|
1746
|
+
<ButtonInputGroup label="Role">
|
|
1747
|
+
<CheckboxButton
|
|
1748
|
+
text="Students"
|
|
1749
|
+
onChanged={(checked) => {
|
|
1750
|
+
const newAdvancedFilterState = {
|
|
1751
|
+
...pendingAdvancedFilterState,
|
|
1752
|
+
includeLearners: checked,
|
|
1753
|
+
};
|
|
1754
|
+
dispatch({
|
|
1755
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1756
|
+
advancedFilterState: newAdvancedFilterState,
|
|
1757
|
+
});
|
|
1758
|
+
}}
|
|
1759
|
+
checked={pendingAdvancedFilterState.includeLearners}
|
|
1760
|
+
ariaLabel="show logs from students"
|
|
1761
|
+
checkedVariant={Variant.Light}
|
|
1762
|
+
uncheckedVariant={Variant.Light}
|
|
1763
|
+
/>
|
|
1764
|
+
<CheckboxButton
|
|
1765
|
+
text="Teaching Team Members"
|
|
1766
|
+
onChanged={(checked) => {
|
|
1767
|
+
const newAdvancedFilterState = {
|
|
1768
|
+
...pendingAdvancedFilterState,
|
|
1769
|
+
includeTTMs: checked,
|
|
1770
|
+
};
|
|
1771
|
+
dispatch({
|
|
1772
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1773
|
+
advancedFilterState: newAdvancedFilterState,
|
|
1774
|
+
});
|
|
1775
|
+
}}
|
|
1776
|
+
checked={pendingAdvancedFilterState.includeTTMs}
|
|
1777
|
+
ariaLabel="show logs from teaching team members"
|
|
1778
|
+
checkedVariant={Variant.Light}
|
|
1779
|
+
uncheckedVariant={Variant.Light}
|
|
1780
|
+
/>
|
|
1781
|
+
<CheckboxButton
|
|
1782
|
+
text="Admins"
|
|
1783
|
+
onChanged={(checked) => {
|
|
1784
|
+
const newAdvancedFilterState = {
|
|
1785
|
+
...pendingAdvancedFilterState,
|
|
1786
|
+
includeAdmins: checked,
|
|
1787
|
+
};
|
|
1788
|
+
dispatch({
|
|
1789
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1790
|
+
advancedFilterState: newAdvancedFilterState,
|
|
1791
|
+
});
|
|
1792
|
+
}}
|
|
1793
|
+
checked={pendingAdvancedFilterState.includeAdmins}
|
|
1794
|
+
ariaLabel="show logs from admins"
|
|
1795
|
+
checkedVariant={Variant.Light}
|
|
1796
|
+
uncheckedVariant={Variant.Light}
|
|
1797
|
+
/>
|
|
1798
|
+
</ButtonInputGroup>
|
|
1799
|
+
</TabBox>
|
|
1800
|
+
|
|
1801
|
+
{/* Course Info */}
|
|
1802
|
+
<TabBox title="Course">
|
|
1803
|
+
{/* Name */}
|
|
1804
|
+
<div className="input-group mb-2">
|
|
1805
|
+
<span className="input-group-text">
|
|
1806
|
+
Course Name
|
|
1807
|
+
</span>
|
|
1808
|
+
<input
|
|
1809
|
+
type="text"
|
|
1810
|
+
className="form-control"
|
|
1811
|
+
aria-label="query for course name"
|
|
1812
|
+
value={pendingAdvancedFilterState.courseName}
|
|
1813
|
+
placeholder="e.g. GLC 200"
|
|
1814
|
+
onChange={(e) => {
|
|
1815
|
+
const newAdvancedFilterState = {
|
|
1816
|
+
...pendingAdvancedFilterState,
|
|
1817
|
+
courseName: e.target.value,
|
|
1818
|
+
};
|
|
1819
|
+
dispatch({
|
|
1820
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1821
|
+
advancedFilterState: newAdvancedFilterState,
|
|
1822
|
+
});
|
|
1823
|
+
}}
|
|
1824
|
+
/>
|
|
1825
|
+
</div>
|
|
1826
|
+
{/* Canvas Id */}
|
|
1827
|
+
<div className="input-group mb-2">
|
|
1828
|
+
<span className="input-group-text">
|
|
1829
|
+
Course Canvas Id
|
|
1830
|
+
</span>
|
|
1831
|
+
<input
|
|
1832
|
+
type="text"
|
|
1833
|
+
className="form-control"
|
|
1834
|
+
aria-label="query for course canvas id"
|
|
1835
|
+
value={pendingAdvancedFilterState.courseId}
|
|
1836
|
+
placeholder="e.g. 15948"
|
|
1837
|
+
onChange={(e) => {
|
|
1838
|
+
const { value } = e.target;
|
|
1839
|
+
// Only update if value contains only numbers
|
|
1840
|
+
if (/^\d+$/.test(value) || value === '') {
|
|
1841
|
+
const newAdvancedFilterState = {
|
|
1842
|
+
...pendingAdvancedFilterState,
|
|
1843
|
+
courseId: (
|
|
1628
1844
|
(e.target.value)
|
|
1629
1845
|
.trim()
|
|
1630
|
-
)
|
|
1631
|
-
}
|
|
1632
|
-
dispatch({
|
|
1633
|
-
type: ActionType.UpdateAdvancedFilterState,
|
|
1634
|
-
advancedFilterState,
|
|
1635
|
-
});
|
|
1636
|
-
}}
|
|
1637
|
-
/>
|
|
1638
|
-
</div>
|
|
1639
|
-
{/* Role */}
|
|
1640
|
-
<ButtonInputGroup label="Role">
|
|
1641
|
-
<CheckboxButton
|
|
1642
|
-
text="Students"
|
|
1643
|
-
onChanged={(checked) => {
|
|
1644
|
-
advancedFilterState.includeLearners = checked;
|
|
1846
|
+
),
|
|
1847
|
+
};
|
|
1645
1848
|
dispatch({
|
|
1646
1849
|
type: ActionType.UpdateAdvancedFilterState,
|
|
1647
|
-
advancedFilterState,
|
|
1850
|
+
advancedFilterState: newAdvancedFilterState,
|
|
1648
1851
|
});
|
|
1649
|
-
}
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1852
|
+
}
|
|
1853
|
+
}}
|
|
1854
|
+
/>
|
|
1855
|
+
</div>
|
|
1856
|
+
</TabBox>
|
|
1857
|
+
|
|
1858
|
+
{/* Device Info */}
|
|
1859
|
+
<TabBox title="Device">
|
|
1860
|
+
<ButtonInputGroup label="Device Type">
|
|
1861
|
+
<RadioButton
|
|
1862
|
+
text="All Devices"
|
|
1863
|
+
ariaLabel="show logs from all devices"
|
|
1864
|
+
selected={pendingAdvancedFilterState.isMobile === undefined}
|
|
1865
|
+
onSelected={() => {
|
|
1866
|
+
const newAdvancedFilterState = {
|
|
1867
|
+
...pendingAdvancedFilterState,
|
|
1868
|
+
isMobile: undefined,
|
|
1869
|
+
};
|
|
1870
|
+
dispatch({
|
|
1871
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1872
|
+
advancedFilterState: newAdvancedFilterState,
|
|
1873
|
+
});
|
|
1874
|
+
}}
|
|
1875
|
+
selectedVariant={Variant.Light}
|
|
1876
|
+
unselectedVariant={Variant.Light}
|
|
1877
|
+
/>
|
|
1878
|
+
<RadioButton
|
|
1879
|
+
text="Mobile Only"
|
|
1880
|
+
ariaLabel="show logs from mobile devices"
|
|
1881
|
+
selected={pendingAdvancedFilterState.isMobile === true}
|
|
1882
|
+
onSelected={() => {
|
|
1883
|
+
const newAdvancedFilterState = {
|
|
1884
|
+
...pendingAdvancedFilterState,
|
|
1885
|
+
isMobile: true,
|
|
1886
|
+
};
|
|
1887
|
+
dispatch({
|
|
1888
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1889
|
+
advancedFilterState: newAdvancedFilterState,
|
|
1890
|
+
});
|
|
1891
|
+
}}
|
|
1892
|
+
selectedVariant={Variant.Light}
|
|
1893
|
+
unselectedVariant={Variant.Light}
|
|
1894
|
+
/>
|
|
1895
|
+
<RadioButton
|
|
1896
|
+
text="Desktop Only"
|
|
1897
|
+
ariaLabel="show logs from desktop devices"
|
|
1898
|
+
selected={pendingAdvancedFilterState.isMobile === false}
|
|
1899
|
+
onSelected={() => {
|
|
1900
|
+
const newAdvancedFilterState = {
|
|
1901
|
+
...pendingAdvancedFilterState,
|
|
1902
|
+
isMobile: false,
|
|
1903
|
+
};
|
|
1904
|
+
dispatch({
|
|
1905
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1906
|
+
advancedFilterState: newAdvancedFilterState,
|
|
1907
|
+
});
|
|
1908
|
+
}}
|
|
1909
|
+
noMarginOnRight
|
|
1910
|
+
selectedVariant={Variant.Light}
|
|
1911
|
+
unselectedVariant={Variant.Light}
|
|
1912
|
+
/>
|
|
1913
|
+
</ButtonInputGroup>
|
|
1914
|
+
</TabBox>
|
|
1679
1915
|
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1916
|
+
{/* Source */}
|
|
1917
|
+
<TabBox title="Source">
|
|
1918
|
+
<ButtonInputGroup label="Source Type">
|
|
1919
|
+
<RadioButton
|
|
1920
|
+
text="Both"
|
|
1921
|
+
ariaLabel="show logs from all sources"
|
|
1922
|
+
selected={pendingAdvancedFilterState.source === undefined}
|
|
1923
|
+
onSelected={() => {
|
|
1924
|
+
const newAdvancedFilterState = {
|
|
1925
|
+
...pendingAdvancedFilterState,
|
|
1926
|
+
source: undefined,
|
|
1927
|
+
};
|
|
1928
|
+
dispatch({
|
|
1929
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1930
|
+
advancedFilterState: newAdvancedFilterState,
|
|
1931
|
+
});
|
|
1932
|
+
}}
|
|
1933
|
+
selectedVariant={Variant.Light}
|
|
1934
|
+
unselectedVariant={Variant.Light}
|
|
1935
|
+
/>
|
|
1936
|
+
<RadioButton
|
|
1937
|
+
text="Client Only"
|
|
1938
|
+
ariaLabel="show logs from client source"
|
|
1939
|
+
selected={pendingAdvancedFilterState.source === LogSource.Client}
|
|
1940
|
+
onSelected={() => {
|
|
1941
|
+
const newAdvancedFilterState = {
|
|
1942
|
+
...pendingAdvancedFilterState,
|
|
1943
|
+
source: LogSource.Client,
|
|
1944
|
+
};
|
|
1945
|
+
dispatch({
|
|
1946
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1947
|
+
advancedFilterState: newAdvancedFilterState,
|
|
1948
|
+
});
|
|
1949
|
+
}}
|
|
1950
|
+
selectedVariant={Variant.Light}
|
|
1951
|
+
unselectedVariant={Variant.Light}
|
|
1952
|
+
/>
|
|
1953
|
+
<RadioButton
|
|
1954
|
+
text="Server Only"
|
|
1955
|
+
ariaLabel="show logs from server source"
|
|
1956
|
+
selected={pendingAdvancedFilterState.source === LogSource.Server}
|
|
1957
|
+
onSelected={() => {
|
|
1958
|
+
const newAdvancedFilterState = {
|
|
1959
|
+
...pendingAdvancedFilterState,
|
|
1960
|
+
source: LogSource.Server,
|
|
1961
|
+
};
|
|
1962
|
+
dispatch({
|
|
1963
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1964
|
+
advancedFilterState: newAdvancedFilterState,
|
|
1965
|
+
});
|
|
1966
|
+
}}
|
|
1967
|
+
noMarginOnRight
|
|
1968
|
+
selectedVariant={Variant.Light}
|
|
1969
|
+
unselectedVariant={Variant.Light}
|
|
1970
|
+
/>
|
|
1971
|
+
</ButtonInputGroup>
|
|
1972
|
+
|
|
1973
|
+
{/* Server filters */}
|
|
1974
|
+
{pendingAdvancedFilterState.source !== LogSource.Client && (
|
|
1975
|
+
<div className="mt-2">
|
|
1976
|
+
{/* Route path */}
|
|
1683
1977
|
<div className="input-group mb-2">
|
|
1684
1978
|
<span className="input-group-text">
|
|
1685
|
-
|
|
1979
|
+
Server Route Path
|
|
1686
1980
|
</span>
|
|
1687
1981
|
<input
|
|
1688
1982
|
type="text"
|
|
1689
1983
|
className="form-control"
|
|
1690
|
-
aria-label="query for
|
|
1691
|
-
value={
|
|
1692
|
-
placeholder="e.g.
|
|
1984
|
+
aria-label="query for server route path"
|
|
1985
|
+
value={pendingAdvancedFilterState.routePath}
|
|
1986
|
+
placeholder="e.g. /api/ttm/courses/12345"
|
|
1693
1987
|
onChange={(e) => {
|
|
1694
|
-
|
|
1988
|
+
const newAdvancedFilterState = {
|
|
1989
|
+
...pendingAdvancedFilterState,
|
|
1990
|
+
routePath: (
|
|
1991
|
+
(e.target.value)
|
|
1992
|
+
.trim()
|
|
1993
|
+
),
|
|
1994
|
+
};
|
|
1695
1995
|
dispatch({
|
|
1696
1996
|
type: ActionType.UpdateAdvancedFilterState,
|
|
1697
|
-
advancedFilterState,
|
|
1997
|
+
advancedFilterState: newAdvancedFilterState,
|
|
1698
1998
|
});
|
|
1699
1999
|
}}
|
|
1700
2000
|
/>
|
|
1701
2001
|
</div>
|
|
1702
|
-
|
|
2002
|
+
|
|
2003
|
+
{/* Route template */}
|
|
1703
2004
|
<div className="input-group mb-2">
|
|
1704
2005
|
<span className="input-group-text">
|
|
1705
|
-
|
|
2006
|
+
Server Route Template
|
|
1706
2007
|
</span>
|
|
1707
2008
|
<input
|
|
1708
2009
|
type="text"
|
|
1709
2010
|
className="form-control"
|
|
1710
|
-
aria-label="query for
|
|
1711
|
-
value={
|
|
1712
|
-
placeholder="e.g.
|
|
2011
|
+
aria-label="query for server route template"
|
|
2012
|
+
value={pendingAdvancedFilterState.routeTemplate}
|
|
2013
|
+
placeholder="e.g. /api/ttm/courses/:courseId"
|
|
1713
2014
|
onChange={(e) => {
|
|
1714
|
-
const
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
advancedFilterState.courseId = (
|
|
2015
|
+
const newAdvancedFilterState = {
|
|
2016
|
+
...pendingAdvancedFilterState,
|
|
2017
|
+
routeTemplate: (
|
|
1718
2018
|
(e.target.value)
|
|
1719
2019
|
.trim()
|
|
1720
|
-
)
|
|
1721
|
-
}
|
|
2020
|
+
),
|
|
2021
|
+
};
|
|
1722
2022
|
dispatch({
|
|
1723
2023
|
type: ActionType.UpdateAdvancedFilterState,
|
|
1724
|
-
advancedFilterState,
|
|
2024
|
+
advancedFilterState: newAdvancedFilterState,
|
|
1725
2025
|
});
|
|
1726
2026
|
}}
|
|
1727
2027
|
/>
|
|
1728
2028
|
</div>
|
|
1729
|
-
</
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
<RadioButton
|
|
1735
|
-
text="All Devices"
|
|
1736
|
-
ariaLabel="show logs from all devices"
|
|
1737
|
-
selected={advancedFilterState.isMobile === undefined}
|
|
1738
|
-
onSelected={() => {
|
|
1739
|
-
advancedFilterState.isMobile = undefined;
|
|
1740
|
-
dispatch({
|
|
1741
|
-
type: ActionType.UpdateAdvancedFilterState,
|
|
1742
|
-
advancedFilterState,
|
|
1743
|
-
});
|
|
1744
|
-
}}
|
|
1745
|
-
/>
|
|
1746
|
-
<RadioButton
|
|
1747
|
-
text="Mobile Only"
|
|
1748
|
-
ariaLabel="show logs from mobile devices"
|
|
1749
|
-
selected={advancedFilterState.isMobile === true}
|
|
1750
|
-
onSelected={() => {
|
|
1751
|
-
advancedFilterState.isMobile = true;
|
|
1752
|
-
dispatch({
|
|
1753
|
-
type: ActionType.UpdateAdvancedFilterState,
|
|
1754
|
-
advancedFilterState,
|
|
1755
|
-
});
|
|
1756
|
-
}}
|
|
1757
|
-
/>
|
|
1758
|
-
<RadioButton
|
|
1759
|
-
text="Desktop Only"
|
|
1760
|
-
ariaLabel="show logs from desktop devices"
|
|
1761
|
-
selected={advancedFilterState.isMobile === false}
|
|
1762
|
-
onSelected={() => {
|
|
1763
|
-
advancedFilterState.isMobile = false;
|
|
1764
|
-
dispatch({
|
|
1765
|
-
type: ActionType.UpdateAdvancedFilterState,
|
|
1766
|
-
advancedFilterState,
|
|
1767
|
-
});
|
|
1768
|
-
}}
|
|
1769
|
-
noMarginOnRight
|
|
1770
|
-
/>
|
|
1771
|
-
</ButtonInputGroup>
|
|
1772
|
-
</TabBox>
|
|
1773
|
-
|
|
1774
|
-
{/* Source */}
|
|
1775
|
-
<TabBox title="Source">
|
|
1776
|
-
<ButtonInputGroup label="Source Type">
|
|
1777
|
-
<RadioButton
|
|
1778
|
-
text="Both"
|
|
1779
|
-
ariaLabel="show logs from all sources"
|
|
1780
|
-
selected={advancedFilterState.source === undefined}
|
|
1781
|
-
onSelected={() => {
|
|
1782
|
-
advancedFilterState.source = undefined;
|
|
1783
|
-
dispatch({
|
|
1784
|
-
type: ActionType.UpdateAdvancedFilterState,
|
|
1785
|
-
advancedFilterState,
|
|
1786
|
-
});
|
|
1787
|
-
}}
|
|
1788
|
-
/>
|
|
1789
|
-
<RadioButton
|
|
1790
|
-
text="Client Only"
|
|
1791
|
-
ariaLabel="show logs from client source"
|
|
1792
|
-
selected={advancedFilterState.source === LogSource.Client}
|
|
1793
|
-
onSelected={() => {
|
|
1794
|
-
advancedFilterState.source = LogSource.Client;
|
|
1795
|
-
dispatch({
|
|
1796
|
-
type: ActionType.UpdateAdvancedFilterState,
|
|
1797
|
-
advancedFilterState,
|
|
1798
|
-
});
|
|
1799
|
-
}}
|
|
1800
|
-
/>
|
|
1801
|
-
<RadioButton
|
|
1802
|
-
text="Server Only"
|
|
1803
|
-
ariaLabel="show logs from server source"
|
|
1804
|
-
selected={advancedFilterState.source === LogSource.Server}
|
|
1805
|
-
onSelected={() => {
|
|
1806
|
-
advancedFilterState.source = LogSource.Server;
|
|
1807
|
-
dispatch({
|
|
1808
|
-
type: ActionType.UpdateAdvancedFilterState,
|
|
1809
|
-
advancedFilterState,
|
|
1810
|
-
});
|
|
1811
|
-
}}
|
|
1812
|
-
noMarginOnRight
|
|
1813
|
-
/>
|
|
1814
|
-
</ButtonInputGroup>
|
|
1815
|
-
|
|
1816
|
-
{/* Server filters */}
|
|
1817
|
-
{advancedFilterState.source !== LogSource.Client && (
|
|
1818
|
-
<div className="mt-2">
|
|
1819
|
-
{/* Route path */}
|
|
1820
|
-
<div className="input-group mb-2">
|
|
1821
|
-
<span className="input-group-text">
|
|
1822
|
-
Server Route Path
|
|
1823
|
-
</span>
|
|
1824
|
-
<input
|
|
1825
|
-
type="text"
|
|
1826
|
-
className="form-control"
|
|
1827
|
-
aria-label="query for server route path"
|
|
1828
|
-
value={advancedFilterState.routePath}
|
|
1829
|
-
placeholder="e.g. /api/ttm/courses/12345"
|
|
1830
|
-
onChange={(e) => {
|
|
1831
|
-
advancedFilterState.courseName = (
|
|
1832
|
-
(e.target.value)
|
|
1833
|
-
.trim()
|
|
1834
|
-
);
|
|
1835
|
-
dispatch({
|
|
1836
|
-
type: ActionType.UpdateAdvancedFilterState,
|
|
1837
|
-
advancedFilterState,
|
|
1838
|
-
});
|
|
1839
|
-
}}
|
|
1840
|
-
/>
|
|
1841
|
-
</div>
|
|
1842
|
-
|
|
1843
|
-
{/* Route template */}
|
|
1844
|
-
<div className="input-group mb-2">
|
|
1845
|
-
<span className="input-group-text">
|
|
1846
|
-
Server Route Template
|
|
1847
|
-
</span>
|
|
1848
|
-
<input
|
|
1849
|
-
type="text"
|
|
1850
|
-
className="form-control"
|
|
1851
|
-
aria-label="query for server route template"
|
|
1852
|
-
value={advancedFilterState.routeTemplate}
|
|
1853
|
-
placeholder="e.g. /api/ttm/courses/:courseId"
|
|
1854
|
-
onChange={(e) => {
|
|
1855
|
-
advancedFilterState.courseName = (
|
|
1856
|
-
(e.target.value)
|
|
1857
|
-
.trim()
|
|
1858
|
-
);
|
|
1859
|
-
dispatch({
|
|
1860
|
-
type: ActionType.UpdateAdvancedFilterState,
|
|
1861
|
-
advancedFilterState,
|
|
1862
|
-
});
|
|
1863
|
-
}}
|
|
1864
|
-
/>
|
|
1865
|
-
</div>
|
|
1866
|
-
</div>
|
|
1867
|
-
)}
|
|
1868
|
-
</TabBox>
|
|
1869
|
-
</>
|
|
1870
|
-
);
|
|
1871
|
-
}
|
|
2029
|
+
</div>
|
|
2030
|
+
)}
|
|
2031
|
+
</TabBox>
|
|
2032
|
+
</>
|
|
2033
|
+
);
|
|
1872
2034
|
}
|
|
2035
|
+
}
|
|
1873
2036
|
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
// Actually filter the logs
|
|
1887
|
-
// > Perform filters
|
|
1888
|
-
const logs: Log[] = [];
|
|
1889
|
-
Object.keys(logMap).forEach((year) => {
|
|
1890
|
-
Object.keys(logMap[year]).forEach((month) => {
|
|
1891
|
-
logMap[year][month].forEach((log) => {
|
|
1892
|
-
/* ----------- Date Filter ---------- */
|
|
1893
|
-
|
|
1894
|
-
// Before start date
|
|
1895
|
-
if (
|
|
1896
|
-
// Previous year
|
|
1897
|
-
log.year < dateFilterState.startDate.year
|
|
1898
|
-
// Same year, earlier month
|
|
1899
|
-
|| (
|
|
1900
|
-
(log.year === dateFilterState.startDate.year)
|
|
1901
|
-
&& (log.month < dateFilterState.startDate.month)
|
|
1902
|
-
)
|
|
1903
|
-
// Same year, same month, earlier day
|
|
1904
|
-
|| (
|
|
1905
|
-
(log.year === dateFilterState.startDate.year)
|
|
1906
|
-
&& (log.month === dateFilterState.startDate.month)
|
|
1907
|
-
&& (log.day < dateFilterState.startDate.day)
|
|
1908
|
-
)
|
|
1909
|
-
) {
|
|
1910
|
-
return;
|
|
1911
|
-
}
|
|
1912
|
-
|
|
1913
|
-
// After end date
|
|
1914
|
-
if (
|
|
1915
|
-
// Later year
|
|
1916
|
-
log.year > dateFilterState.endDate.year
|
|
1917
|
-
// Same year, later month
|
|
1918
|
-
|| (
|
|
1919
|
-
(log.year === dateFilterState.endDate.year)
|
|
1920
|
-
&& (log.month > dateFilterState.endDate.month)
|
|
1921
|
-
)
|
|
1922
|
-
// Same year, same month, later day
|
|
1923
|
-
|| (
|
|
1924
|
-
(log.year === dateFilterState.endDate.year)
|
|
1925
|
-
&& (log.month === dateFilterState.endDate.month)
|
|
1926
|
-
&& (log.day > dateFilterState.endDate.day)
|
|
1927
|
-
)
|
|
1928
|
-
) {
|
|
1929
|
-
return;
|
|
1930
|
-
}
|
|
1931
|
-
|
|
1932
|
-
/* --------- Context Filter --------- */
|
|
1933
|
-
|
|
1934
|
-
// Context doesn't match
|
|
1935
|
-
if (
|
|
1936
|
-
// Whole context is deselected
|
|
1937
|
-
contextFilterState[log.context] === false
|
|
1938
|
-
// None of the subcontexts are selected
|
|
1939
|
-
|| (
|
|
1940
|
-
// Has subcontexts
|
|
1941
|
-
typeof contextFilterState[log.context] !== 'boolean'
|
|
1942
|
-
// None of the subcontexts are selected
|
|
1943
|
-
&& Object.values(contextFilterState[log.context] ?? {})
|
|
1944
|
-
.every((isSelected) => {
|
|
1945
|
-
return !isSelected;
|
|
1946
|
-
})
|
|
1947
|
-
)
|
|
1948
|
-
) {
|
|
1949
|
-
return;
|
|
1950
|
-
}
|
|
1951
|
-
|
|
1952
|
-
// Subcontext doesn't match
|
|
1953
|
-
if (
|
|
1954
|
-
// Log context is not "uncategorized" (no point in further filters)
|
|
1955
|
-
log.context !== LogBuiltInMetadata.Context.Uncategorized
|
|
1956
|
-
// Log has a subcontext
|
|
1957
|
-
&& log.subcontext
|
|
1958
|
-
// Context has subcontexts
|
|
1959
|
-
&& (
|
|
1960
|
-
contextFilterState[log.context]
|
|
1961
|
-
&& contextFilterState[log.context] !== false
|
|
1962
|
-
&& contextFilterState[log.context] !== true
|
|
1963
|
-
)
|
|
1964
|
-
// Subcontext is not selected
|
|
1965
|
-
&& !(contextFilterState as any)[log.context][log.subcontext]
|
|
1966
|
-
) {
|
|
1967
|
-
return;
|
|
1968
|
-
}
|
|
1969
|
-
|
|
1970
|
-
/* -------------- Tags -------------- */
|
|
1971
|
-
|
|
1972
|
-
// No tags match
|
|
1973
|
-
if (
|
|
1974
|
-
// At least one tag is required
|
|
1975
|
-
Object.values(tagFilterState)
|
|
1976
|
-
.filter((isSelected) => {
|
|
1977
|
-
return isSelected;
|
|
1978
|
-
})
|
|
1979
|
-
.length > 0
|
|
1980
|
-
// No tags match
|
|
1981
|
-
&& log.tags.every((tag) => {
|
|
1982
|
-
return !tagFilterState[tag];
|
|
1983
|
-
})
|
|
1984
|
-
) {
|
|
1985
|
-
return;
|
|
1986
|
-
}
|
|
1987
|
-
|
|
1988
|
-
/* ------- Actions and Errors ------- */
|
|
1989
|
-
|
|
1990
|
-
// Log type doesn't match
|
|
1991
|
-
if (
|
|
1992
|
-
// Filter won't allow all types
|
|
1993
|
-
actionErrorFilterState.type !== undefined
|
|
1994
|
-
// Log type doesn't match
|
|
1995
|
-
&& actionErrorFilterState.type !== log.type
|
|
1996
|
-
) {
|
|
1997
|
-
return;
|
|
1998
|
-
}
|
|
1999
|
-
|
|
2000
|
-
// Filter errors
|
|
2001
|
-
if (log.type === LogType.Error) {
|
|
2002
|
-
// Message doesn't match
|
|
2003
|
-
if (
|
|
2004
|
-
// Message exists
|
|
2005
|
-
log.errorMessage
|
|
2006
|
-
// Message filter exists
|
|
2007
|
-
&& actionErrorFilterState.errorMessage.trim().length > 0
|
|
2008
|
-
// Message doesn't match
|
|
2009
|
-
&& log.errorMessage.toLowerCase().includes(
|
|
2010
|
-
actionErrorFilterState.errorMessage.trim().toLowerCase(),
|
|
2011
|
-
)
|
|
2012
|
-
) {
|
|
2013
|
-
return;
|
|
2014
|
-
}
|
|
2015
|
-
|
|
2016
|
-
// Code doesn't match
|
|
2017
|
-
if (
|
|
2018
|
-
// Code exists
|
|
2019
|
-
log.errorCode
|
|
2020
|
-
// Code filter exists
|
|
2021
|
-
&& actionErrorFilterState.errorCode.trim().length > 0
|
|
2022
|
-
// Code doesn't match
|
|
2023
|
-
&& log.errorCode.toUpperCase().includes(
|
|
2024
|
-
actionErrorFilterState.errorCode.trim().toUpperCase(),
|
|
2025
|
-
)
|
|
2026
|
-
) {
|
|
2027
|
-
return;
|
|
2028
|
-
}
|
|
2029
|
-
}
|
|
2030
|
-
|
|
2031
|
-
// Filter actions
|
|
2032
|
-
if (log.type === LogType.Action) {
|
|
2033
|
-
// Target isn't selected
|
|
2034
|
-
if (
|
|
2035
|
-
// Target exists
|
|
2036
|
-
log.target
|
|
2037
|
-
// Target isn't selected
|
|
2038
|
-
&& !actionErrorFilterState.target[log.target]
|
|
2039
|
-
) {
|
|
2040
|
-
return;
|
|
2041
|
-
}
|
|
2042
|
-
|
|
2043
|
-
// Action
|
|
2044
|
-
if (
|
|
2045
|
-
// Action exists
|
|
2046
|
-
log.action
|
|
2047
|
-
// Action isn't selected
|
|
2048
|
-
&& !actionErrorFilterState.action[log.action]
|
|
2049
|
-
) {
|
|
2050
|
-
return;
|
|
2051
|
-
}
|
|
2052
|
-
}
|
|
2053
|
-
|
|
2054
|
-
/* --------- Advanced Filter -------- */
|
|
2055
|
-
|
|
2056
|
-
// First name doesn't match
|
|
2057
|
-
if (
|
|
2058
|
-
// First name exists
|
|
2059
|
-
log.userFirstName
|
|
2060
|
-
// First name query doesn't match
|
|
2061
|
-
&& !log.userFirstName.toLowerCase().includes(
|
|
2062
|
-
advancedFilterState.userFirstName.toLowerCase().trim(),
|
|
2063
|
-
)
|
|
2064
|
-
) {
|
|
2065
|
-
return;
|
|
2066
|
-
}
|
|
2067
|
-
|
|
2068
|
-
// Last name doesn't match
|
|
2069
|
-
if (
|
|
2070
|
-
// Last name exists
|
|
2071
|
-
log.userLastName
|
|
2072
|
-
// Last name query doesn't match
|
|
2073
|
-
&& !log.userLastName.toLowerCase().includes(
|
|
2074
|
-
advancedFilterState.userLastName.toLowerCase().trim(),
|
|
2075
|
-
)
|
|
2076
|
-
) {
|
|
2077
|
-
return;
|
|
2078
|
-
}
|
|
2079
|
-
|
|
2080
|
-
// Email doesn't match
|
|
2081
|
-
if (
|
|
2082
|
-
// Email exists
|
|
2083
|
-
log.userEmail
|
|
2084
|
-
// Email query doesn't match
|
|
2085
|
-
&& !log.userEmail.toLowerCase().includes(
|
|
2086
|
-
advancedFilterState.userEmail.toLowerCase().trim(),
|
|
2087
|
-
)
|
|
2088
|
-
) {
|
|
2089
|
-
return;
|
|
2090
|
-
}
|
|
2091
|
-
|
|
2092
|
-
// User id doesn't match
|
|
2093
|
-
if (
|
|
2094
|
-
// User id exists
|
|
2095
|
-
log.userId
|
|
2096
|
-
// User id doesn't match
|
|
2097
|
-
&& !String(log.userId).includes(
|
|
2098
|
-
advancedFilterState.userId.trim(),
|
|
2099
|
-
)
|
|
2100
|
-
) {
|
|
2101
|
-
return;
|
|
2102
|
-
}
|
|
2103
|
-
|
|
2104
|
-
// Learner not allowed
|
|
2105
|
-
if (
|
|
2106
|
-
// User is a learner
|
|
2107
|
-
log.isLearner
|
|
2108
|
-
// Learners aren't included
|
|
2109
|
-
&& !advancedFilterState.includeLearners
|
|
2110
|
-
) {
|
|
2111
|
-
return;
|
|
2112
|
-
}
|
|
2113
|
-
|
|
2114
|
-
// TTM not allowed
|
|
2115
|
-
if (
|
|
2116
|
-
// User is a ttm
|
|
2117
|
-
log.isTTM
|
|
2118
|
-
// TTMs aren't included
|
|
2119
|
-
&& !advancedFilterState.includeTTMs
|
|
2120
|
-
) {
|
|
2121
|
-
return;
|
|
2122
|
-
}
|
|
2123
|
-
|
|
2124
|
-
// Admin not allowed
|
|
2125
|
-
if (
|
|
2126
|
-
// User is an admin
|
|
2127
|
-
log.isAdmin
|
|
2128
|
-
// Admins aren't included
|
|
2129
|
-
&& !advancedFilterState.includeAdmins
|
|
2130
|
-
) {
|
|
2131
|
-
return;
|
|
2132
|
-
}
|
|
2133
|
-
|
|
2134
|
-
// Course Id doesn't match
|
|
2135
|
-
if (
|
|
2136
|
-
// Course Id exists
|
|
2137
|
-
log.courseId
|
|
2138
|
-
// Course Id doesn't match
|
|
2139
|
-
&& !String(log.courseId).includes(
|
|
2140
|
-
advancedFilterState.courseId.trim(),
|
|
2141
|
-
)
|
|
2142
|
-
) {
|
|
2143
|
-
return;
|
|
2144
|
-
}
|
|
2145
|
-
|
|
2146
|
-
// Course name doesn't match
|
|
2147
|
-
if (
|
|
2148
|
-
// Course name exists
|
|
2149
|
-
log.courseName
|
|
2150
|
-
// Course name doesn't match
|
|
2151
|
-
&& !String(log.courseName).includes(
|
|
2152
|
-
advancedFilterState.courseName.trim(),
|
|
2153
|
-
)
|
|
2154
|
-
) {
|
|
2155
|
-
return;
|
|
2156
|
-
}
|
|
2157
|
-
|
|
2158
|
-
// Mobile filter doesn't match
|
|
2159
|
-
if (
|
|
2160
|
-
// Mobile filter exists
|
|
2161
|
-
advancedFilterState.isMobile !== undefined
|
|
2162
|
-
// Device info exists
|
|
2163
|
-
&& log.device
|
|
2164
|
-
// Mobile filter doesn't match
|
|
2165
|
-
&& (advancedFilterState.isMobile !== log.device.isMobile)
|
|
2166
|
-
) {
|
|
2167
|
-
return;
|
|
2168
|
-
}
|
|
2169
|
-
|
|
2170
|
-
// Log source doesn't match
|
|
2171
|
-
if (
|
|
2172
|
-
// Source filter exists
|
|
2173
|
-
advancedFilterState.source !== undefined
|
|
2174
|
-
// Source info exists
|
|
2175
|
-
&& log.source
|
|
2176
|
-
// Source filter doesn't match
|
|
2177
|
-
&& (advancedFilterState.source !== log.source)
|
|
2178
|
-
) {
|
|
2179
|
-
return;
|
|
2180
|
-
}
|
|
2181
|
-
|
|
2182
|
-
// Route path doesn't match (Only for server source)
|
|
2183
|
-
if (
|
|
2184
|
-
// Source is server
|
|
2185
|
-
(log.source === LogSource.Server)
|
|
2186
|
-
// Route path is being filtered
|
|
2187
|
-
&& (advancedFilterState.routePath.trim().length)
|
|
2188
|
-
// Route path doesn't match
|
|
2189
|
-
&& !(log.routePath.includes(advancedFilterState.routePath.trim()))
|
|
2190
|
-
) {
|
|
2191
|
-
return;
|
|
2192
|
-
}
|
|
2193
|
-
|
|
2194
|
-
// Route template doesn't match (Only for server source)
|
|
2195
|
-
if (
|
|
2196
|
-
// Source is server
|
|
2197
|
-
(log.source === LogSource.Server)
|
|
2198
|
-
// Route template is being filtered
|
|
2199
|
-
&& (advancedFilterState.routeTemplate.trim().length)
|
|
2200
|
-
// Route template doesn't match
|
|
2201
|
-
&& !(log.routeTemplate.includes(advancedFilterState.routeTemplate.trim()))
|
|
2202
|
-
) {
|
|
2203
|
-
return;
|
|
2204
|
-
}
|
|
2205
|
-
|
|
2206
|
-
/* -------------- Done -------------- */
|
|
2207
|
-
|
|
2208
|
-
// Made it past all filters. Add to the list
|
|
2209
|
-
logs.push(log);
|
|
2210
|
-
});
|
|
2211
|
-
});
|
|
2212
|
-
});
|
|
2037
|
+
// Filters UI
|
|
2038
|
+
const filters = (
|
|
2039
|
+
<>
|
|
2040
|
+
{filterToggles}
|
|
2041
|
+
{filterDrawer && (
|
|
2042
|
+
<Drawer customBackgroundColor="#eee">
|
|
2043
|
+
{filterDrawer}
|
|
2044
|
+
</Drawer>
|
|
2045
|
+
)}
|
|
2046
|
+
</>
|
|
2047
|
+
);
|
|
2213
2048
|
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
? (
|
|
2222
|
-
<div className="alert alert-warning text-center mt-2">
|
|
2223
|
-
<h4 className="m-1">
|
|
2224
|
-
No Logs to Show
|
|
2225
|
-
</h4>
|
|
2226
|
-
<div>
|
|
2227
|
-
Either your filters are too strict or no matching logs have been
|
|
2228
|
-
created yet.
|
|
2229
|
-
</div>
|
|
2049
|
+
// Body that will be filled with the contents of the panel
|
|
2050
|
+
const body: React.ReactNode = (
|
|
2051
|
+
<>
|
|
2052
|
+
{
|
|
2053
|
+
loading && (
|
|
2054
|
+
<div className="text-center p-5">
|
|
2055
|
+
<LoadingSpinner />
|
|
2230
2056
|
</div>
|
|
2231
2057
|
)
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
const dataTable = (
|
|
2237
|
-
<IntelliTable
|
|
2238
|
-
title="Matching Logs:"
|
|
2239
|
-
csvName={`Logs from ${getHumanReadableDate()}`}
|
|
2240
|
-
id="logs"
|
|
2241
|
-
data={logs}
|
|
2242
|
-
columns={columns}
|
|
2243
|
-
/>
|
|
2244
|
-
);
|
|
2245
|
-
|
|
2246
|
-
// Main body
|
|
2247
|
-
body = (
|
|
2248
|
-
<>
|
|
2249
|
-
{filters}
|
|
2250
|
-
<div className="mt-2">
|
|
2251
|
-
{dataTable}
|
|
2252
|
-
{noLogsNotice}
|
|
2058
|
+
}
|
|
2059
|
+
{!loading && (
|
|
2060
|
+
<div>
|
|
2061
|
+
{filters}
|
|
2253
2062
|
</div>
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2063
|
+
)}
|
|
2064
|
+
{
|
|
2065
|
+
!loading && logs.length === 0 && (
|
|
2066
|
+
<div className="alert alert-warning text-center mt-2">
|
|
2067
|
+
<h4 className="m-1">No Logs to Show</h4>
|
|
2068
|
+
<div>
|
|
2069
|
+
Either your filters are too strict or no matching logs have been
|
|
2070
|
+
created yet.
|
|
2071
|
+
</div>
|
|
2072
|
+
</div>
|
|
2073
|
+
)
|
|
2074
|
+
}
|
|
2075
|
+
<div
|
|
2076
|
+
className={loading || logs.length === 0 ? 'd-none' : undefined}
|
|
2077
|
+
>
|
|
2078
|
+
<IntelliTable
|
|
2079
|
+
title="Matching Logs"
|
|
2080
|
+
csvName={`Logs from ${getHumanReadableDate()}`}
|
|
2081
|
+
id="logs"
|
|
2082
|
+
data={logs}
|
|
2083
|
+
columns={columns}
|
|
2084
|
+
/>
|
|
2085
|
+
</div>
|
|
2086
|
+
{!loading && logs.length > 0 && paginationControls}
|
|
2087
|
+
</>
|
|
2088
|
+
);
|
|
2257
2089
|
|
|
2258
2090
|
/* ---------- Wrap in Modal --------- */
|
|
2259
2091
|
|
|
@@ -2265,9 +2097,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
2265
2097
|
<div className="LogReviewer-inner-container">
|
|
2266
2098
|
<div className="LogReviewer-header">
|
|
2267
2099
|
<div className="LogReviewer-header-title">
|
|
2268
|
-
<h3 className="text-center
|
|
2269
|
-
Log Review Dashboard
|
|
2270
|
-
</h3>
|
|
2100
|
+
<h3 className="text-center">Log Review Dashboard</h3>
|
|
2271
2101
|
</div>
|
|
2272
2102
|
<div style={{ width: 0 }}>
|
|
2273
2103
|
<button
|
|
@@ -2276,15 +2106,11 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
2276
2106
|
aria-label="close log reviewer panel"
|
|
2277
2107
|
onClick={onClose}
|
|
2278
2108
|
>
|
|
2279
|
-
<FontAwesomeIcon
|
|
2280
|
-
icon={faTimes}
|
|
2281
|
-
/>
|
|
2109
|
+
<FontAwesomeIcon icon={faTimes} />
|
|
2282
2110
|
</button>
|
|
2283
2111
|
</div>
|
|
2284
2112
|
</div>
|
|
2285
|
-
<div className="LogReviewer-contents">
|
|
2286
|
-
{body}
|
|
2287
|
-
</div>
|
|
2113
|
+
<div className="LogReviewer-contents">{body}</div>
|
|
2288
2114
|
</div>
|
|
2289
2115
|
</div>
|
|
2290
2116
|
);
|