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/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}`,
@@ -2049,7 +2075,7 @@ const ItemPicker = (props) => {
2049
2075
  /*------------------------------------------------------------------------*/
2050
2076
  /* -------------- Props ------------- */
2051
2077
  // Destructure all props
2052
- const { title, items, onChanged, } = props;
2078
+ const { title, items, onChanged, noBottomMargin, } = props;
2053
2079
  /*------------------------------------------------------------------------*/
2054
2080
  /* Component Functions */
2055
2081
  /*------------------------------------------------------------------------*/
@@ -2059,7 +2085,7 @@ const ItemPicker = (props) => {
2059
2085
  /*----------------------------------------*/
2060
2086
  /* Main UI */
2061
2087
  /*----------------------------------------*/
2062
- return (React.createElement(TabBox, { title: title },
2088
+ return (React.createElement(TabBox, { title: title, noBottomMargin: noBottomMargin },
2063
2089
  React.createElement("div", { style: { overflowX: 'auto' } },
2064
2090
  React.createElement(NestableItemList, { items: items, onChanged: onChanged }))));
2065
2091
  };
@@ -2376,9 +2402,9 @@ const IntelliTable = (props) => {
2376
2402
  // Create the cell UI
2377
2403
  return (React.createElement("th", { key: column.param, scope: "col", id: `IntelliTable-${id}-header-${column.param}` },
2378
2404
  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),
2405
+ React.createElement("span", { className: "text-nowrap" }, column.title),
2380
2406
  React.createElement("div", null,
2381
- React.createElement("button", { type: "button", className: "btn btn-light", "aria-label": sortButtonAriaLabel, onClick: () => {
2407
+ React.createElement("button", { type: "button", className: "btn btn-light btn-sm ms-1", "aria-label": sortButtonAriaLabel, onClick: () => {
2382
2408
  dispatch({
2383
2409
  type: ActionType$1.ToggleSortColumn,
2384
2410
  param: column.param,
@@ -2526,7 +2552,7 @@ const IntelliTable = (props) => {
2526
2552
  React.createElement("h3", { className: "m-0" }, title),
2527
2553
  React.createElement("div", { className: "flex-grow-1 text-end" },
2528
2554
  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: () => {
2555
+ 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
2556
  dispatch({
2531
2557
  type: ActionType$1.ToggleColVisCusModalVisibility,
2532
2558
  });
@@ -2590,6 +2616,11 @@ const style = `
2590
2616
  border: 0.05rem solid black;
2591
2617
  border-radius: 0.5rem;
2592
2618
  overflow: hidden;
2619
+ padding: 0.7rem;
2620
+
2621
+ /* Solid background */
2622
+ background-color: white;
2623
+ color: black;
2593
2624
 
2594
2625
  /* Place contents in flex column */
2595
2626
  flex-direction: column;
@@ -2616,6 +2647,27 @@ const style = `
2616
2647
  /* Vertical scroll */
2617
2648
  overflow-y: auto;
2618
2649
  }
2650
+
2651
+ .LogReviewer-header-close-button {
2652
+ border: 0 !important;
2653
+ background-color: transparent !important;
2654
+ padding-top: 0 !important;
2655
+ padding-bottom: 0 !important;
2656
+ padding-right: 1em !important;
2657
+ margin: 0 !important;
2658
+ color: #444 !important;
2659
+
2660
+ right: 0 !important;
2661
+ position: absolute !important;
2662
+ }
2663
+ .LogReviewer-header-close-button:hover {
2664
+ border: 0 !important;
2665
+ background-color: transparent !important;
2666
+ padding-top: 0 !important;
2667
+ padding-bottom: 0 !important;
2668
+ margin: 0 !important;
2669
+ color: #000 !important;
2670
+ }
2619
2671
  `;
2620
2672
  /*------------------------------------------------------------------------*/
2621
2673
  /* Static Functions */
@@ -2635,7 +2687,7 @@ const genHumanReadableName = (machineReadableName) => {
2635
2687
  // Uppercase! Add a space before
2636
2688
  humanReadableName += ' ';
2637
2689
  }
2638
- humanReadableName += chars;
2690
+ humanReadableName += char;
2639
2691
  });
2640
2692
  // Trim and return
2641
2693
  return humanReadableName.trim();
@@ -2697,7 +2749,19 @@ const reducer = (state, action) => {
2697
2749
  return Object.assign(Object.assign({}, state), { contextFilterState: action.contextFilterState });
2698
2750
  }
2699
2751
  case ActionType.UpdateTagFilterState: {
2700
- return Object.assign(Object.assign({}, state), { tagFilterState: action.tagFilterState });
2752
+ const { tagFilterState } = action;
2753
+ // Select all if every tag is deselected
2754
+ const numTagsSelected = (Object.values(tagFilterState)
2755
+ .filter((isSelected) => {
2756
+ return isSelected;
2757
+ })
2758
+ .length);
2759
+ if (numTagsSelected === 0) {
2760
+ Object.keys(tagFilterState).forEach((tag) => {
2761
+ tagFilterState[tag] = true;
2762
+ });
2763
+ }
2764
+ return Object.assign(Object.assign({}, state), { tagFilterState });
2701
2765
  }
2702
2766
  case ActionType.UpdateActionErrorFilterState: {
2703
2767
  return Object.assign(Object.assign({}, state), { actionErrorFilterState: action.actionErrorFilterState });
@@ -2717,10 +2781,33 @@ const LogReviewer = (props) => {
2717
2781
  /*------------------------------------------------------------------------*/
2718
2782
  /* Setup */
2719
2783
  /*------------------------------------------------------------------------*/
2720
- var _a, _b, _c, _d, _e;
2784
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
2721
2785
  /* -------------- Props ------------- */
2722
2786
  // Destructure props
2723
2787
  const { LogMetadata, onClose, } = props;
2788
+ // Add built-in LogMetadata
2789
+ // > Add "uncategorized" subcontext to each context
2790
+ Object.keys((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {}).forEach((context) => {
2791
+ if (LogMetadata.Context
2792
+ // Context has children already
2793
+ && typeof LogMetadata.Context[context] !== 'string') {
2794
+ LogMetadata.Context[context][LogBuiltInMetadata.Context.Uncategorized] = (LogBuiltInMetadata.Context.Uncategorized);
2795
+ }
2796
+ });
2797
+ // > Add built-in contexts
2798
+ LogMetadata.Context = ((_b = LogMetadata.Context) !== null && _b !== void 0 ? _b : {});
2799
+ Object.keys(LogBuiltInMetadata.Context).forEach((context) => {
2800
+ if (LogMetadata.Context) {
2801
+ LogMetadata.Context[context] = context;
2802
+ }
2803
+ });
2804
+ // > Add built-in targets
2805
+ LogMetadata.Target = ((_c = LogMetadata.Target) !== null && _c !== void 0 ? _c : {});
2806
+ Object.keys(LogBuiltInMetadata.Target).forEach((target) => {
2807
+ if (LogMetadata.Target) {
2808
+ LogMetadata.Target[target] = target;
2809
+ }
2810
+ });
2724
2811
  /* -------------- State ------------- */
2725
2812
  // Create initial date filter state
2726
2813
  const today = getTimeInfoInET();
@@ -2740,7 +2827,7 @@ const LogReviewer = (props) => {
2740
2827
  };
2741
2828
  // Create initial context filter state
2742
2829
  const initContextFilterState = {};
2743
- Object.keys((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {}).forEach((context) => {
2830
+ Object.keys((_d = LogMetadata.Context) !== null && _d !== void 0 ? _d : {}).forEach((context) => {
2744
2831
  var _a, _b;
2745
2832
  const contextValue = ((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {})[context];
2746
2833
  if (typeof contextValue === 'string') {
@@ -2758,7 +2845,7 @@ const LogReviewer = (props) => {
2758
2845
  });
2759
2846
  // Create initial tag filter state
2760
2847
  const initTagFilterState = {};
2761
- Object.values((_b = LogMetadata.Tag) !== null && _b !== void 0 ? _b : {}).forEach((tagValue) => {
2848
+ Object.values((_e = LogMetadata.Tag) !== null && _e !== void 0 ? _e : {}).forEach((tagValue) => {
2762
2849
  initTagFilterState[tagValue] = true;
2763
2850
  });
2764
2851
  // Create advanced filter state
@@ -2785,12 +2872,16 @@ const LogReviewer = (props) => {
2785
2872
  target: {},
2786
2873
  action: {},
2787
2874
  };
2788
- Object.values((_c = LogMetadata.Target) !== null && _c !== void 0 ? _c : {}).forEach((target) => {
2875
+ Object.values((_f = LogMetadata.Target) !== null && _f !== void 0 ? _f : {}).forEach((target) => {
2789
2876
  initActionErrorFilterState.target[target] = true;
2790
2877
  });
2791
2878
  Object.values(LogAction$1).forEach((action) => {
2792
2879
  initActionErrorFilterState.action[action] = true;
2793
2880
  });
2881
+ // Add built-in targets
2882
+ Object.values(LogBuiltInMetadata.Target).forEach((target) => {
2883
+ initActionErrorFilterState.target[target] = true;
2884
+ });
2794
2885
  // Initial state
2795
2886
  const initialState = {
2796
2887
  loading: true,
@@ -2824,15 +2915,18 @@ const LogReviewer = (props) => {
2824
2915
  let month = newDateFilterState.startDate.month;
2825
2916
  while (
2826
2917
  // Earlier year
2827
- (year <= newDateFilterState.endDate.year)
2918
+ (year < newDateFilterState.endDate.year)
2828
2919
  // Current year but included month
2829
2920
  || (year === newDateFilterState.endDate.year
2830
2921
  && month <= newDateFilterState.endDate.month)) {
2831
- // Add to list
2832
- toLoad.push({
2833
- year,
2834
- month,
2835
- });
2922
+ // Add to list if not already loaded
2923
+ if (!logMap[year]
2924
+ || !logMap[year][month]) {
2925
+ toLoad.push({
2926
+ year,
2927
+ month,
2928
+ });
2929
+ }
2836
2930
  // Increment
2837
2931
  month += 1;
2838
2932
  if (month > 12) {
@@ -2856,6 +2950,7 @@ const LogReviewer = (props) => {
2856
2950
  });
2857
2951
  // Check which year/month combos we need to load
2858
2952
  const toLoad = listMonthsToLoad(newDateFilterState);
2953
+ console.log('Filter update:', newDateFilterState, toLoad, logMap);
2859
2954
  // If nothing to load, finished
2860
2955
  if (toLoad.length === 0) {
2861
2956
  return;
@@ -2920,10 +3015,10 @@ const LogReviewer = (props) => {
2920
3015
  /* Filters */
2921
3016
  /*----------------------------------------*/
2922
3017
  // Filter toggle
2923
- const filterToggles = (React.createElement("div", { className: "LogReviewer-filter-toggles d-flex align-items-center justify-content-center" },
3018
+ const filterToggles = (React.createElement("div", { className: "LogReviewer-filter-toggles" },
2924
3019
  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: () => {
3020
+ React.createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
3021
+ 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
3022
  dispatch({
2928
3023
  type: ActionType.ToggleFilterDrawer,
2929
3024
  filterDrawer: FilterDrawer.Date,
@@ -2931,7 +3026,7 @@ const LogReviewer = (props) => {
2931
3026
  } },
2932
3027
  React.createElement(FontAwesomeIcon, { icon: faCalendar, className: "me-2" }),
2933
3028
  "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: () => {
3029
+ 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
3030
  dispatch({
2936
3031
  type: ActionType.ToggleFilterDrawer,
2937
3032
  filterDrawer: FilterDrawer.Context,
@@ -2939,15 +3034,15 @@ const LogReviewer = (props) => {
2939
3034
  } },
2940
3035
  React.createElement(FontAwesomeIcon, { icon: faCircle, className: "me-2" }),
2941
3036
  "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: () => {
3037
+ (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
3038
  dispatch({
2944
3039
  type: ActionType.ToggleFilterDrawer,
2945
3040
  filterDrawer: FilterDrawer.Tag,
2946
3041
  });
2947
3042
  } },
2948
3043
  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: () => {
3044
+ "Tag")),
3045
+ 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
3046
  dispatch({
2952
3047
  type: ActionType.ToggleFilterDrawer,
2953
3048
  filterDrawer: FilterDrawer.Action,
@@ -2955,7 +3050,7 @@ const LogReviewer = (props) => {
2955
3050
  } },
2956
3051
  React.createElement(FontAwesomeIcon, { icon: faHammer, className: "me-2" }),
2957
3052
  "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: () => {
3053
+ 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
3054
  dispatch({
2960
3055
  type: ActionType.ToggleFilterDrawer,
2961
3056
  filterDrawer: FilterDrawer.Advanced,
@@ -2968,25 +3063,29 @@ const LogReviewer = (props) => {
2968
3063
  if (expandedFilterDrawer) {
2969
3064
  if (expandedFilterDrawer === FilterDrawer.Date) {
2970
3065
  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
- });
3066
+ 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) => {
3067
+ dateFilterState.startDate = { month, day, year };
3068
+ handleDateRangeUpdated(dateFilterState);
2976
3069
  } }),
2977
3070
  ' ',
2978
3071
  "to",
2979
3072
  ' ',
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
- });
3073
+ 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) => {
3074
+ if (year < dateFilterState.startDate.year
3075
+ || (year === dateFilterState.startDate.year
3076
+ && month < dateFilterState.startDate.month)
3077
+ || (year === dateFilterState.startDate.year
3078
+ && month === dateFilterState.startDate.month
3079
+ && day < dateFilterState.startDate.day)) {
3080
+ return alert$1('Invalid Start Date', 'The start date cannot be before the end date.');
3081
+ }
3082
+ dateFilterState.endDate = { month, day, year };
3083
+ handleDateRangeUpdated(dateFilterState);
2985
3084
  } })));
2986
3085
  }
2987
3086
  else if (expandedFilterDrawer === FilterDrawer.Context) {
2988
3087
  // Create item picker items
2989
- const pickableItems = (Object.keys((_d = LogMetadata.Context) !== null && _d !== void 0 ? _d : {})
3088
+ const pickableItems = (Object.keys((_g = LogMetadata.Context) !== null && _g !== void 0 ? _g : {})
2990
3089
  .map((context) => {
2991
3090
  var _a;
2992
3091
  const value = ((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {})[context];
@@ -3015,7 +3114,7 @@ const LogReviewer = (props) => {
3015
3114
  }));
3016
3115
  const item = {
3017
3116
  id: context,
3018
- name: context,
3117
+ name: genHumanReadableName(context),
3019
3118
  isGroup: true,
3020
3119
  children,
3021
3120
  };
@@ -3023,6 +3122,8 @@ const LogReviewer = (props) => {
3023
3122
  }));
3024
3123
  // Create filter UI
3025
3124
  filterDrawer = (React.createElement(ItemPicker, { title: "Context", items: pickableItems, onChanged: (updatedItems) => {
3125
+ console.log('Items before:', pickableItems);
3126
+ console.log('Updated Items:', updatedItems);
3026
3127
  // Update our state
3027
3128
  updatedItems.forEach((pickableItem) => {
3028
3129
  if (pickableItem.isGroup) {
@@ -3079,7 +3180,7 @@ const LogReviewer = (props) => {
3079
3180
  }, ariaLabel: "only show error logs", selected: actionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true })),
3080
3181
  (actionErrorFilterState.type === undefined
3081
3182
  || actionErrorFilterState.type === LogType$1.Action) && (React.createElement(TabBox, { title: "Action Log Details" },
3082
- React.createElement(ButtonInputGroup, { label: "Action" }, Object.keys(LogAction$1)
3183
+ React.createElement(ButtonInputGroup, { label: "Action", className: "mb-2" }, Object.keys(LogAction$1)
3083
3184
  .map((action, i) => {
3084
3185
  const description = genHumanReadableName(action);
3085
3186
  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 +3191,20 @@ const LogReviewer = (props) => {
3090
3191
  });
3091
3192
  } }));
3092
3193
  })),
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
- })))),
3194
+ React.createElement(ButtonInputGroup, { label: "Target" },
3195
+ (Object.keys((_h = LogMetadata.Target) !== null && _h !== void 0 ? _h : {}).length === 0) && (React.createElement("div", null, "This app does not have any targets yet.")),
3196
+ Object.keys((_j = LogMetadata.Target) !== null && _j !== void 0 ? _j : {})
3197
+ .map((target, i) => {
3198
+ var _a;
3199
+ const description = genHumanReadableName(target);
3200
+ return (React.createElement(CheckboxButton, { id: `LogReviewer-target-${target}-checkbox`, text: description, ariaLabel: `include logs with target "${description}" in results`, onChanged: (checked) => {
3201
+ actionErrorFilterState.target[target] = checked;
3202
+ dispatch({
3203
+ type: ActionType.UpdateActionErrorFilterState,
3204
+ actionErrorFilterState,
3205
+ });
3206
+ }, noMarginOnRight: i === Object.keys((_a = LogMetadata.Target) !== null && _a !== void 0 ? _a : {}).length - 1 }));
3207
+ })))),
3105
3208
  (actionErrorFilterState.type === undefined
3106
3209
  || actionErrorFilterState.type === LogType$1.Error) && (React.createElement(TabBox, { title: "Error Log Details" },
3107
3210
  React.createElement("div", { className: "input-group mb-2" },
@@ -3263,26 +3366,27 @@ const LogReviewer = (props) => {
3263
3366
  advancedFilterState,
3264
3367
  });
3265
3368
  }, 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
- } })))));
3369
+ advancedFilterState.source !== LogSource$1.Client && (React.createElement("div", { className: "mt-2" },
3370
+ React.createElement("div", { className: "input-group mb-2" },
3371
+ React.createElement("span", { className: "input-group-text" }, "Server Route Path"),
3372
+ 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) => {
3373
+ advancedFilterState.courseName = ((e.target.value)
3374
+ .trim());
3375
+ dispatch({
3376
+ type: ActionType.UpdateAdvancedFilterState,
3377
+ advancedFilterState,
3378
+ });
3379
+ } })),
3380
+ React.createElement("div", { className: "input-group mb-2" },
3381
+ React.createElement("span", { className: "input-group-text" }, "Server Route Template"),
3382
+ 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) => {
3383
+ advancedFilterState.courseName = ((e.target.value)
3384
+ .trim());
3385
+ dispatch({
3386
+ type: ActionType.UpdateAdvancedFilterState,
3387
+ advancedFilterState,
3388
+ });
3389
+ } })))))));
3286
3390
  }
3287
3391
  }
3288
3392
  // Filters UI
@@ -3293,10 +3397,11 @@ const LogReviewer = (props) => {
3293
3397
  // > Perform filters
3294
3398
  const logs = [];
3295
3399
  Object.keys(logMap).forEach((year) => {
3296
- Object.keys(logMap).forEach((month) => {
3400
+ Object.keys(logMap[year]).forEach((month) => {
3297
3401
  logMap[year][month].forEach((log) => {
3298
- /* ----------- Date Filter ---------- */
3299
3402
  var _a;
3403
+ /* ----------- Date Filter ---------- */
3404
+ console.log('Log:', log);
3300
3405
  // Before start date
3301
3406
  if (
3302
3407
  // Previous year
@@ -3308,6 +3413,7 @@ const LogReviewer = (props) => {
3308
3413
  || ((log.year === dateFilterState.startDate.year)
3309
3414
  && (log.month === dateFilterState.startDate.month)
3310
3415
  && (log.day < dateFilterState.startDate.day))) {
3416
+ console.log('RULED OUT: START');
3311
3417
  return;
3312
3418
  }
3313
3419
  // After end date
@@ -3321,6 +3427,7 @@ const LogReviewer = (props) => {
3321
3427
  || ((log.year === dateFilterState.endDate.year)
3322
3428
  && (log.month === dateFilterState.endDate.month)
3323
3429
  && (log.day > dateFilterState.endDate.day))) {
3430
+ console.log('RULED OUT: END');
3324
3431
  return;
3325
3432
  }
3326
3433
  /* --------- Context Filter --------- */
@@ -3333,29 +3440,30 @@ const LogReviewer = (props) => {
3333
3440
  .every((isSelected) => {
3334
3441
  return !isSelected;
3335
3442
  }))) {
3443
+ console.log('RULED OUT: CONTEXT');
3336
3444
  return;
3337
3445
  }
3338
3446
  // Subcontext doesn't match
3339
3447
  if (
3340
- // Log has a subcontext
3341
- log.subcontext
3448
+ // Log context is not "uncategorized" (no point in further filters)
3449
+ log.context !== LogBuiltInMetadata.Context.Uncategorized
3450
+ // Log has a subcontext
3451
+ && log.subcontext
3342
3452
  // Context has subcontexts
3343
3453
  && (contextFilterState[log.context]
3344
3454
  && contextFilterState[log.context] !== false
3345
3455
  && contextFilterState[log.context] !== true)
3346
3456
  // Subcontext is not selected
3347
3457
  && !contextFilterState[log.context][log.subcontext]) {
3458
+ console.log('RULED OUT: SUBCONTEXT');
3348
3459
  return;
3349
3460
  }
3350
3461
  /* -------------- Tags -------------- */
3351
3462
  // 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
- }))) {
3463
+ if (log.tags.every((tag) => {
3464
+ return !tagFilterState[tag];
3465
+ })) {
3466
+ console.log('RULED OUT: TAGS');
3359
3467
  return;
3360
3468
  }
3361
3469
  /* ------- Actions and Errors ------- */
@@ -3365,6 +3473,7 @@ const LogReviewer = (props) => {
3365
3473
  actionErrorFilterState.type !== undefined
3366
3474
  // Log type doesn't match
3367
3475
  && actionErrorFilterState.type !== log.type) {
3476
+ console.log('RULED OUT: TYPE');
3368
3477
  return;
3369
3478
  }
3370
3479
  // Filter errors
@@ -3377,6 +3486,7 @@ const LogReviewer = (props) => {
3377
3486
  && actionErrorFilterState.errorMessage.trim().length > 0
3378
3487
  // Message doesn't match
3379
3488
  && log.errorMessage.toLowerCase().includes(actionErrorFilterState.errorMessage.trim().toLowerCase())) {
3489
+ console.log('RULED OUT: ERROR MESSAGE');
3380
3490
  return;
3381
3491
  }
3382
3492
  // Code doesn't match
@@ -3387,6 +3497,7 @@ const LogReviewer = (props) => {
3387
3497
  && actionErrorFilterState.errorCode.trim().length > 0
3388
3498
  // Code doesn't match
3389
3499
  && log.errorCode.toUpperCase().includes(actionErrorFilterState.errorCode.trim().toUpperCase())) {
3500
+ console.log('RULED OUT: ERROR CODE');
3390
3501
  return;
3391
3502
  }
3392
3503
  }
@@ -3398,6 +3509,7 @@ const LogReviewer = (props) => {
3398
3509
  log.target
3399
3510
  // Target isn't selected
3400
3511
  && !actionErrorFilterState.target[log.target]) {
3512
+ console.log('RULED OUT: TARGET');
3401
3513
  return;
3402
3514
  }
3403
3515
  // Action
@@ -3406,6 +3518,7 @@ const LogReviewer = (props) => {
3406
3518
  log.action
3407
3519
  // Action isn't selected
3408
3520
  && !actionErrorFilterState.action[log.action]) {
3521
+ console.log('RULED OUT: ACTION');
3409
3522
  return;
3410
3523
  }
3411
3524
  }
@@ -3416,6 +3529,7 @@ const LogReviewer = (props) => {
3416
3529
  log.userFirstName
3417
3530
  // First name query doesn't match
3418
3531
  && !log.userFirstName.toLowerCase().includes(advancedFilterState.userFirstName.toLowerCase().trim())) {
3532
+ console.log('RULED OUT: FIRST');
3419
3533
  return;
3420
3534
  }
3421
3535
  // Last name doesn't match
@@ -3424,6 +3538,7 @@ const LogReviewer = (props) => {
3424
3538
  log.userLastName
3425
3539
  // Last name query doesn't match
3426
3540
  && !log.userLastName.toLowerCase().includes(advancedFilterState.userLastName.toLowerCase().trim())) {
3541
+ console.log('RULED OUT: LAST');
3427
3542
  return;
3428
3543
  }
3429
3544
  // Email doesn't match
@@ -3432,6 +3547,7 @@ const LogReviewer = (props) => {
3432
3547
  log.userEmail
3433
3548
  // Email query doesn't match
3434
3549
  && !log.userEmail.toLowerCase().includes(advancedFilterState.userEmail.toLowerCase().trim())) {
3550
+ console.log('RULED OUT: EMAIL');
3435
3551
  return;
3436
3552
  }
3437
3553
  // User id doesn't match
@@ -3440,6 +3556,7 @@ const LogReviewer = (props) => {
3440
3556
  log.userId
3441
3557
  // User id doesn't match
3442
3558
  && !String(log.userId).includes(advancedFilterState.userId.trim())) {
3559
+ console.log('RULED OUT: USER ID');
3443
3560
  return;
3444
3561
  }
3445
3562
  // Learner not allowed
@@ -3448,6 +3565,7 @@ const LogReviewer = (props) => {
3448
3565
  log.isLearner
3449
3566
  // Learners aren't included
3450
3567
  && !advancedFilterState.includeLearners) {
3568
+ console.log('RULED OUT: LEARNER');
3451
3569
  return;
3452
3570
  }
3453
3571
  // TTM not allowed
@@ -3456,6 +3574,7 @@ const LogReviewer = (props) => {
3456
3574
  log.isTTM
3457
3575
  // TTMs aren't included
3458
3576
  && !advancedFilterState.includeTTMs) {
3577
+ console.log('RULED OUT: TTM');
3459
3578
  return;
3460
3579
  }
3461
3580
  // Admin not allowed
@@ -3464,6 +3583,7 @@ const LogReviewer = (props) => {
3464
3583
  log.isAdmin
3465
3584
  // Admins aren't included
3466
3585
  && !advancedFilterState.includeAdmins) {
3586
+ console.log('RULED OUT: ADMIN');
3467
3587
  return;
3468
3588
  }
3469
3589
  // Course Id doesn't match
@@ -3472,6 +3592,7 @@ const LogReviewer = (props) => {
3472
3592
  log.courseId
3473
3593
  // Course Id doesn't match
3474
3594
  && !String(log.courseId).includes(advancedFilterState.courseId.trim())) {
3595
+ console.log('RULED OUT: COURSE ID');
3475
3596
  return;
3476
3597
  }
3477
3598
  // Course name doesn't match
@@ -3480,6 +3601,7 @@ const LogReviewer = (props) => {
3480
3601
  log.courseName
3481
3602
  // Course name doesn't match
3482
3603
  && !String(log.courseName).includes(advancedFilterState.courseName.trim())) {
3604
+ console.log('RULED OUT: COURSE NAME');
3483
3605
  return;
3484
3606
  }
3485
3607
  // Mobile filter doesn't match
@@ -3490,6 +3612,7 @@ const LogReviewer = (props) => {
3490
3612
  && log.device
3491
3613
  // Mobile filter doesn't match
3492
3614
  && (advancedFilterState.isMobile === log.device.isMobile)) {
3615
+ console.log('RULED OUT: MOBILE');
3493
3616
  return;
3494
3617
  }
3495
3618
  // Log source doesn't match
@@ -3500,6 +3623,7 @@ const LogReviewer = (props) => {
3500
3623
  && log.source
3501
3624
  // Source filter doesn't match
3502
3625
  && (advancedFilterState.source !== log.source)) {
3626
+ console.log('RULED OUT: SOURCE');
3503
3627
  return;
3504
3628
  }
3505
3629
  // Route path doesn't match (Only for server source)
@@ -3510,6 +3634,7 @@ const LogReviewer = (props) => {
3510
3634
  && (advancedFilterState.routePath.trim().length)
3511
3635
  // Route path doesn't match
3512
3636
  && !(log.routePath.includes(advancedFilterState.routePath.trim()))) {
3637
+ console.log('RULED OUT: PATH');
3513
3638
  return;
3514
3639
  }
3515
3640
  // Route template doesn't match (Only for server source)
@@ -3520,6 +3645,7 @@ const LogReviewer = (props) => {
3520
3645
  && (advancedFilterState.routeTemplate.trim().length)
3521
3646
  // Route template doesn't match
3522
3647
  && !(log.routeTemplate.includes(advancedFilterState.routeTemplate.trim()))) {
3648
+ console.log('RULED OUT: TEMPLATE');
3523
3649
  return;
3524
3650
  }
3525
3651
  /* -------------- Done -------------- */
@@ -3719,11 +3845,17 @@ const LogReviewer = (props) => {
3719
3845
  },
3720
3846
  ];
3721
3847
  // Create intelliTable
3722
- const dataTable = (React.createElement(IntelliTable, { title: "Matching Logs", id: "logs", data: logs, columns: columns }));
3848
+ const dataTable = (logs.length === 0
3849
+ ? (React.createElement(React.Fragment, null,
3850
+ React.createElement("h3", { className: "m-0" }, "Matching Logs:"),
3851
+ React.createElement("div", { className: "alert alert-warning text-center" },
3852
+ React.createElement("h4", { className: "m-1" }, "No Logs to Show"),
3853
+ React.createElement("div", null, "Either your filters are too strict or no matching logs have been created yet."))))
3854
+ : (React.createElement(IntelliTable, { title: "Matching Logs:", id: "logs", data: logs, columns: columns })));
3723
3855
  // Main body
3724
3856
  body = (React.createElement(React.Fragment, null,
3725
3857
  filters,
3726
- dataTable));
3858
+ React.createElement("div", { className: "mt-2" }, dataTable)));
3727
3859
  }
3728
3860
  /* ---------- Wrap in Modal --------- */
3729
3861
  return (React.createElement("div", { className: "LogReviewer-outer-container" },
@@ -3731,14 +3863,10 @@ const LogReviewer = (props) => {
3731
3863
  React.createElement("div", { className: "LogReviewer-inner-container" },
3732
3864
  React.createElement("div", { className: "LogReviewer-header" },
3733
3865
  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 }))),
3866
+ React.createElement("h3", { className: "text-center m-0" }, "Log Review Dashboard")),
3867
+ React.createElement("div", { style: { width: 0 } },
3868
+ 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 },
3869
+ React.createElement(FontAwesomeIcon, { icon: faTimes })))),
3742
3870
  React.createElement("div", { className: "LogReviewer-contents" }, body))));
3743
3871
  };
3744
3872
 
@@ -3884,7 +4012,7 @@ const padZerosLeft = (num, numDigits) => {
3884
4012
  * access to log review
3885
4013
  * @author Gabe Abrams
3886
4014
  */
3887
- const LOG_REVIEW_STATUS_ROUTE = `/admin${ROUTE_PATH_PREFIX}/logs/access`;
4015
+ const LOG_REVIEW_STATUS_ROUTE = `${ROUTE_PATH_PREFIX}/logs/access_allowed`;
3888
4016
 
3889
4017
  // Stored copy of caccl functions
3890
4018
  let _cacclGetLaunchInfo;
@@ -3926,7 +4054,7 @@ const internalGetLogCollection = () => {
3926
4054
  * @param opts.getLaunchInfo CACCL LTI's get launch info function
3927
4055
  * @param [opts.logCollection] mongo collection from dce-mango to use for
3928
4056
  * storing logs. If none is included, logs are written to the console
3929
- * @param [opts.logReviewAdmins=all admins] info on which admins can review
4057
+ * @param [opts.logReviewAdmins=all] info on which admins can review
3930
4058
  * logs from the client. If not included, all Canvas admins are allowed to
3931
4059
  * review logs. If null, no Canvas admins are allowed to review logs.
3932
4060
  * If an array of Canvas userIds (numbers), only Canvas admins with those
@@ -4009,71 +4137,81 @@ const initServer = (opts) => {
4009
4137
  /*----------------------------------------*/
4010
4138
  /* Log Reviewer */
4011
4139
  /*----------------------------------------*/
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;
4140
+ /**
4141
+ * Check if a given user is allowed to review logs
4142
+ * @author Gabe Abrams
4143
+ * @param userId the id of the user
4144
+ * @param isAdmin if true, the user is an admin
4145
+ * @returns true if the user can review logs
4146
+ */
4147
+ const canReviewLogs = (userId, isAdmin) => __awaiter(void 0, void 0, void 0, function* () {
4148
+ // Immediately deny access if user is not an admin
4149
+ if (!isAdmin) {
4150
+ return false;
4151
+ }
4152
+ // If all admins are allowed, we're done
4153
+ if (!opts.logReviewAdmins) {
4154
+ return true;
4155
+ }
4156
+ // Do a dynamic check
4157
+ try {
4158
+ // Array of userIds
4159
+ if (Array.isArray(opts.logReviewAdmins)) {
4160
+ return opts.logReviewAdmins.some((allowedId) => {
4161
+ return (userId === allowedId);
4162
+ });
4031
4163
  }
4032
- catch (err) {
4033
- // If an error occurred, simply return false
4034
- return false;
4164
+ // Must be a collection
4165
+ const matches = yield opts.logReviewAdmins.find({ userId });
4166
+ // Make sure at least one entry matches
4167
+ return matches.length > 0;
4168
+ }
4169
+ catch (err) {
4170
+ // If an error occurred, simply return false
4171
+ return false;
4172
+ }
4173
+ });
4174
+ /**
4175
+ * Check if the current user has access to logs
4176
+ * @author Gabe Abrams
4177
+ * @returns {boolean} true if user has access
4178
+ */
4179
+ opts.app.get(LOG_REVIEW_STATUS_ROUTE, genRouteHandler({
4180
+ handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
4181
+ const { userId, isAdmin } = params;
4182
+ const canReview = yield canReviewLogs(userId, isAdmin);
4183
+ return canReview;
4184
+ }),
4185
+ }));
4186
+ /**
4187
+ * Get all logs for a certain month
4188
+ * @author Gabe Abrams
4189
+ * @param {number} year the year to query (e.g. 2022)
4190
+ * @param {number} month the month to query (e.g. 1 = January)
4191
+ * @returns {Log[]} list of logs from the given month
4192
+ */
4193
+ opts.app.get(`${LOG_REVIEW_ROUTE_PATH_PREFIX}/years/:year/months/:month`, genRouteHandler({
4194
+ paramTypes: {
4195
+ year: ParamType$1.Int,
4196
+ month: ParamType$1.Int,
4197
+ },
4198
+ handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
4199
+ // Get user info
4200
+ const { year, month, userId, isAdmin, } = params;
4201
+ // Validate user
4202
+ const canReview = yield canReviewLogs(userId, isAdmin);
4203
+ if (!canReview) {
4204
+ throw new ErrorWithCode('You cannot access this resource because you do not have the appropriate permissions.', ReactKitErrorCode$1.NotAllowedToReviewLogs);
4035
4205
  }
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
- }
4206
+ // Query for logs
4207
+ const logs = yield _logCollection.find({
4208
+ year,
4209
+ month,
4210
+ });
4211
+ // Return logs
4212
+ return logs;
4213
+ }),
4214
+ }));
4077
4215
  };
4078
4216
 
4079
4217
  // Import shared types
@@ -4373,18 +4511,6 @@ const parseUserAgent = (userAgent) => {
4373
4511
  };
4374
4512
  };
4375
4513
 
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
4514
  /**
4389
4515
  * Generate an express API route handler
4390
4516
  * @author Gabe Abrams
@@ -4757,7 +4883,7 @@ const genRouteHandler = (opts) => {
4757
4883
  }
4758
4884
  : {
4759
4885
  type: LogType$1.Action,
4760
- target: ((_m = opts.target) !== null && _m !== void 0 ? _m : LogBuiltInMetadata.Target.NoSpecificTarget),
4886
+ target: ((_m = opts.target) !== null && _m !== void 0 ? _m : LogBuiltInMetadata.Target.NoTarget),
4761
4887
  action: ((_o = opts.action) !== null && _o !== void 0 ? _o : LogAction$1.Unknown),
4762
4888
  });
4763
4889
  // Source-specific info
@@ -5124,6 +5250,31 @@ const initLogCollection = (Collection) => {
5124
5250
  });
5125
5251
  };
5126
5252
 
5253
+ // Cache user's ability
5254
+ let canReview = undefined;
5255
+ /**
5256
+ * Check if the current user can review logs
5257
+ * @author Gabe Abrams
5258
+ * @returns true if current user can review logs
5259
+ */
5260
+ const canReviewLogs = () => __awaiter(void 0, void 0, void 0, function* () {
5261
+ // If cached, use that value
5262
+ if (canReview !== undefined) {
5263
+ return canReview;
5264
+ }
5265
+ // Ask on server
5266
+ try {
5267
+ canReview = !!(yield visitServerEndpoint({
5268
+ path: LOG_REVIEW_STATUS_ROUTE,
5269
+ method: 'GET',
5270
+ }));
5271
+ }
5272
+ catch (err) {
5273
+ canReview = false;
5274
+ }
5275
+ return canReview;
5276
+ });
5277
+
5127
5278
  /**
5128
5279
  * Days of the week
5129
5280
  * @author Gabe Abrams
@@ -5140,5 +5291,5 @@ var DayOfWeek;
5140
5291
  })(DayOfWeek || (DayOfWeek = {}));
5141
5292
  var DayOfWeek$1 = DayOfWeek;
5142
5293
 
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 };
5294
+ 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
5295
  //# sourceMappingURL=index.js.map