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.
Files changed (32) hide show
  1. package/dist/cjs/index.js +388 -198
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/types/components/ButtonInputGroup.d.ts +1 -0
  4. package/dist/cjs/types/components/Drawer.d.ts +1 -0
  5. package/dist/cjs/types/components/ItemPicker/NestableItemList.d.ts +1 -0
  6. package/dist/cjs/types/components/ItemPicker/index.d.ts +1 -0
  7. package/dist/cjs/types/types/LogAction.d.ts +16 -16
  8. package/dist/cjs/types/types/LogBuiltInMetadata.d.ts +1 -1
  9. package/dist/esm/index.js +388 -198
  10. package/dist/esm/index.js.map +1 -1
  11. package/dist/esm/types/components/ButtonInputGroup.d.ts +1 -0
  12. package/dist/esm/types/components/Drawer.d.ts +1 -0
  13. package/dist/esm/types/components/ItemPicker/NestableItemList.d.ts +1 -0
  14. package/dist/esm/types/components/ItemPicker/index.d.ts +1 -0
  15. package/dist/esm/types/types/LogAction.d.ts +16 -16
  16. package/dist/esm/types/types/LogBuiltInMetadata.d.ts +1 -1
  17. package/dist/index.d.ts +20 -17
  18. package/package.json +1 -1
  19. package/src/components/ButtonInputGroup.tsx +5 -2
  20. package/src/components/CSVDownloadButton.tsx +1 -1
  21. package/src/components/Drawer.tsx +9 -1
  22. package/src/components/IntelliTable.tsx +80 -21
  23. package/src/components/ItemPicker/NestableItemList.tsx +32 -14
  24. package/src/components/ItemPicker/index.tsx +4 -0
  25. package/src/components/LogReviewer.tsx +300 -146
  26. package/src/components/SimpleDateChooser.tsx +20 -7
  27. package/src/helpers/genCSV.ts +21 -4
  28. package/src/helpers/genRouteHandler.ts +2 -2
  29. package/src/helpers/logClientEvent.tsx +6 -1
  30. package/src/server/initServer.ts +11 -4
  31. package/src/types/LogAction.ts +16 -16
  32. 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 += chars;
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: action.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[contextValue._] = {};
580
+ initContextFilterState[context] = {};
509
581
  Object.values((LogMetadata.Context ?? {})[context]).forEach((subcontext) => {
510
- const subcontextValue = contextValue[subcontext];
511
- (initContextFilterState[contextValue._] as { [k: string]: boolean })[subcontextValue] = true;
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.values(LogMetadata.Tag ?? {}).forEach((tagValue) => {
519
- initTagFilterState[tagValue] = true;
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 <= newDateFilterState.endDate.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
- toLoad.push({
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 d-flex align-items-center justify-content-center">
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
- <button
775
- type="button"
776
- id="LogReviewer-toggle-tag-filter-drawer"
777
- className={`btn btn-${FilterDrawer.Tag === expandedFilterDrawer} me-2`}
778
- aria-label="toggle tag filter drawer"
779
- onClick={() => {
780
- dispatch({
781
- type: ActionType.ToggleFilterDrawer,
782
- filterDrawer: FilterDrawer.Tag,
783
- });
784
- }}
785
- >
786
- <FontAwesomeIcon
787
- icon={faTag}
788
- className="me-2"
789
- />
790
- Tag
791
- </button>
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
- dispatch({
848
- type: ActionType.UpdateDateFilterState,
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
- dispatch({
867
- type: ActionType.UpdateDateFilterState,
868
- dateFilterState: {
869
- ...dateFilterState,
870
- endDate: { month, day, year },
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: `${context}-${subcontext}`,
1002
+ id: subcontext,
903
1003
  name: genHumanReadableName(subcontext),
904
1004
  isGroup: false,
905
- checked: !!value[subcontext],
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
- (contextFilterState as any)[pickableItem.id][subcontextItem.id] = (
931
- (subcontextItem as any).checked
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(tagFilterState)
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={`include logs tagged with "${description}" in results`}
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 label="Action">
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.errorMessage}
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
- {/* Route path */}
1398
- <div className="input-group mb-2">
1399
- <span className="input-group-text">
1400
- Server Route Path
1401
- </span>
1402
- <input
1403
- type="text"
1404
- className="form-control"
1405
- aria-label="query for server route path"
1406
- placeholder="e.g. /api/ttm/courses/12345"
1407
- value={advancedFilterState.routePath}
1408
- onChange={(e) => {
1409
- advancedFilterState.courseName = (
1410
- (e.target.value)
1411
- .trim()
1412
- );
1413
- dispatch({
1414
- type: ActionType.UpdateAdvancedFilterState,
1415
- advancedFilterState,
1416
- });
1417
- }}
1418
- />
1419
- </div>
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
- {/* Route template */}
1422
- <div className="input-group mb-2">
1423
- <span className="input-group-text">
1424
- Server Route Template
1425
- </span>
1426
- <input
1427
- type="text"
1428
- className="form-control"
1429
- aria-label="query for server route template"
1430
- value={advancedFilterState.routeTemplate}
1431
- placeholder="e.g. /api/ttm/courses/:courseId"
1432
- onChange={(e) => {
1433
- advancedFilterState.courseName = (
1434
- (e.target.value)
1435
- .trim()
1436
- );
1437
- dispatch({
1438
- type: ActionType.UpdateAdvancedFilterState,
1439
- advancedFilterState,
1440
- });
1441
- }}
1442
- />
1443
- </div>
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
- Object.values(contextFilterState[log.context] ?? {})
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
- // Log has at least one tag
1546
- log.tags.length > 0
1547
- // No tags match
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 === log.device.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
- {dataTable}
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
- <h1 className="m-0">
2162
+ <h3 className="text-center m-0">
2005
2163
  Log Review Dashboard
2006
- </h1>
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}