dce-reactkit 3.2.2-beta.4 → 3.2.2-beta.40
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 +388 -198
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/ButtonInputGroup.d.ts +1 -0
- package/dist/cjs/types/components/Drawer.d.ts +1 -0
- package/dist/cjs/types/components/ItemPicker/NestableItemList.d.ts +1 -0
- package/dist/cjs/types/components/ItemPicker/index.d.ts +1 -0
- package/dist/cjs/types/types/LogAction.d.ts +16 -16
- package/dist/cjs/types/types/LogBuiltInMetadata.d.ts +1 -1
- package/dist/esm/index.js +388 -198
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/ButtonInputGroup.d.ts +1 -0
- package/dist/esm/types/components/Drawer.d.ts +1 -0
- package/dist/esm/types/components/ItemPicker/NestableItemList.d.ts +1 -0
- package/dist/esm/types/components/ItemPicker/index.d.ts +1 -0
- package/dist/esm/types/types/LogAction.d.ts +16 -16
- package/dist/esm/types/types/LogBuiltInMetadata.d.ts +1 -1
- package/dist/index.d.ts +20 -17
- package/package.json +1 -1
- package/src/components/ButtonInputGroup.tsx +5 -2
- package/src/components/CSVDownloadButton.tsx +1 -1
- package/src/components/Drawer.tsx +9 -1
- package/src/components/IntelliTable.tsx +80 -21
- package/src/components/ItemPicker/NestableItemList.tsx +32 -14
- package/src/components/ItemPicker/index.tsx +4 -0
- package/src/components/LogReviewer.tsx +300 -146
- package/src/components/SimpleDateChooser.tsx +20 -7
- package/src/helpers/genCSV.ts +21 -4
- package/src/helpers/genRouteHandler.ts +2 -2
- package/src/helpers/logClientEvent.tsx +6 -1
- package/src/server/initServer.ts +11 -4
- package/src/types/LogAction.ts +16 -16
- package/src/types/LogBuiltInMetadata.ts +5 -5
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
// Import shared helpers
|
|
22
22
|
import visitServerEndpoint from '../helpers/visitServerEndpoint';
|
|
23
23
|
import getTimeInfoInET from '../helpers/getTimeInfoInET';
|
|
24
|
-
import { showFatalError } from './AppWrapper';
|
|
24
|
+
import { alert, showFatalError } from './AppWrapper';
|
|
25
25
|
|
|
26
26
|
// Import shared constants
|
|
27
27
|
import LOG_REVIEW_ROUTE_PATH_PREFIX from '../constants/LOG_REVIEW_ROUTE_PATH_PREFIX';
|
|
@@ -34,6 +34,7 @@ import LogType from '../types/LogType';
|
|
|
34
34
|
import LogAction from '../types/LogAction';
|
|
35
35
|
import ParamType from '../types/ParamType';
|
|
36
36
|
import IntelliTableColumn from '../types/IntelliTableColumn';
|
|
37
|
+
import LogBuiltInMetadata from '../types/LogBuiltInMetadata';
|
|
37
38
|
|
|
38
39
|
// Import shared components
|
|
39
40
|
import SimpleDateChooser from './SimpleDateChooser';
|
|
@@ -121,7 +122,7 @@ type ContextFilterState = {
|
|
|
121
122
|
|
|
122
123
|
// Tag filter state
|
|
123
124
|
type TagFilterState = {
|
|
124
|
-
[k: string]: boolean
|
|
125
|
+
[k: string]: boolean // tag => true if in the list of tags to show
|
|
125
126
|
};
|
|
126
127
|
|
|
127
128
|
// Action filter state (only relevant for action logs)
|
|
@@ -206,6 +207,11 @@ const style = `
|
|
|
206
207
|
border: 0.05rem solid black;
|
|
207
208
|
border-radius: 0.5rem;
|
|
208
209
|
overflow: hidden;
|
|
210
|
+
padding: 0.7rem;
|
|
211
|
+
|
|
212
|
+
/* Solid background */
|
|
213
|
+
background-color: white;
|
|
214
|
+
color: black;
|
|
209
215
|
|
|
210
216
|
/* Place contents in flex column */
|
|
211
217
|
flex-direction: column;
|
|
@@ -232,6 +238,27 @@ const style = `
|
|
|
232
238
|
/* Vertical scroll */
|
|
233
239
|
overflow-y: auto;
|
|
234
240
|
}
|
|
241
|
+
|
|
242
|
+
.LogReviewer-header-close-button {
|
|
243
|
+
border: 0 !important;
|
|
244
|
+
background-color: transparent !important;
|
|
245
|
+
padding-top: 0 !important;
|
|
246
|
+
padding-bottom: 0 !important;
|
|
247
|
+
padding-right: 1em !important;
|
|
248
|
+
margin: 0 !important;
|
|
249
|
+
color: #444 !important;
|
|
250
|
+
|
|
251
|
+
right: 0 !important;
|
|
252
|
+
position: absolute !important;
|
|
253
|
+
}
|
|
254
|
+
.LogReviewer-header-close-button:hover {
|
|
255
|
+
border: 0 !important;
|
|
256
|
+
background-color: transparent !important;
|
|
257
|
+
padding-top: 0 !important;
|
|
258
|
+
padding-bottom: 0 !important;
|
|
259
|
+
margin: 0 !important;
|
|
260
|
+
color: #000 !important;
|
|
261
|
+
}
|
|
235
262
|
`;
|
|
236
263
|
|
|
237
264
|
/*------------------------------------------------------------------------*/
|
|
@@ -254,7 +281,7 @@ const genHumanReadableName = (machineReadableName: string) => {
|
|
|
254
281
|
// Uppercase! Add a space before
|
|
255
282
|
humanReadableName += ' ';
|
|
256
283
|
}
|
|
257
|
-
humanReadableName +=
|
|
284
|
+
humanReadableName += char;
|
|
258
285
|
});
|
|
259
286
|
|
|
260
287
|
// Trim and return
|
|
@@ -437,9 +464,25 @@ const reducer = (state: State, action: Action): State => {
|
|
|
437
464
|
};
|
|
438
465
|
}
|
|
439
466
|
case ActionType.UpdateTagFilterState: {
|
|
467
|
+
const { tagFilterState } = action;
|
|
468
|
+
|
|
469
|
+
// Select all if every tag is deselected
|
|
470
|
+
const numTagsSelected = (
|
|
471
|
+
Object.values(tagFilterState)
|
|
472
|
+
.filter((isSelected) => {
|
|
473
|
+
return isSelected;
|
|
474
|
+
})
|
|
475
|
+
.length
|
|
476
|
+
);
|
|
477
|
+
if (numTagsSelected === 0) {
|
|
478
|
+
Object.keys(tagFilterState).forEach((tag) => {
|
|
479
|
+
tagFilterState[tag] = true;
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
440
483
|
return {
|
|
441
484
|
...state,
|
|
442
|
-
tagFilterState
|
|
485
|
+
tagFilterState,
|
|
443
486
|
};
|
|
444
487
|
}
|
|
445
488
|
case ActionType.UpdateActionErrorFilterState: {
|
|
@@ -477,6 +520,35 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
477
520
|
onClose,
|
|
478
521
|
} = props;
|
|
479
522
|
|
|
523
|
+
// Add built-in LogMetadata
|
|
524
|
+
// > Add "uncategorized" subcontext to each context
|
|
525
|
+
Object.keys(LogMetadata.Context ?? {}).forEach((context) => {
|
|
526
|
+
if (
|
|
527
|
+
// Context exists
|
|
528
|
+
LogMetadata.Context
|
|
529
|
+
// Context has children already
|
|
530
|
+
&& typeof LogMetadata.Context[context] !== 'string'
|
|
531
|
+
) {
|
|
532
|
+
(LogMetadata.Context as any)[context][LogBuiltInMetadata.Context.Uncategorized] = (
|
|
533
|
+
LogBuiltInMetadata.Context.Uncategorized
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
})
|
|
537
|
+
// > Add built-in contexts
|
|
538
|
+
LogMetadata.Context = (LogMetadata.Context ?? {});
|
|
539
|
+
Object.keys(LogBuiltInMetadata.Context).forEach((context) => {
|
|
540
|
+
if (LogMetadata.Context) {
|
|
541
|
+
LogMetadata.Context[context] = context;
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
// > Add built-in targets
|
|
545
|
+
LogMetadata.Target = (LogMetadata.Target ?? {});
|
|
546
|
+
Object.keys(LogBuiltInMetadata.Target).forEach((target) => {
|
|
547
|
+
if (LogMetadata.Target) {
|
|
548
|
+
LogMetadata.Target[target] = target;
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
|
|
480
552
|
/* -------------- State ------------- */
|
|
481
553
|
|
|
482
554
|
// Create initial date filter state
|
|
@@ -501,22 +573,27 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
501
573
|
Object.keys(LogMetadata.Context ?? {}).forEach((context) => {
|
|
502
574
|
const contextValue = (LogMetadata.Context ?? {})[context];
|
|
503
575
|
if (typeof contextValue === 'string') {
|
|
504
|
-
// Case: no subcontexts
|
|
576
|
+
// Case: no subcontexts, init as checked
|
|
505
577
|
initContextFilterState[contextValue] = true;
|
|
506
578
|
} else {
|
|
507
579
|
// Case: subcontexts exist
|
|
508
|
-
initContextFilterState[
|
|
580
|
+
initContextFilterState[context] = {};
|
|
509
581
|
Object.values((LogMetadata.Context ?? {})[context]).forEach((subcontext) => {
|
|
510
|
-
|
|
511
|
-
(
|
|
582
|
+
// Skip self ("_")
|
|
583
|
+
if (subcontext === '_') {
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Initialize as checked
|
|
588
|
+
(initContextFilterState[context] as any)[subcontext] = true;
|
|
512
589
|
});
|
|
513
590
|
}
|
|
514
591
|
});
|
|
515
592
|
|
|
516
593
|
// Create initial tag filter state
|
|
517
594
|
const initTagFilterState: TagFilterState = {};
|
|
518
|
-
Object.
|
|
519
|
-
initTagFilterState[
|
|
595
|
+
Object.keys(LogMetadata.Tag ?? {}).forEach((tag) => {
|
|
596
|
+
initTagFilterState[tag] = false;
|
|
520
597
|
});
|
|
521
598
|
|
|
522
599
|
// Create advanced filter state
|
|
@@ -550,6 +627,10 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
550
627
|
Object.values(LogAction).forEach((action) => {
|
|
551
628
|
initActionErrorFilterState.action[action] = true;
|
|
552
629
|
});
|
|
630
|
+
// Add built-in targets
|
|
631
|
+
Object.values(LogBuiltInMetadata.Target).forEach((target) => {
|
|
632
|
+
initActionErrorFilterState.target[target] = true;
|
|
633
|
+
});
|
|
553
634
|
|
|
554
635
|
// Initial state
|
|
555
636
|
const initialState: State = {
|
|
@@ -600,18 +681,23 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
600
681
|
let month = newDateFilterState.startDate.month;
|
|
601
682
|
while (
|
|
602
683
|
// Earlier year
|
|
603
|
-
(year
|
|
684
|
+
(year < newDateFilterState.endDate.year)
|
|
604
685
|
// Current year but included month
|
|
605
686
|
|| (
|
|
606
687
|
year === newDateFilterState.endDate.year
|
|
607
688
|
&& month <= newDateFilterState.endDate.month
|
|
608
689
|
)
|
|
609
690
|
) {
|
|
610
|
-
// Add to list
|
|
611
|
-
|
|
612
|
-
year
|
|
613
|
-
month
|
|
614
|
-
|
|
691
|
+
// Add to list if not already loaded
|
|
692
|
+
if (
|
|
693
|
+
!logMap[year]
|
|
694
|
+
|| !logMap[year][month]
|
|
695
|
+
) {
|
|
696
|
+
toLoad.push({
|
|
697
|
+
year,
|
|
698
|
+
month,
|
|
699
|
+
});
|
|
700
|
+
}
|
|
615
701
|
|
|
616
702
|
// Increment
|
|
617
703
|
month += 1;
|
|
@@ -727,16 +813,16 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
727
813
|
|
|
728
814
|
// Filter toggle
|
|
729
815
|
const filterToggles = (
|
|
730
|
-
<div className="LogReviewer-filter-toggles
|
|
816
|
+
<div className="LogReviewer-filter-toggles">
|
|
731
817
|
<h3 className="m-0">
|
|
732
818
|
Filters:
|
|
733
819
|
</h3>
|
|
734
|
-
<div className="LogReviewer-filter-toggle-buttons">
|
|
820
|
+
<div className="LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0">
|
|
735
821
|
{/* Date */}
|
|
736
822
|
<button
|
|
737
823
|
type="button"
|
|
738
824
|
id="LogReviewer-toggle-date-filter-drawer"
|
|
739
|
-
className={`btn btn-${FilterDrawer.Date === expandedFilterDrawer} me-2`}
|
|
825
|
+
className={`btn btn-${FilterDrawer.Date === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
740
826
|
aria-label="toggle date filter drawer"
|
|
741
827
|
onClick={() => {
|
|
742
828
|
dispatch({
|
|
@@ -755,7 +841,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
755
841
|
<button
|
|
756
842
|
type="button"
|
|
757
843
|
id="LogReviewer-toggle-context-filter-drawer"
|
|
758
|
-
className={`btn btn-${FilterDrawer.Context === expandedFilterDrawer} me-2`}
|
|
844
|
+
className={`btn btn-${FilterDrawer.Context === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
759
845
|
aria-label="toggle context filter drawer"
|
|
760
846
|
onClick={() => {
|
|
761
847
|
dispatch({
|
|
@@ -771,29 +857,32 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
771
857
|
Context
|
|
772
858
|
</button>
|
|
773
859
|
{/* Tag */}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
860
|
+
{/* Skip if no tags are used */}
|
|
861
|
+
{(LogMetadata.Tag && Object.keys(LogMetadata.Tag).length > 0) && (
|
|
862
|
+
<button
|
|
863
|
+
type="button"
|
|
864
|
+
id="LogReviewer-toggle-tag-filter-drawer"
|
|
865
|
+
className={`btn btn-${FilterDrawer.Tag === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
866
|
+
aria-label="toggle tag filter drawer"
|
|
867
|
+
onClick={() => {
|
|
868
|
+
dispatch({
|
|
869
|
+
type: ActionType.ToggleFilterDrawer,
|
|
870
|
+
filterDrawer: FilterDrawer.Tag,
|
|
871
|
+
});
|
|
872
|
+
}}
|
|
873
|
+
>
|
|
874
|
+
<FontAwesomeIcon
|
|
875
|
+
icon={faTag}
|
|
876
|
+
className="me-2"
|
|
877
|
+
/>
|
|
878
|
+
Tag
|
|
879
|
+
</button>
|
|
880
|
+
)}
|
|
792
881
|
{/* Action */}
|
|
793
882
|
<button
|
|
794
883
|
type="button"
|
|
795
884
|
id="LogReviewer-toggle-action-filter-drawer"
|
|
796
|
-
className={`btn btn-${FilterDrawer.Action === expandedFilterDrawer} me-2`}
|
|
885
|
+
className={`btn btn-${FilterDrawer.Action === expandedFilterDrawer ? 'warning' : 'light'} me-2`}
|
|
797
886
|
aria-label="toggle action and error filter drawer"
|
|
798
887
|
onClick={() => {
|
|
799
888
|
dispatch({
|
|
@@ -812,7 +901,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
812
901
|
<button
|
|
813
902
|
type="button"
|
|
814
903
|
id="LogReviewer-toggle-advanced-filter-drawer"
|
|
815
|
-
className={`btn btn-${FilterDrawer.Advanced === expandedFilterDrawer}`}
|
|
904
|
+
className={`btn btn-${FilterDrawer.Advanced === expandedFilterDrawer ? 'warning' : 'light'}`}
|
|
816
905
|
aria-label="toggle advanced filter drawer"
|
|
817
906
|
onClick={() => {
|
|
818
907
|
dispatch({
|
|
@@ -843,14 +932,11 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
843
932
|
year={dateFilterState.startDate.year}
|
|
844
933
|
month={dateFilterState.startDate.month}
|
|
845
934
|
day={dateFilterState.startDate.day}
|
|
935
|
+
chooseFromPast
|
|
936
|
+
numMonthsToShow={12}
|
|
846
937
|
onChange={(month, day, year) => {
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
dateFilterState: {
|
|
850
|
-
...dateFilterState,
|
|
851
|
-
startDate: { month, day, year },
|
|
852
|
-
},
|
|
853
|
-
});
|
|
938
|
+
dateFilterState.startDate = { month, day, year };
|
|
939
|
+
handleDateRangeUpdated(dateFilterState);
|
|
854
940
|
}}
|
|
855
941
|
/>
|
|
856
942
|
{' '}
|
|
@@ -862,14 +948,28 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
862
948
|
year={dateFilterState.endDate.year}
|
|
863
949
|
month={dateFilterState.endDate.month}
|
|
864
950
|
day={dateFilterState.endDate.day}
|
|
951
|
+
chooseFromPast
|
|
952
|
+
numMonthsToShow={12}
|
|
865
953
|
onChange={(month, day, year) => {
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
954
|
+
if (
|
|
955
|
+
year < dateFilterState.startDate.year
|
|
956
|
+
|| (
|
|
957
|
+
year === dateFilterState.startDate.year
|
|
958
|
+
&& month < dateFilterState.startDate.month
|
|
959
|
+
)
|
|
960
|
+
|| (
|
|
961
|
+
year === dateFilterState.startDate.year
|
|
962
|
+
&& month === dateFilterState.startDate.month
|
|
963
|
+
&& day < dateFilterState.startDate.day
|
|
964
|
+
)
|
|
965
|
+
) {
|
|
966
|
+
return alert(
|
|
967
|
+
'Invalid Start Date',
|
|
968
|
+
'The start date cannot be before the end date.',
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
dateFilterState.endDate = { month, day, year };
|
|
972
|
+
handleDateRangeUpdated(dateFilterState);
|
|
873
973
|
}}
|
|
874
974
|
/>
|
|
875
975
|
</TabBox>
|
|
@@ -895,20 +995,20 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
895
995
|
const children: PickableItem[] = (
|
|
896
996
|
Object.keys(value)
|
|
897
997
|
.filter((subcontext) => {
|
|
898
|
-
return subcontext !== '_'
|
|
998
|
+
return subcontext !== '_';
|
|
899
999
|
})
|
|
900
1000
|
.map((subcontext) => {
|
|
901
1001
|
return {
|
|
902
|
-
id:
|
|
1002
|
+
id: subcontext,
|
|
903
1003
|
name: genHumanReadableName(subcontext),
|
|
904
1004
|
isGroup: false,
|
|
905
|
-
checked:
|
|
1005
|
+
checked: (contextFilterState[context] as any)[subcontext],
|
|
906
1006
|
};
|
|
907
1007
|
})
|
|
908
1008
|
);
|
|
909
1009
|
const item: PickableItem = {
|
|
910
1010
|
id: context,
|
|
911
|
-
name: context,
|
|
1011
|
+
name: genHumanReadableName(context),
|
|
912
1012
|
isGroup: true,
|
|
913
1013
|
children,
|
|
914
1014
|
};
|
|
@@ -927,9 +1027,13 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
927
1027
|
if (pickableItem.isGroup) {
|
|
928
1028
|
// Has subcontexts
|
|
929
1029
|
pickableItem.children.forEach((subcontextItem) => {
|
|
930
|
-
(
|
|
931
|
-
(
|
|
932
|
-
|
|
1030
|
+
if (!subcontextItem.isGroup) {
|
|
1031
|
+
(
|
|
1032
|
+
contextFilterState[pickableItem.id] as { [k: string]: boolean }
|
|
1033
|
+
)[subcontextItem.id] = (
|
|
1034
|
+
subcontextItem.checked
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
933
1037
|
});
|
|
934
1038
|
} else {
|
|
935
1039
|
// No subcontexts
|
|
@@ -950,17 +1054,22 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
950
1054
|
filterDrawer = (
|
|
951
1055
|
<TabBox title="Tags">
|
|
952
1056
|
{
|
|
953
|
-
Object.keys(
|
|
1057
|
+
Object.keys(LogMetadata.Tag ?? {})
|
|
954
1058
|
.map((tag, i) => {
|
|
955
1059
|
const description = genHumanReadableName(tag);
|
|
956
1060
|
return (
|
|
957
1061
|
<CheckboxButton
|
|
958
1062
|
id={`LogReviewer-tag-${tag}-checkbox`}
|
|
959
1063
|
text={description}
|
|
960
|
-
ariaLabel={`
|
|
1064
|
+
ariaLabel={`require that logs be tagged with "${description}" or any other selected tag`}
|
|
961
1065
|
noMarginOnRight={i === Object.keys(tagFilterState).length - 1}
|
|
1066
|
+
checked={tagFilterState[tag]}
|
|
962
1067
|
onChanged={(checked) => {
|
|
963
1068
|
tagFilterState[tag] = checked;
|
|
1069
|
+
dispatch({
|
|
1070
|
+
type: ActionType.UpdateTagFilterState,
|
|
1071
|
+
tagFilterState,
|
|
1072
|
+
});
|
|
964
1073
|
}}
|
|
965
1074
|
/>
|
|
966
1075
|
);
|
|
@@ -1023,19 +1132,25 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1023
1132
|
) && (
|
|
1024
1133
|
<TabBox title="Action Log Details">
|
|
1025
1134
|
{/* Action */}
|
|
1026
|
-
<ButtonInputGroup
|
|
1135
|
+
<ButtonInputGroup
|
|
1136
|
+
label="Action"
|
|
1137
|
+
className="mb-2"
|
|
1138
|
+
>
|
|
1027
1139
|
{
|
|
1028
1140
|
Object.keys(LogAction)
|
|
1029
1141
|
.map((action, i) => {
|
|
1030
1142
|
const description = genHumanReadableName(action);
|
|
1031
1143
|
return (
|
|
1032
1144
|
<CheckboxButton
|
|
1145
|
+
key={action}
|
|
1033
1146
|
id={`LogReviewer-action-${action}-checkbox`}
|
|
1034
1147
|
text={description}
|
|
1035
1148
|
ariaLabel={`include logs with action type "${description}" in results`}
|
|
1036
1149
|
noMarginOnRight={i === Object.keys(LogAction).length - 1}
|
|
1150
|
+
checked={actionErrorFilterState.action[action]}
|
|
1037
1151
|
onChanged={(checked) => {
|
|
1038
1152
|
actionErrorFilterState.action[action] = checked;
|
|
1153
|
+
console.log(actionErrorFilterState, action, checked);
|
|
1039
1154
|
dispatch({
|
|
1040
1155
|
type: ActionType.UpdateActionErrorFilterState,
|
|
1041
1156
|
actionErrorFilterState,
|
|
@@ -1048,17 +1163,27 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1048
1163
|
</ButtonInputGroup>
|
|
1049
1164
|
{/* Target */}
|
|
1050
1165
|
<ButtonInputGroup label="Target">
|
|
1166
|
+
{/* Nothing here */}
|
|
1167
|
+
{(Object.keys(LogMetadata.Target ?? {}).length === 0) && (
|
|
1168
|
+
<div>
|
|
1169
|
+
This app does not have any targets yet.
|
|
1170
|
+
</div>
|
|
1171
|
+
)}
|
|
1172
|
+
{/* List of targets */}
|
|
1051
1173
|
{
|
|
1052
1174
|
Object.keys(LogMetadata.Target ?? {})
|
|
1053
1175
|
.map((target, i) => {
|
|
1054
1176
|
const description = genHumanReadableName(target);
|
|
1055
1177
|
return (
|
|
1056
1178
|
<CheckboxButton
|
|
1179
|
+
key={target}
|
|
1057
1180
|
id={`LogReviewer-target-${target}-checkbox`}
|
|
1058
1181
|
text={description}
|
|
1059
1182
|
ariaLabel={`include logs with target "${description}" in results`}
|
|
1183
|
+
checked={actionErrorFilterState.target[target]}
|
|
1060
1184
|
onChanged={(checked) => {
|
|
1061
1185
|
actionErrorFilterState.target[target] = checked;
|
|
1186
|
+
console.log(actionErrorFilterState, target, checked);
|
|
1062
1187
|
dispatch({
|
|
1063
1188
|
type: ActionType.UpdateActionErrorFilterState,
|
|
1064
1189
|
actionErrorFilterState,
|
|
@@ -1108,7 +1233,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1108
1233
|
type="text"
|
|
1109
1234
|
className="form-control"
|
|
1110
1235
|
aria-label="query for error code"
|
|
1111
|
-
value={actionErrorFilterState.
|
|
1236
|
+
value={actionErrorFilterState.errorCode}
|
|
1112
1237
|
onChange={(e) => {
|
|
1113
1238
|
actionErrorFilterState.errorCode = (
|
|
1114
1239
|
(e.target.value)
|
|
@@ -1394,53 +1519,58 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1394
1519
|
/>
|
|
1395
1520
|
</ButtonInputGroup>
|
|
1396
1521
|
|
|
1397
|
-
{/*
|
|
1398
|
-
|
|
1399
|
-
<
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1522
|
+
{/* Server filters */}
|
|
1523
|
+
{advancedFilterState.source !== LogSource.Client && (
|
|
1524
|
+
<div className="mt-2">
|
|
1525
|
+
{/* Route path */}
|
|
1526
|
+
<div className="input-group mb-2">
|
|
1527
|
+
<span className="input-group-text">
|
|
1528
|
+
Server Route Path
|
|
1529
|
+
</span>
|
|
1530
|
+
<input
|
|
1531
|
+
type="text"
|
|
1532
|
+
className="form-control"
|
|
1533
|
+
aria-label="query for server route path"
|
|
1534
|
+
placeholder="e.g. /api/ttm/courses/12345"
|
|
1535
|
+
value={advancedFilterState.routePath}
|
|
1536
|
+
onChange={(e) => {
|
|
1537
|
+
advancedFilterState.courseName = (
|
|
1538
|
+
(e.target.value)
|
|
1539
|
+
.trim()
|
|
1540
|
+
);
|
|
1541
|
+
dispatch({
|
|
1542
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1543
|
+
advancedFilterState,
|
|
1544
|
+
});
|
|
1545
|
+
}}
|
|
1546
|
+
/>
|
|
1547
|
+
</div>
|
|
1420
1548
|
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1549
|
+
{/* Route template */}
|
|
1550
|
+
<div className="input-group mb-2">
|
|
1551
|
+
<span className="input-group-text">
|
|
1552
|
+
Server Route Template
|
|
1553
|
+
</span>
|
|
1554
|
+
<input
|
|
1555
|
+
type="text"
|
|
1556
|
+
className="form-control"
|
|
1557
|
+
aria-label="query for server route template"
|
|
1558
|
+
value={advancedFilterState.routeTemplate}
|
|
1559
|
+
placeholder="e.g. /api/ttm/courses/:courseId"
|
|
1560
|
+
onChange={(e) => {
|
|
1561
|
+
advancedFilterState.courseName = (
|
|
1562
|
+
(e.target.value)
|
|
1563
|
+
.trim()
|
|
1564
|
+
);
|
|
1565
|
+
dispatch({
|
|
1566
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
1567
|
+
advancedFilterState,
|
|
1568
|
+
});
|
|
1569
|
+
}}
|
|
1570
|
+
/>
|
|
1571
|
+
</div>
|
|
1572
|
+
</div>
|
|
1573
|
+
)}
|
|
1444
1574
|
</TabBox>
|
|
1445
1575
|
</>
|
|
1446
1576
|
);
|
|
@@ -1463,7 +1593,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1463
1593
|
// > Perform filters
|
|
1464
1594
|
const logs: Log[] = [];
|
|
1465
1595
|
Object.keys(logMap).forEach((year) => {
|
|
1466
|
-
Object.keys(logMap).forEach((month) => {
|
|
1596
|
+
Object.keys(logMap[year]).forEach((month) => {
|
|
1467
1597
|
logMap[year][month].forEach((log) => {
|
|
1468
1598
|
/* ----------- Date Filter ---------- */
|
|
1469
1599
|
|
|
@@ -1513,19 +1643,25 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1513
1643
|
contextFilterState[log.context] === false
|
|
1514
1644
|
// None of the subcontexts are selected
|
|
1515
1645
|
|| (
|
|
1516
|
-
|
|
1646
|
+
// Has subcontexts
|
|
1647
|
+
typeof contextFilterState[log.context] !== 'boolean'
|
|
1648
|
+
// None of the subcontexts are selected
|
|
1649
|
+
&& Object.values(contextFilterState[log.context] ?? {})
|
|
1517
1650
|
.every((isSelected) => {
|
|
1518
1651
|
return !isSelected;
|
|
1519
1652
|
})
|
|
1520
1653
|
)
|
|
1521
1654
|
) {
|
|
1655
|
+
console.log(log.context, contextFilterState);
|
|
1522
1656
|
return;
|
|
1523
1657
|
}
|
|
1524
1658
|
|
|
1525
1659
|
// Subcontext doesn't match
|
|
1526
1660
|
if (
|
|
1661
|
+
// Log context is not "uncategorized" (no point in further filters)
|
|
1662
|
+
log.context !== LogBuiltInMetadata.Context.Uncategorized
|
|
1527
1663
|
// Log has a subcontext
|
|
1528
|
-
log.subcontext
|
|
1664
|
+
&& log.subcontext
|
|
1529
1665
|
// Context has subcontexts
|
|
1530
1666
|
&& (
|
|
1531
1667
|
contextFilterState[log.context]
|
|
@@ -1542,14 +1678,16 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1542
1678
|
|
|
1543
1679
|
// No tags match
|
|
1544
1680
|
if (
|
|
1545
|
-
//
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
log.tags.every((tag) => {
|
|
1550
|
-
return !tagFilterState[tag];
|
|
1681
|
+
// At least one tag is required
|
|
1682
|
+
Object.values(tagFilterState)
|
|
1683
|
+
.filter((isSelected) => {
|
|
1684
|
+
return isSelected;
|
|
1551
1685
|
})
|
|
1552
|
-
|
|
1686
|
+
.length > 0
|
|
1687
|
+
// No tags match
|
|
1688
|
+
&& log.tags.every((tag) => {
|
|
1689
|
+
return !tagFilterState[tag];
|
|
1690
|
+
})
|
|
1553
1691
|
) {
|
|
1554
1692
|
return;
|
|
1555
1693
|
}
|
|
@@ -1731,7 +1869,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1731
1869
|
// Device info exists
|
|
1732
1870
|
&& log.device
|
|
1733
1871
|
// Mobile filter doesn't match
|
|
1734
|
-
&& (advancedFilterState.isMobile
|
|
1872
|
+
&& (advancedFilterState.isMobile !== log.device.isMobile)
|
|
1735
1873
|
) {
|
|
1736
1874
|
return;
|
|
1737
1875
|
}
|
|
@@ -1807,18 +1945,18 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1807
1945
|
type: ParamType.Int,
|
|
1808
1946
|
},
|
|
1809
1947
|
{
|
|
1810
|
-
title: 'Student',
|
|
1948
|
+
title: 'Student?',
|
|
1811
1949
|
param: 'isLearner',
|
|
1812
1950
|
type: ParamType.Boolean,
|
|
1813
1951
|
},
|
|
1814
1952
|
{
|
|
1815
|
-
title: 'Teaching Staff',
|
|
1953
|
+
title: 'Teaching Staff?',
|
|
1816
1954
|
param: 'isTTM',
|
|
1817
1955
|
type: ParamType.Boolean,
|
|
1818
1956
|
startsHidden: true,
|
|
1819
1957
|
},
|
|
1820
1958
|
{
|
|
1821
|
-
title: 'Admin',
|
|
1959
|
+
title: 'Admin?',
|
|
1822
1960
|
param: 'isAdmin',
|
|
1823
1961
|
type: ParamType.Boolean,
|
|
1824
1962
|
startsHidden: true,
|
|
@@ -1853,7 +1991,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1853
1991
|
startsHidden: true,
|
|
1854
1992
|
},
|
|
1855
1993
|
{
|
|
1856
|
-
title: 'Mobile',
|
|
1994
|
+
title: 'Mobile?',
|
|
1857
1995
|
param: 'device.isMobile',
|
|
1858
1996
|
type: ParamType.Boolean,
|
|
1859
1997
|
startsHidden: true,
|
|
@@ -1972,10 +2110,27 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1972
2110
|
},
|
|
1973
2111
|
];
|
|
1974
2112
|
|
|
2113
|
+
// Nothing to show notice
|
|
2114
|
+
const noLogsNotice = (
|
|
2115
|
+
logs.length === 0
|
|
2116
|
+
? (
|
|
2117
|
+
<div className="alert alert-warning text-center mt-2">
|
|
2118
|
+
<h4 className="m-1">
|
|
2119
|
+
No Logs to Show
|
|
2120
|
+
</h4>
|
|
2121
|
+
<div>
|
|
2122
|
+
Either your filters are too strict or no matching logs have been
|
|
2123
|
+
created yet.
|
|
2124
|
+
</div>
|
|
2125
|
+
</div>
|
|
2126
|
+
)
|
|
2127
|
+
: undefined
|
|
2128
|
+
);
|
|
2129
|
+
|
|
1975
2130
|
// Create intelliTable
|
|
1976
2131
|
const dataTable = (
|
|
1977
2132
|
<IntelliTable
|
|
1978
|
-
title="Matching Logs"
|
|
2133
|
+
title="Matching Logs:"
|
|
1979
2134
|
id="logs"
|
|
1980
2135
|
data={logs}
|
|
1981
2136
|
columns={columns}
|
|
@@ -1986,7 +2141,10 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1986
2141
|
body = (
|
|
1987
2142
|
<>
|
|
1988
2143
|
{filters}
|
|
1989
|
-
|
|
2144
|
+
<div className="mt-2">
|
|
2145
|
+
{dataTable}
|
|
2146
|
+
{noLogsNotice}
|
|
2147
|
+
</div>
|
|
1990
2148
|
</>
|
|
1991
2149
|
);
|
|
1992
2150
|
}
|
|
@@ -2001,26 +2159,22 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
2001
2159
|
<div className="LogReviewer-inner-container">
|
|
2002
2160
|
<div className="LogReviewer-header">
|
|
2003
2161
|
<div className="LogReviewer-header-title">
|
|
2004
|
-
<
|
|
2162
|
+
<h3 className="text-center m-0">
|
|
2005
2163
|
Log Review Dashboard
|
|
2006
|
-
</
|
|
2164
|
+
</h3>
|
|
2165
|
+
</div>
|
|
2166
|
+
<div style={{ width: 0 }}>
|
|
2167
|
+
<button
|
|
2168
|
+
type="button"
|
|
2169
|
+
className="LogReviewer-header-close-button btn btn-dark btn-lg pe-0"
|
|
2170
|
+
aria-label="close log reviewer panel"
|
|
2171
|
+
onClick={onClose}
|
|
2172
|
+
>
|
|
2173
|
+
<FontAwesomeIcon
|
|
2174
|
+
icon={faTimes}
|
|
2175
|
+
/>
|
|
2176
|
+
</button>
|
|
2007
2177
|
</div>
|
|
2008
|
-
<button
|
|
2009
|
-
type="button"
|
|
2010
|
-
className="LogReviewer-header-close-button btn btn-lg"
|
|
2011
|
-
aria-label="close log reviewer panel"
|
|
2012
|
-
onClick={onClose}
|
|
2013
|
-
style={{
|
|
2014
|
-
border: 0,
|
|
2015
|
-
backgroundColor: 'transparent',
|
|
2016
|
-
padding: 0,
|
|
2017
|
-
margin: 0,
|
|
2018
|
-
}}
|
|
2019
|
-
>
|
|
2020
|
-
<FontAwesomeIcon
|
|
2021
|
-
icon={faTimes}
|
|
2022
|
-
/>
|
|
2023
|
-
</button>
|
|
2024
2178
|
</div>
|
|
2025
2179
|
<div className="LogReviewer-contents">
|
|
2026
2180
|
{body}
|