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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/esm/index.js CHANGED
@@ -511,17 +511,29 @@ const LOG_ROUTE_PATH = `${ROUTE_PATH_PREFIX}/log`;
511
511
  const LogBuiltInMetadata = {
512
512
  // Contexts
513
513
  Context: {
514
- Uncategorized: 'n/a',
515
- ServerRenderedErrorPage: '_server-rendered-error-page',
516
- ServerEndpointError: '_server-endpoint-error',
517
- ClientFatalError: '_client-fatal-error',
514
+ Uncategorized: 'Uncategorized',
515
+ ServerRenderedErrorPage: 'ServerRenderedErrorPage',
516
+ ServerEndpointError: 'ServerEndpointError',
517
+ ClientFatalError: 'ClientFatalError',
518
518
  },
519
519
  // Targets
520
520
  Target: {
521
- NoSpecificTarget: 'n/a',
521
+ NoTarget: 'NoTarget',
522
522
  },
523
523
  };
524
524
 
525
+ /**
526
+ * Allowed log levels
527
+ * @author Gabe Abrams
528
+ */
529
+ var LogLevel;
530
+ (function (LogLevel) {
531
+ LogLevel["Warn"] = "Warn";
532
+ LogLevel["Info"] = "Info";
533
+ LogLevel["Debug"] = "Debug";
534
+ })(LogLevel || (LogLevel = {}));
535
+ var LogLevel$1 = LogLevel;
536
+
525
537
  // Keep track of whether or not session expiry has already been handled
526
538
  let sessionAlreadyExpired = false;
527
539
  /*------------------------------------------------------------------------*/
@@ -640,7 +652,7 @@ const visitServerEndpoint = (opts) => __awaiter(void 0, void 0, void 0, function
640
652
  * @author Gabe Abrams
641
653
  */
642
654
  const logClientEvent = (opts) => __awaiter(void 0, void 0, void 0, function* () {
643
- var _a, _b, _c, _d, _e, _f;
655
+ var _a, _b, _c, _d, _e, _f, _g;
644
656
  return visitServerEndpoint({
645
657
  path: LOG_ROUTE_PATH,
646
658
  method: 'POST',
@@ -649,8 +661,9 @@ const logClientEvent = (opts) => __awaiter(void 0, void 0, void 0, function* ()
649
661
  ? opts.context
650
662
  : ((_b = ((_a = opts.context) !== null && _a !== void 0 ? _a : {})._) !== null && _b !== void 0 ? _b : LogBuiltInMetadata.Context.Uncategorized)),
651
663
  subcontext: ((_c = opts.subcontext) !== null && _c !== void 0 ? _c : LogBuiltInMetadata.Context.Uncategorized),
652
- tags: JSON.stringify((_d = opts.tags) !== null && _d !== void 0 ? _d : []),
653
- metadata: JSON.stringify((_e = opts.metadata) !== null && _e !== void 0 ? _e : {}),
664
+ level: ((_d = opts.level) !== null && _d !== void 0 ? _d : LogLevel$1.Info),
665
+ tags: JSON.stringify((_e = opts.tags) !== null && _e !== void 0 ? _e : []),
666
+ metadata: JSON.stringify((_f = opts.metadata) !== null && _f !== void 0 ? _f : {}),
654
667
  errorMessage: (opts.error
655
668
  ? opts.error.message
656
669
  : undefined),
@@ -661,7 +674,7 @@ const logClientEvent = (opts) => __awaiter(void 0, void 0, void 0, function* ()
661
674
  ? opts.error.stack
662
675
  : undefined),
663
676
  target: (opts.action
664
- ? ((_f = opts.target) !== null && _f !== void 0 ? _f : LogBuiltInMetadata.Target.NoSpecificTarget)
677
+ ? ((_g = opts.target) !== null && _g !== void 0 ? _g : LogBuiltInMetadata.Target.NoTarget)
665
678
  : undefined),
666
679
  action: (opts.action
667
680
  ? opts.action
@@ -1167,14 +1180,14 @@ const ButtonInputGroup = (props) => {
1167
1180
  /*------------------------------------------------------------------------*/
1168
1181
  /* -------------- Props ------------- */
1169
1182
  // Destructure all props
1170
- const { label, minLabelWidth, children, } = props;
1183
+ const { label, minLabelWidth, children, className, } = props;
1171
1184
  /*------------------------------------------------------------------------*/
1172
1185
  /* Render */
1173
1186
  /*------------------------------------------------------------------------*/
1174
1187
  /*----------------------------------------*/
1175
1188
  /* Main UI */
1176
1189
  /*----------------------------------------*/
1177
- return (React.createElement("div", { className: "input-group" },
1190
+ return (React.createElement("div", { className: `input-group ${className !== null && className !== void 0 ? className : ''}` },
1178
1191
  React.createElement("div", { className: "input-group-prepend d-flex w-100" },
1179
1192
  React.createElement("span", { className: "input-group-text", style: {
1180
1193
  minWidth: (minLabelWidth !== null && minLabelWidth !== void 0 ? minLabelWidth : undefined),
@@ -1344,12 +1357,25 @@ const SimpleDateChooser = (props) => {
1344
1357
  // Figure out which days are allowed
1345
1358
  const days = [];
1346
1359
  const numDaysInMonth = (new Date(year, month, 0)).getDate();
1347
- const firstDay = (month === today.month
1348
- ? today.day // Current month: start at current date
1349
- : 1 // Future month: start at beginning of month
1350
- );
1351
- for (let day = firstDay; day <= numDaysInMonth; day++) {
1352
- days.push(day);
1360
+ if (chooseFromPast) {
1361
+ // Past selection
1362
+ const numDaysToAdd = ((month === today.month)
1363
+ ? today.day // Current month, only add up to today
1364
+ : numDaysInMonth // Past month, add all days
1365
+ );
1366
+ for (let day = 1; day <= numDaysToAdd; day++) {
1367
+ days.push(day);
1368
+ }
1369
+ }
1370
+ else {
1371
+ // Future selection: add all remaining days of the month
1372
+ const firstDay = (month === today.month
1373
+ ? today.day // Current month: start at current date
1374
+ : 1 // Future month: start at beginning of month
1375
+ );
1376
+ for (let day = firstDay; day <= numDaysInMonth; day++) {
1377
+ days.push(day);
1378
+ }
1353
1379
  }
1354
1380
  choices.push({
1355
1381
  choiceName: `${monthName} ${year}`,
@@ -1890,24 +1916,25 @@ const CopiableBox = (props) => {
1890
1916
  /**
1891
1917
  * Reusable nested item picker
1892
1918
  * @author Yuen Ler Chow
1919
+ * @author Gabe Abrams
1893
1920
  */
1894
1921
  /* ------------- Actions ------------ */
1895
1922
  // Types of actions
1896
1923
  var ActionType$2;
1897
1924
  (function (ActionType) {
1898
- // Toggle whether the children are being shown
1899
- ActionType["ToggleItems"] = "toggle-items";
1925
+ // Toggle whether a child are being shown
1926
+ ActionType["ToggleChild"] = "toggle-child";
1900
1927
  })(ActionType$2 || (ActionType$2 = {}));
1901
1928
  /**
1902
1929
  * Reducer that executes actions
1903
- * @author Yuen Ler Chow
1930
+ * @author Gabe Abrams
1904
1931
  * @param state current state
1905
1932
  * @param action action to execute
1906
1933
  */
1907
1934
  const reducer$2 = (state, action) => {
1908
1935
  switch (action.type) {
1909
- case ActionType$2.ToggleItems: {
1910
- return { isShowingItems: !state.isShowingItems };
1936
+ case ActionType$2.ToggleChild: {
1937
+ return Object.assign(Object.assign({}, state), { childExpanded: Object.assign(Object.assign({}, state.childExpanded), { [String(action.id)]: !state.childExpanded[String(action.id)] }) });
1911
1938
  }
1912
1939
  default: {
1913
1940
  return state;
@@ -1925,14 +1952,19 @@ const NestableItemList = (props) => {
1925
1952
  // Destructure all props
1926
1953
  const { items, onChanged, } = props;
1927
1954
  /* -------------- State ------------- */
1955
+ // Create initial map of child expanded booleans
1956
+ const initChildExpanded = {};
1957
+ items.forEach((item) => {
1958
+ initChildExpanded[String(item.id)] = false;
1959
+ });
1928
1960
  // Initial state
1929
1961
  const initialState = {
1930
- isShowingItems: false,
1962
+ childExpanded: initChildExpanded,
1931
1963
  };
1932
1964
  // Initialize state
1933
1965
  const [state, dispatch] = useReducer(reducer$2, initialState);
1934
1966
  // Destructure common state
1935
- const { isShowingItems, } = state;
1967
+ const { childExpanded, } = state;
1936
1968
  /*------------------------------------------------------------------------*/
1937
1969
  /* Component Functions */
1938
1970
  /*------------------------------------------------------------------------*/
@@ -2020,14 +2052,15 @@ const NestableItemList = (props) => {
2020
2052
  backgroundColor: 'transparent',
2021
2053
  }, type: "button", onClick: () => {
2022
2054
  dispatch({
2023
- type: ActionType$2.ToggleItems,
2055
+ type: ActionType$2.ToggleChild,
2056
+ id: item.id,
2024
2057
  });
2025
- }, "aria-label": `${isShowingItems ? 'Hide' : 'Show'} items in ${item.name}` },
2026
- React.createElement(FontAwesomeIcon, { icon: isShowingItems ? faChevronDown : faChevronRight })))),
2058
+ }, "aria-label": `${childExpanded[item.id] ? 'Hide' : 'Show'} items in ${item.name}` },
2059
+ React.createElement(FontAwesomeIcon, { icon: childExpanded[item.id] ? faChevronDown : faChevronRight })))),
2027
2060
  React.createElement(CheckboxButton, { className: `NestableItemList-CheckboxButton-${item.id}`, text: item.name, checked: item.isGroup ? allChecked(item.children) : item.checked, dashed: item.isGroup ? !noneChecked(item.children) : false, onChanged: (checked) => {
2028
2061
  onChanged(changeChecked(item.id, checked, items));
2029
2062
  }, ariaLabel: `Select ${item.name}`, checkedVariant: Variant$1.Light }),
2030
- item.isGroup && isShowingItems && (React.createElement("div", { className: "NestableItemList-children-container", style: {
2063
+ (item.isGroup && childExpanded[item.id]) && (React.createElement("div", { className: "NestableItemList-children-container", style: {
2031
2064
  paddingLeft: '2.2rem',
2032
2065
  } },
2033
2066
  React.createElement(NestableItemList, { items: item.children, onChanged: (updatedItems) => {
@@ -2049,7 +2082,7 @@ const ItemPicker = (props) => {
2049
2082
  /*------------------------------------------------------------------------*/
2050
2083
  /* -------------- Props ------------- */
2051
2084
  // Destructure all props
2052
- const { title, items, onChanged, } = props;
2085
+ const { title, items, onChanged, noBottomMargin, } = props;
2053
2086
  /*------------------------------------------------------------------------*/
2054
2087
  /* Component Functions */
2055
2088
  /*------------------------------------------------------------------------*/
@@ -2059,7 +2092,7 @@ const ItemPicker = (props) => {
2059
2092
  /*----------------------------------------*/
2060
2093
  /* Main UI */
2061
2094
  /*----------------------------------------*/
2062
- return (React.createElement(TabBox, { title: title },
2095
+ return (React.createElement(TabBox, { title: title, noBottomMargin: noBottomMargin },
2063
2096
  React.createElement("div", { style: { overflowX: 'auto' } },
2064
2097
  React.createElement(NestableItemList, { items: items, onChanged: onChanged }))));
2065
2098
  };
@@ -2376,9 +2409,9 @@ const IntelliTable = (props) => {
2376
2409
  // Create the cell UI
2377
2410
  return (React.createElement("th", { key: column.param, scope: "col", id: `IntelliTable-${id}-header-${column.param}` },
2378
2411
  React.createElement("div", { className: "d-flex align-items-center justify-content-center flex-row h-100" },
2379
- React.createElement("h4", { className: "m-0" }, column.title),
2412
+ React.createElement("span", { className: "text-nowrap" }, column.title),
2380
2413
  React.createElement("div", null,
2381
- React.createElement("button", { type: "button", className: "btn btn-light", "aria-label": sortButtonAriaLabel, onClick: () => {
2414
+ React.createElement("button", { type: "button", className: "btn btn-light btn-sm ms-1", "aria-label": sortButtonAriaLabel, onClick: () => {
2382
2415
  dispatch({
2383
2416
  type: ActionType$1.ToggleSortColumn,
2384
2417
  param: column.param,
@@ -2486,7 +2519,7 @@ const IntelliTable = (props) => {
2486
2519
  }
2487
2520
  else if (column.type === ParamType$1.String) {
2488
2521
  fullValue = String(value).trim();
2489
- const noValue = (value.trim().length) === 0;
2522
+ const noValue = (String(fullValue).trim().length === 0);
2490
2523
  visibleValue = (noValue
2491
2524
  ? (React.createElement(FontAwesomeIcon, { icon: faMinus }))
2492
2525
  : fullValue);
@@ -2526,7 +2559,7 @@ const IntelliTable = (props) => {
2526
2559
  React.createElement("h3", { className: "m-0" }, title),
2527
2560
  React.createElement("div", { className: "flex-grow-1 text-end" },
2528
2561
  React.createElement(CSVDownloadButton, { "aria-label": `download data as csv for ${title}`, id: `IntelliTable-${id}-download-as-csv`, filename: `${title}.csv`, csv: csv }),
2529
- React.createElement("button", { type: "button", className: "btn btn-secondary", "aria-label": `show panel for customizing which columns show in table ${title}`, id: `IntelliTable-${id}-show-column-customization-modal`, onClick: () => {
2562
+ React.createElement("button", { type: "button", className: "btn btn-secondary ms-2", "aria-label": `show panel for customizing which columns show in table ${title}`, id: `IntelliTable-${id}-show-column-customization-modal`, onClick: () => {
2530
2563
  dispatch({
2531
2564
  type: ActionType$1.ToggleColVisCusModalVisibility,
2532
2565
  });
@@ -2590,6 +2623,11 @@ const style = `
2590
2623
  border: 0.05rem solid black;
2591
2624
  border-radius: 0.5rem;
2592
2625
  overflow: hidden;
2626
+ padding: 0.7rem;
2627
+
2628
+ /* Solid background */
2629
+ background-color: white;
2630
+ color: black;
2593
2631
 
2594
2632
  /* Place contents in flex column */
2595
2633
  flex-direction: column;
@@ -2616,6 +2654,27 @@ const style = `
2616
2654
  /* Vertical scroll */
2617
2655
  overflow-y: auto;
2618
2656
  }
2657
+
2658
+ .LogReviewer-header-close-button {
2659
+ border: 0 !important;
2660
+ background-color: transparent !important;
2661
+ padding-top: 0 !important;
2662
+ padding-bottom: 0 !important;
2663
+ padding-right: 1em !important;
2664
+ margin: 0 !important;
2665
+ color: #444 !important;
2666
+
2667
+ right: 0 !important;
2668
+ position: absolute !important;
2669
+ }
2670
+ .LogReviewer-header-close-button:hover {
2671
+ border: 0 !important;
2672
+ background-color: transparent !important;
2673
+ padding-top: 0 !important;
2674
+ padding-bottom: 0 !important;
2675
+ margin: 0 !important;
2676
+ color: #000 !important;
2677
+ }
2619
2678
  `;
2620
2679
  /*------------------------------------------------------------------------*/
2621
2680
  /* Static Functions */
@@ -2635,7 +2694,7 @@ const genHumanReadableName = (machineReadableName) => {
2635
2694
  // Uppercase! Add a space before
2636
2695
  humanReadableName += ' ';
2637
2696
  }
2638
- humanReadableName += chars;
2697
+ humanReadableName += char;
2639
2698
  });
2640
2699
  // Trim and return
2641
2700
  return humanReadableName.trim();
@@ -2697,7 +2756,19 @@ const reducer = (state, action) => {
2697
2756
  return Object.assign(Object.assign({}, state), { contextFilterState: action.contextFilterState });
2698
2757
  }
2699
2758
  case ActionType.UpdateTagFilterState: {
2700
- return Object.assign(Object.assign({}, state), { tagFilterState: action.tagFilterState });
2759
+ const { tagFilterState } = action;
2760
+ // Select all if every tag is deselected
2761
+ const numTagsSelected = (Object.values(tagFilterState)
2762
+ .filter((isSelected) => {
2763
+ return isSelected;
2764
+ })
2765
+ .length);
2766
+ if (numTagsSelected === 0) {
2767
+ Object.keys(tagFilterState).forEach((tag) => {
2768
+ tagFilterState[tag] = true;
2769
+ });
2770
+ }
2771
+ return Object.assign(Object.assign({}, state), { tagFilterState });
2701
2772
  }
2702
2773
  case ActionType.UpdateActionErrorFilterState: {
2703
2774
  return Object.assign(Object.assign({}, state), { actionErrorFilterState: action.actionErrorFilterState });
@@ -2717,10 +2788,35 @@ const LogReviewer = (props) => {
2717
2788
  /*------------------------------------------------------------------------*/
2718
2789
  /* Setup */
2719
2790
  /*------------------------------------------------------------------------*/
2720
- var _a, _b, _c, _d, _e;
2791
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
2721
2792
  /* -------------- Props ------------- */
2722
2793
  // Destructure props
2723
2794
  const { LogMetadata, onClose, } = props;
2795
+ // Add built-in LogMetadata
2796
+ // > Add "uncategorized" subcontext to each context
2797
+ Object.keys((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {}).forEach((context) => {
2798
+ if (
2799
+ // Context exists
2800
+ LogMetadata.Context
2801
+ // Context has children already
2802
+ && typeof LogMetadata.Context[context] !== 'string') {
2803
+ LogMetadata.Context[context][LogBuiltInMetadata.Context.Uncategorized] = (LogBuiltInMetadata.Context.Uncategorized);
2804
+ }
2805
+ });
2806
+ // > Add built-in contexts
2807
+ LogMetadata.Context = ((_b = LogMetadata.Context) !== null && _b !== void 0 ? _b : {});
2808
+ Object.keys(LogBuiltInMetadata.Context).forEach((context) => {
2809
+ if (LogMetadata.Context) {
2810
+ LogMetadata.Context[context] = context;
2811
+ }
2812
+ });
2813
+ // > Add built-in targets
2814
+ LogMetadata.Target = ((_c = LogMetadata.Target) !== null && _c !== void 0 ? _c : {});
2815
+ Object.keys(LogBuiltInMetadata.Target).forEach((target) => {
2816
+ if (LogMetadata.Target) {
2817
+ LogMetadata.Target[target] = target;
2818
+ }
2819
+ });
2724
2820
  /* -------------- State ------------- */
2725
2821
  // Create initial date filter state
2726
2822
  const today = getTimeInfoInET();
@@ -2740,26 +2836,30 @@ const LogReviewer = (props) => {
2740
2836
  };
2741
2837
  // Create initial context filter state
2742
2838
  const initContextFilterState = {};
2743
- Object.keys((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {}).forEach((context) => {
2839
+ Object.keys((_d = LogMetadata.Context) !== null && _d !== void 0 ? _d : {}).forEach((context) => {
2744
2840
  var _a, _b;
2745
2841
  const contextValue = ((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {})[context];
2746
2842
  if (typeof contextValue === 'string') {
2747
- // Case: no subcontexts
2843
+ // Case: no subcontexts, init as checked
2748
2844
  initContextFilterState[contextValue] = true;
2749
2845
  }
2750
2846
  else {
2751
2847
  // Case: subcontexts exist
2752
- initContextFilterState[contextValue._] = {};
2848
+ initContextFilterState[context] = {};
2753
2849
  Object.values(((_b = LogMetadata.Context) !== null && _b !== void 0 ? _b : {})[context]).forEach((subcontext) => {
2754
- const subcontextValue = contextValue[subcontext];
2755
- initContextFilterState[contextValue._][subcontextValue] = true;
2850
+ // Skip self ("_")
2851
+ if (subcontext === '_') {
2852
+ return;
2853
+ }
2854
+ // Initialize as checked
2855
+ initContextFilterState[context][subcontext] = true;
2756
2856
  });
2757
2857
  }
2758
2858
  });
2759
2859
  // Create initial tag filter state
2760
2860
  const initTagFilterState = {};
2761
- Object.values((_b = LogMetadata.Tag) !== null && _b !== void 0 ? _b : {}).forEach((tagValue) => {
2762
- initTagFilterState[tagValue] = true;
2861
+ Object.values((_e = LogMetadata.Tag) !== null && _e !== void 0 ? _e : {}).forEach((tagValue) => {
2862
+ initTagFilterState[tagValue] = false;
2763
2863
  });
2764
2864
  // Create advanced filter state
2765
2865
  const initAdvancedFilterState = {
@@ -2785,12 +2885,16 @@ const LogReviewer = (props) => {
2785
2885
  target: {},
2786
2886
  action: {},
2787
2887
  };
2788
- Object.values((_c = LogMetadata.Target) !== null && _c !== void 0 ? _c : {}).forEach((target) => {
2888
+ Object.values((_f = LogMetadata.Target) !== null && _f !== void 0 ? _f : {}).forEach((target) => {
2789
2889
  initActionErrorFilterState.target[target] = true;
2790
2890
  });
2791
2891
  Object.values(LogAction$1).forEach((action) => {
2792
2892
  initActionErrorFilterState.action[action] = true;
2793
2893
  });
2894
+ // Add built-in targets
2895
+ Object.values(LogBuiltInMetadata.Target).forEach((target) => {
2896
+ initActionErrorFilterState.target[target] = true;
2897
+ });
2794
2898
  // Initial state
2795
2899
  const initialState = {
2796
2900
  loading: true,
@@ -2824,15 +2928,18 @@ const LogReviewer = (props) => {
2824
2928
  let month = newDateFilterState.startDate.month;
2825
2929
  while (
2826
2930
  // Earlier year
2827
- (year <= newDateFilterState.endDate.year)
2931
+ (year < newDateFilterState.endDate.year)
2828
2932
  // Current year but included month
2829
2933
  || (year === newDateFilterState.endDate.year
2830
2934
  && month <= newDateFilterState.endDate.month)) {
2831
- // Add to list
2832
- toLoad.push({
2833
- year,
2834
- month,
2835
- });
2935
+ // Add to list if not already loaded
2936
+ if (!logMap[year]
2937
+ || !logMap[year][month]) {
2938
+ toLoad.push({
2939
+ year,
2940
+ month,
2941
+ });
2942
+ }
2836
2943
  // Increment
2837
2944
  month += 1;
2838
2945
  if (month > 12) {
@@ -2856,6 +2963,7 @@ const LogReviewer = (props) => {
2856
2963
  });
2857
2964
  // Check which year/month combos we need to load
2858
2965
  const toLoad = listMonthsToLoad(newDateFilterState);
2966
+ console.log('Filter update:', newDateFilterState, toLoad, logMap);
2859
2967
  // If nothing to load, finished
2860
2968
  if (toLoad.length === 0) {
2861
2969
  return;
@@ -2920,10 +3028,10 @@ const LogReviewer = (props) => {
2920
3028
  /* Filters */
2921
3029
  /*----------------------------------------*/
2922
3030
  // Filter toggle
2923
- const filterToggles = (React.createElement("div", { className: "LogReviewer-filter-toggles d-flex align-items-center justify-content-center" },
3031
+ const filterToggles = (React.createElement("div", { className: "LogReviewer-filter-toggles" },
2924
3032
  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: () => {
3033
+ React.createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
3034
+ 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
3035
  dispatch({
2928
3036
  type: ActionType.ToggleFilterDrawer,
2929
3037
  filterDrawer: FilterDrawer.Date,
@@ -2931,7 +3039,7 @@ const LogReviewer = (props) => {
2931
3039
  } },
2932
3040
  React.createElement(FontAwesomeIcon, { icon: faCalendar, className: "me-2" }),
2933
3041
  "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: () => {
3042
+ 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
3043
  dispatch({
2936
3044
  type: ActionType.ToggleFilterDrawer,
2937
3045
  filterDrawer: FilterDrawer.Context,
@@ -2939,15 +3047,15 @@ const LogReviewer = (props) => {
2939
3047
  } },
2940
3048
  React.createElement(FontAwesomeIcon, { icon: faCircle, className: "me-2" }),
2941
3049
  "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: () => {
3050
+ (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
3051
  dispatch({
2944
3052
  type: ActionType.ToggleFilterDrawer,
2945
3053
  filterDrawer: FilterDrawer.Tag,
2946
3054
  });
2947
3055
  } },
2948
3056
  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: () => {
3057
+ "Tag")),
3058
+ 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
3059
  dispatch({
2952
3060
  type: ActionType.ToggleFilterDrawer,
2953
3061
  filterDrawer: FilterDrawer.Action,
@@ -2955,7 +3063,7 @@ const LogReviewer = (props) => {
2955
3063
  } },
2956
3064
  React.createElement(FontAwesomeIcon, { icon: faHammer, className: "me-2" }),
2957
3065
  "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: () => {
3066
+ 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
3067
  dispatch({
2960
3068
  type: ActionType.ToggleFilterDrawer,
2961
3069
  filterDrawer: FilterDrawer.Advanced,
@@ -2968,25 +3076,29 @@ const LogReviewer = (props) => {
2968
3076
  if (expandedFilterDrawer) {
2969
3077
  if (expandedFilterDrawer === FilterDrawer.Date) {
2970
3078
  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
- });
3079
+ 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) => {
3080
+ dateFilterState.startDate = { month, day, year };
3081
+ handleDateRangeUpdated(dateFilterState);
2976
3082
  } }),
2977
3083
  ' ',
2978
3084
  "to",
2979
3085
  ' ',
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
- });
3086
+ 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) => {
3087
+ if (year < dateFilterState.startDate.year
3088
+ || (year === dateFilterState.startDate.year
3089
+ && month < dateFilterState.startDate.month)
3090
+ || (year === dateFilterState.startDate.year
3091
+ && month === dateFilterState.startDate.month
3092
+ && day < dateFilterState.startDate.day)) {
3093
+ return alert$1('Invalid Start Date', 'The start date cannot be before the end date.');
3094
+ }
3095
+ dateFilterState.endDate = { month, day, year };
3096
+ handleDateRangeUpdated(dateFilterState);
2985
3097
  } })));
2986
3098
  }
2987
3099
  else if (expandedFilterDrawer === FilterDrawer.Context) {
2988
3100
  // Create item picker items
2989
- const pickableItems = (Object.keys((_d = LogMetadata.Context) !== null && _d !== void 0 ? _d : {})
3101
+ const pickableItems = (Object.keys((_g = LogMetadata.Context) !== null && _g !== void 0 ? _g : {})
2990
3102
  .map((context) => {
2991
3103
  var _a;
2992
3104
  const value = ((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {})[context];
@@ -3007,15 +3119,15 @@ const LogReviewer = (props) => {
3007
3119
  })
3008
3120
  .map((subcontext) => {
3009
3121
  return {
3010
- id: `${context}-${subcontext}`,
3122
+ id: subcontext,
3011
3123
  name: genHumanReadableName(subcontext),
3012
3124
  isGroup: false,
3013
- checked: !!value[subcontext],
3125
+ checked: contextFilterState[context][subcontext],
3014
3126
  };
3015
3127
  }));
3016
3128
  const item = {
3017
3129
  id: context,
3018
- name: context,
3130
+ name: genHumanReadableName(context),
3019
3131
  isGroup: true,
3020
3132
  children,
3021
3133
  };
@@ -3028,7 +3140,9 @@ const LogReviewer = (props) => {
3028
3140
  if (pickableItem.isGroup) {
3029
3141
  // Has subcontexts
3030
3142
  pickableItem.children.forEach((subcontextItem) => {
3031
- contextFilterState[pickableItem.id][subcontextItem.id] = (subcontextItem.checked);
3143
+ if (!subcontextItem.isGroup) {
3144
+ contextFilterState[pickableItem.id][subcontextItem.id] = (subcontextItem.checked);
3145
+ }
3032
3146
  });
3033
3147
  }
3034
3148
  else {
@@ -3047,7 +3161,7 @@ const LogReviewer = (props) => {
3047
3161
  filterDrawer = (React.createElement(TabBox, { title: "Tags" }, Object.keys(tagFilterState)
3048
3162
  .map((tag, i) => {
3049
3163
  const description = genHumanReadableName(tag);
3050
- return (React.createElement(CheckboxButton, { id: `LogReviewer-tag-${tag}-checkbox`, text: description, ariaLabel: `include logs tagged with "${description}" in results`, noMarginOnRight: i === Object.keys(tagFilterState).length - 1, onChanged: (checked) => {
3164
+ return (React.createElement(CheckboxButton, { id: `LogReviewer-tag-${tag}-checkbox`, text: description, ariaLabel: `require that logs be tagged with "${description}" or any other selected tag`, noMarginOnRight: i === Object.keys(tagFilterState).length - 1, checked: tagFilterState[tag], onChanged: (checked) => {
3051
3165
  tagFilterState[tag] = checked;
3052
3166
  } }));
3053
3167
  })));
@@ -3079,7 +3193,7 @@ const LogReviewer = (props) => {
3079
3193
  }, ariaLabel: "only show error logs", selected: actionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true })),
3080
3194
  (actionErrorFilterState.type === undefined
3081
3195
  || actionErrorFilterState.type === LogType$1.Action) && (React.createElement(TabBox, { title: "Action Log Details" },
3082
- React.createElement(ButtonInputGroup, { label: "Action" }, Object.keys(LogAction$1)
3196
+ React.createElement(ButtonInputGroup, { label: "Action", className: "mb-2" }, Object.keys(LogAction$1)
3083
3197
  .map((action, i) => {
3084
3198
  const description = genHumanReadableName(action);
3085
3199
  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 +3204,20 @@ const LogReviewer = (props) => {
3090
3204
  });
3091
3205
  } }));
3092
3206
  })),
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
- })))),
3207
+ React.createElement(ButtonInputGroup, { label: "Target" },
3208
+ (Object.keys((_h = LogMetadata.Target) !== null && _h !== void 0 ? _h : {}).length === 0) && (React.createElement("div", null, "This app does not have any targets yet.")),
3209
+ Object.keys((_j = LogMetadata.Target) !== null && _j !== void 0 ? _j : {})
3210
+ .map((target, i) => {
3211
+ var _a;
3212
+ const description = genHumanReadableName(target);
3213
+ return (React.createElement(CheckboxButton, { id: `LogReviewer-target-${target}-checkbox`, text: description, ariaLabel: `include logs with target "${description}" in results`, onChanged: (checked) => {
3214
+ actionErrorFilterState.target[target] = checked;
3215
+ dispatch({
3216
+ type: ActionType.UpdateActionErrorFilterState,
3217
+ actionErrorFilterState,
3218
+ });
3219
+ }, noMarginOnRight: i === Object.keys((_a = LogMetadata.Target) !== null && _a !== void 0 ? _a : {}).length - 1 }));
3220
+ })))),
3105
3221
  (actionErrorFilterState.type === undefined
3106
3222
  || actionErrorFilterState.type === LogType$1.Error) && (React.createElement(TabBox, { title: "Error Log Details" },
3107
3223
  React.createElement("div", { className: "input-group mb-2" },
@@ -3263,26 +3379,27 @@ const LogReviewer = (props) => {
3263
3379
  advancedFilterState,
3264
3380
  });
3265
3381
  }, 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
- } })))));
3382
+ advancedFilterState.source !== LogSource$1.Client && (React.createElement("div", { className: "mt-2" },
3383
+ React.createElement("div", { className: "input-group mb-2" },
3384
+ React.createElement("span", { className: "input-group-text" }, "Server Route Path"),
3385
+ 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) => {
3386
+ advancedFilterState.courseName = ((e.target.value)
3387
+ .trim());
3388
+ dispatch({
3389
+ type: ActionType.UpdateAdvancedFilterState,
3390
+ advancedFilterState,
3391
+ });
3392
+ } })),
3393
+ React.createElement("div", { className: "input-group mb-2" },
3394
+ React.createElement("span", { className: "input-group-text" }, "Server Route Template"),
3395
+ 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) => {
3396
+ advancedFilterState.courseName = ((e.target.value)
3397
+ .trim());
3398
+ dispatch({
3399
+ type: ActionType.UpdateAdvancedFilterState,
3400
+ advancedFilterState,
3401
+ });
3402
+ } })))))));
3286
3403
  }
3287
3404
  }
3288
3405
  // Filters UI
@@ -3293,10 +3410,11 @@ const LogReviewer = (props) => {
3293
3410
  // > Perform filters
3294
3411
  const logs = [];
3295
3412
  Object.keys(logMap).forEach((year) => {
3296
- Object.keys(logMap).forEach((month) => {
3413
+ Object.keys(logMap[year]).forEach((month) => {
3297
3414
  logMap[year][month].forEach((log) => {
3298
- /* ----------- Date Filter ---------- */
3299
3415
  var _a;
3416
+ /* ----------- Date Filter ---------- */
3417
+ console.log('Log:', log);
3300
3418
  // Before start date
3301
3419
  if (
3302
3420
  // Previous year
@@ -3308,6 +3426,7 @@ const LogReviewer = (props) => {
3308
3426
  || ((log.year === dateFilterState.startDate.year)
3309
3427
  && (log.month === dateFilterState.startDate.month)
3310
3428
  && (log.day < dateFilterState.startDate.day))) {
3429
+ console.log('RULED OUT: START');
3311
3430
  return;
3312
3431
  }
3313
3432
  // After end date
@@ -3321,6 +3440,7 @@ const LogReviewer = (props) => {
3321
3440
  || ((log.year === dateFilterState.endDate.year)
3322
3441
  && (log.month === dateFilterState.endDate.month)
3323
3442
  && (log.day > dateFilterState.endDate.day))) {
3443
+ console.log('RULED OUT: END');
3324
3444
  return;
3325
3445
  }
3326
3446
  /* --------- Context Filter --------- */
@@ -3329,33 +3449,48 @@ const LogReviewer = (props) => {
3329
3449
  // Whole context is deselected
3330
3450
  contextFilterState[log.context] === false
3331
3451
  // None of the subcontexts are selected
3332
- || (Object.values((_a = contextFilterState[log.context]) !== null && _a !== void 0 ? _a : {})
3333
- .every((isSelected) => {
3334
- return !isSelected;
3335
- }))) {
3452
+ || (
3453
+ // Has subcontexts
3454
+ typeof contextFilterState[log.context] !== 'boolean'
3455
+ // None of the subcontexts are selected
3456
+ && Object.values((_a = contextFilterState[log.context]) !== null && _a !== void 0 ? _a : {})
3457
+ .every((isSelected) => {
3458
+ return !isSelected;
3459
+ }))) {
3460
+ // TODO: figure out why context filter is still not working, why actions and targets filter UI sucks and doesn't wrap and doesn't select
3461
+ console.log('RULED OUT: CONTEXT');
3462
+ console.log(log.context, contextFilterState);
3336
3463
  return;
3337
3464
  }
3338
3465
  // Subcontext doesn't match
3339
3466
  if (
3340
- // Log has a subcontext
3341
- log.subcontext
3467
+ // Log context is not "uncategorized" (no point in further filters)
3468
+ log.context !== LogBuiltInMetadata.Context.Uncategorized
3469
+ // Log has a subcontext
3470
+ && log.subcontext
3342
3471
  // Context has subcontexts
3343
3472
  && (contextFilterState[log.context]
3344
3473
  && contextFilterState[log.context] !== false
3345
3474
  && contextFilterState[log.context] !== true)
3346
3475
  // Subcontext is not selected
3347
3476
  && !contextFilterState[log.context][log.subcontext]) {
3477
+ console.log('RULED OUT: SUBCONTEXT');
3348
3478
  return;
3349
3479
  }
3350
3480
  /* -------------- Tags -------------- */
3351
3481
  // No tags match
3352
3482
  if (
3353
- // Log has at least one tag
3354
- log.tags.length > 0
3483
+ // At least one tag is required
3484
+ Object.values(tagFilterState)
3485
+ .filter((isSelected) => {
3486
+ return isSelected;
3487
+ })
3488
+ .length > 0
3355
3489
  // No tags match
3356
- && (log.tags.every((tag) => {
3490
+ && log.tags.every((tag) => {
3357
3491
  return !tagFilterState[tag];
3358
- }))) {
3492
+ })) {
3493
+ console.log('RULED OUT: TAGS');
3359
3494
  return;
3360
3495
  }
3361
3496
  /* ------- Actions and Errors ------- */
@@ -3365,6 +3500,7 @@ const LogReviewer = (props) => {
3365
3500
  actionErrorFilterState.type !== undefined
3366
3501
  // Log type doesn't match
3367
3502
  && actionErrorFilterState.type !== log.type) {
3503
+ console.log('RULED OUT: TYPE');
3368
3504
  return;
3369
3505
  }
3370
3506
  // Filter errors
@@ -3377,6 +3513,7 @@ const LogReviewer = (props) => {
3377
3513
  && actionErrorFilterState.errorMessage.trim().length > 0
3378
3514
  // Message doesn't match
3379
3515
  && log.errorMessage.toLowerCase().includes(actionErrorFilterState.errorMessage.trim().toLowerCase())) {
3516
+ console.log('RULED OUT: ERROR MESSAGE');
3380
3517
  return;
3381
3518
  }
3382
3519
  // Code doesn't match
@@ -3387,6 +3524,7 @@ const LogReviewer = (props) => {
3387
3524
  && actionErrorFilterState.errorCode.trim().length > 0
3388
3525
  // Code doesn't match
3389
3526
  && log.errorCode.toUpperCase().includes(actionErrorFilterState.errorCode.trim().toUpperCase())) {
3527
+ console.log('RULED OUT: ERROR CODE');
3390
3528
  return;
3391
3529
  }
3392
3530
  }
@@ -3398,6 +3536,7 @@ const LogReviewer = (props) => {
3398
3536
  log.target
3399
3537
  // Target isn't selected
3400
3538
  && !actionErrorFilterState.target[log.target]) {
3539
+ console.log('RULED OUT: TARGET');
3401
3540
  return;
3402
3541
  }
3403
3542
  // Action
@@ -3406,6 +3545,7 @@ const LogReviewer = (props) => {
3406
3545
  log.action
3407
3546
  // Action isn't selected
3408
3547
  && !actionErrorFilterState.action[log.action]) {
3548
+ console.log('RULED OUT: ACTION');
3409
3549
  return;
3410
3550
  }
3411
3551
  }
@@ -3416,6 +3556,7 @@ const LogReviewer = (props) => {
3416
3556
  log.userFirstName
3417
3557
  // First name query doesn't match
3418
3558
  && !log.userFirstName.toLowerCase().includes(advancedFilterState.userFirstName.toLowerCase().trim())) {
3559
+ console.log('RULED OUT: FIRST');
3419
3560
  return;
3420
3561
  }
3421
3562
  // Last name doesn't match
@@ -3424,6 +3565,7 @@ const LogReviewer = (props) => {
3424
3565
  log.userLastName
3425
3566
  // Last name query doesn't match
3426
3567
  && !log.userLastName.toLowerCase().includes(advancedFilterState.userLastName.toLowerCase().trim())) {
3568
+ console.log('RULED OUT: LAST');
3427
3569
  return;
3428
3570
  }
3429
3571
  // Email doesn't match
@@ -3432,6 +3574,7 @@ const LogReviewer = (props) => {
3432
3574
  log.userEmail
3433
3575
  // Email query doesn't match
3434
3576
  && !log.userEmail.toLowerCase().includes(advancedFilterState.userEmail.toLowerCase().trim())) {
3577
+ console.log('RULED OUT: EMAIL');
3435
3578
  return;
3436
3579
  }
3437
3580
  // User id doesn't match
@@ -3440,6 +3583,7 @@ const LogReviewer = (props) => {
3440
3583
  log.userId
3441
3584
  // User id doesn't match
3442
3585
  && !String(log.userId).includes(advancedFilterState.userId.trim())) {
3586
+ console.log('RULED OUT: USER ID');
3443
3587
  return;
3444
3588
  }
3445
3589
  // Learner not allowed
@@ -3448,6 +3592,7 @@ const LogReviewer = (props) => {
3448
3592
  log.isLearner
3449
3593
  // Learners aren't included
3450
3594
  && !advancedFilterState.includeLearners) {
3595
+ console.log('RULED OUT: LEARNER');
3451
3596
  return;
3452
3597
  }
3453
3598
  // TTM not allowed
@@ -3456,6 +3601,7 @@ const LogReviewer = (props) => {
3456
3601
  log.isTTM
3457
3602
  // TTMs aren't included
3458
3603
  && !advancedFilterState.includeTTMs) {
3604
+ console.log('RULED OUT: TTM');
3459
3605
  return;
3460
3606
  }
3461
3607
  // Admin not allowed
@@ -3464,6 +3610,7 @@ const LogReviewer = (props) => {
3464
3610
  log.isAdmin
3465
3611
  // Admins aren't included
3466
3612
  && !advancedFilterState.includeAdmins) {
3613
+ console.log('RULED OUT: ADMIN');
3467
3614
  return;
3468
3615
  }
3469
3616
  // Course Id doesn't match
@@ -3472,6 +3619,7 @@ const LogReviewer = (props) => {
3472
3619
  log.courseId
3473
3620
  // Course Id doesn't match
3474
3621
  && !String(log.courseId).includes(advancedFilterState.courseId.trim())) {
3622
+ console.log('RULED OUT: COURSE ID');
3475
3623
  return;
3476
3624
  }
3477
3625
  // Course name doesn't match
@@ -3480,6 +3628,7 @@ const LogReviewer = (props) => {
3480
3628
  log.courseName
3481
3629
  // Course name doesn't match
3482
3630
  && !String(log.courseName).includes(advancedFilterState.courseName.trim())) {
3631
+ console.log('RULED OUT: COURSE NAME');
3483
3632
  return;
3484
3633
  }
3485
3634
  // Mobile filter doesn't match
@@ -3490,6 +3639,7 @@ const LogReviewer = (props) => {
3490
3639
  && log.device
3491
3640
  // Mobile filter doesn't match
3492
3641
  && (advancedFilterState.isMobile === log.device.isMobile)) {
3642
+ console.log('RULED OUT: MOBILE');
3493
3643
  return;
3494
3644
  }
3495
3645
  // Log source doesn't match
@@ -3500,6 +3650,7 @@ const LogReviewer = (props) => {
3500
3650
  && log.source
3501
3651
  // Source filter doesn't match
3502
3652
  && (advancedFilterState.source !== log.source)) {
3653
+ console.log('RULED OUT: SOURCE');
3503
3654
  return;
3504
3655
  }
3505
3656
  // Route path doesn't match (Only for server source)
@@ -3510,6 +3661,7 @@ const LogReviewer = (props) => {
3510
3661
  && (advancedFilterState.routePath.trim().length)
3511
3662
  // Route path doesn't match
3512
3663
  && !(log.routePath.includes(advancedFilterState.routePath.trim()))) {
3664
+ console.log('RULED OUT: PATH');
3513
3665
  return;
3514
3666
  }
3515
3667
  // Route template doesn't match (Only for server source)
@@ -3520,6 +3672,7 @@ const LogReviewer = (props) => {
3520
3672
  && (advancedFilterState.routeTemplate.trim().length)
3521
3673
  // Route template doesn't match
3522
3674
  && !(log.routeTemplate.includes(advancedFilterState.routeTemplate.trim()))) {
3675
+ console.log('RULED OUT: TEMPLATE');
3523
3676
  return;
3524
3677
  }
3525
3678
  /* -------------- Done -------------- */
@@ -3719,11 +3872,17 @@ const LogReviewer = (props) => {
3719
3872
  },
3720
3873
  ];
3721
3874
  // Create intelliTable
3722
- const dataTable = (React.createElement(IntelliTable, { title: "Matching Logs", id: "logs", data: logs, columns: columns }));
3875
+ const dataTable = (logs.length === 0
3876
+ ? (React.createElement(React.Fragment, null,
3877
+ React.createElement("h3", { className: "m-0" }, "Matching Logs:"),
3878
+ React.createElement("div", { className: "alert alert-warning text-center" },
3879
+ React.createElement("h4", { className: "m-1" }, "No Logs to Show"),
3880
+ React.createElement("div", null, "Either your filters are too strict or no matching logs have been created yet."))))
3881
+ : (React.createElement(IntelliTable, { title: "Matching Logs:", id: "logs", data: logs, columns: columns })));
3723
3882
  // Main body
3724
3883
  body = (React.createElement(React.Fragment, null,
3725
3884
  filters,
3726
- dataTable));
3885
+ React.createElement("div", { className: "mt-2" }, dataTable)));
3727
3886
  }
3728
3887
  /* ---------- Wrap in Modal --------- */
3729
3888
  return (React.createElement("div", { className: "LogReviewer-outer-container" },
@@ -3731,14 +3890,10 @@ const LogReviewer = (props) => {
3731
3890
  React.createElement("div", { className: "LogReviewer-inner-container" },
3732
3891
  React.createElement("div", { className: "LogReviewer-header" },
3733
3892
  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 }))),
3893
+ React.createElement("h3", { className: "text-center m-0" }, "Log Review Dashboard")),
3894
+ React.createElement("div", { style: { width: 0 } },
3895
+ 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 },
3896
+ React.createElement(FontAwesomeIcon, { icon: faTimes })))),
3742
3897
  React.createElement("div", { className: "LogReviewer-contents" }, body))));
3743
3898
  };
3744
3899
 
@@ -3884,7 +4039,7 @@ const padZerosLeft = (num, numDigits) => {
3884
4039
  * access to log review
3885
4040
  * @author Gabe Abrams
3886
4041
  */
3887
- const LOG_REVIEW_STATUS_ROUTE = `/admin${ROUTE_PATH_PREFIX}/logs/access`;
4042
+ const LOG_REVIEW_STATUS_ROUTE = `${ROUTE_PATH_PREFIX}/logs/access_allowed`;
3888
4043
 
3889
4044
  // Stored copy of caccl functions
3890
4045
  let _cacclGetLaunchInfo;
@@ -3926,7 +4081,7 @@ const internalGetLogCollection = () => {
3926
4081
  * @param opts.getLaunchInfo CACCL LTI's get launch info function
3927
4082
  * @param [opts.logCollection] mongo collection from dce-mango to use for
3928
4083
  * storing logs. If none is included, logs are written to the console
3929
- * @param [opts.logReviewAdmins=all admins] info on which admins can review
4084
+ * @param [opts.logReviewAdmins=all] info on which admins can review
3930
4085
  * logs from the client. If not included, all Canvas admins are allowed to
3931
4086
  * review logs. If null, no Canvas admins are allowed to review logs.
3932
4087
  * If an array of Canvas userIds (numbers), only Canvas admins with those
@@ -4009,71 +4164,81 @@ const initServer = (opts) => {
4009
4164
  /*----------------------------------------*/
4010
4165
  /* Log Reviewer */
4011
4166
  /*----------------------------------------*/
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;
4167
+ /**
4168
+ * Check if a given user is allowed to review logs
4169
+ * @author Gabe Abrams
4170
+ * @param userId the id of the user
4171
+ * @param isAdmin if true, the user is an admin
4172
+ * @returns true if the user can review logs
4173
+ */
4174
+ const canReviewLogs = (userId, isAdmin) => __awaiter(void 0, void 0, void 0, function* () {
4175
+ // Immediately deny access if user is not an admin
4176
+ if (!isAdmin) {
4177
+ return false;
4178
+ }
4179
+ // If all admins are allowed, we're done
4180
+ if (!opts.logReviewAdmins) {
4181
+ return true;
4182
+ }
4183
+ // Do a dynamic check
4184
+ try {
4185
+ // Array of userIds
4186
+ if (Array.isArray(opts.logReviewAdmins)) {
4187
+ return opts.logReviewAdmins.some((allowedId) => {
4188
+ return (userId === allowedId);
4189
+ });
4031
4190
  }
4032
- catch (err) {
4033
- // If an error occurred, simply return false
4034
- return false;
4191
+ // Must be a collection
4192
+ const matches = yield opts.logReviewAdmins.find({ userId });
4193
+ // Make sure at least one entry matches
4194
+ return matches.length > 0;
4195
+ }
4196
+ catch (err) {
4197
+ // If an error occurred, simply return false
4198
+ return false;
4199
+ }
4200
+ });
4201
+ /**
4202
+ * Check if the current user has access to logs
4203
+ * @author Gabe Abrams
4204
+ * @returns {boolean} true if user has access
4205
+ */
4206
+ opts.app.get(LOG_REVIEW_STATUS_ROUTE, genRouteHandler({
4207
+ handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
4208
+ const { userId, isAdmin } = params;
4209
+ const canReview = yield canReviewLogs(userId, isAdmin);
4210
+ return canReview;
4211
+ }),
4212
+ }));
4213
+ /**
4214
+ * Get all logs for a certain month
4215
+ * @author Gabe Abrams
4216
+ * @param {number} year the year to query (e.g. 2022)
4217
+ * @param {number} month the month to query (e.g. 1 = January)
4218
+ * @returns {Log[]} list of logs from the given month
4219
+ */
4220
+ opts.app.get(`${LOG_REVIEW_ROUTE_PATH_PREFIX}/years/:year/months/:month`, genRouteHandler({
4221
+ paramTypes: {
4222
+ year: ParamType$1.Int,
4223
+ month: ParamType$1.Int,
4224
+ },
4225
+ handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
4226
+ // Get user info
4227
+ const { year, month, userId, isAdmin, } = params;
4228
+ // Validate user
4229
+ const canReview = yield canReviewLogs(userId, isAdmin);
4230
+ if (!canReview) {
4231
+ throw new ErrorWithCode('You cannot access this resource because you do not have the appropriate permissions.', ReactKitErrorCode$1.NotAllowedToReviewLogs);
4035
4232
  }
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
- }
4233
+ // Query for logs
4234
+ const logs = yield _logCollection.find({
4235
+ year,
4236
+ month,
4237
+ });
4238
+ // Return logs
4239
+ return logs;
4240
+ }),
4241
+ }));
4077
4242
  };
4078
4243
 
4079
4244
  // Import shared types
@@ -4373,18 +4538,6 @@ const parseUserAgent = (userAgent) => {
4373
4538
  };
4374
4539
  };
4375
4540
 
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
4541
  /**
4389
4542
  * Generate an express API route handler
4390
4543
  * @author Gabe Abrams
@@ -4757,7 +4910,7 @@ const genRouteHandler = (opts) => {
4757
4910
  }
4758
4911
  : {
4759
4912
  type: LogType$1.Action,
4760
- target: ((_m = opts.target) !== null && _m !== void 0 ? _m : LogBuiltInMetadata.Target.NoSpecificTarget),
4913
+ target: ((_m = opts.target) !== null && _m !== void 0 ? _m : LogBuiltInMetadata.Target.NoTarget),
4761
4914
  action: ((_o = opts.action) !== null && _o !== void 0 ? _o : LogAction$1.Unknown),
4762
4915
  });
4763
4916
  // Source-specific info