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

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}`,
@@ -2057,7 +2083,7 @@ const ItemPicker = (props) => {
2057
2083
  /*------------------------------------------------------------------------*/
2058
2084
  /* -------------- Props ------------- */
2059
2085
  // Destructure all props
2060
- const { title, items, onChanged, } = props;
2086
+ const { title, items, onChanged, noBottomMargin, } = props;
2061
2087
  /*------------------------------------------------------------------------*/
2062
2088
  /* Component Functions */
2063
2089
  /*------------------------------------------------------------------------*/
@@ -2067,7 +2093,7 @@ const ItemPicker = (props) => {
2067
2093
  /*----------------------------------------*/
2068
2094
  /* Main UI */
2069
2095
  /*----------------------------------------*/
2070
- return (React__default["default"].createElement(TabBox, { title: title },
2096
+ return (React__default["default"].createElement(TabBox, { title: title, noBottomMargin: noBottomMargin },
2071
2097
  React__default["default"].createElement("div", { style: { overflowX: 'auto' } },
2072
2098
  React__default["default"].createElement(NestableItemList, { items: items, onChanged: onChanged }))));
2073
2099
  };
@@ -2384,9 +2410,9 @@ const IntelliTable = (props) => {
2384
2410
  // Create the cell UI
2385
2411
  return (React__default["default"].createElement("th", { key: column.param, scope: "col", id: `IntelliTable-${id}-header-${column.param}` },
2386
2412
  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),
2413
+ React__default["default"].createElement("span", { className: "text-nowrap" }, column.title),
2388
2414
  React__default["default"].createElement("div", null,
2389
- React__default["default"].createElement("button", { type: "button", className: "btn btn-light", "aria-label": sortButtonAriaLabel, onClick: () => {
2415
+ React__default["default"].createElement("button", { type: "button", className: "btn btn-light btn-sm ms-1", "aria-label": sortButtonAriaLabel, onClick: () => {
2390
2416
  dispatch({
2391
2417
  type: ActionType$1.ToggleSortColumn,
2392
2418
  param: column.param,
@@ -2534,7 +2560,7 @@ const IntelliTable = (props) => {
2534
2560
  React__default["default"].createElement("h3", { className: "m-0" }, title),
2535
2561
  React__default["default"].createElement("div", { className: "flex-grow-1 text-end" },
2536
2562
  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: () => {
2563
+ 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
2564
  dispatch({
2539
2565
  type: ActionType$1.ToggleColVisCusModalVisibility,
2540
2566
  });
@@ -2598,6 +2624,11 @@ const style = `
2598
2624
  border: 0.05rem solid black;
2599
2625
  border-radius: 0.5rem;
2600
2626
  overflow: hidden;
2627
+ padding: 0.7rem;
2628
+
2629
+ /* Solid background */
2630
+ background-color: white;
2631
+ color: black;
2601
2632
 
2602
2633
  /* Place contents in flex column */
2603
2634
  flex-direction: column;
@@ -2624,6 +2655,27 @@ const style = `
2624
2655
  /* Vertical scroll */
2625
2656
  overflow-y: auto;
2626
2657
  }
2658
+
2659
+ .LogReviewer-header-close-button {
2660
+ border: 0 !important;
2661
+ background-color: transparent !important;
2662
+ padding-top: 0 !important;
2663
+ padding-bottom: 0 !important;
2664
+ padding-right: 1em !important;
2665
+ margin: 0 !important;
2666
+ color: #444 !important;
2667
+
2668
+ right: 0 !important;
2669
+ position: absolute !important;
2670
+ }
2671
+ .LogReviewer-header-close-button:hover {
2672
+ border: 0 !important;
2673
+ background-color: transparent !important;
2674
+ padding-top: 0 !important;
2675
+ padding-bottom: 0 !important;
2676
+ margin: 0 !important;
2677
+ color: #000 !important;
2678
+ }
2627
2679
  `;
2628
2680
  /*------------------------------------------------------------------------*/
2629
2681
  /* Static Functions */
@@ -2643,7 +2695,7 @@ const genHumanReadableName = (machineReadableName) => {
2643
2695
  // Uppercase! Add a space before
2644
2696
  humanReadableName += ' ';
2645
2697
  }
2646
- humanReadableName += chars;
2698
+ humanReadableName += char;
2647
2699
  });
2648
2700
  // Trim and return
2649
2701
  return humanReadableName.trim();
@@ -2705,7 +2757,19 @@ const reducer = (state, action) => {
2705
2757
  return Object.assign(Object.assign({}, state), { contextFilterState: action.contextFilterState });
2706
2758
  }
2707
2759
  case ActionType.UpdateTagFilterState: {
2708
- return Object.assign(Object.assign({}, state), { tagFilterState: action.tagFilterState });
2760
+ const { tagFilterState } = action;
2761
+ // Select all if every tag is deselected
2762
+ const numTagsSelected = (Object.values(tagFilterState)
2763
+ .filter((isSelected) => {
2764
+ return isSelected;
2765
+ })
2766
+ .length);
2767
+ if (numTagsSelected === 0) {
2768
+ Object.keys(tagFilterState).forEach((tag) => {
2769
+ tagFilterState[tag] = true;
2770
+ });
2771
+ }
2772
+ return Object.assign(Object.assign({}, state), { tagFilterState });
2709
2773
  }
2710
2774
  case ActionType.UpdateActionErrorFilterState: {
2711
2775
  return Object.assign(Object.assign({}, state), { actionErrorFilterState: action.actionErrorFilterState });
@@ -2725,10 +2789,33 @@ const LogReviewer = (props) => {
2725
2789
  /*------------------------------------------------------------------------*/
2726
2790
  /* Setup */
2727
2791
  /*------------------------------------------------------------------------*/
2728
- var _a, _b, _c, _d, _e;
2792
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
2729
2793
  /* -------------- Props ------------- */
2730
2794
  // Destructure props
2731
2795
  const { LogMetadata, onClose, } = props;
2796
+ // Add built-in LogMetadata
2797
+ // > Add "uncategorized" subcontext to each context
2798
+ Object.keys((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {}).forEach((context) => {
2799
+ if (LogMetadata.Context
2800
+ // Context has children already
2801
+ && typeof LogMetadata.Context[context] !== 'string') {
2802
+ LogMetadata.Context[context][LogBuiltInMetadata.Context.Uncategorized] = (LogBuiltInMetadata.Context.Uncategorized);
2803
+ }
2804
+ });
2805
+ // > Add built-in contexts
2806
+ LogMetadata.Context = ((_b = LogMetadata.Context) !== null && _b !== void 0 ? _b : {});
2807
+ Object.keys(LogBuiltInMetadata.Context).forEach((context) => {
2808
+ if (LogMetadata.Context) {
2809
+ LogMetadata.Context[context] = context;
2810
+ }
2811
+ });
2812
+ // > Add built-in targets
2813
+ LogMetadata.Target = ((_c = LogMetadata.Target) !== null && _c !== void 0 ? _c : {});
2814
+ Object.keys(LogBuiltInMetadata.Target).forEach((target) => {
2815
+ if (LogMetadata.Target) {
2816
+ LogMetadata.Target[target] = target;
2817
+ }
2818
+ });
2732
2819
  /* -------------- State ------------- */
2733
2820
  // Create initial date filter state
2734
2821
  const today = getTimeInfoInET();
@@ -2748,7 +2835,7 @@ const LogReviewer = (props) => {
2748
2835
  };
2749
2836
  // Create initial context filter state
2750
2837
  const initContextFilterState = {};
2751
- Object.keys((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {}).forEach((context) => {
2838
+ Object.keys((_d = LogMetadata.Context) !== null && _d !== void 0 ? _d : {}).forEach((context) => {
2752
2839
  var _a, _b;
2753
2840
  const contextValue = ((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {})[context];
2754
2841
  if (typeof contextValue === 'string') {
@@ -2766,7 +2853,7 @@ const LogReviewer = (props) => {
2766
2853
  });
2767
2854
  // Create initial tag filter state
2768
2855
  const initTagFilterState = {};
2769
- Object.values((_b = LogMetadata.Tag) !== null && _b !== void 0 ? _b : {}).forEach((tagValue) => {
2856
+ Object.values((_e = LogMetadata.Tag) !== null && _e !== void 0 ? _e : {}).forEach((tagValue) => {
2770
2857
  initTagFilterState[tagValue] = true;
2771
2858
  });
2772
2859
  // Create advanced filter state
@@ -2793,12 +2880,16 @@ const LogReviewer = (props) => {
2793
2880
  target: {},
2794
2881
  action: {},
2795
2882
  };
2796
- Object.values((_c = LogMetadata.Target) !== null && _c !== void 0 ? _c : {}).forEach((target) => {
2883
+ Object.values((_f = LogMetadata.Target) !== null && _f !== void 0 ? _f : {}).forEach((target) => {
2797
2884
  initActionErrorFilterState.target[target] = true;
2798
2885
  });
2799
2886
  Object.values(LogAction$1).forEach((action) => {
2800
2887
  initActionErrorFilterState.action[action] = true;
2801
2888
  });
2889
+ // Add built-in targets
2890
+ Object.values(LogBuiltInMetadata.Target).forEach((target) => {
2891
+ initActionErrorFilterState.target[target] = true;
2892
+ });
2802
2893
  // Initial state
2803
2894
  const initialState = {
2804
2895
  loading: true,
@@ -2832,15 +2923,18 @@ const LogReviewer = (props) => {
2832
2923
  let month = newDateFilterState.startDate.month;
2833
2924
  while (
2834
2925
  // Earlier year
2835
- (year <= newDateFilterState.endDate.year)
2926
+ (year < newDateFilterState.endDate.year)
2836
2927
  // Current year but included month
2837
2928
  || (year === newDateFilterState.endDate.year
2838
2929
  && month <= newDateFilterState.endDate.month)) {
2839
- // Add to list
2840
- toLoad.push({
2841
- year,
2842
- month,
2843
- });
2930
+ // Add to list if not already loaded
2931
+ if (!logMap[year]
2932
+ || !logMap[year][month]) {
2933
+ toLoad.push({
2934
+ year,
2935
+ month,
2936
+ });
2937
+ }
2844
2938
  // Increment
2845
2939
  month += 1;
2846
2940
  if (month > 12) {
@@ -2864,6 +2958,7 @@ const LogReviewer = (props) => {
2864
2958
  });
2865
2959
  // Check which year/month combos we need to load
2866
2960
  const toLoad = listMonthsToLoad(newDateFilterState);
2961
+ console.log('Filter update:', newDateFilterState, toLoad, logMap);
2867
2962
  // If nothing to load, finished
2868
2963
  if (toLoad.length === 0) {
2869
2964
  return;
@@ -2928,10 +3023,10 @@ const LogReviewer = (props) => {
2928
3023
  /* Filters */
2929
3024
  /*----------------------------------------*/
2930
3025
  // Filter toggle
2931
- const filterToggles = (React__default["default"].createElement("div", { className: "LogReviewer-filter-toggles d-flex align-items-center justify-content-center" },
3026
+ const filterToggles = (React__default["default"].createElement("div", { className: "LogReviewer-filter-toggles" },
2932
3027
  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: () => {
3028
+ React__default["default"].createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
3029
+ 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
3030
  dispatch({
2936
3031
  type: ActionType.ToggleFilterDrawer,
2937
3032
  filterDrawer: FilterDrawer.Date,
@@ -2939,7 +3034,7 @@ const LogReviewer = (props) => {
2939
3034
  } },
2940
3035
  React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCalendar, className: "me-2" }),
2941
3036
  "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: () => {
3037
+ 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
3038
  dispatch({
2944
3039
  type: ActionType.ToggleFilterDrawer,
2945
3040
  filterDrawer: FilterDrawer.Context,
@@ -2947,15 +3042,15 @@ const LogReviewer = (props) => {
2947
3042
  } },
2948
3043
  React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "me-2" }),
2949
3044
  "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: () => {
3045
+ (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
3046
  dispatch({
2952
3047
  type: ActionType.ToggleFilterDrawer,
2953
3048
  filterDrawer: FilterDrawer.Tag,
2954
3049
  });
2955
3050
  } },
2956
3051
  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: () => {
3052
+ "Tag")),
3053
+ 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
3054
  dispatch({
2960
3055
  type: ActionType.ToggleFilterDrawer,
2961
3056
  filterDrawer: FilterDrawer.Action,
@@ -2963,7 +3058,7 @@ const LogReviewer = (props) => {
2963
3058
  } },
2964
3059
  React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faHammer, className: "me-2" }),
2965
3060
  "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: () => {
3061
+ 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
3062
  dispatch({
2968
3063
  type: ActionType.ToggleFilterDrawer,
2969
3064
  filterDrawer: FilterDrawer.Advanced,
@@ -2976,25 +3071,29 @@ const LogReviewer = (props) => {
2976
3071
  if (expandedFilterDrawer) {
2977
3072
  if (expandedFilterDrawer === FilterDrawer.Date) {
2978
3073
  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
- });
3074
+ 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) => {
3075
+ dateFilterState.startDate = { month, day, year };
3076
+ handleDateRangeUpdated(dateFilterState);
2984
3077
  } }),
2985
3078
  ' ',
2986
3079
  "to",
2987
3080
  ' ',
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
- });
3081
+ 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) => {
3082
+ if (year < dateFilterState.startDate.year
3083
+ || (year === dateFilterState.startDate.year
3084
+ && month < dateFilterState.startDate.month)
3085
+ || (year === dateFilterState.startDate.year
3086
+ && month === dateFilterState.startDate.month
3087
+ && day < dateFilterState.startDate.day)) {
3088
+ return alert$1('Invalid Start Date', 'The start date cannot be before the end date.');
3089
+ }
3090
+ dateFilterState.endDate = { month, day, year };
3091
+ handleDateRangeUpdated(dateFilterState);
2993
3092
  } })));
2994
3093
  }
2995
3094
  else if (expandedFilterDrawer === FilterDrawer.Context) {
2996
3095
  // Create item picker items
2997
- const pickableItems = (Object.keys((_d = LogMetadata.Context) !== null && _d !== void 0 ? _d : {})
3096
+ const pickableItems = (Object.keys((_g = LogMetadata.Context) !== null && _g !== void 0 ? _g : {})
2998
3097
  .map((context) => {
2999
3098
  var _a;
3000
3099
  const value = ((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {})[context];
@@ -3023,7 +3122,7 @@ const LogReviewer = (props) => {
3023
3122
  }));
3024
3123
  const item = {
3025
3124
  id: context,
3026
- name: context,
3125
+ name: genHumanReadableName(context),
3027
3126
  isGroup: true,
3028
3127
  children,
3029
3128
  };
@@ -3031,6 +3130,8 @@ const LogReviewer = (props) => {
3031
3130
  }));
3032
3131
  // Create filter UI
3033
3132
  filterDrawer = (React__default["default"].createElement(ItemPicker, { title: "Context", items: pickableItems, onChanged: (updatedItems) => {
3133
+ console.log('Items before:', pickableItems);
3134
+ console.log('Updated Items:', updatedItems);
3034
3135
  // Update our state
3035
3136
  updatedItems.forEach((pickableItem) => {
3036
3137
  if (pickableItem.isGroup) {
@@ -3087,7 +3188,7 @@ const LogReviewer = (props) => {
3087
3188
  }, ariaLabel: "only show error logs", selected: actionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true })),
3088
3189
  (actionErrorFilterState.type === undefined
3089
3190
  || 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)
3191
+ React__default["default"].createElement(ButtonInputGroup, { label: "Action", className: "mb-2" }, Object.keys(LogAction$1)
3091
3192
  .map((action, i) => {
3092
3193
  const description = genHumanReadableName(action);
3093
3194
  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 +3199,20 @@ const LogReviewer = (props) => {
3098
3199
  });
3099
3200
  } }));
3100
3201
  })),
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
- })))),
3202
+ React__default["default"].createElement(ButtonInputGroup, { label: "Target" },
3203
+ (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.")),
3204
+ Object.keys((_j = LogMetadata.Target) !== null && _j !== void 0 ? _j : {})
3205
+ .map((target, i) => {
3206
+ var _a;
3207
+ const description = genHumanReadableName(target);
3208
+ return (React__default["default"].createElement(CheckboxButton, { id: `LogReviewer-target-${target}-checkbox`, text: description, ariaLabel: `include logs with target "${description}" in results`, onChanged: (checked) => {
3209
+ actionErrorFilterState.target[target] = checked;
3210
+ dispatch({
3211
+ type: ActionType.UpdateActionErrorFilterState,
3212
+ actionErrorFilterState,
3213
+ });
3214
+ }, noMarginOnRight: i === Object.keys((_a = LogMetadata.Target) !== null && _a !== void 0 ? _a : {}).length - 1 }));
3215
+ })))),
3113
3216
  (actionErrorFilterState.type === undefined
3114
3217
  || actionErrorFilterState.type === LogType$1.Error) && (React__default["default"].createElement(TabBox, { title: "Error Log Details" },
3115
3218
  React__default["default"].createElement("div", { className: "input-group mb-2" },
@@ -3271,26 +3374,27 @@ const LogReviewer = (props) => {
3271
3374
  advancedFilterState,
3272
3375
  });
3273
3376
  }, 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
- } })))));
3377
+ advancedFilterState.source !== LogSource$1.Client && (React__default["default"].createElement("div", { className: "mt-2" },
3378
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
3379
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Path"),
3380
+ 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) => {
3381
+ advancedFilterState.courseName = ((e.target.value)
3382
+ .trim());
3383
+ dispatch({
3384
+ type: ActionType.UpdateAdvancedFilterState,
3385
+ advancedFilterState,
3386
+ });
3387
+ } })),
3388
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
3389
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Template"),
3390
+ 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) => {
3391
+ advancedFilterState.courseName = ((e.target.value)
3392
+ .trim());
3393
+ dispatch({
3394
+ type: ActionType.UpdateAdvancedFilterState,
3395
+ advancedFilterState,
3396
+ });
3397
+ } })))))));
3294
3398
  }
3295
3399
  }
3296
3400
  // Filters UI
@@ -3301,10 +3405,11 @@ const LogReviewer = (props) => {
3301
3405
  // > Perform filters
3302
3406
  const logs = [];
3303
3407
  Object.keys(logMap).forEach((year) => {
3304
- Object.keys(logMap).forEach((month) => {
3408
+ Object.keys(logMap[year]).forEach((month) => {
3305
3409
  logMap[year][month].forEach((log) => {
3306
- /* ----------- Date Filter ---------- */
3307
3410
  var _a;
3411
+ /* ----------- Date Filter ---------- */
3412
+ console.log('Log:', log);
3308
3413
  // Before start date
3309
3414
  if (
3310
3415
  // Previous year
@@ -3316,6 +3421,7 @@ const LogReviewer = (props) => {
3316
3421
  || ((log.year === dateFilterState.startDate.year)
3317
3422
  && (log.month === dateFilterState.startDate.month)
3318
3423
  && (log.day < dateFilterState.startDate.day))) {
3424
+ console.log('RULED OUT: START');
3319
3425
  return;
3320
3426
  }
3321
3427
  // After end date
@@ -3329,6 +3435,7 @@ const LogReviewer = (props) => {
3329
3435
  || ((log.year === dateFilterState.endDate.year)
3330
3436
  && (log.month === dateFilterState.endDate.month)
3331
3437
  && (log.day > dateFilterState.endDate.day))) {
3438
+ console.log('RULED OUT: END');
3332
3439
  return;
3333
3440
  }
3334
3441
  /* --------- Context Filter --------- */
@@ -3341,29 +3448,30 @@ const LogReviewer = (props) => {
3341
3448
  .every((isSelected) => {
3342
3449
  return !isSelected;
3343
3450
  }))) {
3451
+ console.log('RULED OUT: CONTEXT');
3344
3452
  return;
3345
3453
  }
3346
3454
  // Subcontext doesn't match
3347
3455
  if (
3348
- // Log has a subcontext
3349
- log.subcontext
3456
+ // Log context is not "uncategorized" (no point in further filters)
3457
+ log.context !== LogBuiltInMetadata.Context.Uncategorized
3458
+ // Log has a subcontext
3459
+ && log.subcontext
3350
3460
  // Context has subcontexts
3351
3461
  && (contextFilterState[log.context]
3352
3462
  && contextFilterState[log.context] !== false
3353
3463
  && contextFilterState[log.context] !== true)
3354
3464
  // Subcontext is not selected
3355
3465
  && !contextFilterState[log.context][log.subcontext]) {
3466
+ console.log('RULED OUT: SUBCONTEXT');
3356
3467
  return;
3357
3468
  }
3358
3469
  /* -------------- Tags -------------- */
3359
3470
  // 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
- }))) {
3471
+ if (log.tags.every((tag) => {
3472
+ return !tagFilterState[tag];
3473
+ })) {
3474
+ console.log('RULED OUT: TAGS');
3367
3475
  return;
3368
3476
  }
3369
3477
  /* ------- Actions and Errors ------- */
@@ -3373,6 +3481,7 @@ const LogReviewer = (props) => {
3373
3481
  actionErrorFilterState.type !== undefined
3374
3482
  // Log type doesn't match
3375
3483
  && actionErrorFilterState.type !== log.type) {
3484
+ console.log('RULED OUT: TYPE');
3376
3485
  return;
3377
3486
  }
3378
3487
  // Filter errors
@@ -3385,6 +3494,7 @@ const LogReviewer = (props) => {
3385
3494
  && actionErrorFilterState.errorMessage.trim().length > 0
3386
3495
  // Message doesn't match
3387
3496
  && log.errorMessage.toLowerCase().includes(actionErrorFilterState.errorMessage.trim().toLowerCase())) {
3497
+ console.log('RULED OUT: ERROR MESSAGE');
3388
3498
  return;
3389
3499
  }
3390
3500
  // Code doesn't match
@@ -3395,6 +3505,7 @@ const LogReviewer = (props) => {
3395
3505
  && actionErrorFilterState.errorCode.trim().length > 0
3396
3506
  // Code doesn't match
3397
3507
  && log.errorCode.toUpperCase().includes(actionErrorFilterState.errorCode.trim().toUpperCase())) {
3508
+ console.log('RULED OUT: ERROR CODE');
3398
3509
  return;
3399
3510
  }
3400
3511
  }
@@ -3406,6 +3517,7 @@ const LogReviewer = (props) => {
3406
3517
  log.target
3407
3518
  // Target isn't selected
3408
3519
  && !actionErrorFilterState.target[log.target]) {
3520
+ console.log('RULED OUT: TARGET');
3409
3521
  return;
3410
3522
  }
3411
3523
  // Action
@@ -3414,6 +3526,7 @@ const LogReviewer = (props) => {
3414
3526
  log.action
3415
3527
  // Action isn't selected
3416
3528
  && !actionErrorFilterState.action[log.action]) {
3529
+ console.log('RULED OUT: ACTION');
3417
3530
  return;
3418
3531
  }
3419
3532
  }
@@ -3424,6 +3537,7 @@ const LogReviewer = (props) => {
3424
3537
  log.userFirstName
3425
3538
  // First name query doesn't match
3426
3539
  && !log.userFirstName.toLowerCase().includes(advancedFilterState.userFirstName.toLowerCase().trim())) {
3540
+ console.log('RULED OUT: FIRST');
3427
3541
  return;
3428
3542
  }
3429
3543
  // Last name doesn't match
@@ -3432,6 +3546,7 @@ const LogReviewer = (props) => {
3432
3546
  log.userLastName
3433
3547
  // Last name query doesn't match
3434
3548
  && !log.userLastName.toLowerCase().includes(advancedFilterState.userLastName.toLowerCase().trim())) {
3549
+ console.log('RULED OUT: LAST');
3435
3550
  return;
3436
3551
  }
3437
3552
  // Email doesn't match
@@ -3440,6 +3555,7 @@ const LogReviewer = (props) => {
3440
3555
  log.userEmail
3441
3556
  // Email query doesn't match
3442
3557
  && !log.userEmail.toLowerCase().includes(advancedFilterState.userEmail.toLowerCase().trim())) {
3558
+ console.log('RULED OUT: EMAIL');
3443
3559
  return;
3444
3560
  }
3445
3561
  // User id doesn't match
@@ -3448,6 +3564,7 @@ const LogReviewer = (props) => {
3448
3564
  log.userId
3449
3565
  // User id doesn't match
3450
3566
  && !String(log.userId).includes(advancedFilterState.userId.trim())) {
3567
+ console.log('RULED OUT: USER ID');
3451
3568
  return;
3452
3569
  }
3453
3570
  // Learner not allowed
@@ -3456,6 +3573,7 @@ const LogReviewer = (props) => {
3456
3573
  log.isLearner
3457
3574
  // Learners aren't included
3458
3575
  && !advancedFilterState.includeLearners) {
3576
+ console.log('RULED OUT: LEARNER');
3459
3577
  return;
3460
3578
  }
3461
3579
  // TTM not allowed
@@ -3464,6 +3582,7 @@ const LogReviewer = (props) => {
3464
3582
  log.isTTM
3465
3583
  // TTMs aren't included
3466
3584
  && !advancedFilterState.includeTTMs) {
3585
+ console.log('RULED OUT: TTM');
3467
3586
  return;
3468
3587
  }
3469
3588
  // Admin not allowed
@@ -3472,6 +3591,7 @@ const LogReviewer = (props) => {
3472
3591
  log.isAdmin
3473
3592
  // Admins aren't included
3474
3593
  && !advancedFilterState.includeAdmins) {
3594
+ console.log('RULED OUT: ADMIN');
3475
3595
  return;
3476
3596
  }
3477
3597
  // Course Id doesn't match
@@ -3480,6 +3600,7 @@ const LogReviewer = (props) => {
3480
3600
  log.courseId
3481
3601
  // Course Id doesn't match
3482
3602
  && !String(log.courseId).includes(advancedFilterState.courseId.trim())) {
3603
+ console.log('RULED OUT: COURSE ID');
3483
3604
  return;
3484
3605
  }
3485
3606
  // Course name doesn't match
@@ -3488,6 +3609,7 @@ const LogReviewer = (props) => {
3488
3609
  log.courseName
3489
3610
  // Course name doesn't match
3490
3611
  && !String(log.courseName).includes(advancedFilterState.courseName.trim())) {
3612
+ console.log('RULED OUT: COURSE NAME');
3491
3613
  return;
3492
3614
  }
3493
3615
  // Mobile filter doesn't match
@@ -3498,6 +3620,7 @@ const LogReviewer = (props) => {
3498
3620
  && log.device
3499
3621
  // Mobile filter doesn't match
3500
3622
  && (advancedFilterState.isMobile === log.device.isMobile)) {
3623
+ console.log('RULED OUT: MOBILE');
3501
3624
  return;
3502
3625
  }
3503
3626
  // Log source doesn't match
@@ -3508,6 +3631,7 @@ const LogReviewer = (props) => {
3508
3631
  && log.source
3509
3632
  // Source filter doesn't match
3510
3633
  && (advancedFilterState.source !== log.source)) {
3634
+ console.log('RULED OUT: SOURCE');
3511
3635
  return;
3512
3636
  }
3513
3637
  // Route path doesn't match (Only for server source)
@@ -3518,6 +3642,7 @@ const LogReviewer = (props) => {
3518
3642
  && (advancedFilterState.routePath.trim().length)
3519
3643
  // Route path doesn't match
3520
3644
  && !(log.routePath.includes(advancedFilterState.routePath.trim()))) {
3645
+ console.log('RULED OUT: PATH');
3521
3646
  return;
3522
3647
  }
3523
3648
  // Route template doesn't match (Only for server source)
@@ -3528,6 +3653,7 @@ const LogReviewer = (props) => {
3528
3653
  && (advancedFilterState.routeTemplate.trim().length)
3529
3654
  // Route template doesn't match
3530
3655
  && !(log.routeTemplate.includes(advancedFilterState.routeTemplate.trim()))) {
3656
+ console.log('RULED OUT: TEMPLATE');
3531
3657
  return;
3532
3658
  }
3533
3659
  /* -------------- Done -------------- */
@@ -3727,11 +3853,17 @@ const LogReviewer = (props) => {
3727
3853
  },
3728
3854
  ];
3729
3855
  // Create intelliTable
3730
- const dataTable = (React__default["default"].createElement(IntelliTable, { title: "Matching Logs", id: "logs", data: logs, columns: columns }));
3856
+ const dataTable = (logs.length === 0
3857
+ ? (React__default["default"].createElement(React__default["default"].Fragment, null,
3858
+ React__default["default"].createElement("h3", { className: "m-0" }, "Matching Logs:"),
3859
+ React__default["default"].createElement("div", { className: "alert alert-warning text-center" },
3860
+ React__default["default"].createElement("h4", { className: "m-1" }, "No Logs to Show"),
3861
+ React__default["default"].createElement("div", null, "Either your filters are too strict or no matching logs have been created yet."))))
3862
+ : (React__default["default"].createElement(IntelliTable, { title: "Matching Logs:", id: "logs", data: logs, columns: columns })));
3731
3863
  // Main body
3732
3864
  body = (React__default["default"].createElement(React__default["default"].Fragment, null,
3733
3865
  filters,
3734
- dataTable));
3866
+ React__default["default"].createElement("div", { className: "mt-2" }, dataTable)));
3735
3867
  }
3736
3868
  /* ---------- Wrap in Modal --------- */
3737
3869
  return (React__default["default"].createElement("div", { className: "LogReviewer-outer-container" },
@@ -3739,14 +3871,10 @@ const LogReviewer = (props) => {
3739
3871
  React__default["default"].createElement("div", { className: "LogReviewer-inner-container" },
3740
3872
  React__default["default"].createElement("div", { className: "LogReviewer-header" },
3741
3873
  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 }))),
3874
+ React__default["default"].createElement("h3", { className: "text-center m-0" }, "Log Review Dashboard")),
3875
+ React__default["default"].createElement("div", { style: { width: 0 } },
3876
+ 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 },
3877
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes })))),
3750
3878
  React__default["default"].createElement("div", { className: "LogReviewer-contents" }, body))));
3751
3879
  };
3752
3880
 
@@ -3892,7 +4020,7 @@ const padZerosLeft = (num, numDigits) => {
3892
4020
  * access to log review
3893
4021
  * @author Gabe Abrams
3894
4022
  */
3895
- const LOG_REVIEW_STATUS_ROUTE = `/admin${ROUTE_PATH_PREFIX}/logs/access`;
4023
+ const LOG_REVIEW_STATUS_ROUTE = `${ROUTE_PATH_PREFIX}/logs/access_allowed`;
3896
4024
 
3897
4025
  // Stored copy of caccl functions
3898
4026
  let _cacclGetLaunchInfo;
@@ -3934,7 +4062,7 @@ const internalGetLogCollection = () => {
3934
4062
  * @param opts.getLaunchInfo CACCL LTI's get launch info function
3935
4063
  * @param [opts.logCollection] mongo collection from dce-mango to use for
3936
4064
  * storing logs. If none is included, logs are written to the console
3937
- * @param [opts.logReviewAdmins=all admins] info on which admins can review
4065
+ * @param [opts.logReviewAdmins=all] info on which admins can review
3938
4066
  * logs from the client. If not included, all Canvas admins are allowed to
3939
4067
  * review logs. If null, no Canvas admins are allowed to review logs.
3940
4068
  * If an array of Canvas userIds (numbers), only Canvas admins with those
@@ -4017,71 +4145,81 @@ const initServer = (opts) => {
4017
4145
  /*----------------------------------------*/
4018
4146
  /* Log Reviewer */
4019
4147
  /*----------------------------------------*/
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;
4148
+ /**
4149
+ * Check if a given user is allowed to review logs
4150
+ * @author Gabe Abrams
4151
+ * @param userId the id of the user
4152
+ * @param isAdmin if true, the user is an admin
4153
+ * @returns true if the user can review logs
4154
+ */
4155
+ const canReviewLogs = (userId, isAdmin) => __awaiter(void 0, void 0, void 0, function* () {
4156
+ // Immediately deny access if user is not an admin
4157
+ if (!isAdmin) {
4158
+ return false;
4159
+ }
4160
+ // If all admins are allowed, we're done
4161
+ if (!opts.logReviewAdmins) {
4162
+ return true;
4163
+ }
4164
+ // Do a dynamic check
4165
+ try {
4166
+ // Array of userIds
4167
+ if (Array.isArray(opts.logReviewAdmins)) {
4168
+ return opts.logReviewAdmins.some((allowedId) => {
4169
+ return (userId === allowedId);
4170
+ });
4039
4171
  }
4040
- catch (err) {
4041
- // If an error occurred, simply return false
4042
- return false;
4172
+ // Must be a collection
4173
+ const matches = yield opts.logReviewAdmins.find({ userId });
4174
+ // Make sure at least one entry matches
4175
+ return matches.length > 0;
4176
+ }
4177
+ catch (err) {
4178
+ // If an error occurred, simply return false
4179
+ return false;
4180
+ }
4181
+ });
4182
+ /**
4183
+ * Check if the current user has access to logs
4184
+ * @author Gabe Abrams
4185
+ * @returns {boolean} true if user has access
4186
+ */
4187
+ opts.app.get(LOG_REVIEW_STATUS_ROUTE, genRouteHandler({
4188
+ handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
4189
+ const { userId, isAdmin } = params;
4190
+ const canReview = yield canReviewLogs(userId, isAdmin);
4191
+ return canReview;
4192
+ }),
4193
+ }));
4194
+ /**
4195
+ * Get all logs for a certain month
4196
+ * @author Gabe Abrams
4197
+ * @param {number} year the year to query (e.g. 2022)
4198
+ * @param {number} month the month to query (e.g. 1 = January)
4199
+ * @returns {Log[]} list of logs from the given month
4200
+ */
4201
+ opts.app.get(`${LOG_REVIEW_ROUTE_PATH_PREFIX}/years/:year/months/:month`, genRouteHandler({
4202
+ paramTypes: {
4203
+ year: ParamType$1.Int,
4204
+ month: ParamType$1.Int,
4205
+ },
4206
+ handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
4207
+ // Get user info
4208
+ const { year, month, userId, isAdmin, } = params;
4209
+ // Validate user
4210
+ const canReview = yield canReviewLogs(userId, isAdmin);
4211
+ if (!canReview) {
4212
+ throw new ErrorWithCode('You cannot access this resource because you do not have the appropriate permissions.', ReactKitErrorCode$1.NotAllowedToReviewLogs);
4043
4213
  }
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
- }
4214
+ // Query for logs
4215
+ const logs = yield _logCollection.find({
4216
+ year,
4217
+ month,
4218
+ });
4219
+ // Return logs
4220
+ return logs;
4221
+ }),
4222
+ }));
4085
4223
  };
4086
4224
 
4087
4225
  // Import shared types
@@ -4381,18 +4519,6 @@ const parseUserAgent = (userAgent) => {
4381
4519
  };
4382
4520
  };
4383
4521
 
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
4522
  /**
4397
4523
  * Generate an express API route handler
4398
4524
  * @author Gabe Abrams
@@ -4765,7 +4891,7 @@ const genRouteHandler = (opts) => {
4765
4891
  }
4766
4892
  : {
4767
4893
  type: LogType$1.Action,
4768
- target: ((_m = opts.target) !== null && _m !== void 0 ? _m : LogBuiltInMetadata.Target.NoSpecificTarget),
4894
+ target: ((_m = opts.target) !== null && _m !== void 0 ? _m : LogBuiltInMetadata.Target.NoTarget),
4769
4895
  action: ((_o = opts.action) !== null && _o !== void 0 ? _o : LogAction$1.Unknown),
4770
4896
  });
4771
4897
  // Source-specific info
@@ -5132,6 +5258,31 @@ const initLogCollection = (Collection) => {
5132
5258
  });
5133
5259
  };
5134
5260
 
5261
+ // Cache user's ability
5262
+ let canReview = undefined;
5263
+ /**
5264
+ * Check if the current user can review logs
5265
+ * @author Gabe Abrams
5266
+ * @returns true if current user can review logs
5267
+ */
5268
+ const canReviewLogs = () => __awaiter(void 0, void 0, void 0, function* () {
5269
+ // If cached, use that value
5270
+ if (canReview !== undefined) {
5271
+ return canReview;
5272
+ }
5273
+ // Ask on server
5274
+ try {
5275
+ canReview = !!(yield visitServerEndpoint({
5276
+ path: LOG_REVIEW_STATUS_ROUTE,
5277
+ method: 'GET',
5278
+ }));
5279
+ }
5280
+ catch (err) {
5281
+ canReview = false;
5282
+ }
5283
+ return canReview;
5284
+ });
5285
+
5135
5286
  /**
5136
5287
  * Days of the week
5137
5288
  * @author Gabe Abrams
@@ -5184,6 +5335,7 @@ exports.Variant = Variant$1;
5184
5335
  exports.abbreviate = abbreviate;
5185
5336
  exports.alert = alert$1;
5186
5337
  exports.avg = avg;
5338
+ exports.canReviewLogs = canReviewLogs;
5187
5339
  exports.ceilToNumDecimals = ceilToNumDecimals;
5188
5340
  exports.confirm = confirm;
5189
5341
  exports.floorToNumDecimals = floorToNumDecimals;