dce-reactkit 3.9.4-beta-prompt.2 → 3.9.4-beta-logreviewer.2
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 +610 -867
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/AppWrapper.d.ts +0 -24
- package/dist/cjs/types/index.d.ts +2 -2
- package/dist/cjs/types/types/ModalType.d.ts +0 -1
- package/dist/esm/index.js +612 -868
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/AppWrapper.d.ts +0 -24
- package/dist/esm/types/index.d.ts +2 -2
- 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 +809 -1143
- package/src/components/Modal/index.tsx +0 -3
- package/src/index.ts +0 -2
- package/src/server/initServer.ts +150 -17
- package/src/types/ModalType.tsx +0 -1
|
@@ -10,6 +10,8 @@ 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,
|
|
@@ -63,13 +65,6 @@ type Props = {
|
|
|
63
65
|
onClose: () => void,
|
|
64
66
|
};
|
|
65
67
|
|
|
66
|
-
// Map of loaded logs (year => month => Log[])
|
|
67
|
-
type LogMap = {
|
|
68
|
-
[k: string]: {
|
|
69
|
-
[k: string]: Log[]
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
73
68
|
// Triple of year/month/day
|
|
74
69
|
type DateTriple = {
|
|
75
70
|
// Full year
|
|
@@ -532,8 +527,8 @@ type State = {
|
|
|
532
527
|
/* -------------- Logs -------------- */
|
|
533
528
|
// True if currently loading
|
|
534
529
|
loading: boolean,
|
|
535
|
-
// Loaded logs
|
|
536
|
-
|
|
530
|
+
// Loaded logs
|
|
531
|
+
logs: Log[],
|
|
537
532
|
/* ------------- Filters ------------ */
|
|
538
533
|
// Current expanded filter drawer
|
|
539
534
|
expandedFilterDrawer: FilterDrawer | undefined,
|
|
@@ -547,6 +542,10 @@ type State = {
|
|
|
547
542
|
actionErrorFilterState: ActionErrorFilterState,
|
|
548
543
|
// State of the advanced filter
|
|
549
544
|
advancedFilterState: AdvancedFilterState,
|
|
545
|
+
// Current page number
|
|
546
|
+
pageNumber: number,
|
|
547
|
+
// If true, there is another page to load
|
|
548
|
+
hasAnotherPage: boolean,
|
|
550
549
|
};
|
|
551
550
|
|
|
552
551
|
/* ------------- Actions ------------ */
|
|
@@ -555,7 +554,7 @@ type State = {
|
|
|
555
554
|
enum ActionType {
|
|
556
555
|
// Show the loading bar
|
|
557
556
|
StartLoading = 'start-loading',
|
|
558
|
-
// Finish loading
|
|
557
|
+
// Finish loading logs
|
|
559
558
|
FinishLoading = 'finish-loading',
|
|
560
559
|
// Reset filters to initial values
|
|
561
560
|
ResetFilters = 'reset-filters',
|
|
@@ -573,6 +572,12 @@ enum ActionType {
|
|
|
573
572
|
UpdateActionErrorFilterState = 'update-action-error-filter-state',
|
|
574
573
|
// Update the advanced filter state
|
|
575
574
|
UpdateAdvancedFilterState = 'update-advanced-filter-state',
|
|
575
|
+
// Increment the page number
|
|
576
|
+
IncrementPageNumber = 'increment-page-number',
|
|
577
|
+
// Decrement the page number
|
|
578
|
+
DecrementPageNumber = 'decrement-page-number',
|
|
579
|
+
// Set has another page
|
|
580
|
+
SetHasAnotherPage = 'set-has-another-page',
|
|
576
581
|
}
|
|
577
582
|
|
|
578
583
|
// Action definitions
|
|
@@ -580,8 +585,8 @@ type Action = (
|
|
|
580
585
|
| {
|
|
581
586
|
// Action type
|
|
582
587
|
type: ActionType.FinishLoading,
|
|
583
|
-
// Updated
|
|
584
|
-
|
|
588
|
+
// Updated logs
|
|
589
|
+
logs: Log[],
|
|
585
590
|
}
|
|
586
591
|
| {
|
|
587
592
|
// Action type
|
|
@@ -629,6 +634,16 @@ type Action = (
|
|
|
629
634
|
// New advanced filter state
|
|
630
635
|
advancedFilterState: AdvancedFilterState,
|
|
631
636
|
}
|
|
637
|
+
| {
|
|
638
|
+
type: ActionType.IncrementPageNumber,
|
|
639
|
+
}
|
|
640
|
+
| {
|
|
641
|
+
type: ActionType.DecrementPageNumber,
|
|
642
|
+
}
|
|
643
|
+
| {
|
|
644
|
+
type: ActionType.SetHasAnotherPage,
|
|
645
|
+
hasAnotherPage: boolean,
|
|
646
|
+
}
|
|
632
647
|
| {
|
|
633
648
|
// Action type
|
|
634
649
|
type: (
|
|
@@ -656,7 +671,7 @@ const reducer = (state: State, action: Action): State => {
|
|
|
656
671
|
return {
|
|
657
672
|
...state,
|
|
658
673
|
loading: false,
|
|
659
|
-
|
|
674
|
+
logs: action.logs,
|
|
660
675
|
};
|
|
661
676
|
}
|
|
662
677
|
case ActionType.ToggleFilterDrawer: {
|
|
@@ -715,6 +730,18 @@ const reducer = (state: State, action: Action): State => {
|
|
|
715
730
|
advancedFilterState: action.advancedFilterState,
|
|
716
731
|
};
|
|
717
732
|
}
|
|
733
|
+
case ActionType.IncrementPageNumber: {
|
|
734
|
+
return {
|
|
735
|
+
...state,
|
|
736
|
+
pageNumber: state.pageNumber + 1,
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
case ActionType.DecrementPageNumber: {
|
|
740
|
+
return {
|
|
741
|
+
...state,
|
|
742
|
+
pageNumber: state.pageNumber - 1,
|
|
743
|
+
};
|
|
744
|
+
}
|
|
718
745
|
default: {
|
|
719
746
|
return state;
|
|
720
747
|
}
|
|
@@ -850,13 +877,15 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
850
877
|
// Initial state
|
|
851
878
|
const initialState: State = {
|
|
852
879
|
loading: true,
|
|
853
|
-
|
|
880
|
+
logs: [],
|
|
854
881
|
expandedFilterDrawer: undefined,
|
|
855
882
|
dateFilterState: initDateFilterState,
|
|
856
883
|
contextFilterState: initContextFilterState,
|
|
857
884
|
tagFilterState: initTagFilterState,
|
|
858
885
|
actionErrorFilterState: initActionErrorFilterState,
|
|
859
886
|
advancedFilterState: initAdvancedFilterState,
|
|
887
|
+
pageNumber: 1,
|
|
888
|
+
hasAnotherPage: false,
|
|
860
889
|
};
|
|
861
890
|
|
|
862
891
|
// Initialize state
|
|
@@ -865,13 +894,15 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
865
894
|
// Destructure common state
|
|
866
895
|
const {
|
|
867
896
|
loading,
|
|
868
|
-
|
|
897
|
+
logs,
|
|
869
898
|
expandedFilterDrawer,
|
|
870
899
|
dateFilterState,
|
|
871
900
|
contextFilterState,
|
|
872
901
|
tagFilterState,
|
|
873
902
|
actionErrorFilterState,
|
|
874
903
|
advancedFilterState,
|
|
904
|
+
pageNumber,
|
|
905
|
+
hasAnotherPage,
|
|
875
906
|
} = state;
|
|
876
907
|
|
|
877
908
|
/*------------------------------------------------------------------------*/
|
|
@@ -879,106 +910,48 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
879
910
|
/*------------------------------------------------------------------------*/
|
|
880
911
|
|
|
881
912
|
/**
|
|
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
|
|
913
|
+
* Fetch logs from the server based on current filters
|
|
932
914
|
*/
|
|
933
|
-
const
|
|
934
|
-
|
|
935
|
-
) => {
|
|
936
|
-
// Update state
|
|
937
|
-
dispatch({
|
|
938
|
-
type: ActionType.UpdateDateFilterState,
|
|
939
|
-
dateFilterState: newDateFilterState,
|
|
940
|
-
});
|
|
915
|
+
const fetchLogs = async () => {
|
|
916
|
+
dispatch({ type: ActionType.StartLoading });
|
|
941
917
|
|
|
942
|
-
|
|
943
|
-
|
|
918
|
+
try {
|
|
919
|
+
// Prepare filter parameters
|
|
920
|
+
const filters = {
|
|
921
|
+
startDate: dateFilterState.startDate,
|
|
922
|
+
endDate: dateFilterState.endDate,
|
|
923
|
+
contextFilterState,
|
|
924
|
+
tagFilterState,
|
|
925
|
+
actionErrorFilterState,
|
|
926
|
+
advancedFilterState,
|
|
927
|
+
};
|
|
944
928
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
return;
|
|
948
|
-
}
|
|
929
|
+
// Send filters to the server
|
|
930
|
+
let fetchedLogs: Log[] = [];
|
|
949
931
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
932
|
+
const response = await visitServerEndpoint({
|
|
933
|
+
path: `${LOG_REVIEW_ROUTE_PATH_PREFIX}/logs`,
|
|
934
|
+
method: 'GET',
|
|
935
|
+
params: {
|
|
936
|
+
pageNumber,
|
|
937
|
+
filters,
|
|
938
|
+
},
|
|
939
|
+
});
|
|
954
940
|
|
|
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
|
-
});
|
|
941
|
+
fetchedLogs = fetchedLogs.concat(response.items);
|
|
942
|
+
dispatch({
|
|
943
|
+
type: ActionType.SetHasAnotherPage,
|
|
944
|
+
hasAnotherPage: response.hasAnotherPage,
|
|
945
|
+
});
|
|
966
946
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
}
|
|
947
|
+
// Update logs in state
|
|
948
|
+
dispatch({
|
|
949
|
+
type: ActionType.FinishLoading,
|
|
950
|
+
logs: fetchedLogs,
|
|
951
|
+
});
|
|
973
952
|
} catch (err) {
|
|
974
953
|
return showFatalError(err);
|
|
975
954
|
}
|
|
976
|
-
|
|
977
|
-
// Finish loading
|
|
978
|
-
dispatch({
|
|
979
|
-
type: ActionType.FinishLoading,
|
|
980
|
-
logMap,
|
|
981
|
-
});
|
|
982
955
|
};
|
|
983
956
|
|
|
984
957
|
/*------------------------------------------------------------------------*/
|
|
@@ -986,21 +959,21 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
986
959
|
/*------------------------------------------------------------------------*/
|
|
987
960
|
|
|
988
961
|
/**
|
|
989
|
-
*
|
|
990
|
-
* @author Gabe Abrams
|
|
962
|
+
* Fetch logs whenever filters change
|
|
991
963
|
*/
|
|
992
|
-
useEffect(
|
|
993
|
-
()
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
964
|
+
useEffect(() => {
|
|
965
|
+
fetchLogs();
|
|
966
|
+
}, [
|
|
967
|
+
dateFilterState,
|
|
968
|
+
contextFilterState,
|
|
969
|
+
tagFilterState,
|
|
970
|
+
actionErrorFilterState,
|
|
971
|
+
advancedFilterState,
|
|
972
|
+
]);
|
|
999
973
|
|
|
1000
974
|
/*------------------------------------------------------------------------*/
|
|
1001
975
|
/* ------------------------------- Render ------------------------------- */
|
|
1002
976
|
/*------------------------------------------------------------------------*/
|
|
1003
|
-
|
|
1004
977
|
/*----------------------------------------*/
|
|
1005
978
|
/* --------------- Main UI -------------- */
|
|
1006
979
|
/*----------------------------------------*/
|
|
@@ -1020,176 +993,218 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1020
993
|
|
|
1021
994
|
/* ------------ Review UI ----------- */
|
|
1022
995
|
|
|
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
|
-
|
|
996
|
+
/*----------------------------------------*/
|
|
997
|
+
/* ------------ Pagination -------------- */
|
|
998
|
+
/*----------------------------------------*/
|
|
999
|
+
|
|
1000
|
+
const paginationControls = logs.length > 0 && (
|
|
1001
|
+
<div className="text-center mt-3">
|
|
1002
|
+
<button
|
|
1003
|
+
type="button"
|
|
1004
|
+
className="btn btn-secondary me-2"
|
|
1005
|
+
disabled={pageNumber <= 1}
|
|
1006
|
+
onClick={() => {
|
|
1007
|
+
dispatch({
|
|
1008
|
+
type: ActionType.DecrementPageNumber,
|
|
1009
|
+
});
|
|
1010
|
+
}}
|
|
1011
|
+
>
|
|
1012
|
+
<FontAwesomeIcon icon={faArrowLeft} className="me-2" />
|
|
1013
|
+
Previous Page
|
|
1014
|
+
</button>
|
|
1015
|
+
<span className="mx-3">
|
|
1016
|
+
Page
|
|
1017
|
+
{' '}
|
|
1018
|
+
{pageNumber}
|
|
1019
|
+
</span>
|
|
1020
|
+
<button
|
|
1021
|
+
type="button"
|
|
1022
|
+
className="btn btn-secondary ms-2"
|
|
1023
|
+
disabled={!hasAnotherPage}
|
|
1024
|
+
onClick={() => {
|
|
1025
|
+
dispatch({
|
|
1026
|
+
type: ActionType.IncrementPageNumber,
|
|
1027
|
+
});
|
|
1028
|
+
}}
|
|
1029
|
+
>
|
|
1030
|
+
Next Page
|
|
1031
|
+
<FontAwesomeIcon icon={faArrowRight} className="ms-2" />
|
|
1032
|
+
</button>
|
|
1033
|
+
</div>
|
|
1034
|
+
);
|
|
1035
|
+
|
|
1036
|
+
/*----------------------------------------*/
|
|
1037
|
+
/* --------------- Filters -------------- */
|
|
1038
|
+
/*----------------------------------------*/
|
|
1039
|
+
|
|
1040
|
+
// Filter toggle
|
|
1041
|
+
const filterToggles = (
|
|
1042
|
+
<div className="LogReviewer-filter-toggles">
|
|
1043
|
+
<h3 className="m-0">
|
|
1044
|
+
Filters:
|
|
1045
|
+
</h3>
|
|
1046
|
+
<div className="LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0">
|
|
1047
|
+
{/* Date */}
|
|
1048
|
+
<button
|
|
1049
|
+
type="button"
|
|
1050
|
+
id="LogReviewer-toggle-date-filter-drawer"
|
|
1051
|
+
className={`btn btn-${FilterDrawer.Date === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
1052
|
+
aria-label="toggle date filter drawer"
|
|
1053
|
+
onClick={() => {
|
|
1054
|
+
dispatch({
|
|
1055
|
+
type: ActionType.ToggleFilterDrawer,
|
|
1056
|
+
filterDrawer: FilterDrawer.Date,
|
|
1057
|
+
});
|
|
1058
|
+
}}
|
|
1059
|
+
>
|
|
1060
|
+
<FontAwesomeIcon
|
|
1061
|
+
icon={faCalendar}
|
|
1062
|
+
className="me-2"
|
|
1063
|
+
/>
|
|
1064
|
+
Date
|
|
1065
|
+
</button>
|
|
1066
|
+
{/* Context */}
|
|
1067
|
+
<button
|
|
1068
|
+
type="button"
|
|
1069
|
+
id="LogReviewer-toggle-context-filter-drawer"
|
|
1070
|
+
className={`btn btn-${FilterDrawer.Context === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
1071
|
+
aria-label="toggle context filter drawer"
|
|
1072
|
+
onClick={() => {
|
|
1073
|
+
dispatch({
|
|
1074
|
+
type: ActionType.ToggleFilterDrawer,
|
|
1075
|
+
filterDrawer: FilterDrawer.Context,
|
|
1076
|
+
});
|
|
1077
|
+
}}
|
|
1078
|
+
>
|
|
1079
|
+
<FontAwesomeIcon
|
|
1080
|
+
icon={faCircle}
|
|
1081
|
+
className="me-2"
|
|
1082
|
+
/>
|
|
1083
|
+
Context
|
|
1084
|
+
</button>
|
|
1085
|
+
{/* Tag */}
|
|
1086
|
+
{/* Skip if no tags are used */}
|
|
1087
|
+
{(LogMetadata.Tag && Object.keys(LogMetadata.Tag).length > 0) && (
|
|
1088
|
+
<button
|
|
1089
|
+
type="button"
|
|
1090
|
+
id="LogReviewer-toggle-tag-filter-drawer"
|
|
1091
|
+
className={`btn btn-${FilterDrawer.Tag === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
1092
|
+
aria-label="toggle tag filter drawer"
|
|
1093
|
+
onClick={() => {
|
|
1094
|
+
dispatch({
|
|
1095
|
+
type: ActionType.ToggleFilterDrawer,
|
|
1096
|
+
filterDrawer: FilterDrawer.Tag,
|
|
1097
|
+
});
|
|
1098
|
+
}}
|
|
1099
|
+
>
|
|
1100
|
+
<FontAwesomeIcon
|
|
1101
|
+
icon={faTag}
|
|
1102
|
+
className="me-2"
|
|
1103
|
+
/>
|
|
1104
|
+
Tag
|
|
1105
|
+
</button>
|
|
1106
|
+
)}
|
|
1107
|
+
{/* Action */}
|
|
1108
|
+
<button
|
|
1109
|
+
type="button"
|
|
1110
|
+
id="LogReviewer-toggle-action-filter-drawer"
|
|
1111
|
+
className={`btn btn-${FilterDrawer.Action === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
1112
|
+
aria-label="toggle action and error filter drawer"
|
|
1113
|
+
onClick={() => {
|
|
1114
|
+
dispatch({
|
|
1115
|
+
type: ActionType.ToggleFilterDrawer,
|
|
1116
|
+
filterDrawer: FilterDrawer.Action,
|
|
1117
|
+
});
|
|
1118
|
+
}}
|
|
1119
|
+
>
|
|
1120
|
+
<FontAwesomeIcon
|
|
1121
|
+
icon={faHammer}
|
|
1122
|
+
className="me-2"
|
|
1123
|
+
/>
|
|
1124
|
+
Action
|
|
1125
|
+
</button>
|
|
1126
|
+
{/* Advanced */}
|
|
1127
|
+
<button
|
|
1128
|
+
type="button"
|
|
1129
|
+
id="LogReviewer-toggle-advanced-filter-drawer"
|
|
1130
|
+
className={`btn btn-${FilterDrawer.Advanced === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
1131
|
+
aria-label="toggle advanced filter drawer"
|
|
1132
|
+
onClick={() => {
|
|
1133
|
+
dispatch({
|
|
1134
|
+
type: ActionType.ToggleFilterDrawer,
|
|
1135
|
+
filterDrawer: FilterDrawer.Advanced,
|
|
1136
|
+
});
|
|
1137
|
+
}}
|
|
1138
|
+
>
|
|
1139
|
+
<FontAwesomeIcon
|
|
1140
|
+
icon={faList}
|
|
1141
|
+
className="me-2"
|
|
1142
|
+
/>
|
|
1143
|
+
Advanced
|
|
1144
|
+
</button>
|
|
1145
|
+
{/* Reset */}
|
|
1146
|
+
<button
|
|
1147
|
+
type="button"
|
|
1148
|
+
id="LogReviewer-reset-filters-button"
|
|
1149
|
+
className="btn btn-light"
|
|
1150
|
+
aria-label="reset filters"
|
|
1151
|
+
onClick={() => {
|
|
1152
|
+
dispatch({
|
|
1153
|
+
type: ActionType.ResetFilters,
|
|
1154
|
+
initActionErrorFilterState,
|
|
1155
|
+
initAdvancedFilterState,
|
|
1156
|
+
initContextFilterState,
|
|
1157
|
+
initDateFilterState,
|
|
1158
|
+
initTagFilterState,
|
|
1159
|
+
});
|
|
1160
|
+
}}
|
|
1161
|
+
>
|
|
1162
|
+
<FontAwesomeIcon
|
|
1163
|
+
icon={faTimes}
|
|
1164
|
+
/>
|
|
1165
|
+
{' '}
|
|
1166
|
+
Reset
|
|
1167
|
+
</button>
|
|
1168
|
+
</div>
|
|
1169
|
+
</div>
|
|
1170
|
+
);
|
|
1171
|
+
|
|
1172
|
+
// Filter drawer
|
|
1173
|
+
let filterDrawer: React.ReactNode;
|
|
1174
|
+
if (expandedFilterDrawer) {
|
|
1175
|
+
if (expandedFilterDrawer === FilterDrawer.Date) {
|
|
1176
|
+
filterDrawer = (
|
|
1177
|
+
<TabBox title="Date">
|
|
1178
|
+
<SimpleDateChooser
|
|
1179
|
+
ariaLabel="filter start date"
|
|
1180
|
+
name="filter-start-date"
|
|
1181
|
+
year={dateFilterState.startDate.year}
|
|
1182
|
+
month={dateFilterState.startDate.month}
|
|
1183
|
+
day={dateFilterState.startDate.day}
|
|
1184
|
+
chooseFromPast
|
|
1185
|
+
numMonthsToShow={36}
|
|
1186
|
+
onChange={(month, day, year) => {
|
|
1187
|
+
dateFilterState.startDate = { month, day, year };
|
|
1140
1188
|
dispatch({
|
|
1141
|
-
type: ActionType.
|
|
1142
|
-
|
|
1143
|
-
initAdvancedFilterState,
|
|
1144
|
-
initContextFilterState,
|
|
1145
|
-
initDateFilterState,
|
|
1146
|
-
initTagFilterState,
|
|
1189
|
+
type: ActionType.UpdateDateFilterState,
|
|
1190
|
+
dateFilterState,
|
|
1147
1191
|
});
|
|
1148
1192
|
}}
|
|
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
|
|
1193
|
+
/>
|
|
1194
|
+
{' '}
|
|
1195
|
+
to
|
|
1196
|
+
{' '}
|
|
1197
|
+
<SimpleDateChooser
|
|
1198
|
+
ariaLabel="filter end date"
|
|
1199
|
+
name="filter-end-date"
|
|
1200
|
+
year={dateFilterState.endDate.year}
|
|
1201
|
+
month={dateFilterState.endDate.month}
|
|
1202
|
+
day={dateFilterState.endDate.day}
|
|
1203
|
+
chooseFromPast
|
|
1204
|
+
numMonthsToShow={12}
|
|
1205
|
+
onChange={(month, day, year) => {
|
|
1206
|
+
if (
|
|
1207
|
+
year < dateFilterState.startDate.year
|
|
1193
1208
|
|| (
|
|
1194
1209
|
year === dateFilterState.startDate.year
|
|
1195
1210
|
&& month < dateFilterState.startDate.month
|
|
@@ -1199,137 +1214,140 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1199
1214
|
&& month === dateFilterState.startDate.month
|
|
1200
1215
|
&& day < dateFilterState.startDate.day
|
|
1201
1216
|
)
|
|
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);
|
|
1217
|
+
) {
|
|
1218
|
+
return alert(
|
|
1219
|
+
'Invalid Start Date',
|
|
1220
|
+
'The start date cannot be before the end date.',
|
|
1221
|
+
);
|
|
1243
1222
|
}
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1223
|
+
dateFilterState.endDate = { month, day, year };
|
|
1224
|
+
dispatch({
|
|
1225
|
+
type: ActionType.UpdateDateFilterState,
|
|
1226
|
+
dateFilterState,
|
|
1227
|
+
});
|
|
1228
|
+
}}
|
|
1229
|
+
/>
|
|
1230
|
+
</TabBox>
|
|
1231
|
+
);
|
|
1232
|
+
} else if (expandedFilterDrawer === FilterDrawer.Context) {
|
|
1233
|
+
// Create item picker items
|
|
1234
|
+
const builtInPickableItem: PickableItem = {
|
|
1235
|
+
id: 'built-in-contexts',
|
|
1236
|
+
name: 'Auto-logged',
|
|
1237
|
+
isGroup: true,
|
|
1238
|
+
children: [],
|
|
1239
|
+
};
|
|
1240
|
+
const pickableItems: PickableItem[] = [];
|
|
1241
|
+
Object.keys(contextMap)
|
|
1242
|
+
.forEach((context) => {
|
|
1243
|
+
const value = contextMap[context];
|
|
1244
|
+
if (typeof value === 'string') {
|
|
1245
|
+
// No subcategories
|
|
1264
1246
|
const item: PickableItem = {
|
|
1265
1247
|
id: context,
|
|
1266
1248
|
name: genHumanReadableName(context),
|
|
1267
|
-
isGroup:
|
|
1268
|
-
|
|
1249
|
+
isGroup: false,
|
|
1250
|
+
checked: !!contextFilterState[context],
|
|
1269
1251
|
};
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1252
|
+
|
|
1253
|
+
// Add built-in items to its own folder
|
|
1254
|
+
const isBuiltIn = context in LogBuiltInMetadata.Context;
|
|
1255
|
+
if (isBuiltIn) {
|
|
1256
|
+
// Add to built-in pickable item
|
|
1257
|
+
builtInPickableItem.children.push(item);
|
|
1258
|
+
} else {
|
|
1259
|
+
// Add to pickable items list
|
|
1260
|
+
pickableItems.push(item);
|
|
1261
|
+
}
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
// Has subcategories
|
|
1266
|
+
const children: PickableItem[] = (
|
|
1267
|
+
Object.keys(value)
|
|
1268
|
+
// Remove parent name
|
|
1269
|
+
.filter((subcontext) => {
|
|
1270
|
+
return subcontext !== '_';
|
|
1271
|
+
})
|
|
1272
|
+
// Create child pickable items
|
|
1273
|
+
.map((subcontext) => {
|
|
1274
|
+
return {
|
|
1275
|
+
id: subcontext,
|
|
1276
|
+
name: genHumanReadableName(subcontext),
|
|
1277
|
+
isGroup: false,
|
|
1278
|
+
checked: (contextFilterState[context] as any)[subcontext],
|
|
1279
|
+
};
|
|
1280
|
+
})
|
|
1281
|
+
);
|
|
1282
|
+
const item: PickableItem = {
|
|
1283
|
+
id: context,
|
|
1284
|
+
name: genHumanReadableName(context),
|
|
1285
|
+
isGroup: true,
|
|
1286
|
+
children,
|
|
1287
|
+
};
|
|
1288
|
+
pickableItems.push(item);
|
|
1289
|
+
});
|
|
1290
|
+
// Add built-in contexts to end ofl ist
|
|
1291
|
+
pickableItems.push(builtInPickableItem);
|
|
1292
|
+
|
|
1293
|
+
// Create filter UI
|
|
1294
|
+
filterDrawer = (
|
|
1295
|
+
<ItemPicker
|
|
1296
|
+
title="Context"
|
|
1297
|
+
items={pickableItems}
|
|
1298
|
+
onChanged={(updatedItems) => {
|
|
1299
|
+
// Update our state
|
|
1300
|
+
updatedItems.forEach((pickableItem) => {
|
|
1301
|
+
if (pickableItem.isGroup) {
|
|
1302
|
+
// Has subcontexts
|
|
1303
|
+
|
|
1304
|
+
if (pickableItem.id === 'built-in-contexts') {
|
|
1305
|
+
// Built-in
|
|
1306
|
+
|
|
1307
|
+
// Treat as if these were top-level contexts
|
|
1308
|
+
pickableItem.children.forEach((subcontextItem) => {
|
|
1309
|
+
contextFilterState[subcontextItem.id] = (
|
|
1310
|
+
'checked' in subcontextItem
|
|
1293
1311
|
&& 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
|
-
}
|
|
1312
|
+
);
|
|
1313
|
+
});
|
|
1308
1314
|
} else {
|
|
1309
|
-
//
|
|
1310
|
-
(
|
|
1311
|
-
|
|
1312
|
-
|
|
1315
|
+
// Not built-in
|
|
1316
|
+
pickableItem.children.forEach((subcontextItem) => {
|
|
1317
|
+
if (!subcontextItem.isGroup) {
|
|
1318
|
+
(
|
|
1319
|
+
contextFilterState[pickableItem.id] as { [k: string]: boolean }
|
|
1320
|
+
)[subcontextItem.id] = (
|
|
1321
|
+
subcontextItem.checked
|
|
1322
|
+
);
|
|
1323
|
+
}
|
|
1324
|
+
});
|
|
1313
1325
|
}
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1326
|
+
} else {
|
|
1327
|
+
// No subcontexts
|
|
1328
|
+
(contextFilterState as any)[pickableItem.id] = (
|
|
1329
|
+
pickableItem.checked
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1332
|
+
});
|
|
1333
|
+
dispatch({
|
|
1334
|
+
type: ActionType.UpdateContextFilterState,
|
|
1335
|
+
contextFilterState,
|
|
1336
|
+
});
|
|
1337
|
+
}}
|
|
1338
|
+
/>
|
|
1339
|
+
);
|
|
1340
|
+
} else if (expandedFilterDrawer === FilterDrawer.Tag) {
|
|
1341
|
+
// Create filter UI
|
|
1342
|
+
filterDrawer = (
|
|
1343
|
+
<TabBox title="Tags">
|
|
1344
|
+
<div>
|
|
1345
|
+
If any tags are selected, logs must contain at least one
|
|
1346
|
+
(but not necessarily all) of the
|
|
1347
|
+
selected tags.
|
|
1348
|
+
</div>
|
|
1349
|
+
<div className="d-flex gap-1 flex-wrap">
|
|
1350
|
+
{
|
|
1333
1351
|
Object.keys(LogMetadata.Tag ?? {})
|
|
1334
1352
|
.map((tag) => {
|
|
1335
1353
|
const description = genHumanReadableName(tag);
|
|
@@ -1351,58 +1369,58 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1351
1369
|
);
|
|
1352
1370
|
})
|
|
1353
1371
|
}
|
|
1354
|
-
|
|
1372
|
+
</div>
|
|
1373
|
+
</TabBox>
|
|
1374
|
+
);
|
|
1375
|
+
} else if (expandedFilterDrawer === FilterDrawer.Action) {
|
|
1376
|
+
// Create filter UI
|
|
1377
|
+
filterDrawer = (
|
|
1378
|
+
<>
|
|
1379
|
+
{/* Log Type */}
|
|
1380
|
+
<TabBox title="Log Type">
|
|
1381
|
+
<RadioButton
|
|
1382
|
+
id="LogReviewer-type-all"
|
|
1383
|
+
text="All Logs"
|
|
1384
|
+
onSelected={() => {
|
|
1385
|
+
actionErrorFilterState.type = undefined;
|
|
1386
|
+
dispatch({
|
|
1387
|
+
type: ActionType.UpdateActionErrorFilterState,
|
|
1388
|
+
actionErrorFilterState,
|
|
1389
|
+
});
|
|
1390
|
+
}}
|
|
1391
|
+
ariaLabel="show logs of all types"
|
|
1392
|
+
selected={actionErrorFilterState.type === undefined}
|
|
1393
|
+
/>
|
|
1394
|
+
<RadioButton
|
|
1395
|
+
id="LogReviewer-type-action-only"
|
|
1396
|
+
text="Action Logs Only"
|
|
1397
|
+
onSelected={() => {
|
|
1398
|
+
actionErrorFilterState.type = LogType.Action;
|
|
1399
|
+
dispatch({
|
|
1400
|
+
type: ActionType.UpdateActionErrorFilterState,
|
|
1401
|
+
actionErrorFilterState,
|
|
1402
|
+
});
|
|
1403
|
+
}}
|
|
1404
|
+
ariaLabel="only show action logs"
|
|
1405
|
+
selected={actionErrorFilterState.type === LogType.Action}
|
|
1406
|
+
/>
|
|
1407
|
+
<RadioButton
|
|
1408
|
+
id="LogReviewer-type-error-only"
|
|
1409
|
+
text="Action Error Only"
|
|
1410
|
+
onSelected={() => {
|
|
1411
|
+
actionErrorFilterState.type = LogType.Error;
|
|
1412
|
+
dispatch({
|
|
1413
|
+
type: ActionType.UpdateActionErrorFilterState,
|
|
1414
|
+
actionErrorFilterState,
|
|
1415
|
+
});
|
|
1416
|
+
}}
|
|
1417
|
+
ariaLabel="only show error logs"
|
|
1418
|
+
selected={actionErrorFilterState.type === LogType.Error}
|
|
1419
|
+
noMarginOnRight
|
|
1420
|
+
/>
|
|
1355
1421
|
</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
|
-
{
|
|
1422
|
+
{/* Actions */}
|
|
1423
|
+
{
|
|
1406
1424
|
(
|
|
1407
1425
|
actionErrorFilterState.type === undefined
|
|
1408
1426
|
|| actionErrorFilterState.type === LogType.Action
|
|
@@ -1471,8 +1489,8 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1471
1489
|
</TabBox>
|
|
1472
1490
|
)
|
|
1473
1491
|
}
|
|
1474
|
-
|
|
1475
|
-
|
|
1492
|
+
{/* Errors */}
|
|
1493
|
+
{
|
|
1476
1494
|
(
|
|
1477
1495
|
actionErrorFilterState.type === undefined
|
|
1478
1496
|
|| actionErrorFilterState.type === LogType.Error
|
|
@@ -1525,160 +1543,300 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1525
1543
|
</TabBox>
|
|
1526
1544
|
)
|
|
1527
1545
|
}
|
|
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
|
-
|
|
1546
|
+
</>
|
|
1547
|
+
);
|
|
1548
|
+
} else if (expandedFilterDrawer === FilterDrawer.Advanced) {
|
|
1549
|
+
// Create advanced filter ui
|
|
1550
|
+
filterDrawer = (
|
|
1551
|
+
<>
|
|
1552
|
+
{/* User Info */}
|
|
1553
|
+
<TabBox title="User">
|
|
1554
|
+
{/* First Name */}
|
|
1555
|
+
<div className="input-group mb-2">
|
|
1556
|
+
<span className="input-group-text">
|
|
1557
|
+
User First Name
|
|
1558
|
+
</span>
|
|
1559
|
+
<input
|
|
1560
|
+
type="text"
|
|
1561
|
+
className="form-control"
|
|
1562
|
+
aria-label="query for user first name"
|
|
1563
|
+
value={advancedFilterState.userFirstName}
|
|
1564
|
+
placeholder="e.g. Divardo"
|
|
1565
|
+
onChange={(e) => {
|
|
1566
|
+
advancedFilterState.userFirstName = e.target.value;
|
|
1567
|
+
dispatch({
|
|
1568
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1569
|
+
advancedFilterState,
|
|
1570
|
+
});
|
|
1571
|
+
}}
|
|
1572
|
+
/>
|
|
1573
|
+
</div>
|
|
1574
|
+
{/* Last Name */}
|
|
1575
|
+
<div className="input-group mb-2">
|
|
1576
|
+
<span className="input-group-text">
|
|
1577
|
+
User Last Name
|
|
1578
|
+
</span>
|
|
1579
|
+
<input
|
|
1580
|
+
type="text"
|
|
1581
|
+
className="form-control"
|
|
1582
|
+
aria-label="query for user last name"
|
|
1583
|
+
value={advancedFilterState.userLastName}
|
|
1584
|
+
placeholder="e.g. Calicci"
|
|
1585
|
+
onChange={(e) => {
|
|
1586
|
+
advancedFilterState.userLastName = e.target.value;
|
|
1587
|
+
dispatch({
|
|
1588
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1589
|
+
advancedFilterState,
|
|
1590
|
+
});
|
|
1591
|
+
}}
|
|
1592
|
+
/>
|
|
1593
|
+
</div>
|
|
1594
|
+
{/* Email */}
|
|
1595
|
+
<div className="input-group mb-2">
|
|
1596
|
+
<span className="input-group-text">
|
|
1597
|
+
User Email
|
|
1598
|
+
</span>
|
|
1599
|
+
<input
|
|
1600
|
+
type="text"
|
|
1601
|
+
className="form-control"
|
|
1602
|
+
aria-label="query for user email"
|
|
1603
|
+
value={advancedFilterState.userEmail}
|
|
1604
|
+
placeholder="e.g. calicci@fas.harvard.edu"
|
|
1605
|
+
onChange={(e) => {
|
|
1606
|
+
advancedFilterState.userEmail = (
|
|
1607
|
+
(e.target.value)
|
|
1608
|
+
.trim()
|
|
1609
|
+
);
|
|
1610
|
+
dispatch({
|
|
1611
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1612
|
+
advancedFilterState,
|
|
1613
|
+
});
|
|
1614
|
+
}}
|
|
1615
|
+
/>
|
|
1616
|
+
</div>
|
|
1617
|
+
{/* Canvas Id */}
|
|
1618
|
+
<div className="input-group mb-2">
|
|
1619
|
+
<span className="input-group-text">
|
|
1620
|
+
User Canvas Id
|
|
1621
|
+
</span>
|
|
1622
|
+
<input
|
|
1623
|
+
type="text"
|
|
1624
|
+
className="form-control"
|
|
1625
|
+
aria-label="query for user canvas id"
|
|
1626
|
+
value={advancedFilterState.userId}
|
|
1627
|
+
placeholder="e.g. 104985"
|
|
1628
|
+
onChange={(e) => {
|
|
1629
|
+
const { value } = e.target;
|
|
1630
|
+
// Only update if value contains only numbers
|
|
1631
|
+
if (/^\d+$/.test(value)) {
|
|
1632
|
+
advancedFilterState.userId = (
|
|
1589
1633
|
(e.target.value)
|
|
1590
1634
|
.trim()
|
|
1591
1635
|
);
|
|
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
|
-
|
|
1636
|
+
}
|
|
1637
|
+
dispatch({
|
|
1638
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1639
|
+
advancedFilterState,
|
|
1640
|
+
});
|
|
1641
|
+
}}
|
|
1642
|
+
/>
|
|
1643
|
+
</div>
|
|
1644
|
+
{/* Role */}
|
|
1645
|
+
<ButtonInputGroup label="Role">
|
|
1646
|
+
<CheckboxButton
|
|
1647
|
+
text="Students"
|
|
1648
|
+
onChanged={(checked) => {
|
|
1649
|
+
advancedFilterState.includeLearners = checked;
|
|
1650
|
+
dispatch({
|
|
1651
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1652
|
+
advancedFilterState,
|
|
1653
|
+
});
|
|
1654
|
+
}}
|
|
1655
|
+
checked={advancedFilterState.includeLearners}
|
|
1656
|
+
ariaLabel="show logs from students"
|
|
1657
|
+
/>
|
|
1658
|
+
<CheckboxButton
|
|
1659
|
+
text="Teaching Team Members"
|
|
1660
|
+
onChanged={(checked) => {
|
|
1661
|
+
advancedFilterState.includeTTMs = checked;
|
|
1662
|
+
dispatch({
|
|
1663
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1664
|
+
advancedFilterState,
|
|
1665
|
+
});
|
|
1666
|
+
}}
|
|
1667
|
+
checked={advancedFilterState.includeTTMs}
|
|
1668
|
+
ariaLabel="show logs from teaching team members"
|
|
1669
|
+
/>
|
|
1670
|
+
<CheckboxButton
|
|
1671
|
+
text="Admins"
|
|
1672
|
+
onChanged={(checked) => {
|
|
1673
|
+
advancedFilterState.includeAdmins = checked;
|
|
1674
|
+
dispatch({
|
|
1675
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1676
|
+
advancedFilterState,
|
|
1677
|
+
});
|
|
1678
|
+
}}
|
|
1679
|
+
checked={advancedFilterState.includeAdmins}
|
|
1680
|
+
ariaLabel="show logs from admins"
|
|
1681
|
+
/>
|
|
1682
|
+
</ButtonInputGroup>
|
|
1683
|
+
</TabBox>
|
|
1684
|
+
|
|
1685
|
+
{/* Course Info */}
|
|
1686
|
+
<TabBox title="Course">
|
|
1687
|
+
{/* Name */}
|
|
1688
|
+
<div className="input-group mb-2">
|
|
1689
|
+
<span className="input-group-text">
|
|
1690
|
+
Course Name
|
|
1691
|
+
</span>
|
|
1692
|
+
<input
|
|
1693
|
+
type="text"
|
|
1694
|
+
className="form-control"
|
|
1695
|
+
aria-label="query for course name"
|
|
1696
|
+
value={advancedFilterState.courseName}
|
|
1697
|
+
placeholder="e.g. GLC 200"
|
|
1698
|
+
onChange={(e) => {
|
|
1699
|
+
advancedFilterState.courseName = e.target.value;
|
|
1700
|
+
dispatch({
|
|
1701
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1702
|
+
advancedFilterState,
|
|
1703
|
+
});
|
|
1704
|
+
}}
|
|
1705
|
+
/>
|
|
1706
|
+
</div>
|
|
1707
|
+
{/* Canvas Id */}
|
|
1708
|
+
<div className="input-group mb-2">
|
|
1709
|
+
<span className="input-group-text">
|
|
1710
|
+
Course Canvas Id
|
|
1711
|
+
</span>
|
|
1712
|
+
<input
|
|
1713
|
+
type="text"
|
|
1714
|
+
className="form-control"
|
|
1715
|
+
aria-label="query for course canvas id"
|
|
1716
|
+
value={advancedFilterState.courseId}
|
|
1717
|
+
placeholder="e.g. 15948"
|
|
1718
|
+
onChange={(e) => {
|
|
1719
|
+
const { value } = e.target;
|
|
1720
|
+
// Only update if value contains only numbers
|
|
1721
|
+
if (/^\d+$/.test(value)) {
|
|
1722
|
+
advancedFilterState.courseId = (
|
|
1723
|
+
(e.target.value)
|
|
1724
|
+
.trim()
|
|
1725
|
+
);
|
|
1726
|
+
}
|
|
1727
|
+
dispatch({
|
|
1728
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1729
|
+
advancedFilterState,
|
|
1730
|
+
});
|
|
1731
|
+
}}
|
|
1732
|
+
/>
|
|
1733
|
+
</div>
|
|
1734
|
+
</TabBox>
|
|
1666
1735
|
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1736
|
+
{/* Device Info */}
|
|
1737
|
+
<TabBox title="Device">
|
|
1738
|
+
<ButtonInputGroup label="Device Type">
|
|
1739
|
+
<RadioButton
|
|
1740
|
+
text="All Devices"
|
|
1741
|
+
ariaLabel="show logs from all devices"
|
|
1742
|
+
selected={advancedFilterState.isMobile === undefined}
|
|
1743
|
+
onSelected={() => {
|
|
1744
|
+
advancedFilterState.isMobile = undefined;
|
|
1745
|
+
dispatch({
|
|
1746
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1747
|
+
advancedFilterState,
|
|
1748
|
+
});
|
|
1749
|
+
}}
|
|
1750
|
+
/>
|
|
1751
|
+
<RadioButton
|
|
1752
|
+
text="Mobile Only"
|
|
1753
|
+
ariaLabel="show logs from mobile devices"
|
|
1754
|
+
selected={advancedFilterState.isMobile === true}
|
|
1755
|
+
onSelected={() => {
|
|
1756
|
+
advancedFilterState.isMobile = true;
|
|
1757
|
+
dispatch({
|
|
1758
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1759
|
+
advancedFilterState,
|
|
1760
|
+
});
|
|
1761
|
+
}}
|
|
1762
|
+
/>
|
|
1763
|
+
<RadioButton
|
|
1764
|
+
text="Desktop Only"
|
|
1765
|
+
ariaLabel="show logs from desktop devices"
|
|
1766
|
+
selected={advancedFilterState.isMobile === false}
|
|
1767
|
+
onSelected={() => {
|
|
1768
|
+
advancedFilterState.isMobile = false;
|
|
1769
|
+
dispatch({
|
|
1770
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1771
|
+
advancedFilterState,
|
|
1772
|
+
});
|
|
1773
|
+
}}
|
|
1774
|
+
noMarginOnRight
|
|
1775
|
+
/>
|
|
1776
|
+
</ButtonInputGroup>
|
|
1777
|
+
</TabBox>
|
|
1778
|
+
|
|
1779
|
+
{/* Source */}
|
|
1780
|
+
<TabBox title="Source">
|
|
1781
|
+
<ButtonInputGroup label="Source Type">
|
|
1782
|
+
<RadioButton
|
|
1783
|
+
text="Both"
|
|
1784
|
+
ariaLabel="show logs from all sources"
|
|
1785
|
+
selected={advancedFilterState.source === undefined}
|
|
1786
|
+
onSelected={() => {
|
|
1787
|
+
advancedFilterState.source = undefined;
|
|
1788
|
+
dispatch({
|
|
1789
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1790
|
+
advancedFilterState,
|
|
1791
|
+
});
|
|
1792
|
+
}}
|
|
1793
|
+
/>
|
|
1794
|
+
<RadioButton
|
|
1795
|
+
text="Client Only"
|
|
1796
|
+
ariaLabel="show logs from client source"
|
|
1797
|
+
selected={advancedFilterState.source === LogSource.Client}
|
|
1798
|
+
onSelected={() => {
|
|
1799
|
+
advancedFilterState.source = LogSource.Client;
|
|
1800
|
+
dispatch({
|
|
1801
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1802
|
+
advancedFilterState,
|
|
1803
|
+
});
|
|
1804
|
+
}}
|
|
1805
|
+
/>
|
|
1806
|
+
<RadioButton
|
|
1807
|
+
text="Server Only"
|
|
1808
|
+
ariaLabel="show logs from server source"
|
|
1809
|
+
selected={advancedFilterState.source === LogSource.Server}
|
|
1810
|
+
onSelected={() => {
|
|
1811
|
+
advancedFilterState.source = LogSource.Server;
|
|
1812
|
+
dispatch({
|
|
1813
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1814
|
+
advancedFilterState,
|
|
1815
|
+
});
|
|
1816
|
+
}}
|
|
1817
|
+
noMarginOnRight
|
|
1818
|
+
/>
|
|
1819
|
+
</ButtonInputGroup>
|
|
1820
|
+
|
|
1821
|
+
{/* Server filters */}
|
|
1822
|
+
{advancedFilterState.source !== LogSource.Client && (
|
|
1823
|
+
<div className="mt-2">
|
|
1824
|
+
{/* Route path */}
|
|
1670
1825
|
<div className="input-group mb-2">
|
|
1671
1826
|
<span className="input-group-text">
|
|
1672
|
-
|
|
1827
|
+
Server Route Path
|
|
1673
1828
|
</span>
|
|
1674
1829
|
<input
|
|
1675
1830
|
type="text"
|
|
1676
1831
|
className="form-control"
|
|
1677
|
-
aria-label="query for
|
|
1678
|
-
value={advancedFilterState.
|
|
1679
|
-
placeholder="e.g.
|
|
1832
|
+
aria-label="query for server route path"
|
|
1833
|
+
value={advancedFilterState.routePath}
|
|
1834
|
+
placeholder="e.g. /api/ttm/courses/12345"
|
|
1680
1835
|
onChange={(e) => {
|
|
1681
|
-
advancedFilterState.courseName =
|
|
1836
|
+
advancedFilterState.courseName = (
|
|
1837
|
+
(e.target.value)
|
|
1838
|
+
.trim()
|
|
1839
|
+
);
|
|
1682
1840
|
dispatch({
|
|
1683
1841
|
type: ActionType.UpdateAdvancedFilterState,
|
|
1684
1842
|
advancedFilterState,
|
|
@@ -1686,26 +1844,23 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1686
1844
|
}}
|
|
1687
1845
|
/>
|
|
1688
1846
|
</div>
|
|
1689
|
-
|
|
1847
|
+
|
|
1848
|
+
{/* Route template */}
|
|
1690
1849
|
<div className="input-group mb-2">
|
|
1691
1850
|
<span className="input-group-text">
|
|
1692
|
-
|
|
1851
|
+
Server Route Template
|
|
1693
1852
|
</span>
|
|
1694
1853
|
<input
|
|
1695
1854
|
type="text"
|
|
1696
1855
|
className="form-control"
|
|
1697
|
-
aria-label="query for
|
|
1698
|
-
value={advancedFilterState.
|
|
1699
|
-
placeholder="e.g.
|
|
1856
|
+
aria-label="query for server route template"
|
|
1857
|
+
value={advancedFilterState.routeTemplate}
|
|
1858
|
+
placeholder="e.g. /api/ttm/courses/:courseId"
|
|
1700
1859
|
onChange={(e) => {
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
(e.target.value)
|
|
1706
|
-
.trim()
|
|
1707
|
-
);
|
|
1708
|
-
}
|
|
1860
|
+
advancedFilterState.courseName = (
|
|
1861
|
+
(e.target.value)
|
|
1862
|
+
.trim()
|
|
1863
|
+
);
|
|
1709
1864
|
dispatch({
|
|
1710
1865
|
type: ActionType.UpdateAdvancedFilterState,
|
|
1711
1866
|
advancedFilterState,
|
|
@@ -1713,534 +1868,51 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1713
1868
|
}}
|
|
1714
1869
|
/>
|
|
1715
1870
|
</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
|
-
}
|
|
1871
|
+
</div>
|
|
1872
|
+
)}
|
|
1873
|
+
</TabBox>
|
|
1874
|
+
</>
|
|
1875
|
+
);
|
|
1859
1876
|
}
|
|
1877
|
+
}
|
|
1860
1878
|
|
|
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
|
-
/*----------------------------------------*/
|
|
1879
|
+
// Filters UI
|
|
1880
|
+
const filters = (
|
|
1881
|
+
<>
|
|
1882
|
+
{filterToggles}
|
|
1883
|
+
{filterDrawer && (
|
|
1884
|
+
<Drawer customBackgroundColor="#eee">
|
|
1885
|
+
{filterDrawer}
|
|
1886
|
+
</Drawer>
|
|
1887
|
+
)}
|
|
1888
|
+
</>
|
|
1889
|
+
);
|
|
2204
1890
|
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
1891
|
+
// Main body
|
|
1892
|
+
body = (
|
|
1893
|
+
<>
|
|
1894
|
+
{filters}
|
|
1895
|
+
<div className="mt-2">
|
|
1896
|
+
<IntelliTable
|
|
1897
|
+
title="Matching Logs:"
|
|
1898
|
+
csvName={`Logs from ${getHumanReadableDate()}`}
|
|
1899
|
+
id="logs"
|
|
1900
|
+
data={logs}
|
|
1901
|
+
columns={columns}
|
|
1902
|
+
/>
|
|
1903
|
+
{logs.length === 0 && (
|
|
2209
1904
|
<div className="alert alert-warning text-center mt-2">
|
|
2210
|
-
<h4 className="m-1">
|
|
2211
|
-
No Logs to Show
|
|
2212
|
-
</h4>
|
|
1905
|
+
<h4 className="m-1">No Logs to Show</h4>
|
|
2213
1906
|
<div>
|
|
2214
1907
|
Either your filters are too strict or no matching logs have been
|
|
2215
1908
|
created yet.
|
|
2216
1909
|
</div>
|
|
2217
1910
|
</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
|
-
}
|
|
1911
|
+
)}
|
|
1912
|
+
{paginationControls}
|
|
1913
|
+
</div>
|
|
1914
|
+
</>
|
|
1915
|
+
);
|
|
2244
1916
|
|
|
2245
1917
|
/* ---------- Wrap in Modal --------- */
|
|
2246
1918
|
|
|
@@ -2252,9 +1924,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
2252
1924
|
<div className="LogReviewer-inner-container">
|
|
2253
1925
|
<div className="LogReviewer-header">
|
|
2254
1926
|
<div className="LogReviewer-header-title">
|
|
2255
|
-
<h3 className="text-center m-0">
|
|
2256
|
-
Log Review Dashboard
|
|
2257
|
-
</h3>
|
|
1927
|
+
<h3 className="text-center m-0">Log Review Dashboard</h3>
|
|
2258
1928
|
</div>
|
|
2259
1929
|
<div style={{ width: 0 }}>
|
|
2260
1930
|
<button
|
|
@@ -2263,15 +1933,11 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
2263
1933
|
aria-label="close log reviewer panel"
|
|
2264
1934
|
onClick={onClose}
|
|
2265
1935
|
>
|
|
2266
|
-
<FontAwesomeIcon
|
|
2267
|
-
icon={faTimes}
|
|
2268
|
-
/>
|
|
1936
|
+
<FontAwesomeIcon icon={faTimes} />
|
|
2269
1937
|
</button>
|
|
2270
1938
|
</div>
|
|
2271
1939
|
</div>
|
|
2272
|
-
<div className="LogReviewer-contents">
|
|
2273
|
-
{body}
|
|
2274
|
-
</div>
|
|
1940
|
+
<div className="LogReviewer-contents">{body}</div>
|
|
2275
1941
|
</div>
|
|
2276
1942
|
</div>
|
|
2277
1943
|
);
|