dce-reactkit 3.2.2-beta.3 → 3.2.2-beta.31

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 CHANGED
@@ -519,17 +519,29 @@ const LOG_ROUTE_PATH = `${ROUTE_PATH_PREFIX}/log`;
519
519
  const LogBuiltInMetadata = {
520
520
  // Contexts
521
521
  Context: {
522
- Uncategorized: 'n/a',
523
- ServerRenderedErrorPage: '_server-rendered-error-page',
524
- ServerEndpointError: '_server-endpoint-error',
525
- ClientFatalError: '_client-fatal-error',
522
+ Uncategorized: 'Uncategorized',
523
+ ServerRenderedErrorPage: 'ServerRenderedErrorPage',
524
+ ServerEndpointError: 'ServerEndpointError',
525
+ ClientFatalError: 'ClientFatalError',
526
526
  },
527
527
  // Targets
528
528
  Target: {
529
- NoSpecificTarget: 'n/a',
529
+ NoTarget: 'NoTarget',
530
530
  },
531
531
  };
532
532
 
533
+ /**
534
+ * Allowed log levels
535
+ * @author Gabe Abrams
536
+ */
537
+ var LogLevel;
538
+ (function (LogLevel) {
539
+ LogLevel["Warn"] = "Warn";
540
+ LogLevel["Info"] = "Info";
541
+ LogLevel["Debug"] = "Debug";
542
+ })(LogLevel || (LogLevel = {}));
543
+ var LogLevel$1 = LogLevel;
544
+
533
545
  // Keep track of whether or not session expiry has already been handled
534
546
  let sessionAlreadyExpired = false;
535
547
  /*------------------------------------------------------------------------*/
@@ -648,7 +660,7 @@ const visitServerEndpoint = (opts) => __awaiter(void 0, void 0, void 0, function
648
660
  * @author Gabe Abrams
649
661
  */
650
662
  const logClientEvent = (opts) => __awaiter(void 0, void 0, void 0, function* () {
651
- var _a, _b, _c, _d, _e, _f;
663
+ var _a, _b, _c, _d, _e, _f, _g;
652
664
  return visitServerEndpoint({
653
665
  path: LOG_ROUTE_PATH,
654
666
  method: 'POST',
@@ -657,8 +669,9 @@ const logClientEvent = (opts) => __awaiter(void 0, void 0, void 0, function* ()
657
669
  ? opts.context
658
670
  : ((_b = ((_a = opts.context) !== null && _a !== void 0 ? _a : {})._) !== null && _b !== void 0 ? _b : LogBuiltInMetadata.Context.Uncategorized)),
659
671
  subcontext: ((_c = opts.subcontext) !== null && _c !== void 0 ? _c : LogBuiltInMetadata.Context.Uncategorized),
660
- tags: JSON.stringify((_d = opts.tags) !== null && _d !== void 0 ? _d : []),
661
- metadata: JSON.stringify((_e = opts.metadata) !== null && _e !== void 0 ? _e : {}),
672
+ level: ((_d = opts.level) !== null && _d !== void 0 ? _d : LogLevel$1.Info),
673
+ tags: JSON.stringify((_e = opts.tags) !== null && _e !== void 0 ? _e : []),
674
+ metadata: JSON.stringify((_f = opts.metadata) !== null && _f !== void 0 ? _f : {}),
662
675
  errorMessage: (opts.error
663
676
  ? opts.error.message
664
677
  : undefined),
@@ -669,7 +682,7 @@ const logClientEvent = (opts) => __awaiter(void 0, void 0, void 0, function* ()
669
682
  ? opts.error.stack
670
683
  : undefined),
671
684
  target: (opts.action
672
- ? ((_f = opts.target) !== null && _f !== void 0 ? _f : LogBuiltInMetadata.Target.NoSpecificTarget)
685
+ ? ((_g = opts.target) !== null && _g !== void 0 ? _g : LogBuiltInMetadata.Target.NoTarget)
673
686
  : undefined),
674
687
  action: (opts.action
675
688
  ? opts.action
@@ -1175,14 +1188,14 @@ const ButtonInputGroup = (props) => {
1175
1188
  /*------------------------------------------------------------------------*/
1176
1189
  /* -------------- Props ------------- */
1177
1190
  // Destructure all props
1178
- const { label, minLabelWidth, children, } = props;
1191
+ const { label, minLabelWidth, children, className, } = props;
1179
1192
  /*------------------------------------------------------------------------*/
1180
1193
  /* Render */
1181
1194
  /*------------------------------------------------------------------------*/
1182
1195
  /*----------------------------------------*/
1183
1196
  /* Main UI */
1184
1197
  /*----------------------------------------*/
1185
- return (React__default["default"].createElement("div", { className: "input-group" },
1198
+ return (React__default["default"].createElement("div", { className: `input-group ${className !== null && className !== void 0 ? className : ''}` },
1186
1199
  React__default["default"].createElement("div", { className: "input-group-prepend d-flex w-100" },
1187
1200
  React__default["default"].createElement("span", { className: "input-group-text", style: {
1188
1201
  minWidth: (minLabelWidth !== null && minLabelWidth !== void 0 ? minLabelWidth : undefined),
@@ -1352,12 +1365,25 @@ const SimpleDateChooser = (props) => {
1352
1365
  // Figure out which days are allowed
1353
1366
  const days = [];
1354
1367
  const numDaysInMonth = (new Date(year, month, 0)).getDate();
1355
- const firstDay = (month === today.month
1356
- ? today.day // Current month: start at current date
1357
- : 1 // Future month: start at beginning of month
1358
- );
1359
- for (let day = firstDay; day <= numDaysInMonth; day++) {
1360
- days.push(day);
1368
+ if (chooseFromPast) {
1369
+ // Past selection
1370
+ const numDaysToAdd = ((month === today.month)
1371
+ ? today.day // Current month, only add up to today
1372
+ : numDaysInMonth // Past month, add all days
1373
+ );
1374
+ for (let day = 1; day <= numDaysToAdd; day++) {
1375
+ days.push(day);
1376
+ }
1377
+ }
1378
+ else {
1379
+ // Future selection: add all remaining days of the month
1380
+ const firstDay = (month === today.month
1381
+ ? today.day // Current month: start at current date
1382
+ : 1 // Future month: start at beginning of month
1383
+ );
1384
+ for (let day = firstDay; day <= numDaysInMonth; day++) {
1385
+ days.push(day);
1386
+ }
1361
1387
  }
1362
1388
  choices.push({
1363
1389
  choiceName: `${monthName} ${year}`,
@@ -1898,24 +1924,25 @@ const CopiableBox = (props) => {
1898
1924
  /**
1899
1925
  * Reusable nested item picker
1900
1926
  * @author Yuen Ler Chow
1927
+ * @author Gabe Abrams
1901
1928
  */
1902
1929
  /* ------------- Actions ------------ */
1903
1930
  // Types of actions
1904
1931
  var ActionType$2;
1905
1932
  (function (ActionType) {
1906
- // Toggle whether the children are being shown
1907
- ActionType["ToggleItems"] = "toggle-items";
1933
+ // Toggle whether a child are being shown
1934
+ ActionType["ToggleChild"] = "toggle-child";
1908
1935
  })(ActionType$2 || (ActionType$2 = {}));
1909
1936
  /**
1910
1937
  * Reducer that executes actions
1911
- * @author Yuen Ler Chow
1938
+ * @author Gabe Abrams
1912
1939
  * @param state current state
1913
1940
  * @param action action to execute
1914
1941
  */
1915
1942
  const reducer$2 = (state, action) => {
1916
1943
  switch (action.type) {
1917
- case ActionType$2.ToggleItems: {
1918
- return { isShowingItems: !state.isShowingItems };
1944
+ case ActionType$2.ToggleChild: {
1945
+ return Object.assign(Object.assign({}, state), { childExpanded: Object.assign(Object.assign({}, state.childExpanded), { [String(action.id)]: !state.childExpanded[String(action.id)] }) });
1919
1946
  }
1920
1947
  default: {
1921
1948
  return state;
@@ -1933,14 +1960,19 @@ const NestableItemList = (props) => {
1933
1960
  // Destructure all props
1934
1961
  const { items, onChanged, } = props;
1935
1962
  /* -------------- State ------------- */
1963
+ // Create initial map of child expanded booleans
1964
+ const initChildExpanded = {};
1965
+ items.forEach((item) => {
1966
+ initChildExpanded[String(item.id)] = false;
1967
+ });
1936
1968
  // Initial state
1937
1969
  const initialState = {
1938
- isShowingItems: false,
1970
+ childExpanded: initChildExpanded,
1939
1971
  };
1940
1972
  // Initialize state
1941
1973
  const [state, dispatch] = React.useReducer(reducer$2, initialState);
1942
1974
  // Destructure common state
1943
- const { isShowingItems, } = state;
1975
+ const { childExpanded, } = state;
1944
1976
  /*------------------------------------------------------------------------*/
1945
1977
  /* Component Functions */
1946
1978
  /*------------------------------------------------------------------------*/
@@ -2028,14 +2060,15 @@ const NestableItemList = (props) => {
2028
2060
  backgroundColor: 'transparent',
2029
2061
  }, type: "button", onClick: () => {
2030
2062
  dispatch({
2031
- type: ActionType$2.ToggleItems,
2063
+ type: ActionType$2.ToggleChild,
2064
+ id: item.id,
2032
2065
  });
2033
- }, "aria-label": `${isShowingItems ? 'Hide' : 'Show'} items in ${item.name}` },
2034
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: isShowingItems ? freeSolidSvgIcons.faChevronDown : freeSolidSvgIcons.faChevronRight })))),
2066
+ }, "aria-label": `${childExpanded[item.id] ? 'Hide' : 'Show'} items in ${item.name}` },
2067
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: childExpanded[item.id] ? freeSolidSvgIcons.faChevronDown : freeSolidSvgIcons.faChevronRight })))),
2035
2068
  React__default["default"].createElement(CheckboxButton, { className: `NestableItemList-CheckboxButton-${item.id}`, text: item.name, checked: item.isGroup ? allChecked(item.children) : item.checked, dashed: item.isGroup ? !noneChecked(item.children) : false, onChanged: (checked) => {
2036
2069
  onChanged(changeChecked(item.id, checked, items));
2037
2070
  }, ariaLabel: `Select ${item.name}`, checkedVariant: Variant$1.Light }),
2038
- item.isGroup && isShowingItems && (React__default["default"].createElement("div", { className: "NestableItemList-children-container", style: {
2071
+ (item.isGroup && childExpanded[item.id]) && (React__default["default"].createElement("div", { className: "NestableItemList-children-container", style: {
2039
2072
  paddingLeft: '2.2rem',
2040
2073
  } },
2041
2074
  React__default["default"].createElement(NestableItemList, { items: item.children, onChanged: (updatedItems) => {
@@ -2057,7 +2090,7 @@ const ItemPicker = (props) => {
2057
2090
  /*------------------------------------------------------------------------*/
2058
2091
  /* -------------- Props ------------- */
2059
2092
  // Destructure all props
2060
- const { title, items, onChanged, } = props;
2093
+ const { title, items, onChanged, noBottomMargin, } = props;
2061
2094
  /*------------------------------------------------------------------------*/
2062
2095
  /* Component Functions */
2063
2096
  /*------------------------------------------------------------------------*/
@@ -2067,7 +2100,7 @@ const ItemPicker = (props) => {
2067
2100
  /*----------------------------------------*/
2068
2101
  /* Main UI */
2069
2102
  /*----------------------------------------*/
2070
- return (React__default["default"].createElement(TabBox, { title: title },
2103
+ return (React__default["default"].createElement(TabBox, { title: title, noBottomMargin: noBottomMargin },
2071
2104
  React__default["default"].createElement("div", { style: { overflowX: 'auto' } },
2072
2105
  React__default["default"].createElement(NestableItemList, { items: items, onChanged: onChanged }))));
2073
2106
  };
@@ -2384,9 +2417,9 @@ const IntelliTable = (props) => {
2384
2417
  // Create the cell UI
2385
2418
  return (React__default["default"].createElement("th", { key: column.param, scope: "col", id: `IntelliTable-${id}-header-${column.param}` },
2386
2419
  React__default["default"].createElement("div", { className: "d-flex align-items-center justify-content-center flex-row h-100" },
2387
- React__default["default"].createElement("h4", { className: "m-0" }, column.title),
2420
+ React__default["default"].createElement("span", { className: "text-nowrap" }, column.title),
2388
2421
  React__default["default"].createElement("div", null,
2389
- React__default["default"].createElement("button", { type: "button", className: "btn btn-light", "aria-label": sortButtonAriaLabel, onClick: () => {
2422
+ React__default["default"].createElement("button", { type: "button", className: "btn btn-light btn-sm ms-1", "aria-label": sortButtonAriaLabel, onClick: () => {
2390
2423
  dispatch({
2391
2424
  type: ActionType$1.ToggleSortColumn,
2392
2425
  param: column.param,
@@ -2494,7 +2527,7 @@ const IntelliTable = (props) => {
2494
2527
  }
2495
2528
  else if (column.type === ParamType$1.String) {
2496
2529
  fullValue = String(value).trim();
2497
- const noValue = (value.trim().length) === 0;
2530
+ const noValue = (String(fullValue).trim().length === 0);
2498
2531
  visibleValue = (noValue
2499
2532
  ? (React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faMinus }))
2500
2533
  : fullValue);
@@ -2534,7 +2567,7 @@ const IntelliTable = (props) => {
2534
2567
  React__default["default"].createElement("h3", { className: "m-0" }, title),
2535
2568
  React__default["default"].createElement("div", { className: "flex-grow-1 text-end" },
2536
2569
  React__default["default"].createElement(CSVDownloadButton, { "aria-label": `download data as csv for ${title}`, id: `IntelliTable-${id}-download-as-csv`, filename: `${title}.csv`, csv: csv }),
2537
- React__default["default"].createElement("button", { type: "button", className: "btn btn-secondary", "aria-label": `show panel for customizing which columns show in table ${title}`, id: `IntelliTable-${id}-show-column-customization-modal`, onClick: () => {
2570
+ React__default["default"].createElement("button", { type: "button", className: "btn btn-secondary ms-2", "aria-label": `show panel for customizing which columns show in table ${title}`, id: `IntelliTable-${id}-show-column-customization-modal`, onClick: () => {
2538
2571
  dispatch({
2539
2572
  type: ActionType$1.ToggleColVisCusModalVisibility,
2540
2573
  });
@@ -2598,6 +2631,11 @@ const style = `
2598
2631
  border: 0.05rem solid black;
2599
2632
  border-radius: 0.5rem;
2600
2633
  overflow: hidden;
2634
+ padding: 0.7rem;
2635
+
2636
+ /* Solid background */
2637
+ background-color: white;
2638
+ color: black;
2601
2639
 
2602
2640
  /* Place contents in flex column */
2603
2641
  flex-direction: column;
@@ -2624,6 +2662,27 @@ const style = `
2624
2662
  /* Vertical scroll */
2625
2663
  overflow-y: auto;
2626
2664
  }
2665
+
2666
+ .LogReviewer-header-close-button {
2667
+ border: 0 !important;
2668
+ background-color: transparent !important;
2669
+ padding-top: 0 !important;
2670
+ padding-bottom: 0 !important;
2671
+ padding-right: 1em !important;
2672
+ margin: 0 !important;
2673
+ color: #444 !important;
2674
+
2675
+ right: 0 !important;
2676
+ position: absolute !important;
2677
+ }
2678
+ .LogReviewer-header-close-button:hover {
2679
+ border: 0 !important;
2680
+ background-color: transparent !important;
2681
+ padding-top: 0 !important;
2682
+ padding-bottom: 0 !important;
2683
+ margin: 0 !important;
2684
+ color: #000 !important;
2685
+ }
2627
2686
  `;
2628
2687
  /*------------------------------------------------------------------------*/
2629
2688
  /* Static Functions */
@@ -2643,7 +2702,7 @@ const genHumanReadableName = (machineReadableName) => {
2643
2702
  // Uppercase! Add a space before
2644
2703
  humanReadableName += ' ';
2645
2704
  }
2646
- humanReadableName += chars;
2705
+ humanReadableName += char;
2647
2706
  });
2648
2707
  // Trim and return
2649
2708
  return humanReadableName.trim();
@@ -2705,7 +2764,19 @@ const reducer = (state, action) => {
2705
2764
  return Object.assign(Object.assign({}, state), { contextFilterState: action.contextFilterState });
2706
2765
  }
2707
2766
  case ActionType.UpdateTagFilterState: {
2708
- return Object.assign(Object.assign({}, state), { tagFilterState: action.tagFilterState });
2767
+ const { tagFilterState } = action;
2768
+ // Select all if every tag is deselected
2769
+ const numTagsSelected = (Object.values(tagFilterState)
2770
+ .filter((isSelected) => {
2771
+ return isSelected;
2772
+ })
2773
+ .length);
2774
+ if (numTagsSelected === 0) {
2775
+ Object.keys(tagFilterState).forEach((tag) => {
2776
+ tagFilterState[tag] = true;
2777
+ });
2778
+ }
2779
+ return Object.assign(Object.assign({}, state), { tagFilterState });
2709
2780
  }
2710
2781
  case ActionType.UpdateActionErrorFilterState: {
2711
2782
  return Object.assign(Object.assign({}, state), { actionErrorFilterState: action.actionErrorFilterState });
@@ -2725,10 +2796,35 @@ const LogReviewer = (props) => {
2725
2796
  /*------------------------------------------------------------------------*/
2726
2797
  /* Setup */
2727
2798
  /*------------------------------------------------------------------------*/
2728
- var _a, _b, _c, _d, _e;
2799
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
2729
2800
  /* -------------- Props ------------- */
2730
2801
  // Destructure props
2731
2802
  const { LogMetadata, onClose, } = props;
2803
+ // Add built-in LogMetadata
2804
+ // > Add "uncategorized" subcontext to each context
2805
+ Object.keys((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {}).forEach((context) => {
2806
+ if (
2807
+ // Context exists
2808
+ LogMetadata.Context
2809
+ // Context has children already
2810
+ && typeof LogMetadata.Context[context] !== 'string') {
2811
+ LogMetadata.Context[context][LogBuiltInMetadata.Context.Uncategorized] = (LogBuiltInMetadata.Context.Uncategorized);
2812
+ }
2813
+ });
2814
+ // > Add built-in contexts
2815
+ LogMetadata.Context = ((_b = LogMetadata.Context) !== null && _b !== void 0 ? _b : {});
2816
+ Object.keys(LogBuiltInMetadata.Context).forEach((context) => {
2817
+ if (LogMetadata.Context) {
2818
+ LogMetadata.Context[context] = context;
2819
+ }
2820
+ });
2821
+ // > Add built-in targets
2822
+ LogMetadata.Target = ((_c = LogMetadata.Target) !== null && _c !== void 0 ? _c : {});
2823
+ Object.keys(LogBuiltInMetadata.Target).forEach((target) => {
2824
+ if (LogMetadata.Target) {
2825
+ LogMetadata.Target[target] = target;
2826
+ }
2827
+ });
2732
2828
  /* -------------- State ------------- */
2733
2829
  // Create initial date filter state
2734
2830
  const today = getTimeInfoInET();
@@ -2748,26 +2844,30 @@ const LogReviewer = (props) => {
2748
2844
  };
2749
2845
  // Create initial context filter state
2750
2846
  const initContextFilterState = {};
2751
- Object.keys((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {}).forEach((context) => {
2847
+ Object.keys((_d = LogMetadata.Context) !== null && _d !== void 0 ? _d : {}).forEach((context) => {
2752
2848
  var _a, _b;
2753
2849
  const contextValue = ((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {})[context];
2754
2850
  if (typeof contextValue === 'string') {
2755
- // Case: no subcontexts
2851
+ // Case: no subcontexts, init as checked
2756
2852
  initContextFilterState[contextValue] = true;
2757
2853
  }
2758
2854
  else {
2759
2855
  // Case: subcontexts exist
2760
- initContextFilterState[contextValue._] = {};
2856
+ initContextFilterState[context] = {};
2761
2857
  Object.values(((_b = LogMetadata.Context) !== null && _b !== void 0 ? _b : {})[context]).forEach((subcontext) => {
2762
- const subcontextValue = contextValue[subcontext];
2763
- initContextFilterState[contextValue._][subcontextValue] = true;
2858
+ // Skip self ("_")
2859
+ if (subcontext === '_') {
2860
+ return;
2861
+ }
2862
+ // Initialize as checked
2863
+ initContextFilterState[context][subcontext] = true;
2764
2864
  });
2765
2865
  }
2766
2866
  });
2767
2867
  // Create initial tag filter state
2768
2868
  const initTagFilterState = {};
2769
- Object.values((_b = LogMetadata.Tag) !== null && _b !== void 0 ? _b : {}).forEach((tagValue) => {
2770
- initTagFilterState[tagValue] = true;
2869
+ Object.values((_e = LogMetadata.Tag) !== null && _e !== void 0 ? _e : {}).forEach((tagValue) => {
2870
+ initTagFilterState[tagValue] = false;
2771
2871
  });
2772
2872
  // Create advanced filter state
2773
2873
  const initAdvancedFilterState = {
@@ -2793,12 +2893,16 @@ const LogReviewer = (props) => {
2793
2893
  target: {},
2794
2894
  action: {},
2795
2895
  };
2796
- Object.values((_c = LogMetadata.Target) !== null && _c !== void 0 ? _c : {}).forEach((target) => {
2896
+ Object.values((_f = LogMetadata.Target) !== null && _f !== void 0 ? _f : {}).forEach((target) => {
2797
2897
  initActionErrorFilterState.target[target] = true;
2798
2898
  });
2799
2899
  Object.values(LogAction$1).forEach((action) => {
2800
2900
  initActionErrorFilterState.action[action] = true;
2801
2901
  });
2902
+ // Add built-in targets
2903
+ Object.values(LogBuiltInMetadata.Target).forEach((target) => {
2904
+ initActionErrorFilterState.target[target] = true;
2905
+ });
2802
2906
  // Initial state
2803
2907
  const initialState = {
2804
2908
  loading: true,
@@ -2832,15 +2936,18 @@ const LogReviewer = (props) => {
2832
2936
  let month = newDateFilterState.startDate.month;
2833
2937
  while (
2834
2938
  // Earlier year
2835
- (year <= newDateFilterState.endDate.year)
2939
+ (year < newDateFilterState.endDate.year)
2836
2940
  // Current year but included month
2837
2941
  || (year === newDateFilterState.endDate.year
2838
2942
  && month <= newDateFilterState.endDate.month)) {
2839
- // Add to list
2840
- toLoad.push({
2841
- year,
2842
- month,
2843
- });
2943
+ // Add to list if not already loaded
2944
+ if (!logMap[year]
2945
+ || !logMap[year][month]) {
2946
+ toLoad.push({
2947
+ year,
2948
+ month,
2949
+ });
2950
+ }
2844
2951
  // Increment
2845
2952
  month += 1;
2846
2953
  if (month > 12) {
@@ -2864,6 +2971,7 @@ const LogReviewer = (props) => {
2864
2971
  });
2865
2972
  // Check which year/month combos we need to load
2866
2973
  const toLoad = listMonthsToLoad(newDateFilterState);
2974
+ console.log('Filter update:', newDateFilterState, toLoad, logMap);
2867
2975
  // If nothing to load, finished
2868
2976
  if (toLoad.length === 0) {
2869
2977
  return;
@@ -2928,10 +3036,10 @@ const LogReviewer = (props) => {
2928
3036
  /* Filters */
2929
3037
  /*----------------------------------------*/
2930
3038
  // Filter toggle
2931
- const filterToggles = (React__default["default"].createElement("div", { className: "LogReviewer-filter-toggles d-flex align-items-center justify-content-center" },
3039
+ const filterToggles = (React__default["default"].createElement("div", { className: "LogReviewer-filter-toggles" },
2932
3040
  React__default["default"].createElement("h3", { className: "m-0" }, "Filters:"),
2933
- React__default["default"].createElement("div", { className: "LogReviewer-filter-toggle-buttons" },
2934
- React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-date-filter-drawer", className: `btn btn-${FilterDrawer.Date === expandedFilterDrawer} me-2`, "aria-label": "toggle date filter drawer", onClick: () => {
3041
+ React__default["default"].createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
3042
+ React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-date-filter-drawer", className: `btn btn-${FilterDrawer.Date === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle date filter drawer", onClick: () => {
2935
3043
  dispatch({
2936
3044
  type: ActionType.ToggleFilterDrawer,
2937
3045
  filterDrawer: FilterDrawer.Date,
@@ -2939,7 +3047,7 @@ const LogReviewer = (props) => {
2939
3047
  } },
2940
3048
  React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCalendar, className: "me-2" }),
2941
3049
  "Date"),
2942
- React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-context-filter-drawer", className: `btn btn-${FilterDrawer.Context === expandedFilterDrawer} me-2`, "aria-label": "toggle context filter drawer", onClick: () => {
3050
+ React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-context-filter-drawer", className: `btn btn-${FilterDrawer.Context === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle context filter drawer", onClick: () => {
2943
3051
  dispatch({
2944
3052
  type: ActionType.ToggleFilterDrawer,
2945
3053
  filterDrawer: FilterDrawer.Context,
@@ -2947,15 +3055,15 @@ const LogReviewer = (props) => {
2947
3055
  } },
2948
3056
  React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "me-2" }),
2949
3057
  "Context"),
2950
- React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-tag-filter-drawer", className: `btn btn-${FilterDrawer.Tag === expandedFilterDrawer} me-2`, "aria-label": "toggle tag filter drawer", onClick: () => {
3058
+ (LogMetadata.Tag && Object.keys(LogMetadata.Tag).length > 0) && (React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-tag-filter-drawer", className: `btn btn-${FilterDrawer.Tag === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle tag filter drawer", onClick: () => {
2951
3059
  dispatch({
2952
3060
  type: ActionType.ToggleFilterDrawer,
2953
3061
  filterDrawer: FilterDrawer.Tag,
2954
3062
  });
2955
3063
  } },
2956
3064
  React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTag, className: "me-2" }),
2957
- "Tag"),
2958
- React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-action-filter-drawer", className: `btn btn-${FilterDrawer.Action === expandedFilterDrawer} me-2`, "aria-label": "toggle action and error filter drawer", onClick: () => {
3065
+ "Tag")),
3066
+ React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-action-filter-drawer", className: `btn btn-${FilterDrawer.Action === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle action and error filter drawer", onClick: () => {
2959
3067
  dispatch({
2960
3068
  type: ActionType.ToggleFilterDrawer,
2961
3069
  filterDrawer: FilterDrawer.Action,
@@ -2963,7 +3071,7 @@ const LogReviewer = (props) => {
2963
3071
  } },
2964
3072
  React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faHammer, className: "me-2" }),
2965
3073
  "Action"),
2966
- React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-advanced-filter-drawer", className: `btn btn-${FilterDrawer.Advanced === expandedFilterDrawer}`, "aria-label": "toggle advanced filter drawer", onClick: () => {
3074
+ React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-advanced-filter-drawer", className: `btn btn-${FilterDrawer.Advanced === expandedFilterDrawer ? 'warning' : 'light'}`, "aria-label": "toggle advanced filter drawer", onClick: () => {
2967
3075
  dispatch({
2968
3076
  type: ActionType.ToggleFilterDrawer,
2969
3077
  filterDrawer: FilterDrawer.Advanced,
@@ -2976,25 +3084,29 @@ const LogReviewer = (props) => {
2976
3084
  if (expandedFilterDrawer) {
2977
3085
  if (expandedFilterDrawer === FilterDrawer.Date) {
2978
3086
  filterDrawer = (React__default["default"].createElement(TabBox, { title: "Date" },
2979
- React__default["default"].createElement(SimpleDateChooser, { ariaLabel: "filter start date", name: "filter-start-date", year: dateFilterState.startDate.year, month: dateFilterState.startDate.month, day: dateFilterState.startDate.day, onChange: (month, day, year) => {
2980
- dispatch({
2981
- type: ActionType.UpdateDateFilterState,
2982
- dateFilterState: Object.assign(Object.assign({}, dateFilterState), { startDate: { month, day, year } }),
2983
- });
3087
+ React__default["default"].createElement(SimpleDateChooser, { ariaLabel: "filter start date", name: "filter-start-date", year: dateFilterState.startDate.year, month: dateFilterState.startDate.month, day: dateFilterState.startDate.day, chooseFromPast: true, numMonthsToShow: 12, onChange: (month, day, year) => {
3088
+ dateFilterState.startDate = { month, day, year };
3089
+ handleDateRangeUpdated(dateFilterState);
2984
3090
  } }),
2985
3091
  ' ',
2986
3092
  "to",
2987
3093
  ' ',
2988
- React__default["default"].createElement(SimpleDateChooser, { ariaLabel: "filter end date", name: "filter-end-date", year: dateFilterState.endDate.year, month: dateFilterState.endDate.month, day: dateFilterState.endDate.day, onChange: (month, day, year) => {
2989
- dispatch({
2990
- type: ActionType.UpdateDateFilterState,
2991
- dateFilterState: Object.assign(Object.assign({}, dateFilterState), { endDate: { month, day, year } }),
2992
- });
3094
+ React__default["default"].createElement(SimpleDateChooser, { ariaLabel: "filter end date", name: "filter-end-date", year: dateFilterState.endDate.year, month: dateFilterState.endDate.month, day: dateFilterState.endDate.day, chooseFromPast: true, numMonthsToShow: 12, onChange: (month, day, year) => {
3095
+ if (year < dateFilterState.startDate.year
3096
+ || (year === dateFilterState.startDate.year
3097
+ && month < dateFilterState.startDate.month)
3098
+ || (year === dateFilterState.startDate.year
3099
+ && month === dateFilterState.startDate.month
3100
+ && day < dateFilterState.startDate.day)) {
3101
+ return alert$1('Invalid Start Date', 'The start date cannot be before the end date.');
3102
+ }
3103
+ dateFilterState.endDate = { month, day, year };
3104
+ handleDateRangeUpdated(dateFilterState);
2993
3105
  } })));
2994
3106
  }
2995
3107
  else if (expandedFilterDrawer === FilterDrawer.Context) {
2996
3108
  // Create item picker items
2997
- const pickableItems = (Object.keys((_d = LogMetadata.Context) !== null && _d !== void 0 ? _d : {})
3109
+ const pickableItems = (Object.keys((_g = LogMetadata.Context) !== null && _g !== void 0 ? _g : {})
2998
3110
  .map((context) => {
2999
3111
  var _a;
3000
3112
  const value = ((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {})[context];
@@ -3015,15 +3127,15 @@ const LogReviewer = (props) => {
3015
3127
  })
3016
3128
  .map((subcontext) => {
3017
3129
  return {
3018
- id: `${context}-${subcontext}`,
3130
+ id: subcontext,
3019
3131
  name: genHumanReadableName(subcontext),
3020
3132
  isGroup: false,
3021
- checked: !!value[subcontext],
3133
+ checked: contextFilterState[context][subcontext],
3022
3134
  };
3023
3135
  }));
3024
3136
  const item = {
3025
3137
  id: context,
3026
- name: context,
3138
+ name: genHumanReadableName(context),
3027
3139
  isGroup: true,
3028
3140
  children,
3029
3141
  };
@@ -3036,7 +3148,9 @@ const LogReviewer = (props) => {
3036
3148
  if (pickableItem.isGroup) {
3037
3149
  // Has subcontexts
3038
3150
  pickableItem.children.forEach((subcontextItem) => {
3039
- contextFilterState[pickableItem.id][subcontextItem.id] = (subcontextItem.checked);
3151
+ if (!subcontextItem.isGroup) {
3152
+ contextFilterState[pickableItem.id][subcontextItem.id] = (subcontextItem.checked);
3153
+ }
3040
3154
  });
3041
3155
  }
3042
3156
  else {
@@ -3055,7 +3169,7 @@ const LogReviewer = (props) => {
3055
3169
  filterDrawer = (React__default["default"].createElement(TabBox, { title: "Tags" }, Object.keys(tagFilterState)
3056
3170
  .map((tag, i) => {
3057
3171
  const description = genHumanReadableName(tag);
3058
- return (React__default["default"].createElement(CheckboxButton, { id: `LogReviewer-tag-${tag}-checkbox`, text: description, ariaLabel: `include logs tagged with "${description}" in results`, noMarginOnRight: i === Object.keys(tagFilterState).length - 1, onChanged: (checked) => {
3172
+ return (React__default["default"].createElement(CheckboxButton, { id: `LogReviewer-tag-${tag}-checkbox`, text: description, ariaLabel: `require that logs be tagged with "${description}" or any other selected tag`, noMarginOnRight: i === Object.keys(tagFilterState).length - 1, checked: tagFilterState[tag], onChanged: (checked) => {
3059
3173
  tagFilterState[tag] = checked;
3060
3174
  } }));
3061
3175
  })));
@@ -3087,7 +3201,7 @@ const LogReviewer = (props) => {
3087
3201
  }, ariaLabel: "only show error logs", selected: actionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true })),
3088
3202
  (actionErrorFilterState.type === undefined
3089
3203
  || actionErrorFilterState.type === LogType$1.Action) && (React__default["default"].createElement(TabBox, { title: "Action Log Details" },
3090
- React__default["default"].createElement(ButtonInputGroup, { label: "Action" }, Object.keys(LogAction$1)
3204
+ React__default["default"].createElement(ButtonInputGroup, { label: "Action", className: "mb-2" }, Object.keys(LogAction$1)
3091
3205
  .map((action, i) => {
3092
3206
  const description = genHumanReadableName(action);
3093
3207
  return (React__default["default"].createElement(CheckboxButton, { id: `LogReviewer-action-${action}-checkbox`, text: description, ariaLabel: `include logs with action type "${description}" in results`, noMarginOnRight: i === Object.keys(LogAction$1).length - 1, onChanged: (checked) => {
@@ -3098,18 +3212,20 @@ const LogReviewer = (props) => {
3098
3212
  });
3099
3213
  } }));
3100
3214
  })),
3101
- React__default["default"].createElement(ButtonInputGroup, { label: "Target" }, Object.keys((_e = LogMetadata.Target) !== null && _e !== void 0 ? _e : {})
3102
- .map((target, i) => {
3103
- var _a;
3104
- const description = genHumanReadableName(target);
3105
- return (React__default["default"].createElement(CheckboxButton, { id: `LogReviewer-target-${target}-checkbox`, text: description, ariaLabel: `include logs with target "${description}" in results`, onChanged: (checked) => {
3106
- actionErrorFilterState.target[target] = checked;
3107
- dispatch({
3108
- type: ActionType.UpdateActionErrorFilterState,
3109
- actionErrorFilterState,
3110
- });
3111
- }, noMarginOnRight: i === Object.keys((_a = LogMetadata.Target) !== null && _a !== void 0 ? _a : {}).length - 1 }));
3112
- })))),
3215
+ React__default["default"].createElement(ButtonInputGroup, { label: "Target" },
3216
+ (Object.keys((_h = LogMetadata.Target) !== null && _h !== void 0 ? _h : {}).length === 0) && (React__default["default"].createElement("div", null, "This app does not have any targets yet.")),
3217
+ Object.keys((_j = LogMetadata.Target) !== null && _j !== void 0 ? _j : {})
3218
+ .map((target, i) => {
3219
+ var _a;
3220
+ const description = genHumanReadableName(target);
3221
+ return (React__default["default"].createElement(CheckboxButton, { id: `LogReviewer-target-${target}-checkbox`, text: description, ariaLabel: `include logs with target "${description}" in results`, onChanged: (checked) => {
3222
+ actionErrorFilterState.target[target] = checked;
3223
+ dispatch({
3224
+ type: ActionType.UpdateActionErrorFilterState,
3225
+ actionErrorFilterState,
3226
+ });
3227
+ }, noMarginOnRight: i === Object.keys((_a = LogMetadata.Target) !== null && _a !== void 0 ? _a : {}).length - 1 }));
3228
+ })))),
3113
3229
  (actionErrorFilterState.type === undefined
3114
3230
  || actionErrorFilterState.type === LogType$1.Error) && (React__default["default"].createElement(TabBox, { title: "Error Log Details" },
3115
3231
  React__default["default"].createElement("div", { className: "input-group mb-2" },
@@ -3271,26 +3387,27 @@ const LogReviewer = (props) => {
3271
3387
  advancedFilterState,
3272
3388
  });
3273
3389
  }, noMarginOnRight: true })),
3274
- React__default["default"].createElement("div", { className: "input-group mb-2" },
3275
- React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Path"),
3276
- React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for server route path", placeholder: "e.g. /api/ttm/courses/12345", value: advancedFilterState.routePath, onChange: (e) => {
3277
- advancedFilterState.courseName = ((e.target.value)
3278
- .trim());
3279
- dispatch({
3280
- type: ActionType.UpdateAdvancedFilterState,
3281
- advancedFilterState,
3282
- });
3283
- } })),
3284
- React__default["default"].createElement("div", { className: "input-group mb-2" },
3285
- React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Template"),
3286
- React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for server route template", value: advancedFilterState.routeTemplate, placeholder: "e.g. /api/ttm/courses/:courseId", onChange: (e) => {
3287
- advancedFilterState.courseName = ((e.target.value)
3288
- .trim());
3289
- dispatch({
3290
- type: ActionType.UpdateAdvancedFilterState,
3291
- advancedFilterState,
3292
- });
3293
- } })))));
3390
+ advancedFilterState.source !== LogSource$1.Client && (React__default["default"].createElement("div", { className: "mt-2" },
3391
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
3392
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Path"),
3393
+ React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for server route path", placeholder: "e.g. /api/ttm/courses/12345", value: advancedFilterState.routePath, onChange: (e) => {
3394
+ advancedFilterState.courseName = ((e.target.value)
3395
+ .trim());
3396
+ dispatch({
3397
+ type: ActionType.UpdateAdvancedFilterState,
3398
+ advancedFilterState,
3399
+ });
3400
+ } })),
3401
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
3402
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Template"),
3403
+ React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for server route template", value: advancedFilterState.routeTemplate, placeholder: "e.g. /api/ttm/courses/:courseId", onChange: (e) => {
3404
+ advancedFilterState.courseName = ((e.target.value)
3405
+ .trim());
3406
+ dispatch({
3407
+ type: ActionType.UpdateAdvancedFilterState,
3408
+ advancedFilterState,
3409
+ });
3410
+ } })))))));
3294
3411
  }
3295
3412
  }
3296
3413
  // Filters UI
@@ -3301,10 +3418,11 @@ const LogReviewer = (props) => {
3301
3418
  // > Perform filters
3302
3419
  const logs = [];
3303
3420
  Object.keys(logMap).forEach((year) => {
3304
- Object.keys(logMap).forEach((month) => {
3421
+ Object.keys(logMap[year]).forEach((month) => {
3305
3422
  logMap[year][month].forEach((log) => {
3306
- /* ----------- Date Filter ---------- */
3307
3423
  var _a;
3424
+ /* ----------- Date Filter ---------- */
3425
+ console.log('Log:', log);
3308
3426
  // Before start date
3309
3427
  if (
3310
3428
  // Previous year
@@ -3316,6 +3434,7 @@ const LogReviewer = (props) => {
3316
3434
  || ((log.year === dateFilterState.startDate.year)
3317
3435
  && (log.month === dateFilterState.startDate.month)
3318
3436
  && (log.day < dateFilterState.startDate.day))) {
3437
+ console.log('RULED OUT: START');
3319
3438
  return;
3320
3439
  }
3321
3440
  // After end date
@@ -3329,6 +3448,7 @@ const LogReviewer = (props) => {
3329
3448
  || ((log.year === dateFilterState.endDate.year)
3330
3449
  && (log.month === dateFilterState.endDate.month)
3331
3450
  && (log.day > dateFilterState.endDate.day))) {
3451
+ console.log('RULED OUT: END');
3332
3452
  return;
3333
3453
  }
3334
3454
  /* --------- Context Filter --------- */
@@ -3337,33 +3457,48 @@ const LogReviewer = (props) => {
3337
3457
  // Whole context is deselected
3338
3458
  contextFilterState[log.context] === false
3339
3459
  // None of the subcontexts are selected
3340
- || (Object.values((_a = contextFilterState[log.context]) !== null && _a !== void 0 ? _a : {})
3341
- .every((isSelected) => {
3342
- return !isSelected;
3343
- }))) {
3460
+ || (
3461
+ // Has subcontexts
3462
+ typeof contextFilterState[log.context] !== 'boolean'
3463
+ // None of the subcontexts are selected
3464
+ && Object.values((_a = contextFilterState[log.context]) !== null && _a !== void 0 ? _a : {})
3465
+ .every((isSelected) => {
3466
+ return !isSelected;
3467
+ }))) {
3468
+ // TODO: figure out why context filter is still not working, why actions and targets filter UI sucks and doesn't wrap and doesn't select
3469
+ console.log('RULED OUT: CONTEXT');
3470
+ console.log(log.context, contextFilterState);
3344
3471
  return;
3345
3472
  }
3346
3473
  // Subcontext doesn't match
3347
3474
  if (
3348
- // Log has a subcontext
3349
- log.subcontext
3475
+ // Log context is not "uncategorized" (no point in further filters)
3476
+ log.context !== LogBuiltInMetadata.Context.Uncategorized
3477
+ // Log has a subcontext
3478
+ && log.subcontext
3350
3479
  // Context has subcontexts
3351
3480
  && (contextFilterState[log.context]
3352
3481
  && contextFilterState[log.context] !== false
3353
3482
  && contextFilterState[log.context] !== true)
3354
3483
  // Subcontext is not selected
3355
3484
  && !contextFilterState[log.context][log.subcontext]) {
3485
+ console.log('RULED OUT: SUBCONTEXT');
3356
3486
  return;
3357
3487
  }
3358
3488
  /* -------------- Tags -------------- */
3359
3489
  // No tags match
3360
3490
  if (
3361
- // Log has at least one tag
3362
- log.tags.length > 0
3491
+ // At least one tag is required
3492
+ Object.values(tagFilterState)
3493
+ .filter((isSelected) => {
3494
+ return isSelected;
3495
+ })
3496
+ .length > 0
3363
3497
  // No tags match
3364
- && (log.tags.every((tag) => {
3498
+ && log.tags.every((tag) => {
3365
3499
  return !tagFilterState[tag];
3366
- }))) {
3500
+ })) {
3501
+ console.log('RULED OUT: TAGS');
3367
3502
  return;
3368
3503
  }
3369
3504
  /* ------- Actions and Errors ------- */
@@ -3373,6 +3508,7 @@ const LogReviewer = (props) => {
3373
3508
  actionErrorFilterState.type !== undefined
3374
3509
  // Log type doesn't match
3375
3510
  && actionErrorFilterState.type !== log.type) {
3511
+ console.log('RULED OUT: TYPE');
3376
3512
  return;
3377
3513
  }
3378
3514
  // Filter errors
@@ -3385,6 +3521,7 @@ const LogReviewer = (props) => {
3385
3521
  && actionErrorFilterState.errorMessage.trim().length > 0
3386
3522
  // Message doesn't match
3387
3523
  && log.errorMessage.toLowerCase().includes(actionErrorFilterState.errorMessage.trim().toLowerCase())) {
3524
+ console.log('RULED OUT: ERROR MESSAGE');
3388
3525
  return;
3389
3526
  }
3390
3527
  // Code doesn't match
@@ -3395,6 +3532,7 @@ const LogReviewer = (props) => {
3395
3532
  && actionErrorFilterState.errorCode.trim().length > 0
3396
3533
  // Code doesn't match
3397
3534
  && log.errorCode.toUpperCase().includes(actionErrorFilterState.errorCode.trim().toUpperCase())) {
3535
+ console.log('RULED OUT: ERROR CODE');
3398
3536
  return;
3399
3537
  }
3400
3538
  }
@@ -3406,6 +3544,7 @@ const LogReviewer = (props) => {
3406
3544
  log.target
3407
3545
  // Target isn't selected
3408
3546
  && !actionErrorFilterState.target[log.target]) {
3547
+ console.log('RULED OUT: TARGET');
3409
3548
  return;
3410
3549
  }
3411
3550
  // Action
@@ -3414,6 +3553,7 @@ const LogReviewer = (props) => {
3414
3553
  log.action
3415
3554
  // Action isn't selected
3416
3555
  && !actionErrorFilterState.action[log.action]) {
3556
+ console.log('RULED OUT: ACTION');
3417
3557
  return;
3418
3558
  }
3419
3559
  }
@@ -3424,6 +3564,7 @@ const LogReviewer = (props) => {
3424
3564
  log.userFirstName
3425
3565
  // First name query doesn't match
3426
3566
  && !log.userFirstName.toLowerCase().includes(advancedFilterState.userFirstName.toLowerCase().trim())) {
3567
+ console.log('RULED OUT: FIRST');
3427
3568
  return;
3428
3569
  }
3429
3570
  // Last name doesn't match
@@ -3432,6 +3573,7 @@ const LogReviewer = (props) => {
3432
3573
  log.userLastName
3433
3574
  // Last name query doesn't match
3434
3575
  && !log.userLastName.toLowerCase().includes(advancedFilterState.userLastName.toLowerCase().trim())) {
3576
+ console.log('RULED OUT: LAST');
3435
3577
  return;
3436
3578
  }
3437
3579
  // Email doesn't match
@@ -3440,6 +3582,7 @@ const LogReviewer = (props) => {
3440
3582
  log.userEmail
3441
3583
  // Email query doesn't match
3442
3584
  && !log.userEmail.toLowerCase().includes(advancedFilterState.userEmail.toLowerCase().trim())) {
3585
+ console.log('RULED OUT: EMAIL');
3443
3586
  return;
3444
3587
  }
3445
3588
  // User id doesn't match
@@ -3448,6 +3591,7 @@ const LogReviewer = (props) => {
3448
3591
  log.userId
3449
3592
  // User id doesn't match
3450
3593
  && !String(log.userId).includes(advancedFilterState.userId.trim())) {
3594
+ console.log('RULED OUT: USER ID');
3451
3595
  return;
3452
3596
  }
3453
3597
  // Learner not allowed
@@ -3456,6 +3600,7 @@ const LogReviewer = (props) => {
3456
3600
  log.isLearner
3457
3601
  // Learners aren't included
3458
3602
  && !advancedFilterState.includeLearners) {
3603
+ console.log('RULED OUT: LEARNER');
3459
3604
  return;
3460
3605
  }
3461
3606
  // TTM not allowed
@@ -3464,6 +3609,7 @@ const LogReviewer = (props) => {
3464
3609
  log.isTTM
3465
3610
  // TTMs aren't included
3466
3611
  && !advancedFilterState.includeTTMs) {
3612
+ console.log('RULED OUT: TTM');
3467
3613
  return;
3468
3614
  }
3469
3615
  // Admin not allowed
@@ -3472,6 +3618,7 @@ const LogReviewer = (props) => {
3472
3618
  log.isAdmin
3473
3619
  // Admins aren't included
3474
3620
  && !advancedFilterState.includeAdmins) {
3621
+ console.log('RULED OUT: ADMIN');
3475
3622
  return;
3476
3623
  }
3477
3624
  // Course Id doesn't match
@@ -3480,6 +3627,7 @@ const LogReviewer = (props) => {
3480
3627
  log.courseId
3481
3628
  // Course Id doesn't match
3482
3629
  && !String(log.courseId).includes(advancedFilterState.courseId.trim())) {
3630
+ console.log('RULED OUT: COURSE ID');
3483
3631
  return;
3484
3632
  }
3485
3633
  // Course name doesn't match
@@ -3488,6 +3636,7 @@ const LogReviewer = (props) => {
3488
3636
  log.courseName
3489
3637
  // Course name doesn't match
3490
3638
  && !String(log.courseName).includes(advancedFilterState.courseName.trim())) {
3639
+ console.log('RULED OUT: COURSE NAME');
3491
3640
  return;
3492
3641
  }
3493
3642
  // Mobile filter doesn't match
@@ -3498,6 +3647,7 @@ const LogReviewer = (props) => {
3498
3647
  && log.device
3499
3648
  // Mobile filter doesn't match
3500
3649
  && (advancedFilterState.isMobile === log.device.isMobile)) {
3650
+ console.log('RULED OUT: MOBILE');
3501
3651
  return;
3502
3652
  }
3503
3653
  // Log source doesn't match
@@ -3508,6 +3658,7 @@ const LogReviewer = (props) => {
3508
3658
  && log.source
3509
3659
  // Source filter doesn't match
3510
3660
  && (advancedFilterState.source !== log.source)) {
3661
+ console.log('RULED OUT: SOURCE');
3511
3662
  return;
3512
3663
  }
3513
3664
  // Route path doesn't match (Only for server source)
@@ -3518,6 +3669,7 @@ const LogReviewer = (props) => {
3518
3669
  && (advancedFilterState.routePath.trim().length)
3519
3670
  // Route path doesn't match
3520
3671
  && !(log.routePath.includes(advancedFilterState.routePath.trim()))) {
3672
+ console.log('RULED OUT: PATH');
3521
3673
  return;
3522
3674
  }
3523
3675
  // Route template doesn't match (Only for server source)
@@ -3528,6 +3680,7 @@ const LogReviewer = (props) => {
3528
3680
  && (advancedFilterState.routeTemplate.trim().length)
3529
3681
  // Route template doesn't match
3530
3682
  && !(log.routeTemplate.includes(advancedFilterState.routeTemplate.trim()))) {
3683
+ console.log('RULED OUT: TEMPLATE');
3531
3684
  return;
3532
3685
  }
3533
3686
  /* -------------- Done -------------- */
@@ -3727,11 +3880,17 @@ const LogReviewer = (props) => {
3727
3880
  },
3728
3881
  ];
3729
3882
  // Create intelliTable
3730
- const dataTable = (React__default["default"].createElement(IntelliTable, { title: "Matching Logs", id: "logs", data: logs, columns: columns }));
3883
+ const dataTable = (logs.length === 0
3884
+ ? (React__default["default"].createElement(React__default["default"].Fragment, null,
3885
+ React__default["default"].createElement("h3", { className: "m-0" }, "Matching Logs:"),
3886
+ React__default["default"].createElement("div", { className: "alert alert-warning text-center" },
3887
+ React__default["default"].createElement("h4", { className: "m-1" }, "No Logs to Show"),
3888
+ React__default["default"].createElement("div", null, "Either your filters are too strict or no matching logs have been created yet."))))
3889
+ : (React__default["default"].createElement(IntelliTable, { title: "Matching Logs:", id: "logs", data: logs, columns: columns })));
3731
3890
  // Main body
3732
3891
  body = (React__default["default"].createElement(React__default["default"].Fragment, null,
3733
3892
  filters,
3734
- dataTable));
3893
+ React__default["default"].createElement("div", { className: "mt-2" }, dataTable)));
3735
3894
  }
3736
3895
  /* ---------- Wrap in Modal --------- */
3737
3896
  return (React__default["default"].createElement("div", { className: "LogReviewer-outer-container" },
@@ -3739,14 +3898,10 @@ const LogReviewer = (props) => {
3739
3898
  React__default["default"].createElement("div", { className: "LogReviewer-inner-container" },
3740
3899
  React__default["default"].createElement("div", { className: "LogReviewer-header" },
3741
3900
  React__default["default"].createElement("div", { className: "LogReviewer-header-title" },
3742
- React__default["default"].createElement("h1", { className: "m-0" }, "Log Review Dashboard")),
3743
- React__default["default"].createElement("button", { type: "button", className: "LogReviewer-header-close-button btn btn-lg", "aria-label": "close log reviewer panel", onClick: onClose, style: {
3744
- border: 0,
3745
- backgroundColor: 'transparent',
3746
- padding: 0,
3747
- margin: 0,
3748
- } },
3749
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes }))),
3901
+ React__default["default"].createElement("h3", { className: "text-center m-0" }, "Log Review Dashboard")),
3902
+ React__default["default"].createElement("div", { style: { width: 0 } },
3903
+ React__default["default"].createElement("button", { type: "button", className: "LogReviewer-header-close-button btn btn-dark btn-lg pe-0", "aria-label": "close log reviewer panel", onClick: onClose },
3904
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes })))),
3750
3905
  React__default["default"].createElement("div", { className: "LogReviewer-contents" }, body))));
3751
3906
  };
3752
3907
 
@@ -3892,7 +4047,7 @@ const padZerosLeft = (num, numDigits) => {
3892
4047
  * access to log review
3893
4048
  * @author Gabe Abrams
3894
4049
  */
3895
- const LOG_REVIEW_STATUS_ROUTE = `/admin${ROUTE_PATH_PREFIX}/logs/access`;
4050
+ const LOG_REVIEW_STATUS_ROUTE = `${ROUTE_PATH_PREFIX}/logs/access_allowed`;
3896
4051
 
3897
4052
  // Stored copy of caccl functions
3898
4053
  let _cacclGetLaunchInfo;
@@ -3934,7 +4089,7 @@ const internalGetLogCollection = () => {
3934
4089
  * @param opts.getLaunchInfo CACCL LTI's get launch info function
3935
4090
  * @param [opts.logCollection] mongo collection from dce-mango to use for
3936
4091
  * storing logs. If none is included, logs are written to the console
3937
- * @param [opts.logReviewAdmins=all admins] info on which admins can review
4092
+ * @param [opts.logReviewAdmins=all] info on which admins can review
3938
4093
  * logs from the client. If not included, all Canvas admins are allowed to
3939
4094
  * review logs. If null, no Canvas admins are allowed to review logs.
3940
4095
  * If an array of Canvas userIds (numbers), only Canvas admins with those
@@ -4017,71 +4172,81 @@ const initServer = (opts) => {
4017
4172
  /*----------------------------------------*/
4018
4173
  /* Log Reviewer */
4019
4174
  /*----------------------------------------*/
4020
- if (opts.logReviewAdmins !== null) {
4021
- /**
4022
- * Check if a given user is allowed to review logs
4023
- * @author Gabe Abrams
4024
- * @param userId the id of the user
4025
- * @returns true if the user can review logs
4026
- */
4027
- const canReviewLogs = (userId) => __awaiter(void 0, void 0, void 0, function* () {
4028
- try {
4029
- // Array of userIds
4030
- if (Array.isArray(opts.logReviewAdmins)) {
4031
- return opts.logReviewAdmins.some((allowedId) => {
4032
- return (userId === allowedId);
4033
- });
4034
- }
4035
- // Must be a collection
4036
- const matches = yield opts.logReviewAdmins.find({ userId });
4037
- // Make sure at least one entry matches
4038
- return matches.length > 0;
4175
+ /**
4176
+ * Check if a given user is allowed to review logs
4177
+ * @author Gabe Abrams
4178
+ * @param userId the id of the user
4179
+ * @param isAdmin if true, the user is an admin
4180
+ * @returns true if the user can review logs
4181
+ */
4182
+ const canReviewLogs = (userId, isAdmin) => __awaiter(void 0, void 0, void 0, function* () {
4183
+ // Immediately deny access if user is not an admin
4184
+ if (!isAdmin) {
4185
+ return false;
4186
+ }
4187
+ // If all admins are allowed, we're done
4188
+ if (!opts.logReviewAdmins) {
4189
+ return true;
4190
+ }
4191
+ // Do a dynamic check
4192
+ try {
4193
+ // Array of userIds
4194
+ if (Array.isArray(opts.logReviewAdmins)) {
4195
+ return opts.logReviewAdmins.some((allowedId) => {
4196
+ return (userId === allowedId);
4197
+ });
4039
4198
  }
4040
- catch (err) {
4041
- // If an error occurred, simply return false
4042
- return false;
4199
+ // Must be a collection
4200
+ const matches = yield opts.logReviewAdmins.find({ userId });
4201
+ // Make sure at least one entry matches
4202
+ return matches.length > 0;
4203
+ }
4204
+ catch (err) {
4205
+ // If an error occurred, simply return false
4206
+ return false;
4207
+ }
4208
+ });
4209
+ /**
4210
+ * Check if the current user has access to logs
4211
+ * @author Gabe Abrams
4212
+ * @returns {boolean} true if user has access
4213
+ */
4214
+ opts.app.get(LOG_REVIEW_STATUS_ROUTE, genRouteHandler({
4215
+ handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
4216
+ const { userId, isAdmin } = params;
4217
+ const canReview = yield canReviewLogs(userId, isAdmin);
4218
+ return canReview;
4219
+ }),
4220
+ }));
4221
+ /**
4222
+ * Get all logs for a certain month
4223
+ * @author Gabe Abrams
4224
+ * @param {number} year the year to query (e.g. 2022)
4225
+ * @param {number} month the month to query (e.g. 1 = January)
4226
+ * @returns {Log[]} list of logs from the given month
4227
+ */
4228
+ opts.app.get(`${LOG_REVIEW_ROUTE_PATH_PREFIX}/years/:year/months/:month`, genRouteHandler({
4229
+ paramTypes: {
4230
+ year: ParamType$1.Int,
4231
+ month: ParamType$1.Int,
4232
+ },
4233
+ handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
4234
+ // Get user info
4235
+ const { year, month, userId, isAdmin, } = params;
4236
+ // Validate user
4237
+ const canReview = yield canReviewLogs(userId, isAdmin);
4238
+ if (!canReview) {
4239
+ throw new ErrorWithCode('You cannot access this resource because you do not have the appropriate permissions.', ReactKitErrorCode$1.NotAllowedToReviewLogs);
4043
4240
  }
4044
- });
4045
- /**
4046
- * Check if the current user has access to logs
4047
- * @author Gabe Abrams
4048
- * @returns {boolean} true if user has access
4049
- */
4050
- opts.app.get(LOG_REVIEW_STATUS_ROUTE, genRouteHandler({
4051
- handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
4052
- const { userId } = params;
4053
- const canReview = yield canReviewLogs(userId);
4054
- return canReview;
4055
- }),
4056
- }));
4057
- /**
4058
- * Get all logs for a certain month
4059
- * @author Gabe Abrams
4060
- * @param {number} year the year to query (e.g. 2022)
4061
- * @param {number} month the month to query (e.g. 1 = January)
4062
- * @returns {Log[]} list of logs from the given month
4063
- */
4064
- opts.app.post(`${LOG_REVIEW_ROUTE_PATH_PREFIX}/years/:year/months/:month`, genRouteHandler({
4065
- paramTypes: {
4066
- year: ParamType$1.Int,
4067
- month: ParamType$1.Int,
4068
- },
4069
- handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
4070
- // Get user info
4071
- const { userId } = params;
4072
- // Validate user
4073
- // isAdmin is already checked because path starts with '/admin'
4074
- const canReview = yield canReviewLogs(userId);
4075
- if (!canReview) {
4076
- throw new ErrorWithCode('You cannot access this resource because you do not have the appropriate permissions.', ReactKitErrorCode$1.NotAllowedToReviewLogs);
4077
- }
4078
- // Query for logs
4079
- const logs = yield _logCollection.find({ userId });
4080
- // Return logs
4081
- return logs;
4082
- }),
4083
- }));
4084
- }
4241
+ // Query for logs
4242
+ const logs = yield _logCollection.find({
4243
+ year,
4244
+ month,
4245
+ });
4246
+ // Return logs
4247
+ return logs;
4248
+ }),
4249
+ }));
4085
4250
  };
4086
4251
 
4087
4252
  // Import shared types
@@ -4381,18 +4546,6 @@ const parseUserAgent = (userAgent) => {
4381
4546
  };
4382
4547
  };
4383
4548
 
4384
- /**
4385
- * Allowed log levels
4386
- * @author Gabe Abrams
4387
- */
4388
- var LogLevel;
4389
- (function (LogLevel) {
4390
- LogLevel["Warn"] = "Warn";
4391
- LogLevel["Info"] = "Info";
4392
- LogLevel["Debug"] = "Debug";
4393
- })(LogLevel || (LogLevel = {}));
4394
- var LogLevel$1 = LogLevel;
4395
-
4396
4549
  /**
4397
4550
  * Generate an express API route handler
4398
4551
  * @author Gabe Abrams
@@ -4765,7 +4918,7 @@ const genRouteHandler = (opts) => {
4765
4918
  }
4766
4919
  : {
4767
4920
  type: LogType$1.Action,
4768
- target: ((_m = opts.target) !== null && _m !== void 0 ? _m : LogBuiltInMetadata.Target.NoSpecificTarget),
4921
+ target: ((_m = opts.target) !== null && _m !== void 0 ? _m : LogBuiltInMetadata.Target.NoTarget),
4769
4922
  action: ((_o = opts.action) !== null && _o !== void 0 ? _o : LogAction$1.Unknown),
4770
4923
  });
4771
4924
  // Source-specific info