dce-reactkit 3.9.4-beta-prompt.2 → 3.9.4-beta-logreviewer.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +670 -869
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/AppWrapper.d.ts +0 -24
- package/dist/cjs/types/constants/LOG_REVIEW_GET_LOGS_ROUTE.d.ts +6 -0
- package/dist/cjs/types/index.d.ts +2 -2
- package/dist/cjs/types/types/LogReviewerFilterState/ActionErrorFilterState.d.ts +13 -0
- package/dist/cjs/types/types/LogReviewerFilterState/AdvancedFilterState.d.ts +17 -0
- package/dist/cjs/types/types/LogReviewerFilterState/ContextFilterState.d.ts +6 -0
- package/dist/cjs/types/types/LogReviewerFilterState/DateFilterState.d.ts +13 -0
- package/dist/cjs/types/types/LogReviewerFilterState/TagFilterState.d.ts +4 -0
- package/dist/cjs/types/types/LogReviewerFilterState/index.d.ts +17 -0
- package/dist/cjs/types/types/ModalType.d.ts +0 -1
- package/dist/esm/index.js +672 -870
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/AppWrapper.d.ts +0 -24
- package/dist/esm/types/constants/LOG_REVIEW_GET_LOGS_ROUTE.d.ts +6 -0
- package/dist/esm/types/index.d.ts +2 -2
- package/dist/esm/types/types/LogReviewerFilterState/ActionErrorFilterState.d.ts +13 -0
- package/dist/esm/types/types/LogReviewerFilterState/AdvancedFilterState.d.ts +17 -0
- package/dist/esm/types/types/LogReviewerFilterState/ContextFilterState.d.ts +6 -0
- package/dist/esm/types/types/LogReviewerFilterState/DateFilterState.d.ts +13 -0
- package/dist/esm/types/types/LogReviewerFilterState/TagFilterState.d.ts +4 -0
- package/dist/esm/types/types/LogReviewerFilterState/index.d.ts +17 -0
- package/dist/esm/types/types/ModalType.d.ts +0 -1
- package/dist/index.d.ts +1 -26
- package/package.json +1 -1
- package/src/components/AppWrapper.tsx +0 -216
- package/src/components/LogReviewer.tsx +840 -1232
- package/src/components/Modal/index.tsx +0 -3
- package/src/constants/LOG_REVIEW_GET_LOGS_ROUTE.ts +9 -0
- package/src/index.ts +0 -2
- package/src/server/initServer.ts +216 -19
- package/src/types/LogReviewerFilterState/ActionErrorFilterState.ts +21 -0
- package/src/types/LogReviewerFilterState/AdvancedFilterState.ts +33 -0
- package/src/types/LogReviewerFilterState/ContextFilterState.ts +13 -0
- package/src/types/LogReviewerFilterState/DateFilterState.ts +23 -0
- package/src/types/LogReviewerFilterState/TagFilterState.ts +6 -0
- package/src/types/LogReviewerFilterState/index.ts +24 -0
- package/src/types/ModalType.tsx +0 -1
- package/dist/cjs/types/constants/LOG_REVIEW_ROUTE_PATH_PREFIX.d.ts +0 -6
- package/dist/esm/types/constants/LOG_REVIEW_ROUTE_PATH_PREFIX.d.ts +0 -6
- package/src/constants/LOG_REVIEW_ROUTE_PATH_PREFIX.ts +0 -9
|
@@ -10,12 +10,15 @@ import React, { useReducer, useEffect } from 'react';
|
|
|
10
10
|
// Import FontAwesome
|
|
11
11
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
12
12
|
import {
|
|
13
|
+
faArrowLeft,
|
|
14
|
+
faArrowRight,
|
|
13
15
|
faCalendar,
|
|
14
16
|
faCircle,
|
|
15
17
|
faHammer,
|
|
16
18
|
faList,
|
|
17
19
|
faTag,
|
|
18
20
|
faTimes,
|
|
21
|
+
faSearch,
|
|
19
22
|
} from '@fortawesome/free-solid-svg-icons';
|
|
20
23
|
|
|
21
24
|
// Import shared helpers
|
|
@@ -25,7 +28,7 @@ import { alert, showFatalError } from './AppWrapper';
|
|
|
25
28
|
import getHumanReadableDate from '../helpers/getHumanReadableDate';
|
|
26
29
|
|
|
27
30
|
// Import shared constants
|
|
28
|
-
import
|
|
31
|
+
import LOG_REVIEW_GET_LOGS_ROUTE from '../constants/LOG_REVIEW_GET_LOGS_ROUTE';
|
|
29
32
|
|
|
30
33
|
// Import shared types
|
|
31
34
|
import Log from '../types/Log';
|
|
@@ -38,6 +41,12 @@ import IntelliTableColumn from '../types/IntelliTableColumn';
|
|
|
38
41
|
import LogBuiltInMetadata from '../types/LogBuiltInMetadata';
|
|
39
42
|
import LogMetadataContextMap from '../types/LogMetadataContextMap';
|
|
40
43
|
import LogMetadataTargetMap from '../types/LogMetadataTargetMap';
|
|
44
|
+
import LogReviewerFilterState from '../types/LogReviewerFilterState';
|
|
45
|
+
import DateFilterState from '../types/LogReviewerFilterState/DateFilterState';
|
|
46
|
+
import ContextFilterState from '../types/LogReviewerFilterState/ContextFilterState';
|
|
47
|
+
import TagFilterState from '../types/LogReviewerFilterState/TagFilterState';
|
|
48
|
+
import ActionErrorFilterState from '../types/LogReviewerFilterState/ActionErrorFilterState';
|
|
49
|
+
import AdvancedFilterState from '../types/LogReviewerFilterState/AdvancedFilterState';
|
|
41
50
|
|
|
42
51
|
// Import shared components
|
|
43
52
|
import SimpleDateChooser from './SimpleDateChooser';
|
|
@@ -63,13 +72,6 @@ type Props = {
|
|
|
63
72
|
onClose: () => void,
|
|
64
73
|
};
|
|
65
74
|
|
|
66
|
-
// Map of loaded logs (year => month => Log[])
|
|
67
|
-
type LogMap = {
|
|
68
|
-
[k: string]: {
|
|
69
|
-
[k: string]: Log[]
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
73
75
|
// Triple of year/month/day
|
|
74
76
|
type DateTriple = {
|
|
75
77
|
// Full year
|
|
@@ -89,93 +91,6 @@ enum FilterDrawer {
|
|
|
89
91
|
Advanced = 'advanced',
|
|
90
92
|
}
|
|
91
93
|
|
|
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
94
|
/*------------------------------------------------------------------------*/
|
|
180
95
|
/* -------------------------------- Style ------------------------------- */
|
|
181
96
|
/*------------------------------------------------------------------------*/
|
|
@@ -532,8 +447,8 @@ type State = {
|
|
|
532
447
|
/* -------------- Logs -------------- */
|
|
533
448
|
// True if currently loading
|
|
534
449
|
loading: boolean,
|
|
535
|
-
// Loaded logs
|
|
536
|
-
|
|
450
|
+
// Loaded logs
|
|
451
|
+
logs: Log[],
|
|
537
452
|
/* ------------- Filters ------------ */
|
|
538
453
|
// Current expanded filter drawer
|
|
539
454
|
expandedFilterDrawer: FilterDrawer | undefined,
|
|
@@ -547,6 +462,10 @@ type State = {
|
|
|
547
462
|
actionErrorFilterState: ActionErrorFilterState,
|
|
548
463
|
// State of the advanced filter
|
|
549
464
|
advancedFilterState: AdvancedFilterState,
|
|
465
|
+
// Current page number
|
|
466
|
+
pageNumber: number,
|
|
467
|
+
// If true, there is another page to load
|
|
468
|
+
hasAnotherPage: boolean,
|
|
550
469
|
};
|
|
551
470
|
|
|
552
471
|
/* ------------- Actions ------------ */
|
|
@@ -555,7 +474,7 @@ type State = {
|
|
|
555
474
|
enum ActionType {
|
|
556
475
|
// Show the loading bar
|
|
557
476
|
StartLoading = 'start-loading',
|
|
558
|
-
// Finish loading
|
|
477
|
+
// Finish loading logs
|
|
559
478
|
FinishLoading = 'finish-loading',
|
|
560
479
|
// Reset filters to initial values
|
|
561
480
|
ResetFilters = 'reset-filters',
|
|
@@ -573,6 +492,12 @@ enum ActionType {
|
|
|
573
492
|
UpdateActionErrorFilterState = 'update-action-error-filter-state',
|
|
574
493
|
// Update the advanced filter state
|
|
575
494
|
UpdateAdvancedFilterState = 'update-advanced-filter-state',
|
|
495
|
+
// Increment the page number
|
|
496
|
+
IncrementPageNumber = 'increment-page-number',
|
|
497
|
+
// Decrement the page number
|
|
498
|
+
DecrementPageNumber = 'decrement-page-number',
|
|
499
|
+
// Set has another page
|
|
500
|
+
SetHasAnotherPage = 'set-has-another-page',
|
|
576
501
|
}
|
|
577
502
|
|
|
578
503
|
// Action definitions
|
|
@@ -580,8 +505,8 @@ type Action = (
|
|
|
580
505
|
| {
|
|
581
506
|
// Action type
|
|
582
507
|
type: ActionType.FinishLoading,
|
|
583
|
-
// Updated
|
|
584
|
-
|
|
508
|
+
// Updated logs
|
|
509
|
+
logs: Log[],
|
|
585
510
|
}
|
|
586
511
|
| {
|
|
587
512
|
// Action type
|
|
@@ -629,6 +554,16 @@ type Action = (
|
|
|
629
554
|
// New advanced filter state
|
|
630
555
|
advancedFilterState: AdvancedFilterState,
|
|
631
556
|
}
|
|
557
|
+
| {
|
|
558
|
+
type: ActionType.IncrementPageNumber,
|
|
559
|
+
}
|
|
560
|
+
| {
|
|
561
|
+
type: ActionType.DecrementPageNumber,
|
|
562
|
+
}
|
|
563
|
+
| {
|
|
564
|
+
type: ActionType.SetHasAnotherPage,
|
|
565
|
+
hasAnotherPage: boolean,
|
|
566
|
+
}
|
|
632
567
|
| {
|
|
633
568
|
// Action type
|
|
634
569
|
type: (
|
|
@@ -656,7 +591,7 @@ const reducer = (state: State, action: Action): State => {
|
|
|
656
591
|
return {
|
|
657
592
|
...state,
|
|
658
593
|
loading: false,
|
|
659
|
-
|
|
594
|
+
logs: action.logs,
|
|
660
595
|
};
|
|
661
596
|
}
|
|
662
597
|
case ActionType.ToggleFilterDrawer: {
|
|
@@ -715,6 +650,24 @@ const reducer = (state: State, action: Action): State => {
|
|
|
715
650
|
advancedFilterState: action.advancedFilterState,
|
|
716
651
|
};
|
|
717
652
|
}
|
|
653
|
+
case ActionType.IncrementPageNumber: {
|
|
654
|
+
return {
|
|
655
|
+
...state,
|
|
656
|
+
pageNumber: state.pageNumber + 1,
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
case ActionType.DecrementPageNumber: {
|
|
660
|
+
return {
|
|
661
|
+
...state,
|
|
662
|
+
pageNumber: state.pageNumber - 1,
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
case ActionType.SetHasAnotherPage: {
|
|
666
|
+
return {
|
|
667
|
+
...state,
|
|
668
|
+
hasAnotherPage: action.hasAnotherPage,
|
|
669
|
+
};
|
|
670
|
+
}
|
|
718
671
|
default: {
|
|
719
672
|
return state;
|
|
720
673
|
}
|
|
@@ -850,13 +803,15 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
850
803
|
// Initial state
|
|
851
804
|
const initialState: State = {
|
|
852
805
|
loading: true,
|
|
853
|
-
|
|
806
|
+
logs: [],
|
|
854
807
|
expandedFilterDrawer: undefined,
|
|
855
808
|
dateFilterState: initDateFilterState,
|
|
856
809
|
contextFilterState: initContextFilterState,
|
|
857
810
|
tagFilterState: initTagFilterState,
|
|
858
811
|
actionErrorFilterState: initActionErrorFilterState,
|
|
859
812
|
advancedFilterState: initAdvancedFilterState,
|
|
813
|
+
pageNumber: 1,
|
|
814
|
+
hasAnotherPage: false,
|
|
860
815
|
};
|
|
861
816
|
|
|
862
817
|
// Initialize state
|
|
@@ -865,13 +820,15 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
865
820
|
// Destructure common state
|
|
866
821
|
const {
|
|
867
822
|
loading,
|
|
868
|
-
|
|
823
|
+
logs,
|
|
869
824
|
expandedFilterDrawer,
|
|
870
825
|
dateFilterState,
|
|
871
826
|
contextFilterState,
|
|
872
827
|
tagFilterState,
|
|
873
828
|
actionErrorFilterState,
|
|
874
829
|
advancedFilterState,
|
|
830
|
+
pageNumber,
|
|
831
|
+
hasAnotherPage,
|
|
875
832
|
} = state;
|
|
876
833
|
|
|
877
834
|
/*------------------------------------------------------------------------*/
|
|
@@ -879,106 +836,48 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
879
836
|
/*------------------------------------------------------------------------*/
|
|
880
837
|
|
|
881
838
|
/**
|
|
882
|
-
*
|
|
883
|
-
* start or end date and the existing logMap
|
|
884
|
-
* @author Gabe Abrams
|
|
885
|
-
* @param newDateFilterState the new date filter state
|
|
886
|
-
* @returns list of year/month combos that need to be loaded
|
|
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
|
|
839
|
+
* Fetch logs from the server based on current filters
|
|
932
840
|
*/
|
|
933
|
-
const
|
|
934
|
-
|
|
935
|
-
) => {
|
|
936
|
-
// Update state
|
|
937
|
-
dispatch({
|
|
938
|
-
type: ActionType.UpdateDateFilterState,
|
|
939
|
-
dateFilterState: newDateFilterState,
|
|
940
|
-
});
|
|
941
|
-
|
|
942
|
-
// Check which year/month combos we need to load
|
|
943
|
-
const toLoad = listMonthsToLoad(newDateFilterState);
|
|
841
|
+
const fetchLogs = async () => {
|
|
842
|
+
dispatch({ type: ActionType.StartLoading });
|
|
944
843
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
844
|
+
try {
|
|
845
|
+
// Prepare filter parameters
|
|
846
|
+
const filters: LogReviewerFilterState = {
|
|
847
|
+
dateFilterState,
|
|
848
|
+
contextFilterState,
|
|
849
|
+
tagFilterState,
|
|
850
|
+
actionErrorFilterState,
|
|
851
|
+
advancedFilterState,
|
|
852
|
+
};
|
|
949
853
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
854
|
+
// Send filters to the server
|
|
855
|
+
let fetchedLogs: Log[] = [];
|
|
856
|
+
|
|
857
|
+
// Get logs from server
|
|
858
|
+
const response = await visitServerEndpoint({
|
|
859
|
+
path: LOG_REVIEW_GET_LOGS_ROUTE,
|
|
860
|
+
method: 'GET',
|
|
861
|
+
params: {
|
|
862
|
+
pageNumber,
|
|
863
|
+
filters,
|
|
864
|
+
},
|
|
865
|
+
});
|
|
954
866
|
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
// Load
|
|
962
|
-
const logs = await visitServerEndpoint({
|
|
963
|
-
path: `${LOG_REVIEW_ROUTE_PATH_PREFIX}/years/${year}/months/${month}`,
|
|
964
|
-
method: 'GET',
|
|
965
|
-
});
|
|
867
|
+
fetchedLogs = fetchedLogs.concat(response.items);
|
|
868
|
+
dispatch({
|
|
869
|
+
type: ActionType.SetHasAnotherPage,
|
|
870
|
+
hasAnotherPage: response.hasAnotherPage,
|
|
871
|
+
});
|
|
966
872
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
}
|
|
873
|
+
// Update logs in state
|
|
874
|
+
dispatch({
|
|
875
|
+
type: ActionType.FinishLoading,
|
|
876
|
+
logs: fetchedLogs,
|
|
877
|
+
});
|
|
973
878
|
} catch (err) {
|
|
974
879
|
return showFatalError(err);
|
|
975
880
|
}
|
|
976
|
-
|
|
977
|
-
// Finish loading
|
|
978
|
-
dispatch({
|
|
979
|
-
type: ActionType.FinishLoading,
|
|
980
|
-
logMap,
|
|
981
|
-
});
|
|
982
881
|
};
|
|
983
882
|
|
|
984
883
|
/*------------------------------------------------------------------------*/
|
|
@@ -986,21 +885,17 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
986
885
|
/*------------------------------------------------------------------------*/
|
|
987
886
|
|
|
988
887
|
/**
|
|
989
|
-
*
|
|
990
|
-
* @author Gabe Abrams
|
|
888
|
+
* Fetch logs whenever page number changes
|
|
991
889
|
*/
|
|
992
|
-
useEffect(
|
|
993
|
-
()
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
[],
|
|
998
|
-
);
|
|
890
|
+
useEffect(() => {
|
|
891
|
+
fetchLogs();
|
|
892
|
+
}, [
|
|
893
|
+
pageNumber,
|
|
894
|
+
]);
|
|
999
895
|
|
|
1000
896
|
/*------------------------------------------------------------------------*/
|
|
1001
897
|
/* ------------------------------- Render ------------------------------- */
|
|
1002
898
|
/*------------------------------------------------------------------------*/
|
|
1003
|
-
|
|
1004
899
|
/*----------------------------------------*/
|
|
1005
900
|
/* --------------- Main UI -------------- */
|
|
1006
901
|
/*----------------------------------------*/
|
|
@@ -1020,176 +915,238 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1020
915
|
|
|
1021
916
|
/* ------------ Review UI ----------- */
|
|
1022
917
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
918
|
+
/*----------------------------------------*/
|
|
919
|
+
/* ------------ Pagination -------------- */
|
|
920
|
+
/*----------------------------------------*/
|
|
921
|
+
|
|
922
|
+
const paginationControls = logs.length > 0 && (
|
|
923
|
+
<div className="text-center mt-3">
|
|
924
|
+
<button
|
|
925
|
+
type="button"
|
|
926
|
+
className="btn btn-secondary me-2"
|
|
927
|
+
disabled={pageNumber <= 1}
|
|
928
|
+
onClick={() => {
|
|
929
|
+
dispatch({
|
|
930
|
+
type: ActionType.DecrementPageNumber,
|
|
931
|
+
});
|
|
932
|
+
}}
|
|
933
|
+
>
|
|
934
|
+
<FontAwesomeIcon icon={faArrowLeft} className="me-2" />
|
|
935
|
+
Previous Page
|
|
936
|
+
</button>
|
|
937
|
+
<span className="mx-3">
|
|
938
|
+
Page
|
|
939
|
+
{' '}
|
|
940
|
+
{pageNumber}
|
|
941
|
+
</span>
|
|
942
|
+
<button
|
|
943
|
+
type="button"
|
|
944
|
+
className="btn btn-secondary ms-2"
|
|
945
|
+
disabled={!hasAnotherPage}
|
|
946
|
+
onClick={() => {
|
|
947
|
+
dispatch({
|
|
948
|
+
type: ActionType.IncrementPageNumber,
|
|
949
|
+
});
|
|
950
|
+
}}
|
|
951
|
+
>
|
|
952
|
+
Next Page
|
|
953
|
+
<FontAwesomeIcon icon={faArrowRight} className="ms-2" />
|
|
954
|
+
</button>
|
|
955
|
+
</div>
|
|
956
|
+
);
|
|
957
|
+
|
|
958
|
+
/*----------------------------------------*/
|
|
959
|
+
/* --------------- Filters -------------- */
|
|
960
|
+
/*----------------------------------------*/
|
|
961
|
+
|
|
962
|
+
// Filter toggle
|
|
963
|
+
const filterToggles = (
|
|
964
|
+
<div className="LogReviewer-filter-toggles">
|
|
965
|
+
<h3 className="m-0">
|
|
966
|
+
Filters:
|
|
967
|
+
</h3>
|
|
968
|
+
<div className="LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0">
|
|
969
|
+
{/* Date */}
|
|
970
|
+
<button
|
|
971
|
+
type="button"
|
|
972
|
+
id="LogReviewer-toggle-date-filter-drawer"
|
|
973
|
+
className={`btn btn-${FilterDrawer.Date === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
974
|
+
aria-label="toggle date filter drawer"
|
|
975
|
+
onClick={() => {
|
|
976
|
+
dispatch({
|
|
977
|
+
type: ActionType.ToggleFilterDrawer,
|
|
978
|
+
filterDrawer: FilterDrawer.Date,
|
|
979
|
+
});
|
|
980
|
+
}}
|
|
981
|
+
>
|
|
982
|
+
<FontAwesomeIcon
|
|
983
|
+
icon={faCalendar}
|
|
984
|
+
className="me-2"
|
|
985
|
+
/>
|
|
986
|
+
Date
|
|
987
|
+
</button>
|
|
988
|
+
{/* Context */}
|
|
989
|
+
<button
|
|
990
|
+
type="button"
|
|
991
|
+
id="LogReviewer-toggle-context-filter-drawer"
|
|
992
|
+
className={`btn btn-${FilterDrawer.Context === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
993
|
+
aria-label="toggle context filter drawer"
|
|
994
|
+
onClick={() => {
|
|
995
|
+
dispatch({
|
|
996
|
+
type: ActionType.ToggleFilterDrawer,
|
|
997
|
+
filterDrawer: FilterDrawer.Context,
|
|
998
|
+
});
|
|
999
|
+
}}
|
|
1000
|
+
>
|
|
1001
|
+
<FontAwesomeIcon
|
|
1002
|
+
icon={faCircle}
|
|
1003
|
+
className="me-2"
|
|
1004
|
+
/>
|
|
1005
|
+
Context
|
|
1006
|
+
</button>
|
|
1007
|
+
{/* Tag */}
|
|
1008
|
+
{/* Skip if no tags are used */}
|
|
1009
|
+
{(LogMetadata.Tag && Object.keys(LogMetadata.Tag).length > 0) && (
|
|
1010
|
+
<button
|
|
1011
|
+
type="button"
|
|
1012
|
+
id="LogReviewer-toggle-tag-filter-drawer"
|
|
1013
|
+
className={`btn btn-${FilterDrawer.Tag === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
1014
|
+
aria-label="toggle tag filter drawer"
|
|
1015
|
+
onClick={() => {
|
|
1016
|
+
dispatch({
|
|
1017
|
+
type: ActionType.ToggleFilterDrawer,
|
|
1018
|
+
filterDrawer: FilterDrawer.Tag,
|
|
1019
|
+
});
|
|
1020
|
+
}}
|
|
1021
|
+
>
|
|
1022
|
+
<FontAwesomeIcon
|
|
1023
|
+
icon={faTag}
|
|
1024
|
+
className="me-2"
|
|
1025
|
+
/>
|
|
1026
|
+
Tag
|
|
1027
|
+
</button>
|
|
1028
|
+
)}
|
|
1029
|
+
{/* Action */}
|
|
1030
|
+
<button
|
|
1031
|
+
type="button"
|
|
1032
|
+
id="LogReviewer-toggle-action-filter-drawer"
|
|
1033
|
+
className={`btn btn-${FilterDrawer.Action === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
1034
|
+
aria-label="toggle action and error filter drawer"
|
|
1035
|
+
onClick={() => {
|
|
1036
|
+
dispatch({
|
|
1037
|
+
type: ActionType.ToggleFilterDrawer,
|
|
1038
|
+
filterDrawer: FilterDrawer.Action,
|
|
1039
|
+
});
|
|
1040
|
+
}}
|
|
1041
|
+
>
|
|
1042
|
+
<FontAwesomeIcon
|
|
1043
|
+
icon={faHammer}
|
|
1044
|
+
className="me-2"
|
|
1045
|
+
/>
|
|
1046
|
+
Action
|
|
1047
|
+
</button>
|
|
1048
|
+
{/* Advanced */}
|
|
1049
|
+
<button
|
|
1050
|
+
type="button"
|
|
1051
|
+
id="LogReviewer-toggle-advanced-filter-drawer"
|
|
1052
|
+
className={`btn btn-${FilterDrawer.Advanced === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
1053
|
+
aria-label="toggle advanced filter drawer"
|
|
1054
|
+
onClick={() => {
|
|
1055
|
+
dispatch({
|
|
1056
|
+
type: ActionType.ToggleFilterDrawer,
|
|
1057
|
+
filterDrawer: FilterDrawer.Advanced,
|
|
1058
|
+
});
|
|
1059
|
+
}}
|
|
1060
|
+
>
|
|
1061
|
+
<FontAwesomeIcon
|
|
1062
|
+
icon={faList}
|
|
1063
|
+
className="me-2"
|
|
1064
|
+
/>
|
|
1065
|
+
Advanced
|
|
1066
|
+
</button>
|
|
1067
|
+
{/* Reset */}
|
|
1068
|
+
<button
|
|
1069
|
+
type="button"
|
|
1070
|
+
id="LogReviewer-reset-filters-button"
|
|
1071
|
+
className="btn btn-light"
|
|
1072
|
+
aria-label="reset filters"
|
|
1073
|
+
onClick={() => {
|
|
1074
|
+
dispatch({
|
|
1075
|
+
type: ActionType.ResetFilters,
|
|
1076
|
+
initActionErrorFilterState,
|
|
1077
|
+
initAdvancedFilterState,
|
|
1078
|
+
initContextFilterState,
|
|
1079
|
+
initDateFilterState,
|
|
1080
|
+
initTagFilterState,
|
|
1081
|
+
});
|
|
1082
|
+
}}
|
|
1083
|
+
>
|
|
1084
|
+
<FontAwesomeIcon
|
|
1085
|
+
icon={faTimes}
|
|
1086
|
+
/>
|
|
1087
|
+
{' '}
|
|
1088
|
+
Reset
|
|
1089
|
+
</button>
|
|
1090
|
+
|
|
1091
|
+
{/* Submit filter changes */}
|
|
1092
|
+
<button
|
|
1093
|
+
type="button"
|
|
1094
|
+
id="LogReviewer-submit-filters-button"
|
|
1095
|
+
className="btn btn-primary ms-2"
|
|
1096
|
+
aria-label="submit filters"
|
|
1097
|
+
onClick={() => {
|
|
1098
|
+
dispatch({
|
|
1099
|
+
type: ActionType.HideFilterDrawer,
|
|
1100
|
+
});
|
|
1101
|
+
fetchLogs();
|
|
1102
|
+
}}
|
|
1103
|
+
>
|
|
1104
|
+
<FontAwesomeIcon
|
|
1105
|
+
icon={faSearch}
|
|
1106
|
+
/>
|
|
1107
|
+
{' '}
|
|
1108
|
+
Filter
|
|
1109
|
+
</button>
|
|
1110
|
+
</div>
|
|
1111
|
+
</div>
|
|
1112
|
+
);
|
|
1113
|
+
|
|
1114
|
+
// Filter drawer
|
|
1115
|
+
let filterDrawer: React.ReactNode;
|
|
1116
|
+
if (expandedFilterDrawer) {
|
|
1117
|
+
if (expandedFilterDrawer === FilterDrawer.Date) {
|
|
1118
|
+
filterDrawer = (
|
|
1119
|
+
<TabBox title="Date">
|
|
1120
|
+
<SimpleDateChooser
|
|
1121
|
+
ariaLabel="filter start date"
|
|
1122
|
+
name="filter-start-date"
|
|
1123
|
+
year={dateFilterState.startDate.year}
|
|
1124
|
+
month={dateFilterState.startDate.month}
|
|
1125
|
+
day={dateFilterState.startDate.day}
|
|
1126
|
+
chooseFromPast
|
|
1127
|
+
numMonthsToShow={36}
|
|
1128
|
+
onChange={(month, day, year) => {
|
|
1129
|
+
dateFilterState.startDate = { month, day, year };
|
|
1140
1130
|
dispatch({
|
|
1141
|
-
type: ActionType.
|
|
1142
|
-
|
|
1143
|
-
initAdvancedFilterState,
|
|
1144
|
-
initContextFilterState,
|
|
1145
|
-
initDateFilterState,
|
|
1146
|
-
initTagFilterState,
|
|
1131
|
+
type: ActionType.UpdateDateFilterState,
|
|
1132
|
+
dateFilterState,
|
|
1147
1133
|
});
|
|
1148
1134
|
}}
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
filterDrawer = (
|
|
1165
|
-
<TabBox title="Date">
|
|
1166
|
-
<SimpleDateChooser
|
|
1167
|
-
ariaLabel="filter start date"
|
|
1168
|
-
name="filter-start-date"
|
|
1169
|
-
year={dateFilterState.startDate.year}
|
|
1170
|
-
month={dateFilterState.startDate.month}
|
|
1171
|
-
day={dateFilterState.startDate.day}
|
|
1172
|
-
chooseFromPast
|
|
1173
|
-
numMonthsToShow={36}
|
|
1174
|
-
onChange={(month, day, year) => {
|
|
1175
|
-
dateFilterState.startDate = { month, day, year };
|
|
1176
|
-
handleDateRangeUpdated(dateFilterState);
|
|
1177
|
-
}}
|
|
1178
|
-
/>
|
|
1179
|
-
{' '}
|
|
1180
|
-
to
|
|
1181
|
-
{' '}
|
|
1182
|
-
<SimpleDateChooser
|
|
1183
|
-
ariaLabel="filter end date"
|
|
1184
|
-
name="filter-end-date"
|
|
1185
|
-
year={dateFilterState.endDate.year}
|
|
1186
|
-
month={dateFilterState.endDate.month}
|
|
1187
|
-
day={dateFilterState.endDate.day}
|
|
1188
|
-
chooseFromPast
|
|
1189
|
-
numMonthsToShow={12}
|
|
1190
|
-
onChange={(month, day, year) => {
|
|
1191
|
-
if (
|
|
1192
|
-
year < dateFilterState.startDate.year
|
|
1135
|
+
/>
|
|
1136
|
+
{' '}
|
|
1137
|
+
to
|
|
1138
|
+
{' '}
|
|
1139
|
+
<SimpleDateChooser
|
|
1140
|
+
ariaLabel="filter end date"
|
|
1141
|
+
name="filter-end-date"
|
|
1142
|
+
year={dateFilterState.endDate.year}
|
|
1143
|
+
month={dateFilterState.endDate.month}
|
|
1144
|
+
day={dateFilterState.endDate.day}
|
|
1145
|
+
chooseFromPast
|
|
1146
|
+
numMonthsToShow={12}
|
|
1147
|
+
onChange={(month, day, year) => {
|
|
1148
|
+
if (
|
|
1149
|
+
year < dateFilterState.startDate.year
|
|
1193
1150
|
|| (
|
|
1194
1151
|
year === dateFilterState.startDate.year
|
|
1195
1152
|
&& month < dateFilterState.startDate.month
|
|
@@ -1199,137 +1156,140 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1199
1156
|
&& month === dateFilterState.startDate.month
|
|
1200
1157
|
&& day < dateFilterState.startDate.day
|
|
1201
1158
|
)
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
}
|
|
1208
|
-
dateFilterState.endDate = { month, day, year };
|
|
1209
|
-
handleDateRangeUpdated(dateFilterState);
|
|
1210
|
-
}}
|
|
1211
|
-
/>
|
|
1212
|
-
</TabBox>
|
|
1213
|
-
);
|
|
1214
|
-
} else if (expandedFilterDrawer === FilterDrawer.Context) {
|
|
1215
|
-
// Create item picker items
|
|
1216
|
-
const builtInPickableItem: PickableItem = {
|
|
1217
|
-
id: 'built-in-contexts',
|
|
1218
|
-
name: 'Auto-logged',
|
|
1219
|
-
isGroup: true,
|
|
1220
|
-
children: [],
|
|
1221
|
-
};
|
|
1222
|
-
const pickableItems: PickableItem[] = [];
|
|
1223
|
-
Object.keys(contextMap)
|
|
1224
|
-
.forEach((context) => {
|
|
1225
|
-
const value = contextMap[context];
|
|
1226
|
-
if (typeof value === 'string') {
|
|
1227
|
-
// No subcategories
|
|
1228
|
-
const item: PickableItem = {
|
|
1229
|
-
id: context,
|
|
1230
|
-
name: genHumanReadableName(context),
|
|
1231
|
-
isGroup: false,
|
|
1232
|
-
checked: !!contextFilterState[context],
|
|
1233
|
-
};
|
|
1234
|
-
|
|
1235
|
-
// Add built-in items to its own folder
|
|
1236
|
-
const isBuiltIn = context in LogBuiltInMetadata.Context;
|
|
1237
|
-
if (isBuiltIn) {
|
|
1238
|
-
// Add to built-in pickable item
|
|
1239
|
-
builtInPickableItem.children.push(item);
|
|
1240
|
-
} else {
|
|
1241
|
-
// Add to pickable items list
|
|
1242
|
-
pickableItems.push(item);
|
|
1159
|
+
) {
|
|
1160
|
+
return alert(
|
|
1161
|
+
'Invalid Start Date',
|
|
1162
|
+
'The start date cannot be before the end date.',
|
|
1163
|
+
);
|
|
1243
1164
|
}
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1165
|
+
dateFilterState.endDate = { month, day, year };
|
|
1166
|
+
dispatch({
|
|
1167
|
+
type: ActionType.UpdateDateFilterState,
|
|
1168
|
+
dateFilterState,
|
|
1169
|
+
});
|
|
1170
|
+
}}
|
|
1171
|
+
/>
|
|
1172
|
+
</TabBox>
|
|
1173
|
+
);
|
|
1174
|
+
} else if (expandedFilterDrawer === FilterDrawer.Context) {
|
|
1175
|
+
// Create item picker items
|
|
1176
|
+
const builtInPickableItem: PickableItem = {
|
|
1177
|
+
id: 'built-in-contexts',
|
|
1178
|
+
name: 'Auto-logged',
|
|
1179
|
+
isGroup: true,
|
|
1180
|
+
children: [],
|
|
1181
|
+
};
|
|
1182
|
+
const pickableItems: PickableItem[] = [];
|
|
1183
|
+
Object.keys(contextMap)
|
|
1184
|
+
.forEach((context) => {
|
|
1185
|
+
const value = contextMap[context];
|
|
1186
|
+
if (typeof value === 'string') {
|
|
1187
|
+
// No subcategories
|
|
1264
1188
|
const item: PickableItem = {
|
|
1265
1189
|
id: context,
|
|
1266
1190
|
name: genHumanReadableName(context),
|
|
1267
|
-
isGroup:
|
|
1268
|
-
|
|
1191
|
+
isGroup: false,
|
|
1192
|
+
checked: !!contextFilterState[context],
|
|
1269
1193
|
};
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1194
|
+
|
|
1195
|
+
// Add built-in items to its own folder
|
|
1196
|
+
const isBuiltIn = context in LogBuiltInMetadata.Context;
|
|
1197
|
+
if (isBuiltIn) {
|
|
1198
|
+
// Add to built-in pickable item
|
|
1199
|
+
builtInPickableItem.children.push(item);
|
|
1200
|
+
} else {
|
|
1201
|
+
// Add to pickable items list
|
|
1202
|
+
pickableItems.push(item);
|
|
1203
|
+
}
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// Has subcategories
|
|
1208
|
+
const children: PickableItem[] = (
|
|
1209
|
+
Object.keys(value)
|
|
1210
|
+
// Remove parent name
|
|
1211
|
+
.filter((subcontext) => {
|
|
1212
|
+
return subcontext !== '_';
|
|
1213
|
+
})
|
|
1214
|
+
// Create child pickable items
|
|
1215
|
+
.map((subcontext) => {
|
|
1216
|
+
return {
|
|
1217
|
+
id: subcontext,
|
|
1218
|
+
name: genHumanReadableName(subcontext),
|
|
1219
|
+
isGroup: false,
|
|
1220
|
+
checked: (contextFilterState[context] as any)[subcontext],
|
|
1221
|
+
};
|
|
1222
|
+
})
|
|
1223
|
+
);
|
|
1224
|
+
const item: PickableItem = {
|
|
1225
|
+
id: context,
|
|
1226
|
+
name: genHumanReadableName(context),
|
|
1227
|
+
isGroup: true,
|
|
1228
|
+
children,
|
|
1229
|
+
};
|
|
1230
|
+
pickableItems.push(item);
|
|
1231
|
+
});
|
|
1232
|
+
// Add built-in contexts to end ofl ist
|
|
1233
|
+
pickableItems.push(builtInPickableItem);
|
|
1234
|
+
|
|
1235
|
+
// Create filter UI
|
|
1236
|
+
filterDrawer = (
|
|
1237
|
+
<ItemPicker
|
|
1238
|
+
title="Context"
|
|
1239
|
+
items={pickableItems}
|
|
1240
|
+
onChanged={(updatedItems) => {
|
|
1241
|
+
// Update our state
|
|
1242
|
+
updatedItems.forEach((pickableItem) => {
|
|
1243
|
+
if (pickableItem.isGroup) {
|
|
1244
|
+
// Has subcontexts
|
|
1245
|
+
|
|
1246
|
+
if (pickableItem.id === 'built-in-contexts') {
|
|
1247
|
+
// Built-in
|
|
1248
|
+
|
|
1249
|
+
// Treat as if these were top-level contexts
|
|
1250
|
+
pickableItem.children.forEach((subcontextItem) => {
|
|
1251
|
+
contextFilterState[subcontextItem.id] = (
|
|
1252
|
+
'checked' in subcontextItem
|
|
1293
1253
|
&& subcontextItem.checked
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
} else {
|
|
1297
|
-
// Not built-in
|
|
1298
|
-
pickableItem.children.forEach((subcontextItem) => {
|
|
1299
|
-
if (!subcontextItem.isGroup) {
|
|
1300
|
-
(
|
|
1301
|
-
contextFilterState[pickableItem.id] as { [k: string]: boolean }
|
|
1302
|
-
)[subcontextItem.id] = (
|
|
1303
|
-
subcontextItem.checked
|
|
1304
|
-
);
|
|
1305
|
-
}
|
|
1306
|
-
});
|
|
1307
|
-
}
|
|
1254
|
+
);
|
|
1255
|
+
});
|
|
1308
1256
|
} else {
|
|
1309
|
-
//
|
|
1310
|
-
(
|
|
1311
|
-
|
|
1312
|
-
|
|
1257
|
+
// Not built-in
|
|
1258
|
+
pickableItem.children.forEach((subcontextItem) => {
|
|
1259
|
+
if (!subcontextItem.isGroup) {
|
|
1260
|
+
(
|
|
1261
|
+
contextFilterState[pickableItem.id] as { [k: string]: boolean }
|
|
1262
|
+
)[subcontextItem.id] = (
|
|
1263
|
+
subcontextItem.checked
|
|
1264
|
+
);
|
|
1265
|
+
}
|
|
1266
|
+
});
|
|
1313
1267
|
}
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1268
|
+
} else {
|
|
1269
|
+
// No subcontexts
|
|
1270
|
+
(contextFilterState as any)[pickableItem.id] = (
|
|
1271
|
+
pickableItem.checked
|
|
1272
|
+
);
|
|
1273
|
+
}
|
|
1274
|
+
});
|
|
1275
|
+
dispatch({
|
|
1276
|
+
type: ActionType.UpdateContextFilterState,
|
|
1277
|
+
contextFilterState,
|
|
1278
|
+
});
|
|
1279
|
+
}}
|
|
1280
|
+
/>
|
|
1281
|
+
);
|
|
1282
|
+
} else if (expandedFilterDrawer === FilterDrawer.Tag) {
|
|
1283
|
+
// Create filter UI
|
|
1284
|
+
filterDrawer = (
|
|
1285
|
+
<TabBox title="Tags">
|
|
1286
|
+
<div>
|
|
1287
|
+
If any tags are selected, logs must contain at least one
|
|
1288
|
+
(but not necessarily all) of the
|
|
1289
|
+
selected tags.
|
|
1290
|
+
</div>
|
|
1291
|
+
<div className="d-flex gap-1 flex-wrap">
|
|
1292
|
+
{
|
|
1333
1293
|
Object.keys(LogMetadata.Tag ?? {})
|
|
1334
1294
|
.map((tag) => {
|
|
1335
1295
|
const description = genHumanReadableName(tag);
|
|
@@ -1351,58 +1311,58 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1351
1311
|
);
|
|
1352
1312
|
})
|
|
1353
1313
|
}
|
|
1354
|
-
|
|
1314
|
+
</div>
|
|
1315
|
+
</TabBox>
|
|
1316
|
+
);
|
|
1317
|
+
} else if (expandedFilterDrawer === FilterDrawer.Action) {
|
|
1318
|
+
// Create filter UI
|
|
1319
|
+
filterDrawer = (
|
|
1320
|
+
<>
|
|
1321
|
+
{/* Log Type */}
|
|
1322
|
+
<TabBox title="Log Type">
|
|
1323
|
+
<RadioButton
|
|
1324
|
+
id="LogReviewer-type-all"
|
|
1325
|
+
text="All Logs"
|
|
1326
|
+
onSelected={() => {
|
|
1327
|
+
actionErrorFilterState.type = undefined;
|
|
1328
|
+
dispatch({
|
|
1329
|
+
type: ActionType.UpdateActionErrorFilterState,
|
|
1330
|
+
actionErrorFilterState,
|
|
1331
|
+
});
|
|
1332
|
+
}}
|
|
1333
|
+
ariaLabel="show logs of all types"
|
|
1334
|
+
selected={actionErrorFilterState.type === undefined}
|
|
1335
|
+
/>
|
|
1336
|
+
<RadioButton
|
|
1337
|
+
id="LogReviewer-type-action-only"
|
|
1338
|
+
text="Action Logs Only"
|
|
1339
|
+
onSelected={() => {
|
|
1340
|
+
actionErrorFilterState.type = LogType.Action;
|
|
1341
|
+
dispatch({
|
|
1342
|
+
type: ActionType.UpdateActionErrorFilterState,
|
|
1343
|
+
actionErrorFilterState,
|
|
1344
|
+
});
|
|
1345
|
+
}}
|
|
1346
|
+
ariaLabel="only show action logs"
|
|
1347
|
+
selected={actionErrorFilterState.type === LogType.Action}
|
|
1348
|
+
/>
|
|
1349
|
+
<RadioButton
|
|
1350
|
+
id="LogReviewer-type-error-only"
|
|
1351
|
+
text="Action Error Only"
|
|
1352
|
+
onSelected={() => {
|
|
1353
|
+
actionErrorFilterState.type = LogType.Error;
|
|
1354
|
+
dispatch({
|
|
1355
|
+
type: ActionType.UpdateActionErrorFilterState,
|
|
1356
|
+
actionErrorFilterState,
|
|
1357
|
+
});
|
|
1358
|
+
}}
|
|
1359
|
+
ariaLabel="only show error logs"
|
|
1360
|
+
selected={actionErrorFilterState.type === LogType.Error}
|
|
1361
|
+
noMarginOnRight
|
|
1362
|
+
/>
|
|
1355
1363
|
</TabBox>
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
// Create filter UI
|
|
1359
|
-
filterDrawer = (
|
|
1360
|
-
<>
|
|
1361
|
-
{/* Log Type */}
|
|
1362
|
-
<TabBox title="Log Type">
|
|
1363
|
-
<RadioButton
|
|
1364
|
-
id="LogReviewer-type-all"
|
|
1365
|
-
text="All Logs"
|
|
1366
|
-
onSelected={() => {
|
|
1367
|
-
actionErrorFilterState.type = undefined;
|
|
1368
|
-
dispatch({
|
|
1369
|
-
type: ActionType.UpdateActionErrorFilterState,
|
|
1370
|
-
actionErrorFilterState,
|
|
1371
|
-
});
|
|
1372
|
-
}}
|
|
1373
|
-
ariaLabel="show logs of all types"
|
|
1374
|
-
selected={actionErrorFilterState.type === undefined}
|
|
1375
|
-
/>
|
|
1376
|
-
<RadioButton
|
|
1377
|
-
id="LogReviewer-type-action-only"
|
|
1378
|
-
text="Action Logs Only"
|
|
1379
|
-
onSelected={() => {
|
|
1380
|
-
actionErrorFilterState.type = LogType.Action;
|
|
1381
|
-
dispatch({
|
|
1382
|
-
type: ActionType.UpdateActionErrorFilterState,
|
|
1383
|
-
actionErrorFilterState,
|
|
1384
|
-
});
|
|
1385
|
-
}}
|
|
1386
|
-
ariaLabel="only show action logs"
|
|
1387
|
-
selected={actionErrorFilterState.type === LogType.Action}
|
|
1388
|
-
/>
|
|
1389
|
-
<RadioButton
|
|
1390
|
-
id="LogReviewer-type-error-only"
|
|
1391
|
-
text="Action Error Only"
|
|
1392
|
-
onSelected={() => {
|
|
1393
|
-
actionErrorFilterState.type = LogType.Error;
|
|
1394
|
-
dispatch({
|
|
1395
|
-
type: ActionType.UpdateActionErrorFilterState,
|
|
1396
|
-
actionErrorFilterState,
|
|
1397
|
-
});
|
|
1398
|
-
}}
|
|
1399
|
-
ariaLabel="only show error logs"
|
|
1400
|
-
selected={actionErrorFilterState.type === LogType.Error}
|
|
1401
|
-
noMarginOnRight
|
|
1402
|
-
/>
|
|
1403
|
-
</TabBox>
|
|
1404
|
-
{/* Actions */}
|
|
1405
|
-
{
|
|
1364
|
+
{/* Actions */}
|
|
1365
|
+
{
|
|
1406
1366
|
(
|
|
1407
1367
|
actionErrorFilterState.type === undefined
|
|
1408
1368
|
|| actionErrorFilterState.type === LogType.Action
|
|
@@ -1471,8 +1431,8 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1471
1431
|
</TabBox>
|
|
1472
1432
|
)
|
|
1473
1433
|
}
|
|
1474
|
-
|
|
1475
|
-
|
|
1434
|
+
{/* Errors */}
|
|
1435
|
+
{
|
|
1476
1436
|
(
|
|
1477
1437
|
actionErrorFilterState.type === undefined
|
|
1478
1438
|
|| actionErrorFilterState.type === LogType.Error
|
|
@@ -1525,160 +1485,300 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1525
1485
|
</TabBox>
|
|
1526
1486
|
)
|
|
1527
1487
|
}
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1488
|
+
</>
|
|
1489
|
+
);
|
|
1490
|
+
} else if (expandedFilterDrawer === FilterDrawer.Advanced) {
|
|
1491
|
+
// Create advanced filter ui
|
|
1492
|
+
filterDrawer = (
|
|
1493
|
+
<>
|
|
1494
|
+
{/* User Info */}
|
|
1495
|
+
<TabBox title="User">
|
|
1496
|
+
{/* First Name */}
|
|
1497
|
+
<div className="input-group mb-2">
|
|
1498
|
+
<span className="input-group-text">
|
|
1499
|
+
User First Name
|
|
1500
|
+
</span>
|
|
1501
|
+
<input
|
|
1502
|
+
type="text"
|
|
1503
|
+
className="form-control"
|
|
1504
|
+
aria-label="query for user first name"
|
|
1505
|
+
value={advancedFilterState.userFirstName}
|
|
1506
|
+
placeholder="e.g. Divardo"
|
|
1507
|
+
onChange={(e) => {
|
|
1508
|
+
advancedFilterState.userFirstName = e.target.value;
|
|
1509
|
+
dispatch({
|
|
1510
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1511
|
+
advancedFilterState,
|
|
1512
|
+
});
|
|
1513
|
+
}}
|
|
1514
|
+
/>
|
|
1515
|
+
</div>
|
|
1516
|
+
{/* Last Name */}
|
|
1517
|
+
<div className="input-group mb-2">
|
|
1518
|
+
<span className="input-group-text">
|
|
1519
|
+
User Last Name
|
|
1520
|
+
</span>
|
|
1521
|
+
<input
|
|
1522
|
+
type="text"
|
|
1523
|
+
className="form-control"
|
|
1524
|
+
aria-label="query for user last name"
|
|
1525
|
+
value={advancedFilterState.userLastName}
|
|
1526
|
+
placeholder="e.g. Calicci"
|
|
1527
|
+
onChange={(e) => {
|
|
1528
|
+
advancedFilterState.userLastName = e.target.value;
|
|
1529
|
+
dispatch({
|
|
1530
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1531
|
+
advancedFilterState,
|
|
1532
|
+
});
|
|
1533
|
+
}}
|
|
1534
|
+
/>
|
|
1535
|
+
</div>
|
|
1536
|
+
{/* Email */}
|
|
1537
|
+
<div className="input-group mb-2">
|
|
1538
|
+
<span className="input-group-text">
|
|
1539
|
+
User Email
|
|
1540
|
+
</span>
|
|
1541
|
+
<input
|
|
1542
|
+
type="text"
|
|
1543
|
+
className="form-control"
|
|
1544
|
+
aria-label="query for user email"
|
|
1545
|
+
value={advancedFilterState.userEmail}
|
|
1546
|
+
placeholder="e.g. calicci@fas.harvard.edu"
|
|
1547
|
+
onChange={(e) => {
|
|
1548
|
+
advancedFilterState.userEmail = (
|
|
1549
|
+
(e.target.value)
|
|
1550
|
+
.trim()
|
|
1551
|
+
);
|
|
1552
|
+
dispatch({
|
|
1553
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1554
|
+
advancedFilterState,
|
|
1555
|
+
});
|
|
1556
|
+
}}
|
|
1557
|
+
/>
|
|
1558
|
+
</div>
|
|
1559
|
+
{/* Canvas Id */}
|
|
1560
|
+
<div className="input-group mb-2">
|
|
1561
|
+
<span className="input-group-text">
|
|
1562
|
+
User Canvas Id
|
|
1563
|
+
</span>
|
|
1564
|
+
<input
|
|
1565
|
+
type="text"
|
|
1566
|
+
className="form-control"
|
|
1567
|
+
aria-label="query for user canvas id"
|
|
1568
|
+
value={advancedFilterState.userId}
|
|
1569
|
+
placeholder="e.g. 104985"
|
|
1570
|
+
onChange={(e) => {
|
|
1571
|
+
const { value } = e.target;
|
|
1572
|
+
// Only update if value contains only numbers
|
|
1573
|
+
if (/^\d+$/.test(value)) {
|
|
1574
|
+
advancedFilterState.userId = (
|
|
1589
1575
|
(e.target.value)
|
|
1590
1576
|
.trim()
|
|
1591
1577
|
);
|
|
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
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1578
|
+
}
|
|
1579
|
+
dispatch({
|
|
1580
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1581
|
+
advancedFilterState,
|
|
1582
|
+
});
|
|
1583
|
+
}}
|
|
1584
|
+
/>
|
|
1585
|
+
</div>
|
|
1586
|
+
{/* Role */}
|
|
1587
|
+
<ButtonInputGroup label="Role">
|
|
1588
|
+
<CheckboxButton
|
|
1589
|
+
text="Students"
|
|
1590
|
+
onChanged={(checked) => {
|
|
1591
|
+
advancedFilterState.includeLearners = checked;
|
|
1592
|
+
dispatch({
|
|
1593
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1594
|
+
advancedFilterState,
|
|
1595
|
+
});
|
|
1596
|
+
}}
|
|
1597
|
+
checked={advancedFilterState.includeLearners}
|
|
1598
|
+
ariaLabel="show logs from students"
|
|
1599
|
+
/>
|
|
1600
|
+
<CheckboxButton
|
|
1601
|
+
text="Teaching Team Members"
|
|
1602
|
+
onChanged={(checked) => {
|
|
1603
|
+
advancedFilterState.includeTTMs = checked;
|
|
1604
|
+
dispatch({
|
|
1605
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1606
|
+
advancedFilterState,
|
|
1607
|
+
});
|
|
1608
|
+
}}
|
|
1609
|
+
checked={advancedFilterState.includeTTMs}
|
|
1610
|
+
ariaLabel="show logs from teaching team members"
|
|
1611
|
+
/>
|
|
1612
|
+
<CheckboxButton
|
|
1613
|
+
text="Admins"
|
|
1614
|
+
onChanged={(checked) => {
|
|
1615
|
+
advancedFilterState.includeAdmins = checked;
|
|
1616
|
+
dispatch({
|
|
1617
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1618
|
+
advancedFilterState,
|
|
1619
|
+
});
|
|
1620
|
+
}}
|
|
1621
|
+
checked={advancedFilterState.includeAdmins}
|
|
1622
|
+
ariaLabel="show logs from admins"
|
|
1623
|
+
/>
|
|
1624
|
+
</ButtonInputGroup>
|
|
1625
|
+
</TabBox>
|
|
1626
|
+
|
|
1627
|
+
{/* Course Info */}
|
|
1628
|
+
<TabBox title="Course">
|
|
1629
|
+
{/* Name */}
|
|
1630
|
+
<div className="input-group mb-2">
|
|
1631
|
+
<span className="input-group-text">
|
|
1632
|
+
Course Name
|
|
1633
|
+
</span>
|
|
1634
|
+
<input
|
|
1635
|
+
type="text"
|
|
1636
|
+
className="form-control"
|
|
1637
|
+
aria-label="query for course name"
|
|
1638
|
+
value={advancedFilterState.courseName}
|
|
1639
|
+
placeholder="e.g. GLC 200"
|
|
1640
|
+
onChange={(e) => {
|
|
1641
|
+
advancedFilterState.courseName = e.target.value;
|
|
1642
|
+
dispatch({
|
|
1643
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1644
|
+
advancedFilterState,
|
|
1645
|
+
});
|
|
1646
|
+
}}
|
|
1647
|
+
/>
|
|
1648
|
+
</div>
|
|
1649
|
+
{/* Canvas Id */}
|
|
1650
|
+
<div className="input-group mb-2">
|
|
1651
|
+
<span className="input-group-text">
|
|
1652
|
+
Course Canvas Id
|
|
1653
|
+
</span>
|
|
1654
|
+
<input
|
|
1655
|
+
type="text"
|
|
1656
|
+
className="form-control"
|
|
1657
|
+
aria-label="query for course canvas id"
|
|
1658
|
+
value={advancedFilterState.courseId}
|
|
1659
|
+
placeholder="e.g. 15948"
|
|
1660
|
+
onChange={(e) => {
|
|
1661
|
+
const { value } = e.target;
|
|
1662
|
+
// Only update if value contains only numbers
|
|
1663
|
+
if (/^\d+$/.test(value)) {
|
|
1664
|
+
advancedFilterState.courseId = (
|
|
1665
|
+
(e.target.value)
|
|
1666
|
+
.trim()
|
|
1667
|
+
);
|
|
1668
|
+
}
|
|
1669
|
+
dispatch({
|
|
1670
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1671
|
+
advancedFilterState,
|
|
1672
|
+
});
|
|
1673
|
+
}}
|
|
1674
|
+
/>
|
|
1675
|
+
</div>
|
|
1676
|
+
</TabBox>
|
|
1666
1677
|
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1678
|
+
{/* Device Info */}
|
|
1679
|
+
<TabBox title="Device">
|
|
1680
|
+
<ButtonInputGroup label="Device Type">
|
|
1681
|
+
<RadioButton
|
|
1682
|
+
text="All Devices"
|
|
1683
|
+
ariaLabel="show logs from all devices"
|
|
1684
|
+
selected={advancedFilterState.isMobile === undefined}
|
|
1685
|
+
onSelected={() => {
|
|
1686
|
+
advancedFilterState.isMobile = undefined;
|
|
1687
|
+
dispatch({
|
|
1688
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1689
|
+
advancedFilterState,
|
|
1690
|
+
});
|
|
1691
|
+
}}
|
|
1692
|
+
/>
|
|
1693
|
+
<RadioButton
|
|
1694
|
+
text="Mobile Only"
|
|
1695
|
+
ariaLabel="show logs from mobile devices"
|
|
1696
|
+
selected={advancedFilterState.isMobile === true}
|
|
1697
|
+
onSelected={() => {
|
|
1698
|
+
advancedFilterState.isMobile = true;
|
|
1699
|
+
dispatch({
|
|
1700
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1701
|
+
advancedFilterState,
|
|
1702
|
+
});
|
|
1703
|
+
}}
|
|
1704
|
+
/>
|
|
1705
|
+
<RadioButton
|
|
1706
|
+
text="Desktop Only"
|
|
1707
|
+
ariaLabel="show logs from desktop devices"
|
|
1708
|
+
selected={advancedFilterState.isMobile === false}
|
|
1709
|
+
onSelected={() => {
|
|
1710
|
+
advancedFilterState.isMobile = false;
|
|
1711
|
+
dispatch({
|
|
1712
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1713
|
+
advancedFilterState,
|
|
1714
|
+
});
|
|
1715
|
+
}}
|
|
1716
|
+
noMarginOnRight
|
|
1717
|
+
/>
|
|
1718
|
+
</ButtonInputGroup>
|
|
1719
|
+
</TabBox>
|
|
1720
|
+
|
|
1721
|
+
{/* Source */}
|
|
1722
|
+
<TabBox title="Source">
|
|
1723
|
+
<ButtonInputGroup label="Source Type">
|
|
1724
|
+
<RadioButton
|
|
1725
|
+
text="Both"
|
|
1726
|
+
ariaLabel="show logs from all sources"
|
|
1727
|
+
selected={advancedFilterState.source === undefined}
|
|
1728
|
+
onSelected={() => {
|
|
1729
|
+
advancedFilterState.source = undefined;
|
|
1730
|
+
dispatch({
|
|
1731
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1732
|
+
advancedFilterState,
|
|
1733
|
+
});
|
|
1734
|
+
}}
|
|
1735
|
+
/>
|
|
1736
|
+
<RadioButton
|
|
1737
|
+
text="Client Only"
|
|
1738
|
+
ariaLabel="show logs from client source"
|
|
1739
|
+
selected={advancedFilterState.source === LogSource.Client}
|
|
1740
|
+
onSelected={() => {
|
|
1741
|
+
advancedFilterState.source = LogSource.Client;
|
|
1742
|
+
dispatch({
|
|
1743
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1744
|
+
advancedFilterState,
|
|
1745
|
+
});
|
|
1746
|
+
}}
|
|
1747
|
+
/>
|
|
1748
|
+
<RadioButton
|
|
1749
|
+
text="Server Only"
|
|
1750
|
+
ariaLabel="show logs from server source"
|
|
1751
|
+
selected={advancedFilterState.source === LogSource.Server}
|
|
1752
|
+
onSelected={() => {
|
|
1753
|
+
advancedFilterState.source = LogSource.Server;
|
|
1754
|
+
dispatch({
|
|
1755
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1756
|
+
advancedFilterState,
|
|
1757
|
+
});
|
|
1758
|
+
}}
|
|
1759
|
+
noMarginOnRight
|
|
1760
|
+
/>
|
|
1761
|
+
</ButtonInputGroup>
|
|
1762
|
+
|
|
1763
|
+
{/* Server filters */}
|
|
1764
|
+
{advancedFilterState.source !== LogSource.Client && (
|
|
1765
|
+
<div className="mt-2">
|
|
1766
|
+
{/* Route path */}
|
|
1670
1767
|
<div className="input-group mb-2">
|
|
1671
1768
|
<span className="input-group-text">
|
|
1672
|
-
|
|
1769
|
+
Server Route Path
|
|
1673
1770
|
</span>
|
|
1674
1771
|
<input
|
|
1675
1772
|
type="text"
|
|
1676
1773
|
className="form-control"
|
|
1677
|
-
aria-label="query for
|
|
1678
|
-
value={advancedFilterState.
|
|
1679
|
-
placeholder="e.g.
|
|
1774
|
+
aria-label="query for server route path"
|
|
1775
|
+
value={advancedFilterState.routePath}
|
|
1776
|
+
placeholder="e.g. /api/ttm/courses/12345"
|
|
1680
1777
|
onChange={(e) => {
|
|
1681
|
-
advancedFilterState.courseName =
|
|
1778
|
+
advancedFilterState.courseName = (
|
|
1779
|
+
(e.target.value)
|
|
1780
|
+
.trim()
|
|
1781
|
+
);
|
|
1682
1782
|
dispatch({
|
|
1683
1783
|
type: ActionType.UpdateAdvancedFilterState,
|
|
1684
1784
|
advancedFilterState,
|
|
@@ -1686,26 +1786,23 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1686
1786
|
}}
|
|
1687
1787
|
/>
|
|
1688
1788
|
</div>
|
|
1689
|
-
|
|
1789
|
+
|
|
1790
|
+
{/* Route template */}
|
|
1690
1791
|
<div className="input-group mb-2">
|
|
1691
1792
|
<span className="input-group-text">
|
|
1692
|
-
|
|
1793
|
+
Server Route Template
|
|
1693
1794
|
</span>
|
|
1694
1795
|
<input
|
|
1695
1796
|
type="text"
|
|
1696
1797
|
className="form-control"
|
|
1697
|
-
aria-label="query for
|
|
1698
|
-
value={advancedFilterState.
|
|
1699
|
-
placeholder="e.g.
|
|
1798
|
+
aria-label="query for server route template"
|
|
1799
|
+
value={advancedFilterState.routeTemplate}
|
|
1800
|
+
placeholder="e.g. /api/ttm/courses/:courseId"
|
|
1700
1801
|
onChange={(e) => {
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
(e.target.value)
|
|
1706
|
-
.trim()
|
|
1707
|
-
);
|
|
1708
|
-
}
|
|
1802
|
+
advancedFilterState.courseName = (
|
|
1803
|
+
(e.target.value)
|
|
1804
|
+
.trim()
|
|
1805
|
+
);
|
|
1709
1806
|
dispatch({
|
|
1710
1807
|
type: ActionType.UpdateAdvancedFilterState,
|
|
1711
1808
|
advancedFilterState,
|
|
@@ -1713,534 +1810,51 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1713
1810
|
}}
|
|
1714
1811
|
/>
|
|
1715
1812
|
</div>
|
|
1716
|
-
</
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
<RadioButton
|
|
1722
|
-
text="All Devices"
|
|
1723
|
-
ariaLabel="show logs from all devices"
|
|
1724
|
-
selected={advancedFilterState.isMobile === undefined}
|
|
1725
|
-
onSelected={() => {
|
|
1726
|
-
advancedFilterState.isMobile = undefined;
|
|
1727
|
-
dispatch({
|
|
1728
|
-
type: ActionType.UpdateAdvancedFilterState,
|
|
1729
|
-
advancedFilterState,
|
|
1730
|
-
});
|
|
1731
|
-
}}
|
|
1732
|
-
/>
|
|
1733
|
-
<RadioButton
|
|
1734
|
-
text="Mobile Only"
|
|
1735
|
-
ariaLabel="show logs from mobile devices"
|
|
1736
|
-
selected={advancedFilterState.isMobile === true}
|
|
1737
|
-
onSelected={() => {
|
|
1738
|
-
advancedFilterState.isMobile = true;
|
|
1739
|
-
dispatch({
|
|
1740
|
-
type: ActionType.UpdateAdvancedFilterState,
|
|
1741
|
-
advancedFilterState,
|
|
1742
|
-
});
|
|
1743
|
-
}}
|
|
1744
|
-
/>
|
|
1745
|
-
<RadioButton
|
|
1746
|
-
text="Desktop Only"
|
|
1747
|
-
ariaLabel="show logs from desktop devices"
|
|
1748
|
-
selected={advancedFilterState.isMobile === false}
|
|
1749
|
-
onSelected={() => {
|
|
1750
|
-
advancedFilterState.isMobile = false;
|
|
1751
|
-
dispatch({
|
|
1752
|
-
type: ActionType.UpdateAdvancedFilterState,
|
|
1753
|
-
advancedFilterState,
|
|
1754
|
-
});
|
|
1755
|
-
}}
|
|
1756
|
-
noMarginOnRight
|
|
1757
|
-
/>
|
|
1758
|
-
</ButtonInputGroup>
|
|
1759
|
-
</TabBox>
|
|
1760
|
-
|
|
1761
|
-
{/* Source */}
|
|
1762
|
-
<TabBox title="Source">
|
|
1763
|
-
<ButtonInputGroup label="Source Type">
|
|
1764
|
-
<RadioButton
|
|
1765
|
-
text="Both"
|
|
1766
|
-
ariaLabel="show logs from all sources"
|
|
1767
|
-
selected={advancedFilterState.source === undefined}
|
|
1768
|
-
onSelected={() => {
|
|
1769
|
-
advancedFilterState.source = undefined;
|
|
1770
|
-
dispatch({
|
|
1771
|
-
type: ActionType.UpdateAdvancedFilterState,
|
|
1772
|
-
advancedFilterState,
|
|
1773
|
-
});
|
|
1774
|
-
}}
|
|
1775
|
-
/>
|
|
1776
|
-
<RadioButton
|
|
1777
|
-
text="Client Only"
|
|
1778
|
-
ariaLabel="show logs from client source"
|
|
1779
|
-
selected={advancedFilterState.source === LogSource.Client}
|
|
1780
|
-
onSelected={() => {
|
|
1781
|
-
advancedFilterState.source = LogSource.Client;
|
|
1782
|
-
dispatch({
|
|
1783
|
-
type: ActionType.UpdateAdvancedFilterState,
|
|
1784
|
-
advancedFilterState,
|
|
1785
|
-
});
|
|
1786
|
-
}}
|
|
1787
|
-
/>
|
|
1788
|
-
<RadioButton
|
|
1789
|
-
text="Server Only"
|
|
1790
|
-
ariaLabel="show logs from server source"
|
|
1791
|
-
selected={advancedFilterState.source === LogSource.Server}
|
|
1792
|
-
onSelected={() => {
|
|
1793
|
-
advancedFilterState.source = LogSource.Server;
|
|
1794
|
-
dispatch({
|
|
1795
|
-
type: ActionType.UpdateAdvancedFilterState,
|
|
1796
|
-
advancedFilterState,
|
|
1797
|
-
});
|
|
1798
|
-
}}
|
|
1799
|
-
noMarginOnRight
|
|
1800
|
-
/>
|
|
1801
|
-
</ButtonInputGroup>
|
|
1802
|
-
|
|
1803
|
-
{/* Server filters */}
|
|
1804
|
-
{advancedFilterState.source !== LogSource.Client && (
|
|
1805
|
-
<div className="mt-2">
|
|
1806
|
-
{/* Route path */}
|
|
1807
|
-
<div className="input-group mb-2">
|
|
1808
|
-
<span className="input-group-text">
|
|
1809
|
-
Server Route Path
|
|
1810
|
-
</span>
|
|
1811
|
-
<input
|
|
1812
|
-
type="text"
|
|
1813
|
-
className="form-control"
|
|
1814
|
-
aria-label="query for server route path"
|
|
1815
|
-
value={advancedFilterState.routePath}
|
|
1816
|
-
placeholder="e.g. /api/ttm/courses/12345"
|
|
1817
|
-
onChange={(e) => {
|
|
1818
|
-
advancedFilterState.courseName = (
|
|
1819
|
-
(e.target.value)
|
|
1820
|
-
.trim()
|
|
1821
|
-
);
|
|
1822
|
-
dispatch({
|
|
1823
|
-
type: ActionType.UpdateAdvancedFilterState,
|
|
1824
|
-
advancedFilterState,
|
|
1825
|
-
});
|
|
1826
|
-
}}
|
|
1827
|
-
/>
|
|
1828
|
-
</div>
|
|
1829
|
-
|
|
1830
|
-
{/* Route template */}
|
|
1831
|
-
<div className="input-group mb-2">
|
|
1832
|
-
<span className="input-group-text">
|
|
1833
|
-
Server Route Template
|
|
1834
|
-
</span>
|
|
1835
|
-
<input
|
|
1836
|
-
type="text"
|
|
1837
|
-
className="form-control"
|
|
1838
|
-
aria-label="query for server route template"
|
|
1839
|
-
value={advancedFilterState.routeTemplate}
|
|
1840
|
-
placeholder="e.g. /api/ttm/courses/:courseId"
|
|
1841
|
-
onChange={(e) => {
|
|
1842
|
-
advancedFilterState.courseName = (
|
|
1843
|
-
(e.target.value)
|
|
1844
|
-
.trim()
|
|
1845
|
-
);
|
|
1846
|
-
dispatch({
|
|
1847
|
-
type: ActionType.UpdateAdvancedFilterState,
|
|
1848
|
-
advancedFilterState,
|
|
1849
|
-
});
|
|
1850
|
-
}}
|
|
1851
|
-
/>
|
|
1852
|
-
</div>
|
|
1853
|
-
</div>
|
|
1854
|
-
)}
|
|
1855
|
-
</TabBox>
|
|
1856
|
-
</>
|
|
1857
|
-
);
|
|
1858
|
-
}
|
|
1813
|
+
</div>
|
|
1814
|
+
)}
|
|
1815
|
+
</TabBox>
|
|
1816
|
+
</>
|
|
1817
|
+
);
|
|
1859
1818
|
}
|
|
1819
|
+
}
|
|
1860
1820
|
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
// Actually filter the logs
|
|
1874
|
-
// > Perform filters
|
|
1875
|
-
const logs: Log[] = [];
|
|
1876
|
-
Object.keys(logMap).forEach((year) => {
|
|
1877
|
-
Object.keys(logMap[year]).forEach((month) => {
|
|
1878
|
-
logMap[year][month].forEach((log) => {
|
|
1879
|
-
/* ----------- Date Filter ---------- */
|
|
1880
|
-
|
|
1881
|
-
// Before start date
|
|
1882
|
-
if (
|
|
1883
|
-
// Previous year
|
|
1884
|
-
log.year < dateFilterState.startDate.year
|
|
1885
|
-
// Same year, earlier month
|
|
1886
|
-
|| (
|
|
1887
|
-
(log.year === dateFilterState.startDate.year)
|
|
1888
|
-
&& (log.month < dateFilterState.startDate.month)
|
|
1889
|
-
)
|
|
1890
|
-
// Same year, same month, earlier day
|
|
1891
|
-
|| (
|
|
1892
|
-
(log.year === dateFilterState.startDate.year)
|
|
1893
|
-
&& (log.month === dateFilterState.startDate.month)
|
|
1894
|
-
&& (log.day < dateFilterState.startDate.day)
|
|
1895
|
-
)
|
|
1896
|
-
) {
|
|
1897
|
-
return;
|
|
1898
|
-
}
|
|
1899
|
-
|
|
1900
|
-
// After end date
|
|
1901
|
-
if (
|
|
1902
|
-
// Later year
|
|
1903
|
-
log.year > dateFilterState.endDate.year
|
|
1904
|
-
// Same year, later month
|
|
1905
|
-
|| (
|
|
1906
|
-
(log.year === dateFilterState.endDate.year)
|
|
1907
|
-
&& (log.month > dateFilterState.endDate.month)
|
|
1908
|
-
)
|
|
1909
|
-
// Same year, same month, later day
|
|
1910
|
-
|| (
|
|
1911
|
-
(log.year === dateFilterState.endDate.year)
|
|
1912
|
-
&& (log.month === dateFilterState.endDate.month)
|
|
1913
|
-
&& (log.day > dateFilterState.endDate.day)
|
|
1914
|
-
)
|
|
1915
|
-
) {
|
|
1916
|
-
return;
|
|
1917
|
-
}
|
|
1918
|
-
|
|
1919
|
-
/* --------- Context Filter --------- */
|
|
1920
|
-
|
|
1921
|
-
// Context doesn't match
|
|
1922
|
-
if (
|
|
1923
|
-
// Whole context is deselected
|
|
1924
|
-
contextFilterState[log.context] === false
|
|
1925
|
-
// None of the subcontexts are selected
|
|
1926
|
-
|| (
|
|
1927
|
-
// Has subcontexts
|
|
1928
|
-
typeof contextFilterState[log.context] !== 'boolean'
|
|
1929
|
-
// None of the subcontexts are selected
|
|
1930
|
-
&& Object.values(contextFilterState[log.context] ?? {})
|
|
1931
|
-
.every((isSelected) => {
|
|
1932
|
-
return !isSelected;
|
|
1933
|
-
})
|
|
1934
|
-
)
|
|
1935
|
-
) {
|
|
1936
|
-
return;
|
|
1937
|
-
}
|
|
1938
|
-
|
|
1939
|
-
// Subcontext doesn't match
|
|
1940
|
-
if (
|
|
1941
|
-
// Log context is not "uncategorized" (no point in further filters)
|
|
1942
|
-
log.context !== LogBuiltInMetadata.Context.Uncategorized
|
|
1943
|
-
// Log has a subcontext
|
|
1944
|
-
&& log.subcontext
|
|
1945
|
-
// Context has subcontexts
|
|
1946
|
-
&& (
|
|
1947
|
-
contextFilterState[log.context]
|
|
1948
|
-
&& contextFilterState[log.context] !== false
|
|
1949
|
-
&& contextFilterState[log.context] !== true
|
|
1950
|
-
)
|
|
1951
|
-
// Subcontext is not selected
|
|
1952
|
-
&& !(contextFilterState as any)[log.context][log.subcontext]
|
|
1953
|
-
) {
|
|
1954
|
-
return;
|
|
1955
|
-
}
|
|
1956
|
-
|
|
1957
|
-
/* -------------- Tags -------------- */
|
|
1958
|
-
|
|
1959
|
-
// No tags match
|
|
1960
|
-
if (
|
|
1961
|
-
// At least one tag is required
|
|
1962
|
-
Object.values(tagFilterState)
|
|
1963
|
-
.filter((isSelected) => {
|
|
1964
|
-
return isSelected;
|
|
1965
|
-
})
|
|
1966
|
-
.length > 0
|
|
1967
|
-
// No tags match
|
|
1968
|
-
&& log.tags.every((tag) => {
|
|
1969
|
-
return !tagFilterState[tag];
|
|
1970
|
-
})
|
|
1971
|
-
) {
|
|
1972
|
-
return;
|
|
1973
|
-
}
|
|
1974
|
-
|
|
1975
|
-
/* ------- Actions and Errors ------- */
|
|
1976
|
-
|
|
1977
|
-
// Log type doesn't match
|
|
1978
|
-
if (
|
|
1979
|
-
// Filter won't allow all types
|
|
1980
|
-
actionErrorFilterState.type !== undefined
|
|
1981
|
-
// Log type doesn't match
|
|
1982
|
-
&& actionErrorFilterState.type !== log.type
|
|
1983
|
-
) {
|
|
1984
|
-
return;
|
|
1985
|
-
}
|
|
1986
|
-
|
|
1987
|
-
// Filter errors
|
|
1988
|
-
if (log.type === LogType.Error) {
|
|
1989
|
-
// Message doesn't match
|
|
1990
|
-
if (
|
|
1991
|
-
// Message exists
|
|
1992
|
-
log.errorMessage
|
|
1993
|
-
// Message filter exists
|
|
1994
|
-
&& actionErrorFilterState.errorMessage.trim().length > 0
|
|
1995
|
-
// Message doesn't match
|
|
1996
|
-
&& log.errorMessage.toLowerCase().includes(
|
|
1997
|
-
actionErrorFilterState.errorMessage.trim().toLowerCase(),
|
|
1998
|
-
)
|
|
1999
|
-
) {
|
|
2000
|
-
return;
|
|
2001
|
-
}
|
|
2002
|
-
|
|
2003
|
-
// Code doesn't match
|
|
2004
|
-
if (
|
|
2005
|
-
// Code exists
|
|
2006
|
-
log.errorCode
|
|
2007
|
-
// Code filter exists
|
|
2008
|
-
&& actionErrorFilterState.errorCode.trim().length > 0
|
|
2009
|
-
// Code doesn't match
|
|
2010
|
-
&& log.errorCode.toUpperCase().includes(
|
|
2011
|
-
actionErrorFilterState.errorCode.trim().toUpperCase(),
|
|
2012
|
-
)
|
|
2013
|
-
) {
|
|
2014
|
-
return;
|
|
2015
|
-
}
|
|
2016
|
-
}
|
|
2017
|
-
|
|
2018
|
-
// Filter actions
|
|
2019
|
-
if (log.type === LogType.Action) {
|
|
2020
|
-
// Target isn't selected
|
|
2021
|
-
if (
|
|
2022
|
-
// Target exists
|
|
2023
|
-
log.target
|
|
2024
|
-
// Target isn't selected
|
|
2025
|
-
&& !actionErrorFilterState.target[log.target]
|
|
2026
|
-
) {
|
|
2027
|
-
return;
|
|
2028
|
-
}
|
|
2029
|
-
|
|
2030
|
-
// Action
|
|
2031
|
-
if (
|
|
2032
|
-
// Action exists
|
|
2033
|
-
log.action
|
|
2034
|
-
// Action isn't selected
|
|
2035
|
-
&& !actionErrorFilterState.action[log.action]
|
|
2036
|
-
) {
|
|
2037
|
-
return;
|
|
2038
|
-
}
|
|
2039
|
-
}
|
|
2040
|
-
|
|
2041
|
-
/* --------- Advanced Filter -------- */
|
|
2042
|
-
|
|
2043
|
-
// First name doesn't match
|
|
2044
|
-
if (
|
|
2045
|
-
// First name exists
|
|
2046
|
-
log.userFirstName
|
|
2047
|
-
// First name query doesn't match
|
|
2048
|
-
&& !log.userFirstName.toLowerCase().includes(
|
|
2049
|
-
advancedFilterState.userFirstName.toLowerCase().trim(),
|
|
2050
|
-
)
|
|
2051
|
-
) {
|
|
2052
|
-
return;
|
|
2053
|
-
}
|
|
2054
|
-
|
|
2055
|
-
// Last name doesn't match
|
|
2056
|
-
if (
|
|
2057
|
-
// Last name exists
|
|
2058
|
-
log.userLastName
|
|
2059
|
-
// Last name query doesn't match
|
|
2060
|
-
&& !log.userLastName.toLowerCase().includes(
|
|
2061
|
-
advancedFilterState.userLastName.toLowerCase().trim(),
|
|
2062
|
-
)
|
|
2063
|
-
) {
|
|
2064
|
-
return;
|
|
2065
|
-
}
|
|
2066
|
-
|
|
2067
|
-
// Email doesn't match
|
|
2068
|
-
if (
|
|
2069
|
-
// Email exists
|
|
2070
|
-
log.userEmail
|
|
2071
|
-
// Email query doesn't match
|
|
2072
|
-
&& !log.userEmail.toLowerCase().includes(
|
|
2073
|
-
advancedFilterState.userEmail.toLowerCase().trim(),
|
|
2074
|
-
)
|
|
2075
|
-
) {
|
|
2076
|
-
return;
|
|
2077
|
-
}
|
|
2078
|
-
|
|
2079
|
-
// User id doesn't match
|
|
2080
|
-
if (
|
|
2081
|
-
// User id exists
|
|
2082
|
-
log.userId
|
|
2083
|
-
// User id doesn't match
|
|
2084
|
-
&& !String(log.userId).includes(
|
|
2085
|
-
advancedFilterState.userId.trim(),
|
|
2086
|
-
)
|
|
2087
|
-
) {
|
|
2088
|
-
return;
|
|
2089
|
-
}
|
|
2090
|
-
|
|
2091
|
-
// Learner not allowed
|
|
2092
|
-
if (
|
|
2093
|
-
// User is a learner
|
|
2094
|
-
log.isLearner
|
|
2095
|
-
// Learners aren't included
|
|
2096
|
-
&& !advancedFilterState.includeLearners
|
|
2097
|
-
) {
|
|
2098
|
-
return;
|
|
2099
|
-
}
|
|
2100
|
-
|
|
2101
|
-
// TTM not allowed
|
|
2102
|
-
if (
|
|
2103
|
-
// User is a ttm
|
|
2104
|
-
log.isTTM
|
|
2105
|
-
// TTMs aren't included
|
|
2106
|
-
&& !advancedFilterState.includeTTMs
|
|
2107
|
-
) {
|
|
2108
|
-
return;
|
|
2109
|
-
}
|
|
2110
|
-
|
|
2111
|
-
// Admin not allowed
|
|
2112
|
-
if (
|
|
2113
|
-
// User is an admin
|
|
2114
|
-
log.isAdmin
|
|
2115
|
-
// Admins aren't included
|
|
2116
|
-
&& !advancedFilterState.includeAdmins
|
|
2117
|
-
) {
|
|
2118
|
-
return;
|
|
2119
|
-
}
|
|
2120
|
-
|
|
2121
|
-
// Course Id doesn't match
|
|
2122
|
-
if (
|
|
2123
|
-
// Course Id exists
|
|
2124
|
-
log.courseId
|
|
2125
|
-
// Course Id doesn't match
|
|
2126
|
-
&& !String(log.courseId).includes(
|
|
2127
|
-
advancedFilterState.courseId.trim(),
|
|
2128
|
-
)
|
|
2129
|
-
) {
|
|
2130
|
-
return;
|
|
2131
|
-
}
|
|
2132
|
-
|
|
2133
|
-
// Course name doesn't match
|
|
2134
|
-
if (
|
|
2135
|
-
// Course name exists
|
|
2136
|
-
log.courseName
|
|
2137
|
-
// Course name doesn't match
|
|
2138
|
-
&& !String(log.courseName).includes(
|
|
2139
|
-
advancedFilterState.courseName.trim(),
|
|
2140
|
-
)
|
|
2141
|
-
) {
|
|
2142
|
-
return;
|
|
2143
|
-
}
|
|
2144
|
-
|
|
2145
|
-
// Mobile filter doesn't match
|
|
2146
|
-
if (
|
|
2147
|
-
// Mobile filter exists
|
|
2148
|
-
advancedFilterState.isMobile !== undefined
|
|
2149
|
-
// Device info exists
|
|
2150
|
-
&& log.device
|
|
2151
|
-
// Mobile filter doesn't match
|
|
2152
|
-
&& (advancedFilterState.isMobile !== log.device.isMobile)
|
|
2153
|
-
) {
|
|
2154
|
-
return;
|
|
2155
|
-
}
|
|
2156
|
-
|
|
2157
|
-
// Log source doesn't match
|
|
2158
|
-
if (
|
|
2159
|
-
// Source filter exists
|
|
2160
|
-
advancedFilterState.source !== undefined
|
|
2161
|
-
// Source info exists
|
|
2162
|
-
&& log.source
|
|
2163
|
-
// Source filter doesn't match
|
|
2164
|
-
&& (advancedFilterState.source !== log.source)
|
|
2165
|
-
) {
|
|
2166
|
-
return;
|
|
2167
|
-
}
|
|
2168
|
-
|
|
2169
|
-
// Route path doesn't match (Only for server source)
|
|
2170
|
-
if (
|
|
2171
|
-
// Source is server
|
|
2172
|
-
(log.source === LogSource.Server)
|
|
2173
|
-
// Route path is being filtered
|
|
2174
|
-
&& (advancedFilterState.routePath.trim().length)
|
|
2175
|
-
// Route path doesn't match
|
|
2176
|
-
&& !(log.routePath.includes(advancedFilterState.routePath.trim()))
|
|
2177
|
-
) {
|
|
2178
|
-
return;
|
|
2179
|
-
}
|
|
2180
|
-
|
|
2181
|
-
// Route template doesn't match (Only for server source)
|
|
2182
|
-
if (
|
|
2183
|
-
// Source is server
|
|
2184
|
-
(log.source === LogSource.Server)
|
|
2185
|
-
// Route template is being filtered
|
|
2186
|
-
&& (advancedFilterState.routeTemplate.trim().length)
|
|
2187
|
-
// Route template doesn't match
|
|
2188
|
-
&& !(log.routeTemplate.includes(advancedFilterState.routeTemplate.trim()))
|
|
2189
|
-
) {
|
|
2190
|
-
return;
|
|
2191
|
-
}
|
|
2192
|
-
|
|
2193
|
-
/* -------------- Done -------------- */
|
|
2194
|
-
|
|
2195
|
-
// Made it past all filters. Add to the list
|
|
2196
|
-
logs.push(log);
|
|
2197
|
-
});
|
|
2198
|
-
});
|
|
2199
|
-
});
|
|
2200
|
-
|
|
2201
|
-
/*----------------------------------------*/
|
|
2202
|
-
/* ---------------- Data ---------------- */
|
|
2203
|
-
/*----------------------------------------*/
|
|
1821
|
+
// Filters UI
|
|
1822
|
+
const filters = (
|
|
1823
|
+
<>
|
|
1824
|
+
{filterToggles}
|
|
1825
|
+
{filterDrawer && (
|
|
1826
|
+
<Drawer customBackgroundColor="#eee">
|
|
1827
|
+
{filterDrawer}
|
|
1828
|
+
</Drawer>
|
|
1829
|
+
)}
|
|
1830
|
+
</>
|
|
1831
|
+
);
|
|
2204
1832
|
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
1833
|
+
// Main body
|
|
1834
|
+
body = (
|
|
1835
|
+
<>
|
|
1836
|
+
{filters}
|
|
1837
|
+
<div className="mt-2">
|
|
1838
|
+
<IntelliTable
|
|
1839
|
+
title="Matching Logs:"
|
|
1840
|
+
csvName={`Logs from ${getHumanReadableDate()}`}
|
|
1841
|
+
id="logs"
|
|
1842
|
+
data={logs}
|
|
1843
|
+
columns={columns}
|
|
1844
|
+
/>
|
|
1845
|
+
{logs.length === 0 && (
|
|
2209
1846
|
<div className="alert alert-warning text-center mt-2">
|
|
2210
|
-
<h4 className="m-1">
|
|
2211
|
-
No Logs to Show
|
|
2212
|
-
</h4>
|
|
1847
|
+
<h4 className="m-1">No Logs to Show</h4>
|
|
2213
1848
|
<div>
|
|
2214
1849
|
Either your filters are too strict or no matching logs have been
|
|
2215
1850
|
created yet.
|
|
2216
1851
|
</div>
|
|
2217
1852
|
</div>
|
|
2218
|
-
)
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
const dataTable = (
|
|
2224
|
-
<IntelliTable
|
|
2225
|
-
title="Matching Logs:"
|
|
2226
|
-
csvName={`Logs from ${getHumanReadableDate()}`}
|
|
2227
|
-
id="logs"
|
|
2228
|
-
data={logs}
|
|
2229
|
-
columns={columns}
|
|
2230
|
-
/>
|
|
2231
|
-
);
|
|
2232
|
-
|
|
2233
|
-
// Main body
|
|
2234
|
-
body = (
|
|
2235
|
-
<>
|
|
2236
|
-
{filters}
|
|
2237
|
-
<div className="mt-2">
|
|
2238
|
-
{dataTable}
|
|
2239
|
-
{noLogsNotice}
|
|
2240
|
-
</div>
|
|
2241
|
-
</>
|
|
2242
|
-
);
|
|
2243
|
-
}
|
|
1853
|
+
)}
|
|
1854
|
+
{paginationControls}
|
|
1855
|
+
</div>
|
|
1856
|
+
</>
|
|
1857
|
+
);
|
|
2244
1858
|
|
|
2245
1859
|
/* ---------- Wrap in Modal --------- */
|
|
2246
1860
|
|
|
@@ -2252,9 +1866,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
2252
1866
|
<div className="LogReviewer-inner-container">
|
|
2253
1867
|
<div className="LogReviewer-header">
|
|
2254
1868
|
<div className="LogReviewer-header-title">
|
|
2255
|
-
<h3 className="text-center m-0">
|
|
2256
|
-
Log Review Dashboard
|
|
2257
|
-
</h3>
|
|
1869
|
+
<h3 className="text-center m-0">Log Review Dashboard</h3>
|
|
2258
1870
|
</div>
|
|
2259
1871
|
<div style={{ width: 0 }}>
|
|
2260
1872
|
<button
|
|
@@ -2263,15 +1875,11 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
2263
1875
|
aria-label="close log reviewer panel"
|
|
2264
1876
|
onClick={onClose}
|
|
2265
1877
|
>
|
|
2266
|
-
<FontAwesomeIcon
|
|
2267
|
-
icon={faTimes}
|
|
2268
|
-
/>
|
|
1878
|
+
<FontAwesomeIcon icon={faTimes} />
|
|
2269
1879
|
</button>
|
|
2270
1880
|
</div>
|
|
2271
1881
|
</div>
|
|
2272
|
-
<div className="LogReviewer-contents">
|
|
2273
|
-
{body}
|
|
2274
|
-
</div>
|
|
1882
|
+
<div className="LogReviewer-contents">{body}</div>
|
|
2275
1883
|
</div>
|
|
2276
1884
|
</div>
|
|
2277
1885
|
);
|