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/esm/index.js CHANGED
@@ -511,17 +511,29 @@ const LOG_ROUTE_PATH = `${ROUTE_PATH_PREFIX}/log`;
511
511
  const LogBuiltInMetadata = {
512
512
  // Contexts
513
513
  Context: {
514
- Uncategorized: 'n/a',
515
- ServerRenderedErrorPage: '_server-rendered-error-page',
516
- ServerEndpointError: '_server-endpoint-error',
517
- ClientFatalError: '_client-fatal-error',
514
+ Uncategorized: 'Uncategorized',
515
+ ServerRenderedErrorPage: 'ServerRenderedErrorPage',
516
+ ServerEndpointError: 'ServerEndpointError',
517
+ ClientFatalError: 'ClientFatalError',
518
518
  },
519
519
  // Targets
520
520
  Target: {
521
- NoSpecificTarget: 'n/a',
521
+ NoTarget: 'NoTarget',
522
522
  },
523
523
  };
524
524
 
525
+ /**
526
+ * Allowed log levels
527
+ * @author Gabe Abrams
528
+ */
529
+ var LogLevel;
530
+ (function (LogLevel) {
531
+ LogLevel["Warn"] = "Warn";
532
+ LogLevel["Info"] = "Info";
533
+ LogLevel["Debug"] = "Debug";
534
+ })(LogLevel || (LogLevel = {}));
535
+ var LogLevel$1 = LogLevel;
536
+
525
537
  // Keep track of whether or not session expiry has already been handled
526
538
  let sessionAlreadyExpired = false;
527
539
  /*------------------------------------------------------------------------*/
@@ -640,7 +652,7 @@ const visitServerEndpoint = (opts) => __awaiter(void 0, void 0, void 0, function
640
652
  * @author Gabe Abrams
641
653
  */
642
654
  const logClientEvent = (opts) => __awaiter(void 0, void 0, void 0, function* () {
643
- var _a, _b, _c, _d, _e, _f;
655
+ var _a, _b, _c, _d, _e, _f, _g;
644
656
  return visitServerEndpoint({
645
657
  path: LOG_ROUTE_PATH,
646
658
  method: 'POST',
@@ -649,8 +661,9 @@ const logClientEvent = (opts) => __awaiter(void 0, void 0, void 0, function* ()
649
661
  ? opts.context
650
662
  : ((_b = ((_a = opts.context) !== null && _a !== void 0 ? _a : {})._) !== null && _b !== void 0 ? _b : LogBuiltInMetadata.Context.Uncategorized)),
651
663
  subcontext: ((_c = opts.subcontext) !== null && _c !== void 0 ? _c : LogBuiltInMetadata.Context.Uncategorized),
652
- tags: JSON.stringify((_d = opts.tags) !== null && _d !== void 0 ? _d : []),
653
- metadata: JSON.stringify((_e = opts.metadata) !== null && _e !== void 0 ? _e : {}),
664
+ level: ((_d = opts.level) !== null && _d !== void 0 ? _d : LogLevel$1.Info),
665
+ tags: JSON.stringify((_e = opts.tags) !== null && _e !== void 0 ? _e : []),
666
+ metadata: JSON.stringify((_f = opts.metadata) !== null && _f !== void 0 ? _f : {}),
654
667
  errorMessage: (opts.error
655
668
  ? opts.error.message
656
669
  : undefined),
@@ -661,7 +674,7 @@ const logClientEvent = (opts) => __awaiter(void 0, void 0, void 0, function* ()
661
674
  ? opts.error.stack
662
675
  : undefined),
663
676
  target: (opts.action
664
- ? ((_f = opts.target) !== null && _f !== void 0 ? _f : LogBuiltInMetadata.Target.NoSpecificTarget)
677
+ ? ((_g = opts.target) !== null && _g !== void 0 ? _g : LogBuiltInMetadata.Target.NoTarget)
665
678
  : undefined),
666
679
  action: (opts.action
667
680
  ? opts.action
@@ -1167,14 +1180,14 @@ const ButtonInputGroup = (props) => {
1167
1180
  /*------------------------------------------------------------------------*/
1168
1181
  /* -------------- Props ------------- */
1169
1182
  // Destructure all props
1170
- const { label, minLabelWidth, children, } = props;
1183
+ const { label, minLabelWidth, children, className, } = props;
1171
1184
  /*------------------------------------------------------------------------*/
1172
1185
  /* Render */
1173
1186
  /*------------------------------------------------------------------------*/
1174
1187
  /*----------------------------------------*/
1175
1188
  /* Main UI */
1176
1189
  /*----------------------------------------*/
1177
- return (React.createElement("div", { className: "input-group" },
1190
+ return (React.createElement("div", { className: `input-group ${className !== null && className !== void 0 ? className : ''}` },
1178
1191
  React.createElement("div", { className: "input-group-prepend d-flex w-100" },
1179
1192
  React.createElement("span", { className: "input-group-text", style: {
1180
1193
  minWidth: (minLabelWidth !== null && minLabelWidth !== void 0 ? minLabelWidth : undefined),
@@ -1344,12 +1357,25 @@ const SimpleDateChooser = (props) => {
1344
1357
  // Figure out which days are allowed
1345
1358
  const days = [];
1346
1359
  const numDaysInMonth = (new Date(year, month, 0)).getDate();
1347
- const firstDay = (month === today.month
1348
- ? today.day // Current month: start at current date
1349
- : 1 // Future month: start at beginning of month
1350
- );
1351
- for (let day = firstDay; day <= numDaysInMonth; day++) {
1352
- days.push(day);
1360
+ if (chooseFromPast) {
1361
+ // Past selection
1362
+ const numDaysToAdd = ((month === today.month)
1363
+ ? today.day // Current month, only add up to today
1364
+ : numDaysInMonth // Past month, add all days
1365
+ );
1366
+ for (let day = 1; day <= numDaysToAdd; day++) {
1367
+ days.push(day);
1368
+ }
1369
+ }
1370
+ else {
1371
+ // Future selection: add all remaining days of the month
1372
+ const firstDay = (month === today.month
1373
+ ? today.day // Current month: start at current date
1374
+ : 1 // Future month: start at beginning of month
1375
+ );
1376
+ for (let day = firstDay; day <= numDaysInMonth; day++) {
1377
+ days.push(day);
1378
+ }
1353
1379
  }
1354
1380
  choices.push({
1355
1381
  choiceName: `${monthName} ${year}`,
@@ -1890,24 +1916,25 @@ const CopiableBox = (props) => {
1890
1916
  /**
1891
1917
  * Reusable nested item picker
1892
1918
  * @author Yuen Ler Chow
1919
+ * @author Gabe Abrams
1893
1920
  */
1894
1921
  /* ------------- Actions ------------ */
1895
1922
  // Types of actions
1896
1923
  var ActionType$2;
1897
1924
  (function (ActionType) {
1898
- // Toggle whether the children are being shown
1899
- ActionType["ToggleItems"] = "toggle-items";
1925
+ // Toggle whether a child are being shown
1926
+ ActionType["ToggleChild"] = "toggle-child";
1900
1927
  })(ActionType$2 || (ActionType$2 = {}));
1901
1928
  /**
1902
1929
  * Reducer that executes actions
1903
- * @author Yuen Ler Chow
1930
+ * @author Gabe Abrams
1904
1931
  * @param state current state
1905
1932
  * @param action action to execute
1906
1933
  */
1907
1934
  const reducer$2 = (state, action) => {
1908
1935
  switch (action.type) {
1909
- case ActionType$2.ToggleItems: {
1910
- return { isShowingItems: !state.isShowingItems };
1936
+ case ActionType$2.ToggleChild: {
1937
+ return Object.assign(Object.assign({}, state), { childExpanded: Object.assign(Object.assign({}, state.childExpanded), { [String(action.id)]: !state.childExpanded[String(action.id)] }) });
1911
1938
  }
1912
1939
  default: {
1913
1940
  return state;
@@ -1925,14 +1952,19 @@ const NestableItemList = (props) => {
1925
1952
  // Destructure all props
1926
1953
  const { items, onChanged, } = props;
1927
1954
  /* -------------- State ------------- */
1955
+ // Create initial map of child expanded booleans
1956
+ const initChildExpanded = {};
1957
+ items.forEach((item) => {
1958
+ initChildExpanded[String(item.id)] = false;
1959
+ });
1928
1960
  // Initial state
1929
1961
  const initialState = {
1930
- isShowingItems: false,
1962
+ childExpanded: initChildExpanded,
1931
1963
  };
1932
1964
  // Initialize state
1933
1965
  const [state, dispatch] = useReducer(reducer$2, initialState);
1934
1966
  // Destructure common state
1935
- const { isShowingItems, } = state;
1967
+ const { childExpanded, } = state;
1936
1968
  /*------------------------------------------------------------------------*/
1937
1969
  /* Component Functions */
1938
1970
  /*------------------------------------------------------------------------*/
@@ -2020,14 +2052,15 @@ const NestableItemList = (props) => {
2020
2052
  backgroundColor: 'transparent',
2021
2053
  }, type: "button", onClick: () => {
2022
2054
  dispatch({
2023
- type: ActionType$2.ToggleItems,
2055
+ type: ActionType$2.ToggleChild,
2056
+ id: item.id,
2024
2057
  });
2025
- }, "aria-label": `${isShowingItems ? 'Hide' : 'Show'} items in ${item.name}` },
2026
- React.createElement(FontAwesomeIcon, { icon: isShowingItems ? faChevronDown : faChevronRight })))),
2058
+ }, "aria-label": `${childExpanded[item.id] ? 'Hide' : 'Show'} items in ${item.name}` },
2059
+ React.createElement(FontAwesomeIcon, { icon: childExpanded[item.id] ? faChevronDown : faChevronRight })))),
2027
2060
  React.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) => {
2028
2061
  onChanged(changeChecked(item.id, checked, items));
2029
2062
  }, ariaLabel: `Select ${item.name}`, checkedVariant: Variant$1.Light }),
2030
- item.isGroup && isShowingItems && (React.createElement("div", { className: "NestableItemList-children-container", style: {
2063
+ (item.isGroup && childExpanded[item.id]) && (React.createElement("div", { className: "NestableItemList-children-container", style: {
2031
2064
  paddingLeft: '2.2rem',
2032
2065
  } },
2033
2066
  React.createElement(NestableItemList, { items: item.children, onChanged: (updatedItems) => {
@@ -2049,7 +2082,7 @@ const ItemPicker = (props) => {
2049
2082
  /*------------------------------------------------------------------------*/
2050
2083
  /* -------------- Props ------------- */
2051
2084
  // Destructure all props
2052
- const { title, items, onChanged, } = props;
2085
+ const { title, items, onChanged, noBottomMargin, } = props;
2053
2086
  /*------------------------------------------------------------------------*/
2054
2087
  /* Component Functions */
2055
2088
  /*------------------------------------------------------------------------*/
@@ -2059,7 +2092,7 @@ const ItemPicker = (props) => {
2059
2092
  /*----------------------------------------*/
2060
2093
  /* Main UI */
2061
2094
  /*----------------------------------------*/
2062
- return (React.createElement(TabBox, { title: title },
2095
+ return (React.createElement(TabBox, { title: title, noBottomMargin: noBottomMargin },
2063
2096
  React.createElement("div", { style: { overflowX: 'auto' } },
2064
2097
  React.createElement(NestableItemList, { items: items, onChanged: onChanged }))));
2065
2098
  };
@@ -2376,9 +2409,9 @@ const IntelliTable = (props) => {
2376
2409
  // Create the cell UI
2377
2410
  return (React.createElement("th", { key: column.param, scope: "col", id: `IntelliTable-${id}-header-${column.param}` },
2378
2411
  React.createElement("div", { className: "d-flex align-items-center justify-content-center flex-row h-100" },
2379
- React.createElement("h4", { className: "m-0" }, column.title),
2412
+ React.createElement("span", { className: "text-nowrap" }, column.title),
2380
2413
  React.createElement("div", null,
2381
- React.createElement("button", { type: "button", className: "btn btn-light", "aria-label": sortButtonAriaLabel, onClick: () => {
2414
+ React.createElement("button", { type: "button", className: "btn btn-light btn-sm ms-1", "aria-label": sortButtonAriaLabel, onClick: () => {
2382
2415
  dispatch({
2383
2416
  type: ActionType$1.ToggleSortColumn,
2384
2417
  param: column.param,
@@ -2526,7 +2559,7 @@ const IntelliTable = (props) => {
2526
2559
  React.createElement("h3", { className: "m-0" }, title),
2527
2560
  React.createElement("div", { className: "flex-grow-1 text-end" },
2528
2561
  React.createElement(CSVDownloadButton, { "aria-label": `download data as csv for ${title}`, id: `IntelliTable-${id}-download-as-csv`, filename: `${title}.csv`, csv: csv }),
2529
- React.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: () => {
2562
+ React.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: () => {
2530
2563
  dispatch({
2531
2564
  type: ActionType$1.ToggleColVisCusModalVisibility,
2532
2565
  });
@@ -2590,6 +2623,11 @@ const style = `
2590
2623
  border: 0.05rem solid black;
2591
2624
  border-radius: 0.5rem;
2592
2625
  overflow: hidden;
2626
+ padding: 0.7rem;
2627
+
2628
+ /* Solid background */
2629
+ background-color: white;
2630
+ color: black;
2593
2631
 
2594
2632
  /* Place contents in flex column */
2595
2633
  flex-direction: column;
@@ -2616,6 +2654,27 @@ const style = `
2616
2654
  /* Vertical scroll */
2617
2655
  overflow-y: auto;
2618
2656
  }
2657
+
2658
+ .LogReviewer-header-close-button {
2659
+ border: 0 !important;
2660
+ background-color: transparent !important;
2661
+ padding-top: 0 !important;
2662
+ padding-bottom: 0 !important;
2663
+ padding-right: 1em !important;
2664
+ margin: 0 !important;
2665
+ color: #444 !important;
2666
+
2667
+ right: 0 !important;
2668
+ position: absolute !important;
2669
+ }
2670
+ .LogReviewer-header-close-button:hover {
2671
+ border: 0 !important;
2672
+ background-color: transparent !important;
2673
+ padding-top: 0 !important;
2674
+ padding-bottom: 0 !important;
2675
+ margin: 0 !important;
2676
+ color: #000 !important;
2677
+ }
2619
2678
  `;
2620
2679
  /*------------------------------------------------------------------------*/
2621
2680
  /* Static Functions */
@@ -2635,7 +2694,7 @@ const genHumanReadableName = (machineReadableName) => {
2635
2694
  // Uppercase! Add a space before
2636
2695
  humanReadableName += ' ';
2637
2696
  }
2638
- humanReadableName += chars;
2697
+ humanReadableName += char;
2639
2698
  });
2640
2699
  // Trim and return
2641
2700
  return humanReadableName.trim();
@@ -2697,7 +2756,19 @@ const reducer = (state, action) => {
2697
2756
  return Object.assign(Object.assign({}, state), { contextFilterState: action.contextFilterState });
2698
2757
  }
2699
2758
  case ActionType.UpdateTagFilterState: {
2700
- return Object.assign(Object.assign({}, state), { tagFilterState: action.tagFilterState });
2759
+ const { tagFilterState } = action;
2760
+ // Select all if every tag is deselected
2761
+ const numTagsSelected = (Object.values(tagFilterState)
2762
+ .filter((isSelected) => {
2763
+ return isSelected;
2764
+ })
2765
+ .length);
2766
+ if (numTagsSelected === 0) {
2767
+ Object.keys(tagFilterState).forEach((tag) => {
2768
+ tagFilterState[tag] = true;
2769
+ });
2770
+ }
2771
+ return Object.assign(Object.assign({}, state), { tagFilterState });
2701
2772
  }
2702
2773
  case ActionType.UpdateActionErrorFilterState: {
2703
2774
  return Object.assign(Object.assign({}, state), { actionErrorFilterState: action.actionErrorFilterState });
@@ -2717,10 +2788,33 @@ const LogReviewer = (props) => {
2717
2788
  /*------------------------------------------------------------------------*/
2718
2789
  /* Setup */
2719
2790
  /*------------------------------------------------------------------------*/
2720
- var _a, _b, _c, _d, _e;
2791
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
2721
2792
  /* -------------- Props ------------- */
2722
2793
  // Destructure props
2723
2794
  const { LogMetadata, onClose, } = props;
2795
+ // Add built-in LogMetadata
2796
+ // > Add "uncategorized" subcontext to each context
2797
+ Object.keys((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {}).forEach((context) => {
2798
+ if (LogMetadata.Context
2799
+ // Context has children already
2800
+ && typeof LogMetadata.Context[context] !== 'string') {
2801
+ LogMetadata.Context[context][LogBuiltInMetadata.Context.Uncategorized] = (LogBuiltInMetadata.Context.Uncategorized);
2802
+ }
2803
+ });
2804
+ // > Add built-in contexts
2805
+ LogMetadata.Context = ((_b = LogMetadata.Context) !== null && _b !== void 0 ? _b : {});
2806
+ Object.keys(LogBuiltInMetadata.Context).forEach((context) => {
2807
+ if (LogMetadata.Context) {
2808
+ LogMetadata.Context[context] = context;
2809
+ }
2810
+ });
2811
+ // > Add built-in targets
2812
+ LogMetadata.Target = ((_c = LogMetadata.Target) !== null && _c !== void 0 ? _c : {});
2813
+ Object.keys(LogBuiltInMetadata.Target).forEach((target) => {
2814
+ if (LogMetadata.Target) {
2815
+ LogMetadata.Target[target] = target;
2816
+ }
2817
+ });
2724
2818
  /* -------------- State ------------- */
2725
2819
  // Create initial date filter state
2726
2820
  const today = getTimeInfoInET();
@@ -2740,7 +2834,7 @@ const LogReviewer = (props) => {
2740
2834
  };
2741
2835
  // Create initial context filter state
2742
2836
  const initContextFilterState = {};
2743
- Object.keys((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {}).forEach((context) => {
2837
+ Object.keys((_d = LogMetadata.Context) !== null && _d !== void 0 ? _d : {}).forEach((context) => {
2744
2838
  var _a, _b;
2745
2839
  const contextValue = ((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {})[context];
2746
2840
  if (typeof contextValue === 'string') {
@@ -2758,7 +2852,7 @@ const LogReviewer = (props) => {
2758
2852
  });
2759
2853
  // Create initial tag filter state
2760
2854
  const initTagFilterState = {};
2761
- Object.values((_b = LogMetadata.Tag) !== null && _b !== void 0 ? _b : {}).forEach((tagValue) => {
2855
+ Object.values((_e = LogMetadata.Tag) !== null && _e !== void 0 ? _e : {}).forEach((tagValue) => {
2762
2856
  initTagFilterState[tagValue] = true;
2763
2857
  });
2764
2858
  // Create advanced filter state
@@ -2785,12 +2879,16 @@ const LogReviewer = (props) => {
2785
2879
  target: {},
2786
2880
  action: {},
2787
2881
  };
2788
- Object.values((_c = LogMetadata.Target) !== null && _c !== void 0 ? _c : {}).forEach((target) => {
2882
+ Object.values((_f = LogMetadata.Target) !== null && _f !== void 0 ? _f : {}).forEach((target) => {
2789
2883
  initActionErrorFilterState.target[target] = true;
2790
2884
  });
2791
2885
  Object.values(LogAction$1).forEach((action) => {
2792
2886
  initActionErrorFilterState.action[action] = true;
2793
2887
  });
2888
+ // Add built-in targets
2889
+ Object.values(LogBuiltInMetadata.Target).forEach((target) => {
2890
+ initActionErrorFilterState.target[target] = true;
2891
+ });
2794
2892
  // Initial state
2795
2893
  const initialState = {
2796
2894
  loading: true,
@@ -2824,15 +2922,18 @@ const LogReviewer = (props) => {
2824
2922
  let month = newDateFilterState.startDate.month;
2825
2923
  while (
2826
2924
  // Earlier year
2827
- (year <= newDateFilterState.endDate.year)
2925
+ (year < newDateFilterState.endDate.year)
2828
2926
  // Current year but included month
2829
2927
  || (year === newDateFilterState.endDate.year
2830
2928
  && month <= newDateFilterState.endDate.month)) {
2831
- // Add to list
2832
- toLoad.push({
2833
- year,
2834
- month,
2835
- });
2929
+ // Add to list if not already loaded
2930
+ if (!logMap[year]
2931
+ || !logMap[year][month]) {
2932
+ toLoad.push({
2933
+ year,
2934
+ month,
2935
+ });
2936
+ }
2836
2937
  // Increment
2837
2938
  month += 1;
2838
2939
  if (month > 12) {
@@ -2856,6 +2957,7 @@ const LogReviewer = (props) => {
2856
2957
  });
2857
2958
  // Check which year/month combos we need to load
2858
2959
  const toLoad = listMonthsToLoad(newDateFilterState);
2960
+ console.log('Filter update:', newDateFilterState, toLoad, logMap);
2859
2961
  // If nothing to load, finished
2860
2962
  if (toLoad.length === 0) {
2861
2963
  return;
@@ -2920,10 +3022,10 @@ const LogReviewer = (props) => {
2920
3022
  /* Filters */
2921
3023
  /*----------------------------------------*/
2922
3024
  // Filter toggle
2923
- const filterToggles = (React.createElement("div", { className: "LogReviewer-filter-toggles d-flex align-items-center justify-content-center" },
3025
+ const filterToggles = (React.createElement("div", { className: "LogReviewer-filter-toggles" },
2924
3026
  React.createElement("h3", { className: "m-0" }, "Filters:"),
2925
- React.createElement("div", { className: "LogReviewer-filter-toggle-buttons" },
2926
- React.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: () => {
3027
+ React.createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
3028
+ React.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: () => {
2927
3029
  dispatch({
2928
3030
  type: ActionType.ToggleFilterDrawer,
2929
3031
  filterDrawer: FilterDrawer.Date,
@@ -2931,7 +3033,7 @@ const LogReviewer = (props) => {
2931
3033
  } },
2932
3034
  React.createElement(FontAwesomeIcon, { icon: faCalendar, className: "me-2" }),
2933
3035
  "Date"),
2934
- React.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: () => {
3036
+ React.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: () => {
2935
3037
  dispatch({
2936
3038
  type: ActionType.ToggleFilterDrawer,
2937
3039
  filterDrawer: FilterDrawer.Context,
@@ -2939,15 +3041,15 @@ const LogReviewer = (props) => {
2939
3041
  } },
2940
3042
  React.createElement(FontAwesomeIcon, { icon: faCircle, className: "me-2" }),
2941
3043
  "Context"),
2942
- React.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: () => {
3044
+ (LogMetadata.Tag && Object.keys(LogMetadata.Tag).length > 0) && (React.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: () => {
2943
3045
  dispatch({
2944
3046
  type: ActionType.ToggleFilterDrawer,
2945
3047
  filterDrawer: FilterDrawer.Tag,
2946
3048
  });
2947
3049
  } },
2948
3050
  React.createElement(FontAwesomeIcon, { icon: faTag, className: "me-2" }),
2949
- "Tag"),
2950
- React.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: () => {
3051
+ "Tag")),
3052
+ React.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: () => {
2951
3053
  dispatch({
2952
3054
  type: ActionType.ToggleFilterDrawer,
2953
3055
  filterDrawer: FilterDrawer.Action,
@@ -2955,7 +3057,7 @@ const LogReviewer = (props) => {
2955
3057
  } },
2956
3058
  React.createElement(FontAwesomeIcon, { icon: faHammer, className: "me-2" }),
2957
3059
  "Action"),
2958
- React.createElement("button", { type: "button", id: "LogReviewer-toggle-advanced-filter-drawer", className: `btn btn-${FilterDrawer.Advanced === expandedFilterDrawer}`, "aria-label": "toggle advanced filter drawer", onClick: () => {
3060
+ React.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: () => {
2959
3061
  dispatch({
2960
3062
  type: ActionType.ToggleFilterDrawer,
2961
3063
  filterDrawer: FilterDrawer.Advanced,
@@ -2968,25 +3070,29 @@ const LogReviewer = (props) => {
2968
3070
  if (expandedFilterDrawer) {
2969
3071
  if (expandedFilterDrawer === FilterDrawer.Date) {
2970
3072
  filterDrawer = (React.createElement(TabBox, { title: "Date" },
2971
- React.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) => {
2972
- dispatch({
2973
- type: ActionType.UpdateDateFilterState,
2974
- dateFilterState: Object.assign(Object.assign({}, dateFilterState), { startDate: { month, day, year } }),
2975
- });
3073
+ React.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) => {
3074
+ dateFilterState.startDate = { month, day, year };
3075
+ handleDateRangeUpdated(dateFilterState);
2976
3076
  } }),
2977
3077
  ' ',
2978
3078
  "to",
2979
3079
  ' ',
2980
- React.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) => {
2981
- dispatch({
2982
- type: ActionType.UpdateDateFilterState,
2983
- dateFilterState: Object.assign(Object.assign({}, dateFilterState), { endDate: { month, day, year } }),
2984
- });
3080
+ React.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) => {
3081
+ if (year < dateFilterState.startDate.year
3082
+ || (year === dateFilterState.startDate.year
3083
+ && month < dateFilterState.startDate.month)
3084
+ || (year === dateFilterState.startDate.year
3085
+ && month === dateFilterState.startDate.month
3086
+ && day < dateFilterState.startDate.day)) {
3087
+ return alert$1('Invalid Start Date', 'The start date cannot be before the end date.');
3088
+ }
3089
+ dateFilterState.endDate = { month, day, year };
3090
+ handleDateRangeUpdated(dateFilterState);
2985
3091
  } })));
2986
3092
  }
2987
3093
  else if (expandedFilterDrawer === FilterDrawer.Context) {
2988
3094
  // Create item picker items
2989
- const pickableItems = (Object.keys((_d = LogMetadata.Context) !== null && _d !== void 0 ? _d : {})
3095
+ const pickableItems = (Object.keys((_g = LogMetadata.Context) !== null && _g !== void 0 ? _g : {})
2990
3096
  .map((context) => {
2991
3097
  var _a;
2992
3098
  const value = ((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {})[context];
@@ -3007,7 +3113,7 @@ const LogReviewer = (props) => {
3007
3113
  })
3008
3114
  .map((subcontext) => {
3009
3115
  return {
3010
- id: `${context}-${subcontext}`,
3116
+ id: subcontext,
3011
3117
  name: genHumanReadableName(subcontext),
3012
3118
  isGroup: false,
3013
3119
  checked: !!value[subcontext],
@@ -3015,7 +3121,7 @@ const LogReviewer = (props) => {
3015
3121
  }));
3016
3122
  const item = {
3017
3123
  id: context,
3018
- name: context,
3124
+ name: genHumanReadableName(context),
3019
3125
  isGroup: true,
3020
3126
  children,
3021
3127
  };
@@ -3023,6 +3129,8 @@ const LogReviewer = (props) => {
3023
3129
  }));
3024
3130
  // Create filter UI
3025
3131
  filterDrawer = (React.createElement(ItemPicker, { title: "Context", items: pickableItems, onChanged: (updatedItems) => {
3132
+ console.log('Items before:', pickableItems);
3133
+ console.log('Updated Items:', updatedItems);
3026
3134
  // Update our state
3027
3135
  updatedItems.forEach((pickableItem) => {
3028
3136
  if (pickableItem.isGroup) {
@@ -3079,7 +3187,7 @@ const LogReviewer = (props) => {
3079
3187
  }, ariaLabel: "only show error logs", selected: actionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true })),
3080
3188
  (actionErrorFilterState.type === undefined
3081
3189
  || actionErrorFilterState.type === LogType$1.Action) && (React.createElement(TabBox, { title: "Action Log Details" },
3082
- React.createElement(ButtonInputGroup, { label: "Action" }, Object.keys(LogAction$1)
3190
+ React.createElement(ButtonInputGroup, { label: "Action", className: "mb-2" }, Object.keys(LogAction$1)
3083
3191
  .map((action, i) => {
3084
3192
  const description = genHumanReadableName(action);
3085
3193
  return (React.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) => {
@@ -3090,18 +3198,20 @@ const LogReviewer = (props) => {
3090
3198
  });
3091
3199
  } }));
3092
3200
  })),
3093
- React.createElement(ButtonInputGroup, { label: "Target" }, Object.keys((_e = LogMetadata.Target) !== null && _e !== void 0 ? _e : {})
3094
- .map((target, i) => {
3095
- var _a;
3096
- const description = genHumanReadableName(target);
3097
- return (React.createElement(CheckboxButton, { id: `LogReviewer-target-${target}-checkbox`, text: description, ariaLabel: `include logs with target "${description}" in results`, onChanged: (checked) => {
3098
- actionErrorFilterState.target[target] = checked;
3099
- dispatch({
3100
- type: ActionType.UpdateActionErrorFilterState,
3101
- actionErrorFilterState,
3102
- });
3103
- }, noMarginOnRight: i === Object.keys((_a = LogMetadata.Target) !== null && _a !== void 0 ? _a : {}).length - 1 }));
3104
- })))),
3201
+ React.createElement(ButtonInputGroup, { label: "Target" },
3202
+ (Object.keys((_h = LogMetadata.Target) !== null && _h !== void 0 ? _h : {}).length === 0) && (React.createElement("div", null, "This app does not have any targets yet.")),
3203
+ Object.keys((_j = LogMetadata.Target) !== null && _j !== void 0 ? _j : {})
3204
+ .map((target, i) => {
3205
+ var _a;
3206
+ const description = genHumanReadableName(target);
3207
+ return (React.createElement(CheckboxButton, { id: `LogReviewer-target-${target}-checkbox`, text: description, ariaLabel: `include logs with target "${description}" in results`, onChanged: (checked) => {
3208
+ actionErrorFilterState.target[target] = checked;
3209
+ dispatch({
3210
+ type: ActionType.UpdateActionErrorFilterState,
3211
+ actionErrorFilterState,
3212
+ });
3213
+ }, noMarginOnRight: i === Object.keys((_a = LogMetadata.Target) !== null && _a !== void 0 ? _a : {}).length - 1 }));
3214
+ })))),
3105
3215
  (actionErrorFilterState.type === undefined
3106
3216
  || actionErrorFilterState.type === LogType$1.Error) && (React.createElement(TabBox, { title: "Error Log Details" },
3107
3217
  React.createElement("div", { className: "input-group mb-2" },
@@ -3263,26 +3373,27 @@ const LogReviewer = (props) => {
3263
3373
  advancedFilterState,
3264
3374
  });
3265
3375
  }, noMarginOnRight: true })),
3266
- React.createElement("div", { className: "input-group mb-2" },
3267
- React.createElement("span", { className: "input-group-text" }, "Server Route Path"),
3268
- React.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) => {
3269
- advancedFilterState.courseName = ((e.target.value)
3270
- .trim());
3271
- dispatch({
3272
- type: ActionType.UpdateAdvancedFilterState,
3273
- advancedFilterState,
3274
- });
3275
- } })),
3276
- React.createElement("div", { className: "input-group mb-2" },
3277
- React.createElement("span", { className: "input-group-text" }, "Server Route Template"),
3278
- React.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) => {
3279
- advancedFilterState.courseName = ((e.target.value)
3280
- .trim());
3281
- dispatch({
3282
- type: ActionType.UpdateAdvancedFilterState,
3283
- advancedFilterState,
3284
- });
3285
- } })))));
3376
+ advancedFilterState.source !== LogSource$1.Client && (React.createElement("div", { className: "mt-2" },
3377
+ React.createElement("div", { className: "input-group mb-2" },
3378
+ React.createElement("span", { className: "input-group-text" }, "Server Route Path"),
3379
+ React.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) => {
3380
+ advancedFilterState.courseName = ((e.target.value)
3381
+ .trim());
3382
+ dispatch({
3383
+ type: ActionType.UpdateAdvancedFilterState,
3384
+ advancedFilterState,
3385
+ });
3386
+ } })),
3387
+ React.createElement("div", { className: "input-group mb-2" },
3388
+ React.createElement("span", { className: "input-group-text" }, "Server Route Template"),
3389
+ React.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) => {
3390
+ advancedFilterState.courseName = ((e.target.value)
3391
+ .trim());
3392
+ dispatch({
3393
+ type: ActionType.UpdateAdvancedFilterState,
3394
+ advancedFilterState,
3395
+ });
3396
+ } })))))));
3286
3397
  }
3287
3398
  }
3288
3399
  // Filters UI
@@ -3293,10 +3404,11 @@ const LogReviewer = (props) => {
3293
3404
  // > Perform filters
3294
3405
  const logs = [];
3295
3406
  Object.keys(logMap).forEach((year) => {
3296
- Object.keys(logMap).forEach((month) => {
3407
+ Object.keys(logMap[year]).forEach((month) => {
3297
3408
  logMap[year][month].forEach((log) => {
3298
- /* ----------- Date Filter ---------- */
3299
3409
  var _a;
3410
+ /* ----------- Date Filter ---------- */
3411
+ console.log('Log:', log);
3300
3412
  // Before start date
3301
3413
  if (
3302
3414
  // Previous year
@@ -3308,6 +3420,7 @@ const LogReviewer = (props) => {
3308
3420
  || ((log.year === dateFilterState.startDate.year)
3309
3421
  && (log.month === dateFilterState.startDate.month)
3310
3422
  && (log.day < dateFilterState.startDate.day))) {
3423
+ console.log('RULED OUT: START');
3311
3424
  return;
3312
3425
  }
3313
3426
  // After end date
@@ -3321,6 +3434,7 @@ const LogReviewer = (props) => {
3321
3434
  || ((log.year === dateFilterState.endDate.year)
3322
3435
  && (log.month === dateFilterState.endDate.month)
3323
3436
  && (log.day > dateFilterState.endDate.day))) {
3437
+ console.log('RULED OUT: END');
3324
3438
  return;
3325
3439
  }
3326
3440
  /* --------- Context Filter --------- */
@@ -3333,29 +3447,30 @@ const LogReviewer = (props) => {
3333
3447
  .every((isSelected) => {
3334
3448
  return !isSelected;
3335
3449
  }))) {
3450
+ console.log('RULED OUT: CONTEXT');
3336
3451
  return;
3337
3452
  }
3338
3453
  // Subcontext doesn't match
3339
3454
  if (
3340
- // Log has a subcontext
3341
- log.subcontext
3455
+ // Log context is not "uncategorized" (no point in further filters)
3456
+ log.context !== LogBuiltInMetadata.Context.Uncategorized
3457
+ // Log has a subcontext
3458
+ && log.subcontext
3342
3459
  // Context has subcontexts
3343
3460
  && (contextFilterState[log.context]
3344
3461
  && contextFilterState[log.context] !== false
3345
3462
  && contextFilterState[log.context] !== true)
3346
3463
  // Subcontext is not selected
3347
3464
  && !contextFilterState[log.context][log.subcontext]) {
3465
+ console.log('RULED OUT: SUBCONTEXT');
3348
3466
  return;
3349
3467
  }
3350
3468
  /* -------------- Tags -------------- */
3351
3469
  // No tags match
3352
- if (
3353
- // Log has at least one tag
3354
- log.tags.length > 0
3355
- // No tags match
3356
- && (log.tags.every((tag) => {
3357
- return !tagFilterState[tag];
3358
- }))) {
3470
+ if (log.tags.every((tag) => {
3471
+ return !tagFilterState[tag];
3472
+ })) {
3473
+ console.log('RULED OUT: TAGS');
3359
3474
  return;
3360
3475
  }
3361
3476
  /* ------- Actions and Errors ------- */
@@ -3365,6 +3480,7 @@ const LogReviewer = (props) => {
3365
3480
  actionErrorFilterState.type !== undefined
3366
3481
  // Log type doesn't match
3367
3482
  && actionErrorFilterState.type !== log.type) {
3483
+ console.log('RULED OUT: TYPE');
3368
3484
  return;
3369
3485
  }
3370
3486
  // Filter errors
@@ -3377,6 +3493,7 @@ const LogReviewer = (props) => {
3377
3493
  && actionErrorFilterState.errorMessage.trim().length > 0
3378
3494
  // Message doesn't match
3379
3495
  && log.errorMessage.toLowerCase().includes(actionErrorFilterState.errorMessage.trim().toLowerCase())) {
3496
+ console.log('RULED OUT: ERROR MESSAGE');
3380
3497
  return;
3381
3498
  }
3382
3499
  // Code doesn't match
@@ -3387,6 +3504,7 @@ const LogReviewer = (props) => {
3387
3504
  && actionErrorFilterState.errorCode.trim().length > 0
3388
3505
  // Code doesn't match
3389
3506
  && log.errorCode.toUpperCase().includes(actionErrorFilterState.errorCode.trim().toUpperCase())) {
3507
+ console.log('RULED OUT: ERROR CODE');
3390
3508
  return;
3391
3509
  }
3392
3510
  }
@@ -3398,6 +3516,7 @@ const LogReviewer = (props) => {
3398
3516
  log.target
3399
3517
  // Target isn't selected
3400
3518
  && !actionErrorFilterState.target[log.target]) {
3519
+ console.log('RULED OUT: TARGET');
3401
3520
  return;
3402
3521
  }
3403
3522
  // Action
@@ -3406,6 +3525,7 @@ const LogReviewer = (props) => {
3406
3525
  log.action
3407
3526
  // Action isn't selected
3408
3527
  && !actionErrorFilterState.action[log.action]) {
3528
+ console.log('RULED OUT: ACTION');
3409
3529
  return;
3410
3530
  }
3411
3531
  }
@@ -3416,6 +3536,7 @@ const LogReviewer = (props) => {
3416
3536
  log.userFirstName
3417
3537
  // First name query doesn't match
3418
3538
  && !log.userFirstName.toLowerCase().includes(advancedFilterState.userFirstName.toLowerCase().trim())) {
3539
+ console.log('RULED OUT: FIRST');
3419
3540
  return;
3420
3541
  }
3421
3542
  // Last name doesn't match
@@ -3424,6 +3545,7 @@ const LogReviewer = (props) => {
3424
3545
  log.userLastName
3425
3546
  // Last name query doesn't match
3426
3547
  && !log.userLastName.toLowerCase().includes(advancedFilterState.userLastName.toLowerCase().trim())) {
3548
+ console.log('RULED OUT: LAST');
3427
3549
  return;
3428
3550
  }
3429
3551
  // Email doesn't match
@@ -3432,6 +3554,7 @@ const LogReviewer = (props) => {
3432
3554
  log.userEmail
3433
3555
  // Email query doesn't match
3434
3556
  && !log.userEmail.toLowerCase().includes(advancedFilterState.userEmail.toLowerCase().trim())) {
3557
+ console.log('RULED OUT: EMAIL');
3435
3558
  return;
3436
3559
  }
3437
3560
  // User id doesn't match
@@ -3440,6 +3563,7 @@ const LogReviewer = (props) => {
3440
3563
  log.userId
3441
3564
  // User id doesn't match
3442
3565
  && !String(log.userId).includes(advancedFilterState.userId.trim())) {
3566
+ console.log('RULED OUT: USER ID');
3443
3567
  return;
3444
3568
  }
3445
3569
  // Learner not allowed
@@ -3448,6 +3572,7 @@ const LogReviewer = (props) => {
3448
3572
  log.isLearner
3449
3573
  // Learners aren't included
3450
3574
  && !advancedFilterState.includeLearners) {
3575
+ console.log('RULED OUT: LEARNER');
3451
3576
  return;
3452
3577
  }
3453
3578
  // TTM not allowed
@@ -3456,6 +3581,7 @@ const LogReviewer = (props) => {
3456
3581
  log.isTTM
3457
3582
  // TTMs aren't included
3458
3583
  && !advancedFilterState.includeTTMs) {
3584
+ console.log('RULED OUT: TTM');
3459
3585
  return;
3460
3586
  }
3461
3587
  // Admin not allowed
@@ -3464,6 +3590,7 @@ const LogReviewer = (props) => {
3464
3590
  log.isAdmin
3465
3591
  // Admins aren't included
3466
3592
  && !advancedFilterState.includeAdmins) {
3593
+ console.log('RULED OUT: ADMIN');
3467
3594
  return;
3468
3595
  }
3469
3596
  // Course Id doesn't match
@@ -3472,6 +3599,7 @@ const LogReviewer = (props) => {
3472
3599
  log.courseId
3473
3600
  // Course Id doesn't match
3474
3601
  && !String(log.courseId).includes(advancedFilterState.courseId.trim())) {
3602
+ console.log('RULED OUT: COURSE ID');
3475
3603
  return;
3476
3604
  }
3477
3605
  // Course name doesn't match
@@ -3480,6 +3608,7 @@ const LogReviewer = (props) => {
3480
3608
  log.courseName
3481
3609
  // Course name doesn't match
3482
3610
  && !String(log.courseName).includes(advancedFilterState.courseName.trim())) {
3611
+ console.log('RULED OUT: COURSE NAME');
3483
3612
  return;
3484
3613
  }
3485
3614
  // Mobile filter doesn't match
@@ -3490,6 +3619,7 @@ const LogReviewer = (props) => {
3490
3619
  && log.device
3491
3620
  // Mobile filter doesn't match
3492
3621
  && (advancedFilterState.isMobile === log.device.isMobile)) {
3622
+ console.log('RULED OUT: MOBILE');
3493
3623
  return;
3494
3624
  }
3495
3625
  // Log source doesn't match
@@ -3500,6 +3630,7 @@ const LogReviewer = (props) => {
3500
3630
  && log.source
3501
3631
  // Source filter doesn't match
3502
3632
  && (advancedFilterState.source !== log.source)) {
3633
+ console.log('RULED OUT: SOURCE');
3503
3634
  return;
3504
3635
  }
3505
3636
  // Route path doesn't match (Only for server source)
@@ -3510,6 +3641,7 @@ const LogReviewer = (props) => {
3510
3641
  && (advancedFilterState.routePath.trim().length)
3511
3642
  // Route path doesn't match
3512
3643
  && !(log.routePath.includes(advancedFilterState.routePath.trim()))) {
3644
+ console.log('RULED OUT: PATH');
3513
3645
  return;
3514
3646
  }
3515
3647
  // Route template doesn't match (Only for server source)
@@ -3520,6 +3652,7 @@ const LogReviewer = (props) => {
3520
3652
  && (advancedFilterState.routeTemplate.trim().length)
3521
3653
  // Route template doesn't match
3522
3654
  && !(log.routeTemplate.includes(advancedFilterState.routeTemplate.trim()))) {
3655
+ console.log('RULED OUT: TEMPLATE');
3523
3656
  return;
3524
3657
  }
3525
3658
  /* -------------- Done -------------- */
@@ -3719,11 +3852,17 @@ const LogReviewer = (props) => {
3719
3852
  },
3720
3853
  ];
3721
3854
  // Create intelliTable
3722
- const dataTable = (React.createElement(IntelliTable, { title: "Matching Logs", id: "logs", data: logs, columns: columns }));
3855
+ const dataTable = (logs.length === 0
3856
+ ? (React.createElement(React.Fragment, null,
3857
+ React.createElement("h3", { className: "m-0" }, "Matching Logs:"),
3858
+ React.createElement("div", { className: "alert alert-warning text-center" },
3859
+ React.createElement("h4", { className: "m-1" }, "No Logs to Show"),
3860
+ React.createElement("div", null, "Either your filters are too strict or no matching logs have been created yet."))))
3861
+ : (React.createElement(IntelliTable, { title: "Matching Logs:", id: "logs", data: logs, columns: columns })));
3723
3862
  // Main body
3724
3863
  body = (React.createElement(React.Fragment, null,
3725
3864
  filters,
3726
- dataTable));
3865
+ React.createElement("div", { className: "mt-2" }, dataTable)));
3727
3866
  }
3728
3867
  /* ---------- Wrap in Modal --------- */
3729
3868
  return (React.createElement("div", { className: "LogReviewer-outer-container" },
@@ -3731,14 +3870,10 @@ const LogReviewer = (props) => {
3731
3870
  React.createElement("div", { className: "LogReviewer-inner-container" },
3732
3871
  React.createElement("div", { className: "LogReviewer-header" },
3733
3872
  React.createElement("div", { className: "LogReviewer-header-title" },
3734
- React.createElement("h1", { className: "m-0" }, "Log Review Dashboard")),
3735
- React.createElement("button", { type: "button", className: "LogReviewer-header-close-button btn btn-lg", "aria-label": "close log reviewer panel", onClick: onClose, style: {
3736
- border: 0,
3737
- backgroundColor: 'transparent',
3738
- padding: 0,
3739
- margin: 0,
3740
- } },
3741
- React.createElement(FontAwesomeIcon, { icon: faTimes }))),
3873
+ React.createElement("h3", { className: "text-center m-0" }, "Log Review Dashboard")),
3874
+ React.createElement("div", { style: { width: 0 } },
3875
+ React.createElement("button", { type: "button", className: "LogReviewer-header-close-button btn btn-dark btn-lg pe-0", "aria-label": "close log reviewer panel", onClick: onClose },
3876
+ React.createElement(FontAwesomeIcon, { icon: faTimes })))),
3742
3877
  React.createElement("div", { className: "LogReviewer-contents" }, body))));
3743
3878
  };
3744
3879
 
@@ -3884,7 +4019,7 @@ const padZerosLeft = (num, numDigits) => {
3884
4019
  * access to log review
3885
4020
  * @author Gabe Abrams
3886
4021
  */
3887
- const LOG_REVIEW_STATUS_ROUTE = `/admin${ROUTE_PATH_PREFIX}/logs/access`;
4022
+ const LOG_REVIEW_STATUS_ROUTE = `${ROUTE_PATH_PREFIX}/logs/access_allowed`;
3888
4023
 
3889
4024
  // Stored copy of caccl functions
3890
4025
  let _cacclGetLaunchInfo;
@@ -3926,7 +4061,7 @@ const internalGetLogCollection = () => {
3926
4061
  * @param opts.getLaunchInfo CACCL LTI's get launch info function
3927
4062
  * @param [opts.logCollection] mongo collection from dce-mango to use for
3928
4063
  * storing logs. If none is included, logs are written to the console
3929
- * @param [opts.logReviewAdmins=all admins] info on which admins can review
4064
+ * @param [opts.logReviewAdmins=all] info on which admins can review
3930
4065
  * logs from the client. If not included, all Canvas admins are allowed to
3931
4066
  * review logs. If null, no Canvas admins are allowed to review logs.
3932
4067
  * If an array of Canvas userIds (numbers), only Canvas admins with those
@@ -4009,71 +4144,81 @@ const initServer = (opts) => {
4009
4144
  /*----------------------------------------*/
4010
4145
  /* Log Reviewer */
4011
4146
  /*----------------------------------------*/
4012
- if (opts.logReviewAdmins !== null) {
4013
- /**
4014
- * Check if a given user is allowed to review logs
4015
- * @author Gabe Abrams
4016
- * @param userId the id of the user
4017
- * @returns true if the user can review logs
4018
- */
4019
- const canReviewLogs = (userId) => __awaiter(void 0, void 0, void 0, function* () {
4020
- try {
4021
- // Array of userIds
4022
- if (Array.isArray(opts.logReviewAdmins)) {
4023
- return opts.logReviewAdmins.some((allowedId) => {
4024
- return (userId === allowedId);
4025
- });
4026
- }
4027
- // Must be a collection
4028
- const matches = yield opts.logReviewAdmins.find({ userId });
4029
- // Make sure at least one entry matches
4030
- return matches.length > 0;
4147
+ /**
4148
+ * Check if a given user is allowed to review logs
4149
+ * @author Gabe Abrams
4150
+ * @param userId the id of the user
4151
+ * @param isAdmin if true, the user is an admin
4152
+ * @returns true if the user can review logs
4153
+ */
4154
+ const canReviewLogs = (userId, isAdmin) => __awaiter(void 0, void 0, void 0, function* () {
4155
+ // Immediately deny access if user is not an admin
4156
+ if (!isAdmin) {
4157
+ return false;
4158
+ }
4159
+ // If all admins are allowed, we're done
4160
+ if (!opts.logReviewAdmins) {
4161
+ return true;
4162
+ }
4163
+ // Do a dynamic check
4164
+ try {
4165
+ // Array of userIds
4166
+ if (Array.isArray(opts.logReviewAdmins)) {
4167
+ return opts.logReviewAdmins.some((allowedId) => {
4168
+ return (userId === allowedId);
4169
+ });
4031
4170
  }
4032
- catch (err) {
4033
- // If an error occurred, simply return false
4034
- return false;
4171
+ // Must be a collection
4172
+ const matches = yield opts.logReviewAdmins.find({ userId });
4173
+ // Make sure at least one entry matches
4174
+ return matches.length > 0;
4175
+ }
4176
+ catch (err) {
4177
+ // If an error occurred, simply return false
4178
+ return false;
4179
+ }
4180
+ });
4181
+ /**
4182
+ * Check if the current user has access to logs
4183
+ * @author Gabe Abrams
4184
+ * @returns {boolean} true if user has access
4185
+ */
4186
+ opts.app.get(LOG_REVIEW_STATUS_ROUTE, genRouteHandler({
4187
+ handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
4188
+ const { userId, isAdmin } = params;
4189
+ const canReview = yield canReviewLogs(userId, isAdmin);
4190
+ return canReview;
4191
+ }),
4192
+ }));
4193
+ /**
4194
+ * Get all logs for a certain month
4195
+ * @author Gabe Abrams
4196
+ * @param {number} year the year to query (e.g. 2022)
4197
+ * @param {number} month the month to query (e.g. 1 = January)
4198
+ * @returns {Log[]} list of logs from the given month
4199
+ */
4200
+ opts.app.get(`${LOG_REVIEW_ROUTE_PATH_PREFIX}/years/:year/months/:month`, genRouteHandler({
4201
+ paramTypes: {
4202
+ year: ParamType$1.Int,
4203
+ month: ParamType$1.Int,
4204
+ },
4205
+ handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
4206
+ // Get user info
4207
+ const { year, month, userId, isAdmin, } = params;
4208
+ // Validate user
4209
+ const canReview = yield canReviewLogs(userId, isAdmin);
4210
+ if (!canReview) {
4211
+ throw new ErrorWithCode('You cannot access this resource because you do not have the appropriate permissions.', ReactKitErrorCode$1.NotAllowedToReviewLogs);
4035
4212
  }
4036
- });
4037
- /**
4038
- * Check if the current user has access to logs
4039
- * @author Gabe Abrams
4040
- * @returns {boolean} true if user has access
4041
- */
4042
- opts.app.get(LOG_REVIEW_STATUS_ROUTE, genRouteHandler({
4043
- handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
4044
- const { userId } = params;
4045
- const canReview = yield canReviewLogs(userId);
4046
- return canReview;
4047
- }),
4048
- }));
4049
- /**
4050
- * Get all logs for a certain month
4051
- * @author Gabe Abrams
4052
- * @param {number} year the year to query (e.g. 2022)
4053
- * @param {number} month the month to query (e.g. 1 = January)
4054
- * @returns {Log[]} list of logs from the given month
4055
- */
4056
- opts.app.post(`${LOG_REVIEW_ROUTE_PATH_PREFIX}/years/:year/months/:month`, genRouteHandler({
4057
- paramTypes: {
4058
- year: ParamType$1.Int,
4059
- month: ParamType$1.Int,
4060
- },
4061
- handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
4062
- // Get user info
4063
- const { userId } = params;
4064
- // Validate user
4065
- // isAdmin is already checked because path starts with '/admin'
4066
- const canReview = yield canReviewLogs(userId);
4067
- if (!canReview) {
4068
- throw new ErrorWithCode('You cannot access this resource because you do not have the appropriate permissions.', ReactKitErrorCode$1.NotAllowedToReviewLogs);
4069
- }
4070
- // Query for logs
4071
- const logs = yield _logCollection.find({ userId });
4072
- // Return logs
4073
- return logs;
4074
- }),
4075
- }));
4076
- }
4213
+ // Query for logs
4214
+ const logs = yield _logCollection.find({
4215
+ year,
4216
+ month,
4217
+ });
4218
+ // Return logs
4219
+ return logs;
4220
+ }),
4221
+ }));
4077
4222
  };
4078
4223
 
4079
4224
  // Import shared types
@@ -4373,18 +4518,6 @@ const parseUserAgent = (userAgent) => {
4373
4518
  };
4374
4519
  };
4375
4520
 
4376
- /**
4377
- * Allowed log levels
4378
- * @author Gabe Abrams
4379
- */
4380
- var LogLevel;
4381
- (function (LogLevel) {
4382
- LogLevel["Warn"] = "Warn";
4383
- LogLevel["Info"] = "Info";
4384
- LogLevel["Debug"] = "Debug";
4385
- })(LogLevel || (LogLevel = {}));
4386
- var LogLevel$1 = LogLevel;
4387
-
4388
4521
  /**
4389
4522
  * Generate an express API route handler
4390
4523
  * @author Gabe Abrams
@@ -4757,7 +4890,7 @@ const genRouteHandler = (opts) => {
4757
4890
  }
4758
4891
  : {
4759
4892
  type: LogType$1.Action,
4760
- target: ((_m = opts.target) !== null && _m !== void 0 ? _m : LogBuiltInMetadata.Target.NoSpecificTarget),
4893
+ target: ((_m = opts.target) !== null && _m !== void 0 ? _m : LogBuiltInMetadata.Target.NoTarget),
4761
4894
  action: ((_o = opts.action) !== null && _o !== void 0 ? _o : LogAction$1.Unknown),
4762
4895
  });
4763
4896
  // Source-specific info
@@ -5124,6 +5257,31 @@ const initLogCollection = (Collection) => {
5124
5257
  });
5125
5258
  };
5126
5259
 
5260
+ // Cache user's ability
5261
+ let canReview = undefined;
5262
+ /**
5263
+ * Check if the current user can review logs
5264
+ * @author Gabe Abrams
5265
+ * @returns true if current user can review logs
5266
+ */
5267
+ const canReviewLogs = () => __awaiter(void 0, void 0, void 0, function* () {
5268
+ // If cached, use that value
5269
+ if (canReview !== undefined) {
5270
+ return canReview;
5271
+ }
5272
+ // Ask on server
5273
+ try {
5274
+ canReview = !!(yield visitServerEndpoint({
5275
+ path: LOG_REVIEW_STATUS_ROUTE,
5276
+ method: 'GET',
5277
+ }));
5278
+ }
5279
+ catch (err) {
5280
+ canReview = false;
5281
+ }
5282
+ return canReview;
5283
+ });
5284
+
5127
5285
  /**
5128
5286
  * Days of the week
5129
5287
  * @author Gabe Abrams
@@ -5140,5 +5298,5 @@ var DayOfWeek;
5140
5298
  })(DayOfWeek || (DayOfWeek = {}));
5141
5299
  var DayOfWeek$1 = DayOfWeek;
5142
5300
 
5143
- export { AppWrapper, ButtonInputGroup, CSVDownloadButton, CheckboxButton, CopiableBox, DAY_IN_MS, DayOfWeek$1 as DayOfWeek, Drawer, ErrorBox, ErrorWithCode, HOUR_IN_MS, IntelliTable, ItemPicker, LoadingSpinner, LogAction$1 as LogAction, LogBuiltInMetadata, LogReviewer, LogSource$1 as LogSource, LogType$1 as LogType, MINUTE_IN_MS, Modal, ModalButtonType$1 as ModalButtonType, ModalSize$1 as ModalSize, ModalType$1 as ModalType, ParamType$1 as ParamType, PopFailureMark, PopPendingMark, PopSuccessMark, RadioButton, ReactKitErrorCode$1 as ReactKitErrorCode, SimpleDateChooser, TabBox, Variant$1 as Variant, abbreviate, alert$1 as alert, avg, ceilToNumDecimals, confirm, floorToNumDecimals, forceNumIntoBounds, genCSV, genRouteHandler, getHumanReadableDate, getMonthName, getOrdinal, getPartOfDay, getTimeInfoInET, handleError, handleSuccess, initLogCollection, initServer, logClientEvent, onlyKeepLetters, padDecimalZeros, padZerosLeft, parallelLimit, roundToNumDecimals, showFatalError, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, visitServerEndpoint, waitMs };
5301
+ export { AppWrapper, ButtonInputGroup, CSVDownloadButton, CheckboxButton, CopiableBox, DAY_IN_MS, DayOfWeek$1 as DayOfWeek, Drawer, ErrorBox, ErrorWithCode, HOUR_IN_MS, IntelliTable, ItemPicker, LoadingSpinner, LogAction$1 as LogAction, LogBuiltInMetadata, LogReviewer, LogSource$1 as LogSource, LogType$1 as LogType, MINUTE_IN_MS, Modal, ModalButtonType$1 as ModalButtonType, ModalSize$1 as ModalSize, ModalType$1 as ModalType, ParamType$1 as ParamType, PopFailureMark, PopPendingMark, PopSuccessMark, RadioButton, ReactKitErrorCode$1 as ReactKitErrorCode, SimpleDateChooser, TabBox, Variant$1 as Variant, abbreviate, alert$1 as alert, avg, canReviewLogs, ceilToNumDecimals, confirm, floorToNumDecimals, forceNumIntoBounds, genCSV, genRouteHandler, getHumanReadableDate, getMonthName, getOrdinal, getPartOfDay, getTimeInfoInET, handleError, handleSuccess, initLogCollection, initServer, logClientEvent, onlyKeepLetters, padDecimalZeros, padZerosLeft, parallelLimit, roundToNumDecimals, showFatalError, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, visitServerEndpoint, waitMs };
5144
5302
  //# sourceMappingURL=index.js.map