dce-reactkit 4.0.0-beta-date-and-time.1 → 4.0.0-beta-logreviewer.1

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.
Files changed (48) hide show
  1. package/dist/cjs/index.js +712 -943
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/types/components/Pagination.d.ts +9 -0
  4. package/dist/cjs/types/components/SimpleDateChooser.d.ts +1 -3
  5. package/dist/cjs/types/constants/LOG_REVIEW_GET_LOGS_ROUTE.d.ts +6 -0
  6. package/dist/cjs/types/helpers/cloneDeep.d.ts +8 -0
  7. package/dist/cjs/types/index.d.ts +4 -2
  8. package/dist/cjs/types/types/LogReviewerFilterState/ActionErrorFilterState.d.ts +13 -0
  9. package/dist/cjs/types/types/LogReviewerFilterState/AdvancedFilterState.d.ts +17 -0
  10. package/dist/cjs/types/types/LogReviewerFilterState/ContextFilterState.d.ts +6 -0
  11. package/dist/cjs/types/types/LogReviewerFilterState/DateFilterState.d.ts +13 -0
  12. package/dist/cjs/types/types/LogReviewerFilterState/TagFilterState.d.ts +4 -0
  13. package/dist/cjs/types/types/LogReviewerFilterState/index.d.ts +17 -0
  14. package/dist/cjs/types/types/ReactKitErrorCode.d.ts +1 -3
  15. package/dist/esm/index.js +711 -944
  16. package/dist/esm/index.js.map +1 -1
  17. package/dist/esm/types/components/Pagination.d.ts +9 -0
  18. package/dist/esm/types/components/SimpleDateChooser.d.ts +1 -3
  19. package/dist/esm/types/constants/LOG_REVIEW_GET_LOGS_ROUTE.d.ts +6 -0
  20. package/dist/esm/types/helpers/cloneDeep.d.ts +8 -0
  21. package/dist/esm/types/index.d.ts +4 -2
  22. package/dist/esm/types/types/LogReviewerFilterState/ActionErrorFilterState.d.ts +13 -0
  23. package/dist/esm/types/types/LogReviewerFilterState/AdvancedFilterState.d.ts +17 -0
  24. package/dist/esm/types/types/LogReviewerFilterState/ContextFilterState.d.ts +6 -0
  25. package/dist/esm/types/types/LogReviewerFilterState/DateFilterState.d.ts +13 -0
  26. package/dist/esm/types/types/LogReviewerFilterState/TagFilterState.d.ts +4 -0
  27. package/dist/esm/types/types/LogReviewerFilterState/index.d.ts +17 -0
  28. package/dist/esm/types/types/ReactKitErrorCode.d.ts +1 -3
  29. package/dist/index.d.ts +94 -41
  30. package/package.json +3 -3
  31. package/rollup.config.js +0 -2
  32. package/src/components/LogReviewer.tsx +1133 -1307
  33. package/src/components/Pagination.tsx +162 -0
  34. package/src/components/SimpleDateChooser.tsx +26 -62
  35. package/src/constants/LOG_REVIEW_GET_LOGS_ROUTE.ts +9 -0
  36. package/src/helpers/cloneDeep.ts +11 -0
  37. package/src/index.ts +6 -2
  38. package/src/types/LogReviewerFilterState/ActionErrorFilterState.ts +21 -0
  39. package/src/types/LogReviewerFilterState/AdvancedFilterState.ts +33 -0
  40. package/src/types/LogReviewerFilterState/ContextFilterState.ts +13 -0
  41. package/src/types/LogReviewerFilterState/DateFilterState.ts +23 -0
  42. package/src/types/LogReviewerFilterState/TagFilterState.ts +6 -0
  43. package/src/types/LogReviewerFilterState/index.ts +24 -0
  44. package/src/types/ReactKitErrorCode.tsx +1 -3
  45. package/dist/cjs/types/components/SimpleTimeChooser.d.ts +0 -20
  46. package/dist/esm/types/components/SimpleTimeChooser.d.ts +0 -20
  47. package/genEncodedSecret.ts +0 -84
  48. package/src/components/SimpleTimeChooser.tsx +0 -195
package/dist/cjs/index.js CHANGED
@@ -6,6 +6,7 @@ var React = require('react');
6
6
  var freeSolidSvgIcons = require('@fortawesome/free-solid-svg-icons');
7
7
  var reactFontawesome = require('@fortawesome/react-fontawesome');
8
8
  var freeRegularSvgIcons = require('@fortawesome/free-regular-svg-icons');
9
+ var clone = require('nanoclone');
9
10
 
10
11
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
11
12
 
@@ -29,6 +30,7 @@ function _interopNamespace(e) {
29
30
 
30
31
  var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
31
32
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
33
+ var clone__default = /*#__PURE__*/_interopDefaultLegacy(clone);
32
34
 
33
35
  /*! *****************************************************************************
34
36
  Copyright (c) Microsoft Corporation.
@@ -55,7 +57,7 @@ function __awaiter(thisArg, _arguments, P, generator) {
55
57
  });
56
58
  }
57
59
 
58
- // Highest error code = DRK36
60
+ // Highest error code = DRK34
59
61
  /**
60
62
  * List of error codes built into the react kit
61
63
  * @author Gabe Abrams
@@ -66,8 +68,6 @@ var ReactKitErrorCode;
66
68
  ReactKitErrorCode["NoCode"] = "DRK2";
67
69
  ReactKitErrorCode["SessionExpired"] = "DRK3";
68
70
  ReactKitErrorCode["NoCACCLSendRequestFunction"] = "DRK7";
69
- ReactKitErrorCode["SimpleDateChooserInvalidDateRange"] = "DRK35";
70
- ReactKitErrorCode["SimpleDateChooserInvalidNumMonths"] = "DRK36";
71
71
  })(ReactKitErrorCode || (ReactKitErrorCode = {}));
72
72
  var ReactKitErrorCode$1 = ReactKitErrorCode;
73
73
 
@@ -2549,19 +2549,6 @@ const ButtonInputGroup = (props) => {
2549
2549
  : children))));
2550
2550
  };
2551
2551
 
2552
- // Constants
2553
- const ORDINALS = ['th', 'st', 'nd', 'rd'];
2554
- /**
2555
- * Get a number's ordinal
2556
- * @author Gabe Abrams
2557
- * @param num the number being analyzed
2558
- * @returns ordinal
2559
- */
2560
- const getOrdinal = (num) => {
2561
- var _a, _b;
2562
- return ((_b = (_a = ORDINALS[(num - 20) % 10]) !== null && _a !== void 0 ? _a : ORDINALS[num]) !== null && _b !== void 0 ? _b : ORDINALS[0]);
2563
- };
2564
-
2565
2552
  const monthNames = [
2566
2553
  { short: 'Jan', full: 'January' },
2567
2554
  { short: 'Feb', full: 'February' },
@@ -2590,6 +2577,19 @@ const getMonthName = (month) => {
2590
2577
  return ((_a = monthNames[month - 1]) !== null && _a !== void 0 ? _a : monthNames[0]);
2591
2578
  };
2592
2579
 
2580
+ // Constants
2581
+ const ORDINALS = ['th', 'st', 'nd', 'rd'];
2582
+ /**
2583
+ * Get a number's ordinal
2584
+ * @author Gabe Abrams
2585
+ * @param num the number being analyzed
2586
+ * @returns ordinal
2587
+ */
2588
+ const getOrdinal = (num) => {
2589
+ var _a, _b;
2590
+ return ((_b = (_a = ORDINALS[(num - 20) % 10]) !== null && _a !== void 0 ? _a : ORDINALS[num]) !== null && _b !== void 0 ? _b : ORDINALS[0]);
2591
+ };
2592
+
2593
2593
  /**
2594
2594
  * Get current time info in US Boston Eastern Time, independent of machine
2595
2595
  * timezone
@@ -2653,7 +2653,6 @@ const getTimeInfoInET = (dateOrTimestamp) => {
2653
2653
  /**
2654
2654
  * A very simple, lightweight date chooser
2655
2655
  * @author Gabe Abrams
2656
- * @author Gardeniu Liu
2657
2656
  */
2658
2657
  /*------------------------------------------------------------------------*/
2659
2658
  /* ------------------------------ Component ----------------------------- */
@@ -2663,7 +2662,7 @@ const SimpleDateChooser = (props) => {
2663
2662
  /* -------------------------------- Setup ------------------------------- */
2664
2663
  /*------------------------------------------------------------------------*/
2665
2664
  /* -------------- Props ------------- */
2666
- const { ariaLabel, name, onChange, numMonthsToShow = 6, dontAllowFuture, dontAllowPast, } = props;
2665
+ const { ariaLabel, name, onChange, chooseFromPast, numMonthsToShow = 6, } = props;
2667
2666
  /*------------------------------------------------------------------------*/
2668
2667
  /* ------------------------------- Render ------------------------------- */
2669
2668
  /*------------------------------------------------------------------------*/
@@ -2675,28 +2674,14 @@ const SimpleDateChooser = (props) => {
2675
2674
  const choices = [];
2676
2675
  let startYear = today.year;
2677
2676
  let startMonth = today.month;
2678
- // Don't allow past or future dates
2679
- if (dontAllowPast && dontAllowFuture) {
2680
- throw new ErrorWithCode('No past or future dates allowed', ReactKitErrorCode$1.SimpleDateChooserInvalidDateRange);
2681
- }
2682
- // Require numMonthsToShow to be positive
2683
- if (numMonthsToShow <= 0) {
2684
- throw new ErrorWithCode('numMonthsToShow must be positive', ReactKitErrorCode$1.SimpleDateChooserInvalidNumMonths);
2685
- }
2686
- // Recalculate startMonth and startYear when allowing past dates
2687
- if (!dontAllowPast) {
2677
+ if (chooseFromPast) {
2688
2678
  startMonth -= Math.max(0, numMonthsToShow - 1);
2689
2679
  while (startMonth <= 0) {
2690
2680
  startMonth += 12;
2691
2681
  startYear -= 1;
2692
2682
  }
2693
2683
  }
2694
- // Calculate total number of months to show
2695
- let totalMonthsToShow = numMonthsToShow;
2696
- if (!dontAllowPast && !dontAllowFuture) {
2697
- totalMonthsToShow = totalMonthsToShow * 2 - 1;
2698
- }
2699
- for (let i = 0; i < totalMonthsToShow; i++) {
2684
+ for (let i = 0; i < numMonthsToShow; i++) {
2700
2685
  // Get month and year info
2701
2686
  const unmoddedMonth = (startMonth + i);
2702
2687
  let month = unmoddedMonth;
@@ -2715,25 +2700,23 @@ const SimpleDateChooser = (props) => {
2715
2700
  // Figure out which days are allowed
2716
2701
  const days = [];
2717
2702
  const numDaysInMonth = (new Date(year, month, 0)).getDate();
2718
- // Current month
2719
- if (month === today.month && year === today.year) {
2720
- // Past selection: add all previous days of the month
2721
- if (!dontAllowPast) {
2722
- for (let day = 1; day < today.day; day++) {
2723
- days.push(day);
2724
- }
2725
- }
2726
- days.push(today.day); // Add current day
2727
- // Future selection: add all remaining days of the month
2728
- if (!dontAllowFuture) {
2729
- for (let day = today.day + 1; day <= numDaysInMonth; day++) {
2730
- days.push(day);
2731
- }
2703
+ if (chooseFromPast) {
2704
+ // Past selection
2705
+ const numDaysToAdd = ((month === today.month)
2706
+ ? today.day // Current month, only add up to today
2707
+ : numDaysInMonth // Past month, add all days
2708
+ );
2709
+ for (let day = 1; day <= numDaysToAdd; day++) {
2710
+ days.push(day);
2732
2711
  }
2733
2712
  }
2734
- else { // Past or future month
2735
- // Include all days in the month
2736
- for (let day = 1; day <= numDaysInMonth; day++) {
2713
+ else {
2714
+ // Future selection: add all remaining days of the month
2715
+ const firstDay = (month === today.month
2716
+ ? today.day // Current month: start at current date
2717
+ : 1 // Future month: start at beginning of month
2718
+ );
2719
+ for (let day = firstDay; day <= numDaysInMonth; day++) {
2737
2720
  days.push(day);
2738
2721
  }
2739
2722
  }
@@ -2753,8 +2736,8 @@ const SimpleDateChooser = (props) => {
2753
2736
  monthOptions.push(React__default["default"].createElement("option", { key: `${choice.year}-${choice.month}`, value: `${choice.month}-${choice.year}`, "aria-label": `choose ${choice.choiceName}`, onSelect: () => {
2754
2737
  onChange(choice.month, choice.days[0], choice.year);
2755
2738
  } }, choice.choiceName));
2756
- // This is the currently selected month
2757
2739
  if (month === choice.month) {
2740
+ // This is the currently selected month
2758
2741
  // Create day options
2759
2742
  choice.days.forEach((dayChoice) => {
2760
2743
  const ordinal = getOrdinal(dayChoice);
@@ -2776,125 +2759,6 @@ const SimpleDateChooser = (props) => {
2776
2759
  } }, dayOptions)));
2777
2760
  };
2778
2761
 
2779
- /**
2780
- * Pad a number with zeros on the left (e.g. 5 becomes 05 with 2 digit padding)
2781
- * @author Gabe Abrams
2782
- * @param num the number to pad
2783
- * @param numDigits the minimum number of digits before the decimal
2784
- * @returns padded number
2785
- */
2786
- const padZerosLeft = (num, numDigits) => {
2787
- // Convert to string
2788
- let out = String(num);
2789
- // Add zeros
2790
- while (out.split('.')[0].length < numDigits) {
2791
- out = `0${out}`;
2792
- }
2793
- // Return
2794
- return out;
2795
- };
2796
-
2797
- /**
2798
- * A very simple, lightweight time chooser
2799
- * @author Gardenia Liu
2800
- */
2801
- /*------------------------------------------------------------------------*/
2802
- /* ------------------------------ Constants ----------------------------- */
2803
- /*------------------------------------------------------------------------*/
2804
- // Allowed intervals between options
2805
- const ALLOWED_INTERVALS = [15, 30, 60]; // min
2806
- // Default interval to use if an unsupported interval is passed in
2807
- const DEFAULT_INTERVAL = ALLOWED_INTERVALS[0]; // min
2808
- /*------------------------------------------------------------------------*/
2809
- /* ------------------------------ Component ----------------------------- */
2810
- /*------------------------------------------------------------------------*/
2811
- const SimpleTimeChooser = (props) => {
2812
- /*------------------------------------------------------------------------*/
2813
- /* -------------------------------- Setup ------------------------------- */
2814
- /*------------------------------------------------------------------------*/
2815
- /* -------------- Props ------------- */
2816
- const { ariaLabel, name, hour, minute, onChange, } = props;
2817
- let { intervalMin = DEFAULT_INTERVAL, } = props;
2818
- // Use default interval if not supported
2819
- if (ALLOWED_INTERVALS.includes(intervalMin)) {
2820
- intervalMin = DEFAULT_INTERVAL;
2821
- }
2822
- /*------------------------------------------------------------------------*/
2823
- /* ------------------------- Component Functions ------------------------ */
2824
- /*------------------------------------------------------------------------*/
2825
- /**
2826
- * Convert number of minutes since midnight into 24hour and minute format
2827
- * @author Gabe Abrams
2828
- * @param minSinceMidnight total minutes since midnight
2829
- * @returns hours (24) and minutes
2830
- */
2831
- const convertMinSinceMidnightToHoursAndMin = (minSinceMidnight) => {
2832
- return {
2833
- hours: Math.floor(minSinceMidnight / 60),
2834
- minutes: minSinceMidnight % 60,
2835
- };
2836
- };
2837
- /**
2838
- * Convert time in minutes into HH:MM format
2839
- * @author Gardenia Liu
2840
- * @param totalMinutes total minutes since midnight
2841
- * @returns formatted time string
2842
- */
2843
- const formatTime = (totalMinutes) => {
2844
- // Handle special cases
2845
- if (totalMinutes === 0) {
2846
- return '12:00 Midnight';
2847
- }
2848
- if (totalMinutes === 12 * 60) {
2849
- return '12:00 Noon';
2850
- }
2851
- // All normal cases:
2852
- const timeInfo = convertMinSinceMidnightToHoursAndMin(totalMinutes);
2853
- let { hours } = timeInfo;
2854
- const { minutes } = timeInfo;
2855
- // Process 24hr -> 12hr
2856
- const isAM = (hours < 12);
2857
- if (hours === 0) {
2858
- hours = 12;
2859
- }
2860
- else if (hours > 12) {
2861
- hours %= 12;
2862
- }
2863
- // Pad with zeros
2864
- const paddedMinutes = padZerosLeft(minutes, 2);
2865
- // Assemble time string
2866
- return `${hours}:${paddedMinutes} ${isAM ? 'AM' : 'PM'}`;
2867
- };
2868
- /*------------------------------------------------------------------------*/
2869
- /* ------------------------------- Render ------------------------------- */
2870
- /*------------------------------------------------------------------------*/
2871
- /*----------------------------------------*/
2872
- /* --------------- Main UI -------------- */
2873
- /*----------------------------------------*/
2874
- // Generate list of time options
2875
- const times = [];
2876
- for (let time = 0; time < 24 * 60; time += intervalMin) {
2877
- times.push(formatTime(time));
2878
- }
2879
- // Create choice options
2880
- const selectedTimeMin = hour * 60 + minute; // minutes since midnight
2881
- const timeOptions = times.map((timeString, timeIndex) => {
2882
- // Create time option
2883
- const numMinutesForChoice = timeIndex * intervalMin;
2884
- // Render the option
2885
- return (React__default["default"].createElement("option", { key: numMinutesForChoice, value: numMinutesForChoice, "aria-label": `choose ${timeString}` }, timeString));
2886
- });
2887
- return (React__default["default"].createElement("div", { className: "SimpleTimeChooser-container", "aria-label": `time chooser with selected time: ${formatTime(selectedTimeMin)}` },
2888
- React__default["default"].createElement("select", { "aria-label": `time for ${ariaLabel}`, className: "custom-select d-inline-block", style: { width: 'auto' }, id: `SimpleTimeChooser-${name}-time`, value: selectedTimeMin, onChange: (e) => {
2889
- // Parse selector value (string)
2890
- const newTime = Number.parseInt(e.target.value, 10);
2891
- // Convert minutes since midnight to hour and minute
2892
- const timeInfo = convertMinSinceMidnightToHoursAndMin(newTime);
2893
- // Notify parent of change
2894
- onChange(timeInfo.hours, timeInfo.minutes);
2895
- } }, timeOptions)));
2896
- };
2897
-
2898
2762
  /**
2899
2763
  * Drawer container
2900
2764
  * @author Gabe Abrams
@@ -3650,10 +3514,10 @@ const getHumanReadableDate = (dateOrTimestamp) => {
3650
3514
  };
3651
3515
 
3652
3516
  /**
3653
- * Path of the route for storing client-side logs
3517
+ * Path of the route for getting logs for log review
3654
3518
  * @author Gabe Abrams
3655
3519
  */
3656
- const LOG_REVIEW_ROUTE_PATH_PREFIX = `/admin${ROUTE_PATH_PREFIX}/logs`;
3520
+ const LOG_REVIEW_GET_LOGS_ROUTE = `/admin${ROUTE_PATH_PREFIX}/logs`;
3657
3521
 
3658
3522
  /**
3659
3523
  * Source of a log event
@@ -4258,6 +4122,70 @@ const IntelliTable = (props) => {
4258
4122
  } }, table)));
4259
4123
  };
4260
4124
 
4125
+ // Import React
4126
+ /*------------------------------------------------------------------------*/
4127
+ /* ------------------------------ Component ----------------------------- */
4128
+ /*------------------------------------------------------------------------*/
4129
+ const Pagination = ({ currentPage, numPages, loading = false, onPageChanged, }) => {
4130
+ /*------------------------------------------------------------------------*/
4131
+ /* -------------------------------- Setup ------------------------------- */
4132
+ /*------------------------------------------------------------------------*/
4133
+ // Compute pages to display
4134
+ const pages = [];
4135
+ const delta = 2; // how many pages to show on either side of current
4136
+ let start = Math.max(1, currentPage - delta);
4137
+ let end = Math.min(numPages, currentPage + delta);
4138
+ // If we are too close to the beginning or end, shift the window.
4139
+ if (currentPage - delta < 1) {
4140
+ end = Math.min(numPages, end + (1 - (currentPage - delta)));
4141
+ }
4142
+ if (currentPage + delta > numPages) {
4143
+ start = Math.max(1, start - ((currentPage + delta) - numPages));
4144
+ }
4145
+ for (let i = start; i <= end; i++) {
4146
+ pages.push(i);
4147
+ }
4148
+ /*------------------------------------------------------------------------*/
4149
+ /* ------------------------------- Render ------------------------------- */
4150
+ /*------------------------------------------------------------------------*/
4151
+ return (React__default["default"].createElement("nav", { "aria-label": "Page navigation for logs", className: "mt-3" },
4152
+ React__default["default"].createElement("ul", { className: "pagination justify-content-center" },
4153
+ React__default["default"].createElement("li", { className: `page-item ${(currentPage <= 1 || loading) ? 'disabled' : ''}` },
4154
+ React__default["default"].createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(currentPage - 1); }, disabled: currentPage <= 1 || loading, "aria-label": "Go to previous page" },
4155
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faArrowLeft }),
4156
+ ' ',
4157
+ "Prev")),
4158
+ currentPage > 3
4159
+ && pages[0] !== 1
4160
+ && (React__default["default"].createElement("li", { className: "page-item" },
4161
+ React__default["default"].createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(1); }, disabled: loading, "aria-label": "Go to page 1" }, "1"))),
4162
+ currentPage > 4 && (React__default["default"].createElement("li", { className: "page-item disabled" },
4163
+ React__default["default"].createElement("span", { className: "page-link" }, "..."))),
4164
+ pages.map((pageNum) => {
4165
+ return (React__default["default"].createElement("li", { key: pageNum, className: `page-item ${pageNum === currentPage ? 'active' : ''}` },
4166
+ React__default["default"].createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(pageNum); }, disabled: loading, "aria-label": `Go to page ${pageNum}` }, pageNum)));
4167
+ }),
4168
+ currentPage < numPages - 3 && (React__default["default"].createElement("li", { className: "page-item disabled" },
4169
+ React__default["default"].createElement("span", { className: "page-link" }, "..."))),
4170
+ currentPage < numPages - 2
4171
+ && pages[pages.length - 1] !== numPages
4172
+ && (React__default["default"].createElement("li", { className: "page-item" },
4173
+ React__default["default"].createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(numPages); }, disabled: loading, "aria-label": "Go to last page" }, numPages))),
4174
+ React__default["default"].createElement("li", { className: `page-item ${(currentPage >= numPages || loading) ? 'disabled' : ''}` },
4175
+ React__default["default"].createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(currentPage + 1); }, disabled: currentPage >= numPages || loading, "aria-label": "Go to next page" },
4176
+ "Next",
4177
+ ' ',
4178
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faArrowRight }))))));
4179
+ };
4180
+
4181
+ /**
4182
+ * Deeply clones an object
4183
+ * @author Yuen Ler Chow
4184
+ * @param obj the object to clone
4185
+ * @returns a deep clone of the object
4186
+ */
4187
+ const cloneDeep = clone__default["default"];
4188
+
4261
4189
  /**
4262
4190
  * Log reviewer panel that allows users (must be approved admins) to
4263
4191
  * review logs written by dce-reactkit
@@ -4613,7 +4541,7 @@ var ActionType$6;
4613
4541
  (function (ActionType) {
4614
4542
  // Show the loading bar
4615
4543
  ActionType["StartLoading"] = "start-loading";
4616
- // Finish loading one or more months of logs
4544
+ // Finish loading logs
4617
4545
  ActionType["FinishLoading"] = "finish-loading";
4618
4546
  // Reset filters to initial values
4619
4547
  ActionType["ResetFilters"] = "reset-filters";
@@ -4631,6 +4559,14 @@ var ActionType$6;
4631
4559
  ActionType["UpdateActionErrorFilterState"] = "update-action-error-filter-state";
4632
4560
  // Update the advanced filter state
4633
4561
  ActionType["UpdateAdvancedFilterState"] = "update-advanced-filter-state";
4562
+ // Set has another page
4563
+ ActionType["SetHasAnotherPage"] = "set-has-another-page";
4564
+ // Set the number of pages
4565
+ ActionType["SetNumPages"] = "set-num-pages";
4566
+ // Set page number
4567
+ ActionType["SetPageNumber"] = "set-page-number";
4568
+ // Reset user made filter change indicator
4569
+ ActionType["ResetUserMadeFilterChange"] = "reset-user-made-filter-change";
4634
4570
  })(ActionType$6 || (ActionType$6 = {}));
4635
4571
  /**
4636
4572
  * Reducer that executes actions
@@ -4644,7 +4580,7 @@ const reducer$7 = (state, action) => {
4644
4580
  return Object.assign(Object.assign({}, state), { loading: true });
4645
4581
  }
4646
4582
  case ActionType$6.FinishLoading: {
4647
- return Object.assign(Object.assign({}, state), { loading: false, logMap: action.logMap });
4583
+ return Object.assign(Object.assign({}, state), { loading: false, logs: action.logs });
4648
4584
  }
4649
4585
  case ActionType$6.ToggleFilterDrawer: {
4650
4586
  return Object.assign(Object.assign({}, state), { expandedFilterDrawer: (state.expandedFilterDrawer === action.filterDrawer
@@ -4655,22 +4591,34 @@ const reducer$7 = (state, action) => {
4655
4591
  return Object.assign(Object.assign({}, state), { expandedFilterDrawer: undefined });
4656
4592
  }
4657
4593
  case ActionType$6.ResetFilters: {
4658
- return Object.assign(Object.assign({}, state), { dateFilterState: action.initDateFilterState, contextFilterState: action.initContextFilterState, tagFilterState: action.initTagFilterState, actionErrorFilterState: action.initActionErrorFilterState, advancedFilterState: action.initAdvancedFilterState });
4594
+ return Object.assign(Object.assign({}, state), { pendingDateFilterState: action.initDateFilterState, pendingContextFilterState: action.initContextFilterState, pendingTagFilterState: action.initTagFilterState, pendingActionErrorFilterState: action.initActionErrorFilterState, pendingAdvancedFilterState: action.initAdvancedFilterState, pageNumber: 1 });
4659
4595
  }
4660
4596
  case ActionType$6.UpdateDateFilterState: {
4661
- return Object.assign(Object.assign({}, state), { dateFilterState: action.dateFilterState });
4597
+ return Object.assign(Object.assign({}, state), { pendingDateFilterState: action.dateFilterState, userMadeFilterChange: true });
4662
4598
  }
4663
4599
  case ActionType$6.UpdateContextFilterState: {
4664
- return Object.assign(Object.assign({}, state), { contextFilterState: action.contextFilterState });
4600
+ return Object.assign(Object.assign({}, state), { pendingContextFilterState: action.contextFilterState, userMadeFilterChange: true });
4665
4601
  }
4666
4602
  case ActionType$6.UpdateTagFilterState: {
4667
- return Object.assign(Object.assign({}, state), { tagFilterState: action.tagFilterState });
4603
+ return Object.assign(Object.assign({}, state), { pendingTagFilterState: action.tagFilterState, userMadeFilterChange: true });
4668
4604
  }
4669
4605
  case ActionType$6.UpdateActionErrorFilterState: {
4670
- return Object.assign(Object.assign({}, state), { actionErrorFilterState: action.actionErrorFilterState });
4606
+ return Object.assign(Object.assign({}, state), { pendingActionErrorFilterState: action.actionErrorFilterState, userMadeFilterChange: true });
4671
4607
  }
4672
4608
  case ActionType$6.UpdateAdvancedFilterState: {
4673
- return Object.assign(Object.assign({}, state), { advancedFilterState: action.advancedFilterState });
4609
+ return Object.assign(Object.assign({}, state), { pendingAdvancedFilterState: action.advancedFilterState, userMadeFilterChange: true });
4610
+ }
4611
+ case ActionType$6.SetHasAnotherPage: {
4612
+ return Object.assign(Object.assign({}, state), { hasAnotherPage: action.hasAnotherPage });
4613
+ }
4614
+ case ActionType$6.SetNumPages: {
4615
+ return Object.assign(Object.assign({}, state), { numPages: action.numPages });
4616
+ }
4617
+ case ActionType$6.SetPageNumber: {
4618
+ return Object.assign(Object.assign({}, state), { pageNumber: action.pageNumber });
4619
+ }
4620
+ case ActionType$6.ResetUserMadeFilterChange: {
4621
+ return Object.assign(Object.assign({}, state), { userMadeFilterChange: false });
4674
4622
  }
4675
4623
  default: {
4676
4624
  return state;
@@ -4741,7 +4689,7 @@ const LogReviewer = (props) => {
4741
4689
  else {
4742
4690
  // Case: subcontexts exist
4743
4691
  initContextFilterState[context] = {};
4744
- Object.values(contextMap[context]).forEach((subcontext) => {
4692
+ Object.keys(contextMap[context]).forEach((subcontext) => {
4745
4693
  // Skip self ("_")
4746
4694
  if (subcontext === '_') {
4747
4695
  return;
@@ -4789,125 +4737,105 @@ const LogReviewer = (props) => {
4789
4737
  // Initial state
4790
4738
  const initialState = {
4791
4739
  loading: true,
4792
- logMap: {},
4740
+ logs: [],
4793
4741
  expandedFilterDrawer: undefined,
4794
- dateFilterState: initDateFilterState,
4795
- contextFilterState: initContextFilterState,
4796
- tagFilterState: initTagFilterState,
4797
- actionErrorFilterState: initActionErrorFilterState,
4798
- advancedFilterState: initAdvancedFilterState,
4742
+ pendingDateFilterState: initDateFilterState,
4743
+ pendingContextFilterState: initContextFilterState,
4744
+ pendingTagFilterState: initTagFilterState,
4745
+ pendingActionErrorFilterState: initActionErrorFilterState,
4746
+ pendingAdvancedFilterState: initAdvancedFilterState,
4747
+ pageNumber: 1,
4748
+ hasAnotherPage: false,
4749
+ numPages: 1,
4750
+ userMadeFilterChange: false,
4799
4751
  };
4800
4752
  // Initialize state
4801
4753
  const [state, dispatch] = React.useReducer(reducer$7, initialState);
4802
4754
  // Destructure common state
4803
- const { loading, logMap, expandedFilterDrawer, dateFilterState, contextFilterState, tagFilterState, actionErrorFilterState, advancedFilterState, } = state;
4755
+ const { loading, logs, expandedFilterDrawer, pendingDateFilterState, pendingContextFilterState, pendingTagFilterState, pendingActionErrorFilterState, pendingAdvancedFilterState, pageNumber, numPages, userMadeFilterChange, } = state;
4756
+ /* -------------- Refs -------------- */
4757
+ // Initialize refs
4758
+ const activeFiltersRef = React.useRef({
4759
+ dateFilterState: JSON.parse(JSON.stringify(pendingDateFilterState)),
4760
+ contextFilterState: JSON.parse(JSON.stringify(pendingContextFilterState)),
4761
+ tagFilterState: JSON.parse(JSON.stringify(pendingTagFilterState)),
4762
+ actionErrorFilterState: JSON.parse(JSON.stringify(pendingActionErrorFilterState)),
4763
+ advancedFilterState: JSON.parse(JSON.stringify(pendingAdvancedFilterState)),
4764
+ });
4804
4765
  /*------------------------------------------------------------------------*/
4805
4766
  /* ------------------------- Component Functions ------------------------ */
4806
4767
  /*------------------------------------------------------------------------*/
4807
4768
  /**
4808
- * Get the list of year/month combos that need to be loaded given a new
4809
- * start or end date and the existing logMap
4810
- * @author Gabe Abrams
4811
- * @param newDateFilterState the new date filter state
4812
- * @returns list of year/month combos that need to be loaded
4813
- */
4814
- const listMonthsToLoad = (newDateFilterState) => {
4815
- // List of year/month combos that need to be loaded
4816
- const toLoad = [];
4817
- // Loop through dates
4818
- let { year, month } = newDateFilterState.startDate;
4819
- while (
4820
- // Earlier year
4821
- (year < newDateFilterState.endDate.year)
4822
- // Current year but included month
4823
- || (year === newDateFilterState.endDate.year
4824
- && month <= newDateFilterState.endDate.month)) {
4825
- // Add to list if not already loaded
4826
- if (!logMap[year]
4827
- || !logMap[year][month]) {
4828
- toLoad.push({
4829
- year,
4830
- month,
4831
- });
4832
- }
4833
- // Increment
4834
- month += 1;
4835
- if (month > 12) {
4836
- month -= 12;
4837
- year += 1;
4838
- }
4839
- }
4840
- // Return
4841
- return toLoad;
4842
- };
4843
- /**
4844
- * Handle updated start/end dates (updates state, loads if necessary)
4845
- * @author Gabe Abrams
4846
- * @param newDateFilterState the new date filter state
4769
+ * Fetch logs from the server based on current filters
4770
+ * @author Yuen Ler Chow
4771
+ * @param opts object containing all arguments
4772
+ * @param opts.filters the filters to apply
4773
+ * @param opts.pageNum the page number to fetch
4774
+ * @param opts.filtersChanged if true, the filters have changed
4847
4775
  */
4848
- const handleDateRangeUpdated = (newDateFilterState) => __awaiter(void 0, void 0, void 0, function* () {
4849
- // Update state
4850
- dispatch({
4851
- type: ActionType$6.UpdateDateFilterState,
4852
- dateFilterState: newDateFilterState,
4853
- });
4854
- // Check which year/month combos we need to load
4855
- const toLoad = listMonthsToLoad(newDateFilterState);
4856
- // If nothing to load, finished
4857
- if (toLoad.length === 0) {
4858
- return;
4859
- }
4860
- // Start loading
4776
+ const fetchLogs = (opts) => __awaiter(void 0, void 0, void 0, function* () {
4777
+ const { filters, pageNum, filtersChanged, } = opts;
4861
4778
  dispatch({
4862
4779
  type: ActionType$6.StartLoading,
4863
4780
  });
4864
- // Load required months
4865
4781
  try {
4866
- for (let i = 0; i < toLoad.length; i++) {
4867
- // Destructure
4868
- const { year, month } = toLoad[i];
4869
- // Load
4870
- let logs = [];
4871
- let pageNumber = 1;
4872
- let hasAnotherPage = true;
4873
- while (hasAnotherPage) {
4874
- const response = yield visitServerEndpoint({
4875
- path: `${LOG_REVIEW_ROUTE_PATH_PREFIX}/years/${year}/months/${month}`,
4876
- method: 'GET',
4877
- params: {
4878
- pageNumber,
4879
- },
4880
- });
4881
- logs = logs.concat(response.items);
4882
- hasAnotherPage = response.hasAnotherPage;
4883
- pageNumber += 1;
4884
- }
4885
- // Add to map
4886
- if (!logMap[year]) {
4887
- logMap[year] = {};
4888
- }
4889
- logMap[year][month] = logs;
4782
+ // Send filters to the server
4783
+ let fetchedLogs = [];
4784
+ // Get logs from server
4785
+ const response = yield visitServerEndpoint({
4786
+ path: LOG_REVIEW_GET_LOGS_ROUTE,
4787
+ method: 'GET',
4788
+ params: {
4789
+ pageNumber: pageNum,
4790
+ filters,
4791
+ countDocuments: filtersChanged,
4792
+ },
4793
+ });
4794
+ fetchedLogs = fetchedLogs.concat(response.items);
4795
+ dispatch({
4796
+ type: ActionType$6.SetHasAnotherPage,
4797
+ hasAnotherPage: response.hasAnotherPage,
4798
+ });
4799
+ if (filtersChanged && response.numPages !== undefined) {
4800
+ dispatch({
4801
+ type: ActionType$6.SetNumPages,
4802
+ numPages: response.numPages,
4803
+ });
4890
4804
  }
4805
+ // Update logs in state
4806
+ dispatch({
4807
+ type: ActionType$6.FinishLoading,
4808
+ logs: fetchedLogs,
4809
+ });
4810
+ // Update page number
4811
+ dispatch({
4812
+ type: ActionType$6.SetPageNumber,
4813
+ pageNumber: pageNum,
4814
+ });
4891
4815
  }
4892
4816
  catch (err) {
4893
4817
  return showFatalError(err);
4894
4818
  }
4895
- // Finish loading
4896
- dispatch({
4897
- type: ActionType$6.FinishLoading,
4898
- logMap,
4899
- });
4900
4819
  });
4901
4820
  /*------------------------------------------------------------------------*/
4902
4821
  /* ------------------------- Lifecycle Functions ------------------------ */
4903
4822
  /*------------------------------------------------------------------------*/
4904
4823
  /**
4905
- * Mount
4906
- * @author Gabe Abrams
4824
+ * Fetch logs on mount
4825
+ * @author Yuen Ler Chow
4907
4826
  */
4908
4827
  React.useEffect(() => {
4909
- // Perform initial load
4910
- handleDateRangeUpdated(dateFilterState);
4828
+ fetchLogs({
4829
+ filters: {
4830
+ dateFilterState: pendingDateFilterState,
4831
+ contextFilterState: pendingContextFilterState,
4832
+ tagFilterState: pendingTagFilterState,
4833
+ actionErrorFilterState: pendingActionErrorFilterState,
4834
+ advancedFilterState: pendingAdvancedFilterState,
4835
+ },
4836
+ pageNum: 1,
4837
+ filtersChanged: true,
4838
+ });
4911
4839
  }, []);
4912
4840
  /*------------------------------------------------------------------------*/
4913
4841
  /* ------------------------------- Render ------------------------------- */
@@ -4915,718 +4843,534 @@ const LogReviewer = (props) => {
4915
4843
  /*----------------------------------------*/
4916
4844
  /* --------------- Main UI -------------- */
4917
4845
  /*----------------------------------------*/
4918
- // Body that will be filled with the contents of the panel
4919
- let body;
4920
- /* ------------- Loading ------------ */
4921
- if (loading) {
4922
- body = (React__default["default"].createElement("div", { className: "text-center p-5" },
4923
- React__default["default"].createElement(LoadingSpinner, null)));
4924
- }
4925
- /* ------------ Review UI ----------- */
4926
- if (!loading) {
4927
- /*----------------------------------------*/
4928
- /* --------------- Filters -------------- */
4929
- /*----------------------------------------*/
4930
- // Filter toggle
4931
- const filterToggles = (React__default["default"].createElement("div", { className: "LogReviewer-filter-toggles" },
4932
- React__default["default"].createElement("h3", { className: "m-0" }, "Filters:"),
4933
- React__default["default"].createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
4934
- React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-date-filter-drawer", className: `btn btn-${FilterDrawer.Date === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle date filter drawer", onClick: () => {
4935
- dispatch({
4936
- type: ActionType$6.ToggleFilterDrawer,
4937
- filterDrawer: FilterDrawer.Date,
4938
- });
4939
- } },
4940
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCalendar, className: "me-2" }),
4941
- "Date"),
4942
- React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-context-filter-drawer", className: `btn btn-${FilterDrawer.Context === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle context filter drawer", onClick: () => {
4943
- dispatch({
4944
- type: ActionType$6.ToggleFilterDrawer,
4945
- filterDrawer: FilterDrawer.Context,
4946
- });
4947
- } },
4948
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "me-2" }),
4949
- "Context"),
4950
- (LogMetadata.Tag && Object.keys(LogMetadata.Tag).length > 0) && (React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-tag-filter-drawer", className: `btn btn-${FilterDrawer.Tag === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle tag filter drawer", onClick: () => {
4951
- dispatch({
4952
- type: ActionType$6.ToggleFilterDrawer,
4953
- filterDrawer: FilterDrawer.Tag,
4954
- });
4955
- } },
4956
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTag, className: "me-2" }),
4957
- "Tag")),
4958
- React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-action-filter-drawer", className: `btn btn-${FilterDrawer.Action === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle action and error filter drawer", onClick: () => {
4959
- dispatch({
4960
- type: ActionType$6.ToggleFilterDrawer,
4961
- filterDrawer: FilterDrawer.Action,
4962
- });
4963
- } },
4964
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faHammer, className: "me-2" }),
4965
- "Action"),
4966
- React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-advanced-filter-drawer", className: `btn btn-${FilterDrawer.Advanced === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle advanced filter drawer", onClick: () => {
4846
+ /*----------------------------------------*/
4847
+ /* ------------ Pagination -------------- */
4848
+ /*----------------------------------------*/
4849
+ const paginationControls = logs.length > 0 && (React__default["default"].createElement(Pagination, { currentPage: pageNumber, numPages: numPages, loading: loading, onPageChanged: (targetPage) => {
4850
+ const { current: activeFilters } = activeFiltersRef;
4851
+ fetchLogs({
4852
+ filters: {
4853
+ dateFilterState: activeFilters.dateFilterState,
4854
+ contextFilterState: activeFilters.contextFilterState,
4855
+ tagFilterState: activeFilters.tagFilterState,
4856
+ actionErrorFilterState: activeFilters.actionErrorFilterState,
4857
+ advancedFilterState: activeFilters.advancedFilterState,
4858
+ },
4859
+ pageNum: targetPage,
4860
+ filtersChanged: false,
4861
+ });
4862
+ } }));
4863
+ /*----------------------------------------*/
4864
+ /* --------------- Filters -------------- */
4865
+ /*----------------------------------------*/
4866
+ // Filter toggle
4867
+ const filterToggles = (React__default["default"].createElement("div", { className: "LogReviewer-filter-toggles" },
4868
+ React__default["default"].createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
4869
+ React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-date-filter-drawer", className: `btn btn-${FilterDrawer.Date === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle date filter drawer", onClick: () => {
4870
+ dispatch({
4871
+ type: ActionType$6.ToggleFilterDrawer,
4872
+ filterDrawer: FilterDrawer.Date,
4873
+ });
4874
+ } },
4875
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCalendar, className: "me-2" }),
4876
+ "Date"),
4877
+ React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-context-filter-drawer", className: `btn btn-${FilterDrawer.Context === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle context filter drawer", onClick: () => {
4878
+ dispatch({
4879
+ type: ActionType$6.ToggleFilterDrawer,
4880
+ filterDrawer: FilterDrawer.Context,
4881
+ });
4882
+ } },
4883
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "me-2" }),
4884
+ "Context"),
4885
+ (LogMetadata.Tag && Object.keys(LogMetadata.Tag).length > 0) && (React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-tag-filter-drawer", className: `btn btn-${FilterDrawer.Tag === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle tag filter drawer", onClick: () => {
4886
+ dispatch({
4887
+ type: ActionType$6.ToggleFilterDrawer,
4888
+ filterDrawer: FilterDrawer.Tag,
4889
+ });
4890
+ } },
4891
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTag, className: "me-2" }),
4892
+ "Tag")),
4893
+ React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-action-filter-drawer", className: `btn btn-${FilterDrawer.Action === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle action and error filter drawer", onClick: () => {
4894
+ dispatch({
4895
+ type: ActionType$6.ToggleFilterDrawer,
4896
+ filterDrawer: FilterDrawer.Action,
4897
+ });
4898
+ } },
4899
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faHammer, className: "me-2" }),
4900
+ "Action"),
4901
+ React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-advanced-filter-drawer", className: `btn btn-${FilterDrawer.Advanced === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle advanced filter drawer", onClick: () => {
4902
+ dispatch({
4903
+ type: ActionType$6.ToggleFilterDrawer,
4904
+ filterDrawer: FilterDrawer.Advanced,
4905
+ });
4906
+ } },
4907
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faList, className: "me-2" }),
4908
+ "Advanced"),
4909
+ React__default["default"].createElement("button", { type: "button", id: "LogReviewer-reset-filters-button", className: "btn btn-light", "aria-label": "reset filters", onClick: () => {
4910
+ // Save active filters
4911
+ // Deep clone the initial filter states to avoid any reference issues
4912
+ // if the filter states are modified later
4913
+ activeFiltersRef.current = cloneDeep({
4914
+ dateFilterState: initDateFilterState,
4915
+ contextFilterState: initContextFilterState,
4916
+ tagFilterState: initTagFilterState,
4917
+ actionErrorFilterState: initActionErrorFilterState,
4918
+ advancedFilterState: initAdvancedFilterState,
4919
+ });
4920
+ fetchLogs({
4921
+ filters: {
4922
+ dateFilterState: initDateFilterState,
4923
+ contextFilterState: initContextFilterState,
4924
+ tagFilterState: initTagFilterState,
4925
+ actionErrorFilterState: initActionErrorFilterState,
4926
+ advancedFilterState: initAdvancedFilterState,
4927
+ },
4928
+ pageNum: 1,
4929
+ filtersChanged: true,
4930
+ });
4931
+ dispatch({
4932
+ type: ActionType$6.ResetFilters,
4933
+ initActionErrorFilterState,
4934
+ initAdvancedFilterState,
4935
+ initContextFilterState,
4936
+ initDateFilterState,
4937
+ initTagFilterState,
4938
+ });
4939
+ dispatch({
4940
+ type: ActionType$6.HideFilterDrawer,
4941
+ });
4942
+ } },
4943
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes }),
4944
+ ' ',
4945
+ "Reset"),
4946
+ userMadeFilterChange && (React__default["default"].createElement("button", { type: "button", id: "LogReviewer-submit-filters-button", className: "btn btn-primary ms-2", "aria-label": "submit filters", onClick: () => {
4947
+ dispatch({
4948
+ type: ActionType$6.HideFilterDrawer,
4949
+ });
4950
+ // Reset user made filter change indicator
4951
+ dispatch({
4952
+ type: ActionType$6.ResetUserMadeFilterChange,
4953
+ });
4954
+ // Save active filters
4955
+ // Deep clone the pending filter states to avoid any reference issues
4956
+ // if the filter states are modified later
4957
+ activeFiltersRef.current = cloneDeep({
4958
+ dateFilterState: pendingDateFilterState,
4959
+ contextFilterState: pendingContextFilterState,
4960
+ tagFilterState: pendingTagFilterState,
4961
+ actionErrorFilterState: pendingActionErrorFilterState,
4962
+ advancedFilterState: pendingAdvancedFilterState,
4963
+ });
4964
+ fetchLogs({
4965
+ filters: {
4966
+ dateFilterState: pendingDateFilterState,
4967
+ contextFilterState: pendingContextFilterState,
4968
+ tagFilterState: pendingTagFilterState,
4969
+ actionErrorFilterState: pendingActionErrorFilterState,
4970
+ advancedFilterState: pendingAdvancedFilterState,
4971
+ },
4972
+ pageNum: 1,
4973
+ filtersChanged: true,
4974
+ });
4975
+ } },
4976
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faSearch }),
4977
+ ' ',
4978
+ "Apply Filters")))));
4979
+ // Filter drawer
4980
+ let filterDrawer;
4981
+ if (expandedFilterDrawer) {
4982
+ if (expandedFilterDrawer === FilterDrawer.Date) {
4983
+ filterDrawer = (React__default["default"].createElement(TabBox, { title: "Date" },
4984
+ React__default["default"].createElement(SimpleDateChooser, { ariaLabel: "filter start date", name: "filter-start-date", year: pendingDateFilterState.startDate.year, month: pendingDateFilterState.startDate.month, day: pendingDateFilterState.startDate.day, chooseFromPast: true, numMonthsToShow: 36, onChange: (month, day, year) => {
4985
+ const newDateFilterState = Object.assign(Object.assign({}, pendingDateFilterState), { startDate: { month, day, year } });
4967
4986
  dispatch({
4968
- type: ActionType$6.ToggleFilterDrawer,
4969
- filterDrawer: FilterDrawer.Advanced,
4987
+ type: ActionType$6.UpdateDateFilterState,
4988
+ dateFilterState: newDateFilterState,
4970
4989
  });
4971
- } },
4972
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faList, className: "me-2" }),
4973
- "Advanced"),
4974
- React__default["default"].createElement("button", { type: "button", id: "LogReviewer-reset-filters-button", className: "btn btn-light", "aria-label": "reset filters", onClick: () => {
4990
+ } }),
4991
+ ' ',
4992
+ "to",
4993
+ ' ',
4994
+ React__default["default"].createElement(SimpleDateChooser, { ariaLabel: "filter end date", name: "filter-end-date", year: pendingDateFilterState.endDate.year, month: pendingDateFilterState.endDate.month, day: pendingDateFilterState.endDate.day, chooseFromPast: true, numMonthsToShow: 12, onChange: (month, day, year) => {
4995
+ if (year < pendingDateFilterState.startDate.year
4996
+ || (year === pendingDateFilterState.startDate.year
4997
+ && month < pendingDateFilterState.startDate.month)
4998
+ || (year === pendingDateFilterState.startDate.year
4999
+ && month === pendingDateFilterState.startDate.month
5000
+ && day < pendingDateFilterState.startDate.day)) {
5001
+ return alert('Invalid End Date', 'The end date cannot be before the start date.');
5002
+ }
5003
+ const newDateFilterState = Object.assign(Object.assign({}, pendingDateFilterState), { endDate: { month, day, year } });
4975
5004
  dispatch({
4976
- type: ActionType$6.ResetFilters,
4977
- initActionErrorFilterState,
4978
- initAdvancedFilterState,
4979
- initContextFilterState,
4980
- initDateFilterState,
4981
- initTagFilterState,
5005
+ type: ActionType$6.UpdateDateFilterState,
5006
+ dateFilterState: newDateFilterState,
4982
5007
  });
4983
- } },
4984
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes }),
4985
- ' ',
4986
- "Reset"))));
4987
- // Filter drawer
4988
- let filterDrawer;
4989
- if (expandedFilterDrawer) {
4990
- if (expandedFilterDrawer === FilterDrawer.Date) {
4991
- filterDrawer = (React__default["default"].createElement(TabBox, { title: "Date" },
4992
- React__default["default"].createElement(SimpleDateChooser, { ariaLabel: "filter start date", name: "filter-start-date", year: dateFilterState.startDate.year, month: dateFilterState.startDate.month, day: dateFilterState.startDate.day, dontAllowFuture: true, numMonthsToShow: 36, onChange: (month, day, year) => {
4993
- dateFilterState.startDate = { month, day, year };
4994
- handleDateRangeUpdated(dateFilterState);
4995
- } }),
4996
- ' ',
4997
- "to",
4998
- ' ',
4999
- React__default["default"].createElement(SimpleDateChooser, { ariaLabel: "filter end date", name: "filter-end-date", year: dateFilterState.endDate.year, month: dateFilterState.endDate.month, day: dateFilterState.endDate.day, dontAllowFuture: true, numMonthsToShow: 12, onChange: (month, day, year) => {
5000
- if (year < dateFilterState.startDate.year
5001
- || (year === dateFilterState.startDate.year
5002
- && month < dateFilterState.startDate.month)
5003
- || (year === dateFilterState.startDate.year
5004
- && month === dateFilterState.startDate.month
5005
- && day < dateFilterState.startDate.day)) {
5006
- return alert('Invalid Start Date', 'The start date cannot be before the end date.');
5007
- }
5008
- dateFilterState.endDate = { month, day, year };
5009
- handleDateRangeUpdated(dateFilterState);
5010
- } })));
5011
- }
5012
- else if (expandedFilterDrawer === FilterDrawer.Context) {
5013
- // Create item picker items
5014
- const builtInPickableItem = {
5015
- id: 'built-in-contexts',
5016
- name: 'Auto-logged',
5017
- isGroup: true,
5018
- children: [],
5019
- };
5020
- const pickableItems = [];
5021
- Object.keys(contextMap)
5022
- .forEach((context) => {
5023
- const value = contextMap[context];
5024
- if (typeof value === 'string') {
5025
- // No subcategories
5026
- const item = {
5027
- id: context,
5028
- name: genHumanReadableName(context),
5029
- isGroup: false,
5030
- checked: !!contextFilterState[context],
5031
- };
5032
- // Add built-in items to its own folder
5033
- const isBuiltIn = context in LogBuiltInMetadata.Context;
5034
- if (isBuiltIn) {
5035
- // Add to built-in pickable item
5036
- builtInPickableItem.children.push(item);
5037
- }
5038
- else {
5039
- // Add to pickable items list
5040
- pickableItems.push(item);
5041
- }
5042
- return;
5043
- }
5044
- // Has subcategories
5045
- const children = (Object.keys(value)
5046
- // Remove parent name
5047
- .filter((subcontext) => {
5048
- return subcontext !== '_';
5049
- })
5050
- // Create child pickable items
5051
- .map((subcontext) => {
5052
- return {
5053
- id: subcontext,
5054
- name: genHumanReadableName(subcontext),
5055
- isGroup: false,
5056
- checked: contextFilterState[context][subcontext],
5057
- };
5058
- }));
5008
+ } })));
5009
+ }
5010
+ else if (expandedFilterDrawer === FilterDrawer.Context) {
5011
+ // Create item picker items
5012
+ const builtInPickableItem = {
5013
+ id: 'built-in-contexts',
5014
+ name: 'Auto-logged',
5015
+ isGroup: true,
5016
+ children: [],
5017
+ };
5018
+ const pickableItems = [];
5019
+ Object.keys(contextMap)
5020
+ .forEach((context) => {
5021
+ const value = contextMap[context];
5022
+ if (typeof value === 'string') {
5023
+ // No subcategories
5059
5024
  const item = {
5060
5025
  id: context,
5061
5026
  name: genHumanReadableName(context),
5062
- isGroup: true,
5063
- children,
5027
+ isGroup: false,
5028
+ checked: !!pendingContextFilterState[context],
5064
5029
  };
5065
- pickableItems.push(item);
5066
- });
5067
- // Add built-in contexts to end ofl ist
5068
- pickableItems.push(builtInPickableItem);
5069
- // Create filter UI
5070
- filterDrawer = (React__default["default"].createElement(ItemPicker, { title: "Context", items: pickableItems, onChanged: (updatedItems) => {
5071
- // Update our state
5072
- updatedItems.forEach((pickableItem) => {
5073
- if (pickableItem.isGroup) {
5074
- // Has subcontexts
5075
- if (pickableItem.id === 'built-in-contexts') {
5076
- // Built-in
5077
- // Treat as if these were top-level contexts
5078
- pickableItem.children.forEach((subcontextItem) => {
5079
- contextFilterState[subcontextItem.id] = ('checked' in subcontextItem
5080
- && subcontextItem.checked);
5081
- });
5082
- }
5083
- else {
5084
- // Not built-in
5085
- pickableItem.children.forEach((subcontextItem) => {
5086
- if (!subcontextItem.isGroup) {
5087
- contextFilterState[pickableItem.id][subcontextItem.id] = (subcontextItem.checked);
5088
- }
5089
- });
5090
- }
5030
+ // Add built-in items to its own folder
5031
+ const isBuiltIn = context in LogBuiltInMetadata.Context;
5032
+ if (isBuiltIn) {
5033
+ // Add to built-in pickable item
5034
+ builtInPickableItem.children.push(item);
5035
+ }
5036
+ else {
5037
+ // Add to pickable items list
5038
+ pickableItems.push(item);
5039
+ }
5040
+ return;
5041
+ }
5042
+ // Has subcategories
5043
+ const children = (Object.keys(value)
5044
+ // Remove parent name
5045
+ .filter((subcontext) => {
5046
+ return (subcontext !== '_');
5047
+ })
5048
+ // Create child pickable items
5049
+ .map((subcontext) => {
5050
+ return {
5051
+ id: subcontext,
5052
+ name: genHumanReadableName(subcontext),
5053
+ isGroup: false,
5054
+ checked: pendingContextFilterState[context][subcontext],
5055
+ };
5056
+ }));
5057
+ const item = {
5058
+ id: context,
5059
+ name: genHumanReadableName(context),
5060
+ isGroup: true,
5061
+ children,
5062
+ };
5063
+ pickableItems.push(item);
5064
+ });
5065
+ // Add built-in contexts to end of list
5066
+ pickableItems.push(builtInPickableItem);
5067
+ // Create filter UI
5068
+ filterDrawer = (React__default["default"].createElement(ItemPicker, { title: "Context", items: pickableItems, onChanged: (updatedItems) => {
5069
+ // Update our state
5070
+ const newContextFilterState = Object.assign({}, pendingContextFilterState);
5071
+ updatedItems.forEach((pickableItem) => {
5072
+ if (pickableItem.isGroup) {
5073
+ // Has subcontexts
5074
+ if (pickableItem.id === 'built-in-contexts') {
5075
+ // Built-in
5076
+ // Treat as if these were top-level contexts
5077
+ pickableItem.children.forEach((subcontextItem) => {
5078
+ newContextFilterState[subcontextItem.id] = ('checked' in subcontextItem
5079
+ && subcontextItem.checked);
5080
+ });
5091
5081
  }
5092
5082
  else {
5093
- // No subcontexts
5094
- contextFilterState[pickableItem.id] = (pickableItem.checked);
5083
+ // Not built-in
5084
+ pickableItem.children.forEach((subcontextItem) => {
5085
+ if (!subcontextItem.isGroup) {
5086
+ newContextFilterState[pickableItem.id][subcontextItem.id] = (subcontextItem.checked);
5087
+ }
5088
+ });
5095
5089
  }
5096
- });
5097
- dispatch({
5098
- type: ActionType$6.UpdateContextFilterState,
5099
- contextFilterState,
5100
- });
5101
- } }));
5102
- }
5103
- else if (expandedFilterDrawer === FilterDrawer.Tag) {
5104
- // Create filter UI
5105
- filterDrawer = (React__default["default"].createElement(TabBox, { title: "Tags" },
5106
- React__default["default"].createElement("div", null, "If any tags are selected, logs must contain at least one (but not necessarily all) of the selected tags."),
5107
- React__default["default"].createElement("div", { className: "d-flex gap-1 flex-wrap" }, Object.keys((_d = LogMetadata.Tag) !== null && _d !== void 0 ? _d : {})
5108
- .map((tag) => {
5109
- const description = genHumanReadableName(tag);
5110
- return (React__default["default"].createElement(CheckboxButton, { key: tag, id: `LogReviewer-tag-${tag}-checkbox`, text: description, ariaLabel: `require that logs be tagged with "${description}" or any other selected tag`, checked: tagFilterState[tag], onChanged: (checked) => {
5111
- tagFilterState[tag] = checked;
5090
+ }
5091
+ else {
5092
+ // No subcontexts
5093
+ newContextFilterState[pickableItem.id] = (pickableItem.checked);
5094
+ }
5095
+ });
5096
+ // Update state
5097
+ dispatch({
5098
+ type: ActionType$6.UpdateContextFilterState,
5099
+ contextFilterState: newContextFilterState,
5100
+ });
5101
+ } }));
5102
+ }
5103
+ else if (expandedFilterDrawer === FilterDrawer.Tag) {
5104
+ // Create filter UI
5105
+ filterDrawer = (React__default["default"].createElement(TabBox, { title: "Tags" },
5106
+ React__default["default"].createElement("div", null, "If any tags are selected, logs must contain at least one (but not necessarily all) of the selected tags."),
5107
+ React__default["default"].createElement("div", { className: "d-flex gap-1 flex-wrap" }, Object.keys((_d = LogMetadata.Tag) !== null && _d !== void 0 ? _d : {})
5108
+ .map((tag) => {
5109
+ const description = genHumanReadableName(tag);
5110
+ return (React__default["default"].createElement(CheckboxButton, { key: tag, id: `LogReviewer-tag-${tag}-checkbox`, text: description, ariaLabel: `require that logs be tagged with "${description}" or any other selected tag`, checked: pendingTagFilterState[tag], onChanged: (checked) => {
5111
+ const newTagFilterState = Object.assign(Object.assign({}, pendingTagFilterState), { [tag]: checked });
5112
+ dispatch({
5113
+ type: ActionType$6.UpdateTagFilterState,
5114
+ tagFilterState: newTagFilterState,
5115
+ });
5116
+ }, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }));
5117
+ }))));
5118
+ }
5119
+ else if (expandedFilterDrawer === FilterDrawer.Action) {
5120
+ // Create filter UI
5121
+ filterDrawer = (React__default["default"].createElement(React__default["default"].Fragment, null,
5122
+ React__default["default"].createElement(TabBox, { title: "Log Type" },
5123
+ React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-all", text: "All Logs", onSelected: () => {
5124
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { type: undefined });
5125
+ dispatch({
5126
+ type: ActionType$6.UpdateActionErrorFilterState,
5127
+ actionErrorFilterState: newActionErrorFilterState,
5128
+ });
5129
+ }, ariaLabel: "show logs of all types", selected: pendingActionErrorFilterState.type === undefined, unselectedVariant: Variant$1.Light }),
5130
+ React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-action-only", text: "Action Logs Only", onSelected: () => {
5131
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { type: LogType$1.Action });
5132
+ dispatch({
5133
+ type: ActionType$6.UpdateActionErrorFilterState,
5134
+ actionErrorFilterState: newActionErrorFilterState,
5135
+ });
5136
+ }, ariaLabel: "only show action logs", selected: pendingActionErrorFilterState.type === LogType$1.Action, unselectedVariant: Variant$1.Light }),
5137
+ React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-error-only", text: "Action Error Only", onSelected: () => {
5138
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { type: LogType$1.Error });
5139
+ dispatch({
5140
+ type: ActionType$6.UpdateActionErrorFilterState,
5141
+ actionErrorFilterState: newActionErrorFilterState,
5142
+ });
5143
+ }, ariaLabel: "only show error logs", selected: pendingActionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light })),
5144
+ (pendingActionErrorFilterState.type === undefined
5145
+ || pendingActionErrorFilterState.type === LogType$1.Action) && (React__default["default"].createElement(TabBox, { title: "Action Log Details" },
5146
+ React__default["default"].createElement(ButtonInputGroup, { label: "Action", className: "mb-2", wrapButtonsAndAddGaps: true }, Object.keys(LogAction$1)
5147
+ .map((action) => {
5148
+ const description = genHumanReadableName(action);
5149
+ return (React__default["default"].createElement(CheckboxButton, { key: action, id: `LogReviewer-action-${action}-checkbox`, text: description, ariaLabel: `include logs with action type "${description}" in results`, noMarginOnRight: true, checked: pendingActionErrorFilterState.action[action], onChanged: (checked) => {
5150
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { action: Object.assign(Object.assign({}, pendingActionErrorFilterState.action), { [action]: checked }) });
5112
5151
  dispatch({
5113
- type: ActionType$6.UpdateTagFilterState,
5114
- tagFilterState,
5152
+ type: ActionType$6.UpdateActionErrorFilterState,
5153
+ actionErrorFilterState: newActionErrorFilterState,
5115
5154
  });
5116
- } }));
5117
- }))));
5118
- }
5119
- else if (expandedFilterDrawer === FilterDrawer.Action) {
5120
- // Create filter UI
5121
- filterDrawer = (React__default["default"].createElement(React__default["default"].Fragment, null,
5122
- React__default["default"].createElement(TabBox, { title: "Log Type" },
5123
- React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-all", text: "All Logs", onSelected: () => {
5124
- actionErrorFilterState.type = undefined;
5155
+ }, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }));
5156
+ })),
5157
+ React__default["default"].createElement(ButtonInputGroup, { label: "Target", wrapButtonsAndAddGaps: true }, Object.keys(targetMap)
5158
+ .map((target) => {
5159
+ const description = genHumanReadableName(target);
5160
+ return (React__default["default"].createElement(CheckboxButton, { key: target, id: `LogReviewer-target-${target}-checkbox`, text: description, ariaLabel: `include logs with target "${description}" in results`, checked: pendingActionErrorFilterState.target[target], noMarginOnRight: true, onChanged: (checked) => {
5161
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { target: Object.assign(Object.assign({}, pendingActionErrorFilterState.target), { [target]: checked }) });
5125
5162
  dispatch({
5126
5163
  type: ActionType$6.UpdateActionErrorFilterState,
5127
- actionErrorFilterState,
5164
+ actionErrorFilterState: newActionErrorFilterState,
5128
5165
  });
5129
- }, ariaLabel: "show logs of all types", selected: actionErrorFilterState.type === undefined }),
5130
- React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-action-only", text: "Action Logs Only", onSelected: () => {
5131
- actionErrorFilterState.type = LogType$1.Action;
5166
+ }, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }));
5167
+ })))),
5168
+ (pendingActionErrorFilterState.type === undefined
5169
+ || pendingActionErrorFilterState.type === LogType$1.Error) && (React__default["default"].createElement(TabBox, { title: "Error Log Details" },
5170
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5171
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Error Message"),
5172
+ React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for error message", value: pendingActionErrorFilterState.errorMessage, placeholder: "e.g. undefined is not a function", onChange: (e) => {
5173
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { errorMessage: e.target.value });
5132
5174
  dispatch({
5133
5175
  type: ActionType$6.UpdateActionErrorFilterState,
5134
- actionErrorFilterState,
5176
+ actionErrorFilterState: newActionErrorFilterState,
5135
5177
  });
5136
- }, ariaLabel: "only show action logs", selected: actionErrorFilterState.type === LogType$1.Action }),
5137
- React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-error-only", text: "Action Error Only", onSelected: () => {
5138
- actionErrorFilterState.type = LogType$1.Error;
5178
+ } })),
5179
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5180
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Error Code"),
5181
+ React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for error code", value: pendingActionErrorFilterState.errorCode, placeholder: "e.g. GC22", onChange: (e) => {
5182
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { errorCode: ((e.target.value)
5183
+ .trim()
5184
+ .toUpperCase()) });
5139
5185
  dispatch({
5140
5186
  type: ActionType$6.UpdateActionErrorFilterState,
5141
- actionErrorFilterState,
5187
+ actionErrorFilterState: newActionErrorFilterState,
5142
5188
  });
5143
- }, ariaLabel: "only show error logs", selected: actionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true })),
5144
- (actionErrorFilterState.type === undefined
5145
- || actionErrorFilterState.type === LogType$1.Action) && (React__default["default"].createElement(TabBox, { title: "Action Log Details" },
5146
- React__default["default"].createElement(ButtonInputGroup, { label: "Action", className: "mb-2", wrapButtonsAndAddGaps: true }, Object.keys(LogAction$1)
5147
- .map((action) => {
5148
- const description = genHumanReadableName(action);
5149
- return (React__default["default"].createElement(CheckboxButton, { key: action, id: `LogReviewer-action-${action}-checkbox`, text: description, ariaLabel: `include logs with action type "${description}" in results`, noMarginOnRight: true, checked: actionErrorFilterState.action[action], onChanged: (checked) => {
5150
- actionErrorFilterState.action[action] = checked;
5151
- dispatch({
5152
- type: ActionType$6.UpdateActionErrorFilterState,
5153
- actionErrorFilterState,
5154
- });
5155
- } }));
5156
- })),
5157
- React__default["default"].createElement(ButtonInputGroup, { label: "Target", wrapButtonsAndAddGaps: true }, Object.keys(targetMap)
5158
- .map((target) => {
5159
- const description = genHumanReadableName(target);
5160
- return (React__default["default"].createElement(CheckboxButton, { key: target, id: `LogReviewer-target-${target}-checkbox`, text: description, ariaLabel: `include logs with target "${description}" in results`, checked: actionErrorFilterState.target[target], noMarginOnRight: true, onChanged: (checked) => {
5161
- actionErrorFilterState.target[target] = checked;
5162
- dispatch({
5163
- type: ActionType$6.UpdateActionErrorFilterState,
5164
- actionErrorFilterState,
5165
- });
5166
- } }));
5167
- })))),
5168
- (actionErrorFilterState.type === undefined
5169
- || actionErrorFilterState.type === LogType$1.Error) && (React__default["default"].createElement(TabBox, { title: "Error Log Details" },
5170
- React__default["default"].createElement("div", { className: "input-group mb-2" },
5171
- React__default["default"].createElement("span", { className: "input-group-text" }, "Error Message"),
5172
- React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for error message", value: actionErrorFilterState.errorMessage, placeholder: "e.g. undefined is not a function", onChange: (e) => {
5173
- actionErrorFilterState.errorMessage = e.target.value;
5174
- dispatch({
5175
- type: ActionType$6.UpdateActionErrorFilterState,
5176
- actionErrorFilterState,
5177
- });
5178
- } })),
5179
- React__default["default"].createElement("div", { className: "input-group mb-2" },
5180
- React__default["default"].createElement("span", { className: "input-group-text" }, "Error Code"),
5181
- React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for error code", value: actionErrorFilterState.errorCode, placeholder: "e.g. GC22", onChange: (e) => {
5182
- actionErrorFilterState.errorCode = ((e.target.value)
5183
- .trim()
5184
- .toUpperCase());
5185
- dispatch({
5186
- type: ActionType$6.UpdateActionErrorFilterState,
5187
- actionErrorFilterState,
5188
- });
5189
- } }))))));
5190
- }
5191
- else if (expandedFilterDrawer === FilterDrawer.Advanced) {
5192
- // Create advanced filter ui
5193
- filterDrawer = (React__default["default"].createElement(React__default["default"].Fragment, null,
5194
- React__default["default"].createElement(TabBox, { title: "User" },
5195
- React__default["default"].createElement("div", { className: "input-group mb-2" },
5196
- React__default["default"].createElement("span", { className: "input-group-text" }, "User First Name"),
5197
- React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user first name", value: advancedFilterState.userFirstName, placeholder: "e.g. Divardo", onChange: (e) => {
5198
- advancedFilterState.userFirstName = e.target.value;
5199
- dispatch({
5200
- type: ActionType$6.UpdateAdvancedFilterState,
5201
- advancedFilterState,
5202
- });
5203
- } })),
5204
- React__default["default"].createElement("div", { className: "input-group mb-2" },
5205
- React__default["default"].createElement("span", { className: "input-group-text" }, "User Last Name"),
5206
- React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user last name", value: advancedFilterState.userLastName, placeholder: "e.g. Calicci", onChange: (e) => {
5207
- advancedFilterState.userLastName = e.target.value;
5208
- dispatch({
5209
- type: ActionType$6.UpdateAdvancedFilterState,
5210
- advancedFilterState,
5211
- });
5212
- } })),
5213
- React__default["default"].createElement("div", { className: "input-group mb-2" },
5214
- React__default["default"].createElement("span", { className: "input-group-text" }, "User Email"),
5215
- React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user email", value: advancedFilterState.userEmail, placeholder: "e.g. calicci@fas.harvard.edu", onChange: (e) => {
5216
- advancedFilterState.userEmail = ((e.target.value)
5217
- .trim());
5218
- dispatch({
5219
- type: ActionType$6.UpdateAdvancedFilterState,
5220
- advancedFilterState,
5221
- });
5222
- } })),
5223
- React__default["default"].createElement("div", { className: "input-group mb-2" },
5224
- React__default["default"].createElement("span", { className: "input-group-text" }, "User Canvas Id"),
5225
- React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user canvas id", value: advancedFilterState.userId, placeholder: "e.g. 104985", onChange: (e) => {
5226
- const { value } = e.target;
5227
- // Only update if value contains only numbers
5228
- if (/^\d+$/.test(value)) {
5229
- advancedFilterState.userId = ((e.target.value)
5230
- .trim());
5231
- }
5232
- dispatch({
5233
- type: ActionType$6.UpdateAdvancedFilterState,
5234
- advancedFilterState,
5235
- });
5236
- } })),
5237
- React__default["default"].createElement(ButtonInputGroup, { label: "Role" },
5238
- React__default["default"].createElement(CheckboxButton, { text: "Students", onChanged: (checked) => {
5239
- advancedFilterState.includeLearners = checked;
5240
- dispatch({
5241
- type: ActionType$6.UpdateAdvancedFilterState,
5242
- advancedFilterState,
5243
- });
5244
- }, checked: advancedFilterState.includeLearners, ariaLabel: "show logs from students" }),
5245
- React__default["default"].createElement(CheckboxButton, { text: "Teaching Team Members", onChanged: (checked) => {
5246
- advancedFilterState.includeTTMs = checked;
5189
+ } }))))));
5190
+ }
5191
+ else if (expandedFilterDrawer === FilterDrawer.Advanced) {
5192
+ // Create advanced filter ui
5193
+ filterDrawer = (React__default["default"].createElement(React__default["default"].Fragment, null,
5194
+ React__default["default"].createElement(TabBox, { title: "User" },
5195
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5196
+ React__default["default"].createElement("span", { className: "input-group-text" }, "User First Name"),
5197
+ React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user first name", value: pendingAdvancedFilterState.userFirstName, placeholder: "e.g. Divardo", onChange: (e) => {
5198
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userFirstName: e.target.value });
5199
+ dispatch({
5200
+ type: ActionType$6.UpdateAdvancedFilterState,
5201
+ advancedFilterState: newAdvancedFilterState,
5202
+ });
5203
+ } })),
5204
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5205
+ React__default["default"].createElement("span", { className: "input-group-text" }, "User Last Name"),
5206
+ React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user last name", value: pendingAdvancedFilterState.userLastName, placeholder: "e.g. Calicci", onChange: (e) => {
5207
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userLastName: e.target.value });
5208
+ dispatch({
5209
+ type: ActionType$6.UpdateAdvancedFilterState,
5210
+ advancedFilterState: newAdvancedFilterState,
5211
+ });
5212
+ } })),
5213
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5214
+ React__default["default"].createElement("span", { className: "input-group-text" }, "User Email"),
5215
+ React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user email", value: pendingAdvancedFilterState.userEmail, placeholder: "e.g. calicci@fas.harvard.edu", onChange: (e) => {
5216
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userEmail: ((e.target.value)
5217
+ .trim()) });
5218
+ dispatch({
5219
+ type: ActionType$6.UpdateAdvancedFilterState,
5220
+ advancedFilterState: newAdvancedFilterState,
5221
+ });
5222
+ } })),
5223
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5224
+ React__default["default"].createElement("span", { className: "input-group-text" }, "User Canvas Id"),
5225
+ React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user canvas id", value: pendingAdvancedFilterState.userId, placeholder: "e.g. 104985", onChange: (e) => {
5226
+ const { value } = e.target;
5227
+ // Only update if value contains only numbers
5228
+ if (/^\d+$/.test(value) || value === '') {
5229
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userId: ((e.target.value)
5230
+ .trim()) });
5247
5231
  dispatch({
5248
5232
  type: ActionType$6.UpdateAdvancedFilterState,
5249
- advancedFilterState,
5233
+ advancedFilterState: newAdvancedFilterState,
5250
5234
  });
5251
- }, checked: advancedFilterState.includeTTMs, ariaLabel: "show logs from teaching team members" }),
5252
- React__default["default"].createElement(CheckboxButton, { text: "Admins", onChanged: (checked) => {
5253
- advancedFilterState.includeAdmins = checked;
5235
+ }
5236
+ } })),
5237
+ React__default["default"].createElement(ButtonInputGroup, { label: "Role" },
5238
+ React__default["default"].createElement(CheckboxButton, { text: "Students", onChanged: (checked) => {
5239
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { includeLearners: checked });
5240
+ dispatch({
5241
+ type: ActionType$6.UpdateAdvancedFilterState,
5242
+ advancedFilterState: newAdvancedFilterState,
5243
+ });
5244
+ }, checked: pendingAdvancedFilterState.includeLearners, ariaLabel: "show logs from students", checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }),
5245
+ React__default["default"].createElement(CheckboxButton, { text: "Teaching Team Members", onChanged: (checked) => {
5246
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { includeTTMs: checked });
5247
+ dispatch({
5248
+ type: ActionType$6.UpdateAdvancedFilterState,
5249
+ advancedFilterState: newAdvancedFilterState,
5250
+ });
5251
+ }, checked: pendingAdvancedFilterState.includeTTMs, ariaLabel: "show logs from teaching team members", checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }),
5252
+ React__default["default"].createElement(CheckboxButton, { text: "Admins", onChanged: (checked) => {
5253
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { includeAdmins: checked });
5254
+ dispatch({
5255
+ type: ActionType$6.UpdateAdvancedFilterState,
5256
+ advancedFilterState: newAdvancedFilterState,
5257
+ });
5258
+ }, checked: pendingAdvancedFilterState.includeAdmins, ariaLabel: "show logs from admins", checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }))),
5259
+ React__default["default"].createElement(TabBox, { title: "Course" },
5260
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5261
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Course Name"),
5262
+ React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for course name", value: pendingAdvancedFilterState.courseName, placeholder: "e.g. GLC 200", onChange: (e) => {
5263
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { courseName: e.target.value });
5264
+ dispatch({
5265
+ type: ActionType$6.UpdateAdvancedFilterState,
5266
+ advancedFilterState: newAdvancedFilterState,
5267
+ });
5268
+ } })),
5269
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5270
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Course Canvas Id"),
5271
+ React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for course canvas id", value: pendingAdvancedFilterState.courseId, placeholder: "e.g. 15948", onChange: (e) => {
5272
+ const { value } = e.target;
5273
+ // Only update if value contains only numbers
5274
+ if (/^\d+$/.test(value) || value === '') {
5275
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { courseId: ((e.target.value)
5276
+ .trim()) });
5254
5277
  dispatch({
5255
5278
  type: ActionType$6.UpdateAdvancedFilterState,
5256
- advancedFilterState,
5279
+ advancedFilterState: newAdvancedFilterState,
5257
5280
  });
5258
- }, checked: advancedFilterState.includeAdmins, ariaLabel: "show logs from admins" }))),
5259
- React__default["default"].createElement(TabBox, { title: "Course" },
5281
+ }
5282
+ } }))),
5283
+ React__default["default"].createElement(TabBox, { title: "Device" },
5284
+ React__default["default"].createElement(ButtonInputGroup, { label: "Device Type" },
5285
+ React__default["default"].createElement(RadioButton, { text: "All Devices", ariaLabel: "show logs from all devices", selected: pendingAdvancedFilterState.isMobile === undefined, onSelected: () => {
5286
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { isMobile: undefined });
5287
+ dispatch({
5288
+ type: ActionType$6.UpdateAdvancedFilterState,
5289
+ advancedFilterState: newAdvancedFilterState,
5290
+ });
5291
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5292
+ React__default["default"].createElement(RadioButton, { text: "Mobile Only", ariaLabel: "show logs from mobile devices", selected: pendingAdvancedFilterState.isMobile === true, onSelected: () => {
5293
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { isMobile: true });
5294
+ dispatch({
5295
+ type: ActionType$6.UpdateAdvancedFilterState,
5296
+ advancedFilterState: newAdvancedFilterState,
5297
+ });
5298
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5299
+ React__default["default"].createElement(RadioButton, { text: "Desktop Only", ariaLabel: "show logs from desktop devices", selected: pendingAdvancedFilterState.isMobile === false, onSelected: () => {
5300
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { isMobile: false });
5301
+ dispatch({
5302
+ type: ActionType$6.UpdateAdvancedFilterState,
5303
+ advancedFilterState: newAdvancedFilterState,
5304
+ });
5305
+ }, noMarginOnRight: true, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }))),
5306
+ React__default["default"].createElement(TabBox, { title: "Source" },
5307
+ React__default["default"].createElement(ButtonInputGroup, { label: "Source Type" },
5308
+ React__default["default"].createElement(RadioButton, { text: "Both", ariaLabel: "show logs from all sources", selected: pendingAdvancedFilterState.source === undefined, onSelected: () => {
5309
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { source: undefined });
5310
+ dispatch({
5311
+ type: ActionType$6.UpdateAdvancedFilterState,
5312
+ advancedFilterState: newAdvancedFilterState,
5313
+ });
5314
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5315
+ React__default["default"].createElement(RadioButton, { text: "Client Only", ariaLabel: "show logs from client source", selected: pendingAdvancedFilterState.source === LogSource$1.Client, onSelected: () => {
5316
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { source: LogSource$1.Client });
5317
+ dispatch({
5318
+ type: ActionType$6.UpdateAdvancedFilterState,
5319
+ advancedFilterState: newAdvancedFilterState,
5320
+ });
5321
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5322
+ React__default["default"].createElement(RadioButton, { text: "Server Only", ariaLabel: "show logs from server source", selected: pendingAdvancedFilterState.source === LogSource$1.Server, onSelected: () => {
5323
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { source: LogSource$1.Server });
5324
+ dispatch({
5325
+ type: ActionType$6.UpdateAdvancedFilterState,
5326
+ advancedFilterState: newAdvancedFilterState,
5327
+ });
5328
+ }, noMarginOnRight: true, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light })),
5329
+ pendingAdvancedFilterState.source !== LogSource$1.Client && (React__default["default"].createElement("div", { className: "mt-2" },
5260
5330
  React__default["default"].createElement("div", { className: "input-group mb-2" },
5261
- React__default["default"].createElement("span", { className: "input-group-text" }, "Course Name"),
5262
- React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for course name", value: advancedFilterState.courseName, placeholder: "e.g. GLC 200", onChange: (e) => {
5263
- advancedFilterState.courseName = e.target.value;
5331
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Path"),
5332
+ React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for server route path", value: pendingAdvancedFilterState.routePath, placeholder: "e.g. /api/ttm/courses/12345", onChange: (e) => {
5333
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { routePath: ((e.target.value)
5334
+ .trim()) });
5264
5335
  dispatch({
5265
5336
  type: ActionType$6.UpdateAdvancedFilterState,
5266
- advancedFilterState,
5337
+ advancedFilterState: newAdvancedFilterState,
5267
5338
  });
5268
5339
  } })),
5269
5340
  React__default["default"].createElement("div", { className: "input-group mb-2" },
5270
- React__default["default"].createElement("span", { className: "input-group-text" }, "Course Canvas Id"),
5271
- React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for course canvas id", value: advancedFilterState.courseId, placeholder: "e.g. 15948", onChange: (e) => {
5272
- const { value } = e.target;
5273
- // Only update if value contains only numbers
5274
- if (/^\d+$/.test(value)) {
5275
- advancedFilterState.courseId = ((e.target.value)
5276
- .trim());
5277
- }
5278
- dispatch({
5279
- type: ActionType$6.UpdateAdvancedFilterState,
5280
- advancedFilterState,
5281
- });
5282
- } }))),
5283
- React__default["default"].createElement(TabBox, { title: "Device" },
5284
- React__default["default"].createElement(ButtonInputGroup, { label: "Device Type" },
5285
- React__default["default"].createElement(RadioButton, { text: "All Devices", ariaLabel: "show logs from all devices", selected: advancedFilterState.isMobile === undefined, onSelected: () => {
5286
- advancedFilterState.isMobile = undefined;
5287
- dispatch({
5288
- type: ActionType$6.UpdateAdvancedFilterState,
5289
- advancedFilterState,
5290
- });
5291
- } }),
5292
- React__default["default"].createElement(RadioButton, { text: "Mobile Only", ariaLabel: "show logs from mobile devices", selected: advancedFilterState.isMobile === true, onSelected: () => {
5293
- advancedFilterState.isMobile = true;
5294
- dispatch({
5295
- type: ActionType$6.UpdateAdvancedFilterState,
5296
- advancedFilterState,
5297
- });
5298
- } }),
5299
- React__default["default"].createElement(RadioButton, { text: "Desktop Only", ariaLabel: "show logs from desktop devices", selected: advancedFilterState.isMobile === false, onSelected: () => {
5300
- advancedFilterState.isMobile = false;
5301
- dispatch({
5302
- type: ActionType$6.UpdateAdvancedFilterState,
5303
- advancedFilterState,
5304
- });
5305
- }, noMarginOnRight: true }))),
5306
- React__default["default"].createElement(TabBox, { title: "Source" },
5307
- React__default["default"].createElement(ButtonInputGroup, { label: "Source Type" },
5308
- React__default["default"].createElement(RadioButton, { text: "Both", ariaLabel: "show logs from all sources", selected: advancedFilterState.source === undefined, onSelected: () => {
5309
- advancedFilterState.source = undefined;
5310
- dispatch({
5311
- type: ActionType$6.UpdateAdvancedFilterState,
5312
- advancedFilterState,
5313
- });
5314
- } }),
5315
- React__default["default"].createElement(RadioButton, { text: "Client Only", ariaLabel: "show logs from client source", selected: advancedFilterState.source === LogSource$1.Client, onSelected: () => {
5316
- advancedFilterState.source = LogSource$1.Client;
5341
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Template"),
5342
+ React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for server route template", value: pendingAdvancedFilterState.routeTemplate, placeholder: "e.g. /api/ttm/courses/:courseId", onChange: (e) => {
5343
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { routeTemplate: ((e.target.value)
5344
+ .trim()) });
5317
5345
  dispatch({
5318
5346
  type: ActionType$6.UpdateAdvancedFilterState,
5319
- advancedFilterState,
5347
+ advancedFilterState: newAdvancedFilterState,
5320
5348
  });
5321
- } }),
5322
- React__default["default"].createElement(RadioButton, { text: "Server Only", ariaLabel: "show logs from server source", selected: advancedFilterState.source === LogSource$1.Server, onSelected: () => {
5323
- advancedFilterState.source = LogSource$1.Server;
5324
- dispatch({
5325
- type: ActionType$6.UpdateAdvancedFilterState,
5326
- advancedFilterState,
5327
- });
5328
- }, noMarginOnRight: true })),
5329
- advancedFilterState.source !== LogSource$1.Client && (React__default["default"].createElement("div", { className: "mt-2" },
5330
- React__default["default"].createElement("div", { className: "input-group mb-2" },
5331
- React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Path"),
5332
- React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for server route path", value: advancedFilterState.routePath, placeholder: "e.g. /api/ttm/courses/12345", onChange: (e) => {
5333
- advancedFilterState.courseName = ((e.target.value)
5334
- .trim());
5335
- dispatch({
5336
- type: ActionType$6.UpdateAdvancedFilterState,
5337
- advancedFilterState,
5338
- });
5339
- } })),
5340
- React__default["default"].createElement("div", { className: "input-group mb-2" },
5341
- React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Template"),
5342
- React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for server route template", value: advancedFilterState.routeTemplate, placeholder: "e.g. /api/ttm/courses/:courseId", onChange: (e) => {
5343
- advancedFilterState.courseName = ((e.target.value)
5344
- .trim());
5345
- dispatch({
5346
- type: ActionType$6.UpdateAdvancedFilterState,
5347
- advancedFilterState,
5348
- });
5349
- } })))))));
5350
- }
5349
+ } })))))));
5351
5350
  }
5352
- // Filters UI
5353
- const filters = (React__default["default"].createElement(React__default["default"].Fragment, null,
5354
- filterToggles,
5355
- filterDrawer && (React__default["default"].createElement(Drawer, { customBackgroundColor: "#eee" }, filterDrawer))));
5356
- // Actually filter the logs
5357
- // > Perform filters
5358
- const logs = [];
5359
- Object.keys(logMap).forEach((year) => {
5360
- Object.keys(logMap[year]).forEach((month) => {
5361
- logMap[year][month].forEach((log) => {
5362
- /* ----------- Date Filter ---------- */
5363
- var _a;
5364
- // Before start date
5365
- if (
5366
- // Previous year
5367
- log.year < dateFilterState.startDate.year
5368
- // Same year, earlier month
5369
- || ((log.year === dateFilterState.startDate.year)
5370
- && (log.month < dateFilterState.startDate.month))
5371
- // Same year, same month, earlier day
5372
- || ((log.year === dateFilterState.startDate.year)
5373
- && (log.month === dateFilterState.startDate.month)
5374
- && (log.day < dateFilterState.startDate.day))) {
5375
- return;
5376
- }
5377
- // After end date
5378
- if (
5379
- // Later year
5380
- log.year > dateFilterState.endDate.year
5381
- // Same year, later month
5382
- || ((log.year === dateFilterState.endDate.year)
5383
- && (log.month > dateFilterState.endDate.month))
5384
- // Same year, same month, later day
5385
- || ((log.year === dateFilterState.endDate.year)
5386
- && (log.month === dateFilterState.endDate.month)
5387
- && (log.day > dateFilterState.endDate.day))) {
5388
- return;
5389
- }
5390
- /* --------- Context Filter --------- */
5391
- // Context doesn't match
5392
- if (
5393
- // Whole context is deselected
5394
- contextFilterState[log.context] === false
5395
- // None of the subcontexts are selected
5396
- || (
5397
- // Has subcontexts
5398
- typeof contextFilterState[log.context] !== 'boolean'
5399
- // None of the subcontexts are selected
5400
- && Object.values((_a = contextFilterState[log.context]) !== null && _a !== void 0 ? _a : {})
5401
- .every((isSelected) => {
5402
- return !isSelected;
5403
- }))) {
5404
- return;
5405
- }
5406
- // Subcontext doesn't match
5407
- if (
5408
- // Log context is not "uncategorized" (no point in further filters)
5409
- log.context !== LogBuiltInMetadata.Context.Uncategorized
5410
- // Log has a subcontext
5411
- && log.subcontext
5412
- // Context has subcontexts
5413
- && (contextFilterState[log.context]
5414
- && contextFilterState[log.context] !== false
5415
- && contextFilterState[log.context] !== true)
5416
- // Subcontext is not selected
5417
- && !contextFilterState[log.context][log.subcontext]) {
5418
- return;
5419
- }
5420
- /* -------------- Tags -------------- */
5421
- // No tags match
5422
- if (
5423
- // At least one tag is required
5424
- Object.values(tagFilterState)
5425
- .filter((isSelected) => {
5426
- return isSelected;
5427
- })
5428
- .length > 0
5429
- // No tags match
5430
- && log.tags.every((tag) => {
5431
- return !tagFilterState[tag];
5432
- })) {
5433
- return;
5434
- }
5435
- /* ------- Actions and Errors ------- */
5436
- // Log type doesn't match
5437
- if (
5438
- // Filter won't allow all types
5439
- actionErrorFilterState.type !== undefined
5440
- // Log type doesn't match
5441
- && actionErrorFilterState.type !== log.type) {
5442
- return;
5443
- }
5444
- // Filter errors
5445
- if (log.type === LogType$1.Error) {
5446
- // Message doesn't match
5447
- if (
5448
- // Message exists
5449
- log.errorMessage
5450
- // Message filter exists
5451
- && actionErrorFilterState.errorMessage.trim().length > 0
5452
- // Message doesn't match
5453
- && log.errorMessage.toLowerCase().includes(actionErrorFilterState.errorMessage.trim().toLowerCase())) {
5454
- return;
5455
- }
5456
- // Code doesn't match
5457
- if (
5458
- // Code exists
5459
- log.errorCode
5460
- // Code filter exists
5461
- && actionErrorFilterState.errorCode.trim().length > 0
5462
- // Code doesn't match
5463
- && log.errorCode.toUpperCase().includes(actionErrorFilterState.errorCode.trim().toUpperCase())) {
5464
- return;
5465
- }
5466
- }
5467
- // Filter actions
5468
- if (log.type === LogType$1.Action) {
5469
- // Target isn't selected
5470
- if (
5471
- // Target exists
5472
- log.target
5473
- // Target isn't selected
5474
- && !actionErrorFilterState.target[log.target]) {
5475
- return;
5476
- }
5477
- // Action
5478
- if (
5479
- // Action exists
5480
- log.action
5481
- // Action isn't selected
5482
- && !actionErrorFilterState.action[log.action]) {
5483
- return;
5484
- }
5485
- }
5486
- /* --------- Advanced Filter -------- */
5487
- // First name doesn't match
5488
- if (
5489
- // First name exists
5490
- log.userFirstName
5491
- // First name query doesn't match
5492
- && !log.userFirstName.toLowerCase().includes(advancedFilterState.userFirstName.toLowerCase().trim())) {
5493
- return;
5494
- }
5495
- // Last name doesn't match
5496
- if (
5497
- // Last name exists
5498
- log.userLastName
5499
- // Last name query doesn't match
5500
- && !log.userLastName.toLowerCase().includes(advancedFilterState.userLastName.toLowerCase().trim())) {
5501
- return;
5502
- }
5503
- // Email doesn't match
5504
- if (
5505
- // Email exists
5506
- log.userEmail
5507
- // Email query doesn't match
5508
- && !log.userEmail.toLowerCase().includes(advancedFilterState.userEmail.toLowerCase().trim())) {
5509
- return;
5510
- }
5511
- // User id doesn't match
5512
- if (
5513
- // User id exists
5514
- log.userId
5515
- // User id doesn't match
5516
- && !String(log.userId).includes(advancedFilterState.userId.trim())) {
5517
- return;
5518
- }
5519
- // Learner not allowed
5520
- if (
5521
- // User is a learner
5522
- log.isLearner
5523
- // Learners aren't included
5524
- && !advancedFilterState.includeLearners) {
5525
- return;
5526
- }
5527
- // TTM not allowed
5528
- if (
5529
- // User is a ttm
5530
- log.isTTM
5531
- // TTMs aren't included
5532
- && !advancedFilterState.includeTTMs) {
5533
- return;
5534
- }
5535
- // Admin not allowed
5536
- if (
5537
- // User is an admin
5538
- log.isAdmin
5539
- // Admins aren't included
5540
- && !advancedFilterState.includeAdmins) {
5541
- return;
5542
- }
5543
- // Course Id doesn't match
5544
- if (
5545
- // Course Id exists
5546
- log.courseId
5547
- // Course Id doesn't match
5548
- && !String(log.courseId).includes(advancedFilterState.courseId.trim())) {
5549
- return;
5550
- }
5551
- // Course name doesn't match
5552
- if (
5553
- // Course name exists
5554
- log.courseName
5555
- // Course name doesn't match
5556
- && !String(log.courseName).includes(advancedFilterState.courseName.trim())) {
5557
- return;
5558
- }
5559
- // Mobile filter doesn't match
5560
- if (
5561
- // Mobile filter exists
5562
- advancedFilterState.isMobile !== undefined
5563
- // Device info exists
5564
- && log.device
5565
- // Mobile filter doesn't match
5566
- && (advancedFilterState.isMobile !== log.device.isMobile)) {
5567
- return;
5568
- }
5569
- // Log source doesn't match
5570
- if (
5571
- // Source filter exists
5572
- advancedFilterState.source !== undefined
5573
- // Source info exists
5574
- && log.source
5575
- // Source filter doesn't match
5576
- && (advancedFilterState.source !== log.source)) {
5577
- return;
5578
- }
5579
- // Route path doesn't match (Only for server source)
5580
- if (
5581
- // Source is server
5582
- (log.source === LogSource$1.Server)
5583
- // Route path is being filtered
5584
- && (advancedFilterState.routePath.trim().length)
5585
- // Route path doesn't match
5586
- && !(log.routePath.includes(advancedFilterState.routePath.trim()))) {
5587
- return;
5588
- }
5589
- // Route template doesn't match (Only for server source)
5590
- if (
5591
- // Source is server
5592
- (log.source === LogSource$1.Server)
5593
- // Route template is being filtered
5594
- && (advancedFilterState.routeTemplate.trim().length)
5595
- // Route template doesn't match
5596
- && !(log.routeTemplate.includes(advancedFilterState.routeTemplate.trim()))) {
5597
- return;
5598
- }
5599
- /* -------------- Done -------------- */
5600
- // Made it past all filters. Add to the list
5601
- logs.push(log);
5602
- });
5603
- });
5604
- });
5605
- /*----------------------------------------*/
5606
- /* ---------------- Data ---------------- */
5607
- /*----------------------------------------*/
5608
- // Nothing to show notice
5609
- const noLogsNotice = (logs.length === 0
5610
- ? (React__default["default"].createElement("div", { className: "alert alert-warning text-center mt-2" },
5611
- React__default["default"].createElement("h4", { className: "m-1" }, "No Logs to Show"),
5612
- React__default["default"].createElement("div", null, "Either your filters are too strict or no matching logs have been created yet.")))
5613
- : undefined);
5614
- // Create intelliTable
5615
- const dataTable = (React__default["default"].createElement(IntelliTable, { title: "Matching Logs:", csvName: `Logs from ${getHumanReadableDate()}`, id: "logs", data: logs, columns: columns }));
5616
- // Main body
5617
- body = (React__default["default"].createElement(React__default["default"].Fragment, null,
5618
- filters,
5619
- React__default["default"].createElement("div", { className: "mt-2" },
5620
- dataTable,
5621
- noLogsNotice)));
5622
5351
  }
5352
+ // Filters UI
5353
+ const filters = (React__default["default"].createElement(React__default["default"].Fragment, null,
5354
+ filterToggles,
5355
+ filterDrawer && (React__default["default"].createElement(Drawer, { customBackgroundColor: "#eee" }, filterDrawer))));
5356
+ // Body that will be filled with the contents of the panel
5357
+ const body = (React__default["default"].createElement(React__default["default"].Fragment, null,
5358
+ loading && (React__default["default"].createElement("div", { className: "text-center p-5" },
5359
+ React__default["default"].createElement(LoadingSpinner, null))),
5360
+ !loading && (React__default["default"].createElement("div", null, filters)),
5361
+ !loading && logs.length === 0 && (React__default["default"].createElement("div", { className: "alert alert-warning text-center mt-2" },
5362
+ React__default["default"].createElement("h4", { className: "m-1" }, "No Logs to Show"),
5363
+ React__default["default"].createElement("div", null, "Either your filters are too strict or no matching logs have been created yet."))),
5364
+ React__default["default"].createElement("div", { className: loading || logs.length === 0 ? 'd-none' : undefined },
5365
+ React__default["default"].createElement(IntelliTable, { title: "Matching Logs", csvName: `Logs from ${getHumanReadableDate()}`, id: "logs", data: logs, columns: columns })),
5366
+ !loading && logs.length > 0 && paginationControls));
5623
5367
  /* ---------- Wrap in Modal --------- */
5624
5368
  return (React__default["default"].createElement("div", { className: "LogReviewer-outer-container" },
5625
5369
  React__default["default"].createElement("style", null, style$2),
5626
5370
  React__default["default"].createElement("div", { className: "LogReviewer-inner-container" },
5627
5371
  React__default["default"].createElement("div", { className: "LogReviewer-header" },
5628
5372
  React__default["default"].createElement("div", { className: "LogReviewer-header-title" },
5629
- React__default["default"].createElement("h3", { className: "text-center m-0" }, "Log Review Dashboard")),
5373
+ React__default["default"].createElement("h3", { className: "text-center" }, "Log Review Dashboard")),
5630
5374
  React__default["default"].createElement("div", { style: { width: 0 } },
5631
5375
  React__default["default"].createElement("button", { type: "button", className: "LogReviewer-header-close-button btn btn-dark btn-lg pe-0", "aria-label": "close log reviewer panel", onClick: onClose },
5632
5376
  React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes })))),
@@ -15302,6 +15046,12 @@ const HOUR_IN_MS = 3600000;
15302
15046
  */
15303
15047
  const DAY_IN_MS = 86400000;
15304
15048
 
15049
+ /**
15050
+ * Path of the route for storing client-side logs
15051
+ * @author Gabe Abrams
15052
+ */
15053
+ const LOG_REVIEW_ROUTE_PATH_PREFIX = `/admin${ROUTE_PATH_PREFIX}/logs`;
15054
+
15305
15055
  /**
15306
15056
  * Route for checking the status of the current user's
15307
15057
  * access to log review
@@ -15476,6 +15226,24 @@ const padDecimalZeros = (num, numDigits) => {
15476
15226
  return out;
15477
15227
  };
15478
15228
 
15229
+ /**
15230
+ * Pad a number with zeros on the left (e.g. 5 becomes 05 with 2 digit padding)
15231
+ * @author Gabe Abrams
15232
+ * @param num the number to pad
15233
+ * @param numDigits the minimum number of digits before the decimal
15234
+ * @returns padded number
15235
+ */
15236
+ const padZerosLeft = (num, numDigits) => {
15237
+ // Convert to string
15238
+ let out = String(num);
15239
+ // Add zeros
15240
+ while (out.split('.')[0].length < numDigits) {
15241
+ out = `0${out}`;
15242
+ }
15243
+ // Return
15244
+ return out;
15245
+ };
15246
+
15479
15247
  /**
15480
15248
  * Stub a server endpoint response
15481
15249
  * @author Gabe Abrams
@@ -16365,6 +16133,7 @@ exports.ErrorWithCode = ErrorWithCode;
16365
16133
  exports.HOUR_IN_MS = HOUR_IN_MS;
16366
16134
  exports.IntelliTable = IntelliTable;
16367
16135
  exports.ItemPicker = ItemPicker;
16136
+ exports.LOG_REVIEW_GET_LOGS_ROUTE = LOG_REVIEW_GET_LOGS_ROUTE;
16368
16137
  exports.LOG_REVIEW_ROUTE_PATH_PREFIX = LOG_REVIEW_ROUTE_PATH_PREFIX;
16369
16138
  exports.LOG_REVIEW_STATUS_ROUTE = LOG_REVIEW_STATUS_ROUTE;
16370
16139
  exports.LOG_ROUTE_PATH = LOG_ROUTE_PATH;
@@ -16388,7 +16157,6 @@ exports.PopSuccessMark = PopSuccessMark;
16388
16157
  exports.RadioButton = RadioButton;
16389
16158
  exports.ReactKitErrorCode = ReactKitErrorCode$1;
16390
16159
  exports.SimpleDateChooser = SimpleDateChooser;
16391
- exports.SimpleTimeChooser = SimpleTimeChooser;
16392
16160
  exports.TabBox = TabBox;
16393
16161
  exports.ToggleSwitch = ToggleSwitch;
16394
16162
  exports.Tooltip = Tooltip;
@@ -16400,6 +16168,7 @@ exports.avg = avg;
16400
16168
  exports.canReviewLogs = canReviewLogs;
16401
16169
  exports.capitalize = capitalize;
16402
16170
  exports.ceilToNumDecimals = ceilToNumDecimals;
16171
+ exports.cloneDeep = cloneDeep;
16403
16172
  exports.combineClassNames = combineClassNames;
16404
16173
  exports.compareArraysByProp = compareArraysByProp;
16405
16174
  exports.confirm = confirm;