dce-reactkit 3.2.2-beta.2 → 3.2.2-beta.21

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,
@@ -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,33 @@ 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 (LogMetadata.Context
2807
+ // Context has children already
2808
+ && typeof LogMetadata.Context[context] !== 'string') {
2809
+ LogMetadata.Context[context][LogBuiltInMetadata.Context.Uncategorized] = (LogBuiltInMetadata.Context.Uncategorized);
2810
+ }
2811
+ });
2812
+ // > Add built-in contexts
2813
+ LogMetadata.Context = ((_b = LogMetadata.Context) !== null && _b !== void 0 ? _b : {});
2814
+ Object.keys(LogBuiltInMetadata.Context).forEach((context) => {
2815
+ if (LogMetadata.Context) {
2816
+ LogMetadata.Context[context] = context;
2817
+ }
2818
+ });
2819
+ // > Add built-in targets
2820
+ LogMetadata.Target = ((_c = LogMetadata.Target) !== null && _c !== void 0 ? _c : {});
2821
+ Object.keys(LogBuiltInMetadata.Target).forEach((target) => {
2822
+ if (LogMetadata.Target) {
2823
+ LogMetadata.Target[target] = target;
2824
+ }
2825
+ });
2732
2826
  /* -------------- State ------------- */
2733
2827
  // Create initial date filter state
2734
2828
  const today = getTimeInfoInET();
@@ -2748,7 +2842,7 @@ const LogReviewer = (props) => {
2748
2842
  };
2749
2843
  // Create initial context filter state
2750
2844
  const initContextFilterState = {};
2751
- Object.keys((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {}).forEach((context) => {
2845
+ Object.keys((_d = LogMetadata.Context) !== null && _d !== void 0 ? _d : {}).forEach((context) => {
2752
2846
  var _a, _b;
2753
2847
  const contextValue = ((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {})[context];
2754
2848
  if (typeof contextValue === 'string') {
@@ -2766,7 +2860,7 @@ const LogReviewer = (props) => {
2766
2860
  });
2767
2861
  // Create initial tag filter state
2768
2862
  const initTagFilterState = {};
2769
- Object.values((_b = LogMetadata.Tag) !== null && _b !== void 0 ? _b : {}).forEach((tagValue) => {
2863
+ Object.values((_e = LogMetadata.Tag) !== null && _e !== void 0 ? _e : {}).forEach((tagValue) => {
2770
2864
  initTagFilterState[tagValue] = true;
2771
2865
  });
2772
2866
  // Create advanced filter state
@@ -2793,12 +2887,16 @@ const LogReviewer = (props) => {
2793
2887
  target: {},
2794
2888
  action: {},
2795
2889
  };
2796
- Object.values((_c = LogMetadata.Target) !== null && _c !== void 0 ? _c : {}).forEach((target) => {
2890
+ Object.values((_f = LogMetadata.Target) !== null && _f !== void 0 ? _f : {}).forEach((target) => {
2797
2891
  initActionErrorFilterState.target[target] = true;
2798
2892
  });
2799
2893
  Object.values(LogAction$1).forEach((action) => {
2800
2894
  initActionErrorFilterState.action[action] = true;
2801
2895
  });
2896
+ // Add built-in targets
2897
+ Object.values(LogBuiltInMetadata.Target).forEach((target) => {
2898
+ initActionErrorFilterState.target[target] = true;
2899
+ });
2802
2900
  // Initial state
2803
2901
  const initialState = {
2804
2902
  loading: true,
@@ -2832,15 +2930,18 @@ const LogReviewer = (props) => {
2832
2930
  let month = newDateFilterState.startDate.month;
2833
2931
  while (
2834
2932
  // Earlier year
2835
- (year <= newDateFilterState.endDate.year)
2933
+ (year < newDateFilterState.endDate.year)
2836
2934
  // Current year but included month
2837
2935
  || (year === newDateFilterState.endDate.year
2838
2936
  && month <= newDateFilterState.endDate.month)) {
2839
- // Add to list
2840
- toLoad.push({
2841
- year,
2842
- month,
2843
- });
2937
+ // Add to list if not already loaded
2938
+ if (!logMap[year]
2939
+ || !logMap[year][month]) {
2940
+ toLoad.push({
2941
+ year,
2942
+ month,
2943
+ });
2944
+ }
2844
2945
  // Increment
2845
2946
  month += 1;
2846
2947
  if (month > 12) {
@@ -2864,6 +2965,7 @@ const LogReviewer = (props) => {
2864
2965
  });
2865
2966
  // Check which year/month combos we need to load
2866
2967
  const toLoad = listMonthsToLoad(newDateFilterState);
2968
+ console.log('Filter update:', newDateFilterState, toLoad, logMap);
2867
2969
  // If nothing to load, finished
2868
2970
  if (toLoad.length === 0) {
2869
2971
  return;
@@ -2928,10 +3030,10 @@ const LogReviewer = (props) => {
2928
3030
  /* Filters */
2929
3031
  /*----------------------------------------*/
2930
3032
  // Filter toggle
2931
- const filterToggles = (React__default["default"].createElement("div", { className: "LogReviewer-filter-toggles d-flex align-items-center justify-content-center" },
3033
+ const filterToggles = (React__default["default"].createElement("div", { className: "LogReviewer-filter-toggles" },
2932
3034
  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: () => {
3035
+ React__default["default"].createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
3036
+ 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
3037
  dispatch({
2936
3038
  type: ActionType.ToggleFilterDrawer,
2937
3039
  filterDrawer: FilterDrawer.Date,
@@ -2939,7 +3041,7 @@ const LogReviewer = (props) => {
2939
3041
  } },
2940
3042
  React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCalendar, className: "me-2" }),
2941
3043
  "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: () => {
3044
+ 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
3045
  dispatch({
2944
3046
  type: ActionType.ToggleFilterDrawer,
2945
3047
  filterDrawer: FilterDrawer.Context,
@@ -2947,15 +3049,15 @@ const LogReviewer = (props) => {
2947
3049
  } },
2948
3050
  React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "me-2" }),
2949
3051
  "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: () => {
3052
+ (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
3053
  dispatch({
2952
3054
  type: ActionType.ToggleFilterDrawer,
2953
3055
  filterDrawer: FilterDrawer.Tag,
2954
3056
  });
2955
3057
  } },
2956
3058
  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: () => {
3059
+ "Tag")),
3060
+ 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
3061
  dispatch({
2960
3062
  type: ActionType.ToggleFilterDrawer,
2961
3063
  filterDrawer: FilterDrawer.Action,
@@ -2963,7 +3065,7 @@ const LogReviewer = (props) => {
2963
3065
  } },
2964
3066
  React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faHammer, className: "me-2" }),
2965
3067
  "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: () => {
3068
+ 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
3069
  dispatch({
2968
3070
  type: ActionType.ToggleFilterDrawer,
2969
3071
  filterDrawer: FilterDrawer.Advanced,
@@ -2976,25 +3078,29 @@ const LogReviewer = (props) => {
2976
3078
  if (expandedFilterDrawer) {
2977
3079
  if (expandedFilterDrawer === FilterDrawer.Date) {
2978
3080
  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
- });
3081
+ 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) => {
3082
+ dateFilterState.startDate = { month, day, year };
3083
+ handleDateRangeUpdated(dateFilterState);
2984
3084
  } }),
2985
3085
  ' ',
2986
3086
  "to",
2987
3087
  ' ',
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
- });
3088
+ 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) => {
3089
+ if (year < dateFilterState.startDate.year
3090
+ || (year === dateFilterState.startDate.year
3091
+ && month < dateFilterState.startDate.month)
3092
+ || (year === dateFilterState.startDate.year
3093
+ && month === dateFilterState.startDate.month
3094
+ && day < dateFilterState.startDate.day)) {
3095
+ return alert$1('Invalid Start Date', 'The start date cannot be before the end date.');
3096
+ }
3097
+ dateFilterState.endDate = { month, day, year };
3098
+ handleDateRangeUpdated(dateFilterState);
2993
3099
  } })));
2994
3100
  }
2995
3101
  else if (expandedFilterDrawer === FilterDrawer.Context) {
2996
3102
  // Create item picker items
2997
- const pickableItems = (Object.keys((_d = LogMetadata.Context) !== null && _d !== void 0 ? _d : {})
3103
+ const pickableItems = (Object.keys((_g = LogMetadata.Context) !== null && _g !== void 0 ? _g : {})
2998
3104
  .map((context) => {
2999
3105
  var _a;
3000
3106
  const value = ((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {})[context];
@@ -3015,7 +3121,7 @@ const LogReviewer = (props) => {
3015
3121
  })
3016
3122
  .map((subcontext) => {
3017
3123
  return {
3018
- id: `${context}-${subcontext}`,
3124
+ id: subcontext,
3019
3125
  name: genHumanReadableName(subcontext),
3020
3126
  isGroup: false,
3021
3127
  checked: !!value[subcontext],
@@ -3023,7 +3129,7 @@ const LogReviewer = (props) => {
3023
3129
  }));
3024
3130
  const item = {
3025
3131
  id: context,
3026
- name: context,
3132
+ name: genHumanReadableName(context),
3027
3133
  isGroup: true,
3028
3134
  children,
3029
3135
  };
@@ -3031,6 +3137,8 @@ const LogReviewer = (props) => {
3031
3137
  }));
3032
3138
  // Create filter UI
3033
3139
  filterDrawer = (React__default["default"].createElement(ItemPicker, { title: "Context", items: pickableItems, onChanged: (updatedItems) => {
3140
+ console.log('Items before:', pickableItems);
3141
+ console.log('Updated Items:', updatedItems);
3034
3142
  // Update our state
3035
3143
  updatedItems.forEach((pickableItem) => {
3036
3144
  if (pickableItem.isGroup) {
@@ -3087,7 +3195,7 @@ const LogReviewer = (props) => {
3087
3195
  }, ariaLabel: "only show error logs", selected: actionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true })),
3088
3196
  (actionErrorFilterState.type === undefined
3089
3197
  || 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)
3198
+ React__default["default"].createElement(ButtonInputGroup, { label: "Action", className: "mb-2" }, Object.keys(LogAction$1)
3091
3199
  .map((action, i) => {
3092
3200
  const description = genHumanReadableName(action);
3093
3201
  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 +3206,20 @@ const LogReviewer = (props) => {
3098
3206
  });
3099
3207
  } }));
3100
3208
  })),
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
- })))),
3209
+ React__default["default"].createElement(ButtonInputGroup, { label: "Target" },
3210
+ (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.")),
3211
+ Object.keys((_j = LogMetadata.Target) !== null && _j !== void 0 ? _j : {})
3212
+ .map((target, i) => {
3213
+ var _a;
3214
+ const description = genHumanReadableName(target);
3215
+ return (React__default["default"].createElement(CheckboxButton, { id: `LogReviewer-target-${target}-checkbox`, text: description, ariaLabel: `include logs with target "${description}" in results`, onChanged: (checked) => {
3216
+ actionErrorFilterState.target[target] = checked;
3217
+ dispatch({
3218
+ type: ActionType.UpdateActionErrorFilterState,
3219
+ actionErrorFilterState,
3220
+ });
3221
+ }, noMarginOnRight: i === Object.keys((_a = LogMetadata.Target) !== null && _a !== void 0 ? _a : {}).length - 1 }));
3222
+ })))),
3113
3223
  (actionErrorFilterState.type === undefined
3114
3224
  || actionErrorFilterState.type === LogType$1.Error) && (React__default["default"].createElement(TabBox, { title: "Error Log Details" },
3115
3225
  React__default["default"].createElement("div", { className: "input-group mb-2" },
@@ -3271,26 +3381,27 @@ const LogReviewer = (props) => {
3271
3381
  advancedFilterState,
3272
3382
  });
3273
3383
  }, 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
- } })))));
3384
+ advancedFilterState.source !== LogSource$1.Client && (React__default["default"].createElement("div", { className: "mt-2" },
3385
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
3386
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Path"),
3387
+ 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) => {
3388
+ advancedFilterState.courseName = ((e.target.value)
3389
+ .trim());
3390
+ dispatch({
3391
+ type: ActionType.UpdateAdvancedFilterState,
3392
+ advancedFilterState,
3393
+ });
3394
+ } })),
3395
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
3396
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Template"),
3397
+ 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) => {
3398
+ advancedFilterState.courseName = ((e.target.value)
3399
+ .trim());
3400
+ dispatch({
3401
+ type: ActionType.UpdateAdvancedFilterState,
3402
+ advancedFilterState,
3403
+ });
3404
+ } })))))));
3294
3405
  }
3295
3406
  }
3296
3407
  // Filters UI
@@ -3301,10 +3412,11 @@ const LogReviewer = (props) => {
3301
3412
  // > Perform filters
3302
3413
  const logs = [];
3303
3414
  Object.keys(logMap).forEach((year) => {
3304
- Object.keys(logMap).forEach((month) => {
3415
+ Object.keys(logMap[year]).forEach((month) => {
3305
3416
  logMap[year][month].forEach((log) => {
3306
- /* ----------- Date Filter ---------- */
3307
3417
  var _a;
3418
+ /* ----------- Date Filter ---------- */
3419
+ console.log('Log:', log);
3308
3420
  // Before start date
3309
3421
  if (
3310
3422
  // Previous year
@@ -3316,6 +3428,7 @@ const LogReviewer = (props) => {
3316
3428
  || ((log.year === dateFilterState.startDate.year)
3317
3429
  && (log.month === dateFilterState.startDate.month)
3318
3430
  && (log.day < dateFilterState.startDate.day))) {
3431
+ console.log('RULED OUT: START');
3319
3432
  return;
3320
3433
  }
3321
3434
  // After end date
@@ -3329,6 +3442,7 @@ const LogReviewer = (props) => {
3329
3442
  || ((log.year === dateFilterState.endDate.year)
3330
3443
  && (log.month === dateFilterState.endDate.month)
3331
3444
  && (log.day > dateFilterState.endDate.day))) {
3445
+ console.log('RULED OUT: END');
3332
3446
  return;
3333
3447
  }
3334
3448
  /* --------- Context Filter --------- */
@@ -3341,29 +3455,30 @@ const LogReviewer = (props) => {
3341
3455
  .every((isSelected) => {
3342
3456
  return !isSelected;
3343
3457
  }))) {
3458
+ console.log('RULED OUT: CONTEXT');
3344
3459
  return;
3345
3460
  }
3346
3461
  // Subcontext doesn't match
3347
3462
  if (
3348
- // Log has a subcontext
3349
- log.subcontext
3463
+ // Log context is not "uncategorized" (no point in further filters)
3464
+ log.context !== LogBuiltInMetadata.Context.Uncategorized
3465
+ // Log has a subcontext
3466
+ && log.subcontext
3350
3467
  // Context has subcontexts
3351
3468
  && (contextFilterState[log.context]
3352
3469
  && contextFilterState[log.context] !== false
3353
3470
  && contextFilterState[log.context] !== true)
3354
3471
  // Subcontext is not selected
3355
3472
  && !contextFilterState[log.context][log.subcontext]) {
3473
+ console.log('RULED OUT: SUBCONTEXT');
3356
3474
  return;
3357
3475
  }
3358
3476
  /* -------------- Tags -------------- */
3359
3477
  // No tags match
3360
- if (
3361
- // Log has at least one tag
3362
- log.tags.length > 0
3363
- // No tags match
3364
- && (log.tags.every((tag) => {
3365
- return !tagFilterState[tag];
3366
- }))) {
3478
+ if (log.tags.every((tag) => {
3479
+ return !tagFilterState[tag];
3480
+ })) {
3481
+ console.log('RULED OUT: TAGS');
3367
3482
  return;
3368
3483
  }
3369
3484
  /* ------- Actions and Errors ------- */
@@ -3373,6 +3488,7 @@ const LogReviewer = (props) => {
3373
3488
  actionErrorFilterState.type !== undefined
3374
3489
  // Log type doesn't match
3375
3490
  && actionErrorFilterState.type !== log.type) {
3491
+ console.log('RULED OUT: TYPE');
3376
3492
  return;
3377
3493
  }
3378
3494
  // Filter errors
@@ -3385,6 +3501,7 @@ const LogReviewer = (props) => {
3385
3501
  && actionErrorFilterState.errorMessage.trim().length > 0
3386
3502
  // Message doesn't match
3387
3503
  && log.errorMessage.toLowerCase().includes(actionErrorFilterState.errorMessage.trim().toLowerCase())) {
3504
+ console.log('RULED OUT: ERROR MESSAGE');
3388
3505
  return;
3389
3506
  }
3390
3507
  // Code doesn't match
@@ -3395,6 +3512,7 @@ const LogReviewer = (props) => {
3395
3512
  && actionErrorFilterState.errorCode.trim().length > 0
3396
3513
  // Code doesn't match
3397
3514
  && log.errorCode.toUpperCase().includes(actionErrorFilterState.errorCode.trim().toUpperCase())) {
3515
+ console.log('RULED OUT: ERROR CODE');
3398
3516
  return;
3399
3517
  }
3400
3518
  }
@@ -3406,6 +3524,7 @@ const LogReviewer = (props) => {
3406
3524
  log.target
3407
3525
  // Target isn't selected
3408
3526
  && !actionErrorFilterState.target[log.target]) {
3527
+ console.log('RULED OUT: TARGET');
3409
3528
  return;
3410
3529
  }
3411
3530
  // Action
@@ -3414,6 +3533,7 @@ const LogReviewer = (props) => {
3414
3533
  log.action
3415
3534
  // Action isn't selected
3416
3535
  && !actionErrorFilterState.action[log.action]) {
3536
+ console.log('RULED OUT: ACTION');
3417
3537
  return;
3418
3538
  }
3419
3539
  }
@@ -3424,6 +3544,7 @@ const LogReviewer = (props) => {
3424
3544
  log.userFirstName
3425
3545
  // First name query doesn't match
3426
3546
  && !log.userFirstName.toLowerCase().includes(advancedFilterState.userFirstName.toLowerCase().trim())) {
3547
+ console.log('RULED OUT: FIRST');
3427
3548
  return;
3428
3549
  }
3429
3550
  // Last name doesn't match
@@ -3432,6 +3553,7 @@ const LogReviewer = (props) => {
3432
3553
  log.userLastName
3433
3554
  // Last name query doesn't match
3434
3555
  && !log.userLastName.toLowerCase().includes(advancedFilterState.userLastName.toLowerCase().trim())) {
3556
+ console.log('RULED OUT: LAST');
3435
3557
  return;
3436
3558
  }
3437
3559
  // Email doesn't match
@@ -3440,6 +3562,7 @@ const LogReviewer = (props) => {
3440
3562
  log.userEmail
3441
3563
  // Email query doesn't match
3442
3564
  && !log.userEmail.toLowerCase().includes(advancedFilterState.userEmail.toLowerCase().trim())) {
3565
+ console.log('RULED OUT: EMAIL');
3443
3566
  return;
3444
3567
  }
3445
3568
  // User id doesn't match
@@ -3448,6 +3571,7 @@ const LogReviewer = (props) => {
3448
3571
  log.userId
3449
3572
  // User id doesn't match
3450
3573
  && !String(log.userId).includes(advancedFilterState.userId.trim())) {
3574
+ console.log('RULED OUT: USER ID');
3451
3575
  return;
3452
3576
  }
3453
3577
  // Learner not allowed
@@ -3456,6 +3580,7 @@ const LogReviewer = (props) => {
3456
3580
  log.isLearner
3457
3581
  // Learners aren't included
3458
3582
  && !advancedFilterState.includeLearners) {
3583
+ console.log('RULED OUT: LEARNER');
3459
3584
  return;
3460
3585
  }
3461
3586
  // TTM not allowed
@@ -3464,6 +3589,7 @@ const LogReviewer = (props) => {
3464
3589
  log.isTTM
3465
3590
  // TTMs aren't included
3466
3591
  && !advancedFilterState.includeTTMs) {
3592
+ console.log('RULED OUT: TTM');
3467
3593
  return;
3468
3594
  }
3469
3595
  // Admin not allowed
@@ -3472,6 +3598,7 @@ const LogReviewer = (props) => {
3472
3598
  log.isAdmin
3473
3599
  // Admins aren't included
3474
3600
  && !advancedFilterState.includeAdmins) {
3601
+ console.log('RULED OUT: ADMIN');
3475
3602
  return;
3476
3603
  }
3477
3604
  // Course Id doesn't match
@@ -3480,6 +3607,7 @@ const LogReviewer = (props) => {
3480
3607
  log.courseId
3481
3608
  // Course Id doesn't match
3482
3609
  && !String(log.courseId).includes(advancedFilterState.courseId.trim())) {
3610
+ console.log('RULED OUT: COURSE ID');
3483
3611
  return;
3484
3612
  }
3485
3613
  // Course name doesn't match
@@ -3488,6 +3616,7 @@ const LogReviewer = (props) => {
3488
3616
  log.courseName
3489
3617
  // Course name doesn't match
3490
3618
  && !String(log.courseName).includes(advancedFilterState.courseName.trim())) {
3619
+ console.log('RULED OUT: COURSE NAME');
3491
3620
  return;
3492
3621
  }
3493
3622
  // Mobile filter doesn't match
@@ -3498,6 +3627,7 @@ const LogReviewer = (props) => {
3498
3627
  && log.device
3499
3628
  // Mobile filter doesn't match
3500
3629
  && (advancedFilterState.isMobile === log.device.isMobile)) {
3630
+ console.log('RULED OUT: MOBILE');
3501
3631
  return;
3502
3632
  }
3503
3633
  // Log source doesn't match
@@ -3508,6 +3638,7 @@ const LogReviewer = (props) => {
3508
3638
  && log.source
3509
3639
  // Source filter doesn't match
3510
3640
  && (advancedFilterState.source !== log.source)) {
3641
+ console.log('RULED OUT: SOURCE');
3511
3642
  return;
3512
3643
  }
3513
3644
  // Route path doesn't match (Only for server source)
@@ -3518,6 +3649,7 @@ const LogReviewer = (props) => {
3518
3649
  && (advancedFilterState.routePath.trim().length)
3519
3650
  // Route path doesn't match
3520
3651
  && !(log.routePath.includes(advancedFilterState.routePath.trim()))) {
3652
+ console.log('RULED OUT: PATH');
3521
3653
  return;
3522
3654
  }
3523
3655
  // Route template doesn't match (Only for server source)
@@ -3528,6 +3660,7 @@ const LogReviewer = (props) => {
3528
3660
  && (advancedFilterState.routeTemplate.trim().length)
3529
3661
  // Route template doesn't match
3530
3662
  && !(log.routeTemplate.includes(advancedFilterState.routeTemplate.trim()))) {
3663
+ console.log('RULED OUT: TEMPLATE');
3531
3664
  return;
3532
3665
  }
3533
3666
  /* -------------- Done -------------- */
@@ -3727,11 +3860,17 @@ const LogReviewer = (props) => {
3727
3860
  },
3728
3861
  ];
3729
3862
  // Create intelliTable
3730
- const dataTable = (React__default["default"].createElement(IntelliTable, { title: "Matching Logs", id: "logs", data: logs, columns: columns }));
3863
+ const dataTable = (logs.length === 0
3864
+ ? (React__default["default"].createElement(React__default["default"].Fragment, null,
3865
+ React__default["default"].createElement("h3", { className: "m-0" }, "Matching Logs:"),
3866
+ React__default["default"].createElement("div", { className: "alert alert-warning text-center" },
3867
+ React__default["default"].createElement("h4", { className: "m-1" }, "No Logs to Show"),
3868
+ React__default["default"].createElement("div", null, "Either your filters are too strict or no matching logs have been created yet."))))
3869
+ : (React__default["default"].createElement(IntelliTable, { title: "Matching Logs:", id: "logs", data: logs, columns: columns })));
3731
3870
  // Main body
3732
3871
  body = (React__default["default"].createElement(React__default["default"].Fragment, null,
3733
3872
  filters,
3734
- dataTable));
3873
+ React__default["default"].createElement("div", { className: "mt-2" }, dataTable)));
3735
3874
  }
3736
3875
  /* ---------- Wrap in Modal --------- */
3737
3876
  return (React__default["default"].createElement("div", { className: "LogReviewer-outer-container" },
@@ -3739,14 +3878,10 @@ const LogReviewer = (props) => {
3739
3878
  React__default["default"].createElement("div", { className: "LogReviewer-inner-container" },
3740
3879
  React__default["default"].createElement("div", { className: "LogReviewer-header" },
3741
3880
  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 }))),
3881
+ React__default["default"].createElement("h3", { className: "text-center m-0" }, "Log Review Dashboard")),
3882
+ React__default["default"].createElement("div", { style: { width: 0 } },
3883
+ 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 },
3884
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes })))),
3750
3885
  React__default["default"].createElement("div", { className: "LogReviewer-contents" }, body))));
3751
3886
  };
3752
3887
 
@@ -3892,7 +4027,7 @@ const padZerosLeft = (num, numDigits) => {
3892
4027
  * access to log review
3893
4028
  * @author Gabe Abrams
3894
4029
  */
3895
- const LOG_REVIEW_STATUS_ROUTE = `/admin${ROUTE_PATH_PREFIX}/logs/access`;
4030
+ const LOG_REVIEW_STATUS_ROUTE = `${ROUTE_PATH_PREFIX}/logs/access_allowed`;
3896
4031
 
3897
4032
  // Stored copy of caccl functions
3898
4033
  let _cacclGetLaunchInfo;
@@ -3934,7 +4069,7 @@ const internalGetLogCollection = () => {
3934
4069
  * @param opts.getLaunchInfo CACCL LTI's get launch info function
3935
4070
  * @param [opts.logCollection] mongo collection from dce-mango to use for
3936
4071
  * storing logs. If none is included, logs are written to the console
3937
- * @param [opts.logReviewAdmins=all admins] info on which admins can review
4072
+ * @param [opts.logReviewAdmins=all] info on which admins can review
3938
4073
  * logs from the client. If not included, all Canvas admins are allowed to
3939
4074
  * review logs. If null, no Canvas admins are allowed to review logs.
3940
4075
  * If an array of Canvas userIds (numbers), only Canvas admins with those
@@ -4017,71 +4152,81 @@ const initServer = (opts) => {
4017
4152
  /*----------------------------------------*/
4018
4153
  /* Log Reviewer */
4019
4154
  /*----------------------------------------*/
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;
4155
+ /**
4156
+ * Check if a given user is allowed to review logs
4157
+ * @author Gabe Abrams
4158
+ * @param userId the id of the user
4159
+ * @param isAdmin if true, the user is an admin
4160
+ * @returns true if the user can review logs
4161
+ */
4162
+ const canReviewLogs = (userId, isAdmin) => __awaiter(void 0, void 0, void 0, function* () {
4163
+ // Immediately deny access if user is not an admin
4164
+ if (!isAdmin) {
4165
+ return false;
4166
+ }
4167
+ // If all admins are allowed, we're done
4168
+ if (!opts.logReviewAdmins) {
4169
+ return true;
4170
+ }
4171
+ // Do a dynamic check
4172
+ try {
4173
+ // Array of userIds
4174
+ if (Array.isArray(opts.logReviewAdmins)) {
4175
+ return opts.logReviewAdmins.some((allowedId) => {
4176
+ return (userId === allowedId);
4177
+ });
4039
4178
  }
4040
- catch (err) {
4041
- // If an error occurred, simply return false
4042
- return false;
4179
+ // Must be a collection
4180
+ const matches = yield opts.logReviewAdmins.find({ userId });
4181
+ // Make sure at least one entry matches
4182
+ return matches.length > 0;
4183
+ }
4184
+ catch (err) {
4185
+ // If an error occurred, simply return false
4186
+ return false;
4187
+ }
4188
+ });
4189
+ /**
4190
+ * Check if the current user has access to logs
4191
+ * @author Gabe Abrams
4192
+ * @returns {boolean} true if user has access
4193
+ */
4194
+ opts.app.get(LOG_REVIEW_STATUS_ROUTE, genRouteHandler({
4195
+ handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
4196
+ const { userId, isAdmin } = params;
4197
+ const canReview = yield canReviewLogs(userId, isAdmin);
4198
+ return canReview;
4199
+ }),
4200
+ }));
4201
+ /**
4202
+ * Get all logs for a certain month
4203
+ * @author Gabe Abrams
4204
+ * @param {number} year the year to query (e.g. 2022)
4205
+ * @param {number} month the month to query (e.g. 1 = January)
4206
+ * @returns {Log[]} list of logs from the given month
4207
+ */
4208
+ opts.app.get(`${LOG_REVIEW_ROUTE_PATH_PREFIX}/years/:year/months/:month`, genRouteHandler({
4209
+ paramTypes: {
4210
+ year: ParamType$1.Int,
4211
+ month: ParamType$1.Int,
4212
+ },
4213
+ handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
4214
+ // Get user info
4215
+ const { year, month, userId, isAdmin, } = params;
4216
+ // Validate user
4217
+ const canReview = yield canReviewLogs(userId, isAdmin);
4218
+ if (!canReview) {
4219
+ throw new ErrorWithCode('You cannot access this resource because you do not have the appropriate permissions.', ReactKitErrorCode$1.NotAllowedToReviewLogs);
4043
4220
  }
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
- }
4221
+ // Query for logs
4222
+ const logs = yield _logCollection.find({
4223
+ year,
4224
+ month,
4225
+ });
4226
+ // Return logs
4227
+ return logs;
4228
+ }),
4229
+ }));
4085
4230
  };
4086
4231
 
4087
4232
  // Import shared types
@@ -4381,18 +4526,6 @@ const parseUserAgent = (userAgent) => {
4381
4526
  };
4382
4527
  };
4383
4528
 
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
4529
  /**
4397
4530
  * Generate an express API route handler
4398
4531
  * @author Gabe Abrams
@@ -4765,7 +4898,7 @@ const genRouteHandler = (opts) => {
4765
4898
  }
4766
4899
  : {
4767
4900
  type: LogType$1.Action,
4768
- target: ((_m = opts.target) !== null && _m !== void 0 ? _m : LogBuiltInMetadata.Target.NoSpecificTarget),
4901
+ target: ((_m = opts.target) !== null && _m !== void 0 ? _m : LogBuiltInMetadata.Target.NoTarget),
4769
4902
  action: ((_o = opts.action) !== null && _o !== void 0 ? _o : LogAction$1.Unknown),
4770
4903
  });
4771
4904
  // Source-specific info
@@ -5132,6 +5265,31 @@ const initLogCollection = (Collection) => {
5132
5265
  });
5133
5266
  };
5134
5267
 
5268
+ // Cache user's ability
5269
+ let canReview = undefined;
5270
+ /**
5271
+ * Check if the current user can review logs
5272
+ * @author Gabe Abrams
5273
+ * @returns true if current user can review logs
5274
+ */
5275
+ const canReviewLogs = () => __awaiter(void 0, void 0, void 0, function* () {
5276
+ // If cached, use that value
5277
+ if (canReview !== undefined) {
5278
+ return canReview;
5279
+ }
5280
+ // Ask on server
5281
+ try {
5282
+ canReview = !!(yield visitServerEndpoint({
5283
+ path: LOG_REVIEW_STATUS_ROUTE,
5284
+ method: 'GET',
5285
+ }));
5286
+ }
5287
+ catch (err) {
5288
+ canReview = false;
5289
+ }
5290
+ return canReview;
5291
+ });
5292
+
5135
5293
  /**
5136
5294
  * Days of the week
5137
5295
  * @author Gabe Abrams
@@ -5184,6 +5342,7 @@ exports.Variant = Variant$1;
5184
5342
  exports.abbreviate = abbreviate;
5185
5343
  exports.alert = alert$1;
5186
5344
  exports.avg = avg;
5345
+ exports.canReviewLogs = canReviewLogs;
5187
5346
  exports.ceilToNumDecimals = ceilToNumDecimals;
5188
5347
  exports.confirm = confirm;
5189
5348
  exports.floorToNumDecimals = floorToNumDecimals;