dce-reactkit 4.0.2-beta-simple-date-chooser.1 → 4.1.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 (38) hide show
  1. package/dist/cjs/index.js +662 -771
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/types/components/Pagination.d.ts +13 -0
  4. package/dist/cjs/types/constants/LOG_REVIEW_GET_LOGS_ROUTE.d.ts +6 -0
  5. package/dist/cjs/types/helpers/cloneDeep.d.ts +8 -0
  6. package/dist/cjs/types/index.d.ts +4 -1
  7. package/dist/cjs/types/types/LogReviewerFilterState/ActionErrorFilterState.d.ts +17 -0
  8. package/dist/cjs/types/types/LogReviewerFilterState/AdvancedFilterState.d.ts +21 -0
  9. package/dist/cjs/types/types/LogReviewerFilterState/ContextFilterState.d.ts +10 -0
  10. package/dist/cjs/types/types/LogReviewerFilterState/DateFilterState.d.ts +17 -0
  11. package/dist/cjs/types/types/LogReviewerFilterState/TagFilterState.d.ts +8 -0
  12. package/dist/cjs/types/types/LogReviewerFilterState/index.d.ts +17 -0
  13. package/dist/esm/index.js +661 -773
  14. package/dist/esm/index.js.map +1 -1
  15. package/dist/esm/types/components/Pagination.d.ts +13 -0
  16. package/dist/esm/types/constants/LOG_REVIEW_GET_LOGS_ROUTE.d.ts +6 -0
  17. package/dist/esm/types/helpers/cloneDeep.d.ts +8 -0
  18. package/dist/esm/types/index.d.ts +4 -1
  19. package/dist/esm/types/types/LogReviewerFilterState/ActionErrorFilterState.d.ts +17 -0
  20. package/dist/esm/types/types/LogReviewerFilterState/AdvancedFilterState.d.ts +21 -0
  21. package/dist/esm/types/types/LogReviewerFilterState/ContextFilterState.d.ts +10 -0
  22. package/dist/esm/types/types/LogReviewerFilterState/DateFilterState.d.ts +17 -0
  23. package/dist/esm/types/types/LogReviewerFilterState/TagFilterState.d.ts +8 -0
  24. package/dist/esm/types/types/LogReviewerFilterState/index.d.ts +17 -0
  25. package/dist/index.d.ts +98 -1
  26. package/package.json +2 -1
  27. package/src/components/LogReviewer.tsx +1133 -1307
  28. package/src/components/Pagination.tsx +172 -0
  29. package/src/components/SimpleDateChooser.tsx +1 -1
  30. package/src/constants/LOG_REVIEW_GET_LOGS_ROUTE.ts +9 -0
  31. package/src/helpers/cloneDeep.ts +11 -0
  32. package/src/index.ts +6 -0
  33. package/src/types/LogReviewerFilterState/ActionErrorFilterState.ts +24 -0
  34. package/src/types/LogReviewerFilterState/AdvancedFilterState.ts +36 -0
  35. package/src/types/LogReviewerFilterState/ContextFilterState.ts +16 -0
  36. package/src/types/LogReviewerFilterState/DateFilterState.ts +26 -0
  37. package/src/types/LogReviewerFilterState/TagFilterState.ts +9 -0
  38. package/src/types/LogReviewerFilterState/index.ts +24 -0
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.
@@ -3793,10 +3795,10 @@ const getHumanReadableDate = (dateOrTimestamp) => {
3793
3795
  };
3794
3796
 
3795
3797
  /**
3796
- * Path of the route for storing client-side logs
3798
+ * Path of the route for getting logs for log review
3797
3799
  * @author Gabe Abrams
3798
3800
  */
3799
- const LOG_REVIEW_ROUTE_PATH_PREFIX = `/admin${ROUTE_PATH_PREFIX}/logs`;
3801
+ const LOG_REVIEW_GET_LOGS_ROUTE = `/admin${ROUTE_PATH_PREFIX}/logs`;
3800
3802
 
3801
3803
  /**
3802
3804
  * Source of a log event
@@ -4401,6 +4403,71 @@ const IntelliTable = (props) => {
4401
4403
  } }, table)));
4402
4404
  };
4403
4405
 
4406
+ // Import React
4407
+ /*------------------------------------------------------------------------*/
4408
+ /* ------------------------------ Component ----------------------------- */
4409
+ /*------------------------------------------------------------------------*/
4410
+ const Pagination = (props) => {
4411
+ /*------------------------------------------------------------------------*/
4412
+ /* -------------------------------- Setup ------------------------------- */
4413
+ /*------------------------------------------------------------------------*/
4414
+ /* -------------- Props ------------- */
4415
+ // Destructure props
4416
+ const { currentPage, numPages, loading = false, onPageChanged, } = props;
4417
+ /*------------------------------------------------------------------------*/
4418
+ /* ------------------------------- Render ------------------------------- */
4419
+ /*------------------------------------------------------------------------*/
4420
+ // Compute pages to display
4421
+ const pages = [];
4422
+ const delta = 2; // how many pages to show on either side of current
4423
+ let start = Math.max(1, currentPage - delta);
4424
+ let end = Math.min(numPages, currentPage + delta);
4425
+ // If we are too close to the beginning or end, shift the window.
4426
+ if (currentPage - delta < 1) {
4427
+ end = Math.min(numPages, end + (1 - (currentPage - delta)));
4428
+ }
4429
+ if (currentPage + delta > numPages) {
4430
+ start = Math.max(1, start - ((currentPage + delta) - numPages));
4431
+ }
4432
+ for (let i = start; i <= end; i++) {
4433
+ pages.push(i);
4434
+ }
4435
+ // Render
4436
+ return (React__default["default"].createElement("nav", { "aria-label": "Page navigation for logs", className: "mt-3" },
4437
+ React__default["default"].createElement("ul", { className: "pagination justify-content-center" },
4438
+ React__default["default"].createElement("li", { className: `page-item ${(currentPage <= 1 || loading) ? 'disabled' : ''}` },
4439
+ 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" },
4440
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faArrowLeft }),
4441
+ ' ',
4442
+ "Prev")),
4443
+ (currentPage > 3 && pages[0] !== 1) && (React__default["default"].createElement("li", { className: "page-item" },
4444
+ React__default["default"].createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(1); }, disabled: loading, "aria-label": "Go to page 1" }, "1"))),
4445
+ currentPage > 4 && (React__default["default"].createElement("li", { className: "page-item disabled" },
4446
+ React__default["default"].createElement("span", { className: "page-link" }, "..."))),
4447
+ pages.map((pageNum) => {
4448
+ return (React__default["default"].createElement("li", { key: pageNum, className: `page-item ${pageNum === currentPage ? 'active' : ''}` },
4449
+ React__default["default"].createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(pageNum); }, disabled: loading, "aria-label": `Go to page ${pageNum}` }, pageNum)));
4450
+ }),
4451
+ currentPage < numPages - 3 && (React__default["default"].createElement("li", { className: "page-item disabled" },
4452
+ React__default["default"].createElement("span", { className: "page-link" }, "..."))),
4453
+ (currentPage < numPages - 2
4454
+ && pages[pages.length - 1] !== numPages) && (React__default["default"].createElement("li", { className: "page-item" },
4455
+ React__default["default"].createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(numPages); }, disabled: loading, "aria-label": "Go to last page" }, numPages))),
4456
+ React__default["default"].createElement("li", { className: `page-item ${(currentPage >= numPages || loading) ? 'disabled' : ''}` },
4457
+ 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" },
4458
+ "Next",
4459
+ ' ',
4460
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faArrowRight }))))));
4461
+ };
4462
+
4463
+ /**
4464
+ * Deeply clones an object
4465
+ * @author Yuen Ler Chow
4466
+ * @param obj the object to clone
4467
+ * @returns a deep clone of the object
4468
+ */
4469
+ const cloneDeep = clone__default["default"];
4470
+
4404
4471
  /**
4405
4472
  * Log reviewer panel that allows users (must be approved admins) to
4406
4473
  * review logs written by dce-reactkit
@@ -4756,7 +4823,7 @@ var ActionType$6;
4756
4823
  (function (ActionType) {
4757
4824
  // Show the loading bar
4758
4825
  ActionType["StartLoading"] = "start-loading";
4759
- // Finish loading one or more months of logs
4826
+ // Finish loading logs
4760
4827
  ActionType["FinishLoading"] = "finish-loading";
4761
4828
  // Reset filters to initial values
4762
4829
  ActionType["ResetFilters"] = "reset-filters";
@@ -4774,6 +4841,14 @@ var ActionType$6;
4774
4841
  ActionType["UpdateActionErrorFilterState"] = "update-action-error-filter-state";
4775
4842
  // Update the advanced filter state
4776
4843
  ActionType["UpdateAdvancedFilterState"] = "update-advanced-filter-state";
4844
+ // Set has another page
4845
+ ActionType["SetHasAnotherPage"] = "set-has-another-page";
4846
+ // Set the number of pages
4847
+ ActionType["SetNumPages"] = "set-num-pages";
4848
+ // Set page number
4849
+ ActionType["SetPageNumber"] = "set-page-number";
4850
+ // Reset user made filter change indicator
4851
+ ActionType["ResetUserMadeFilterChange"] = "reset-user-made-filter-change";
4777
4852
  })(ActionType$6 || (ActionType$6 = {}));
4778
4853
  /**
4779
4854
  * Reducer that executes actions
@@ -4787,7 +4862,7 @@ const reducer$7 = (state, action) => {
4787
4862
  return Object.assign(Object.assign({}, state), { loading: true });
4788
4863
  }
4789
4864
  case ActionType$6.FinishLoading: {
4790
- return Object.assign(Object.assign({}, state), { loading: false, logMap: action.logMap });
4865
+ return Object.assign(Object.assign({}, state), { loading: false, logs: action.logs });
4791
4866
  }
4792
4867
  case ActionType$6.ToggleFilterDrawer: {
4793
4868
  return Object.assign(Object.assign({}, state), { expandedFilterDrawer: (state.expandedFilterDrawer === action.filterDrawer
@@ -4798,22 +4873,34 @@ const reducer$7 = (state, action) => {
4798
4873
  return Object.assign(Object.assign({}, state), { expandedFilterDrawer: undefined });
4799
4874
  }
4800
4875
  case ActionType$6.ResetFilters: {
4801
- return Object.assign(Object.assign({}, state), { dateFilterState: action.initDateFilterState, contextFilterState: action.initContextFilterState, tagFilterState: action.initTagFilterState, actionErrorFilterState: action.initActionErrorFilterState, advancedFilterState: action.initAdvancedFilterState });
4876
+ return Object.assign(Object.assign({}, state), { pendingDateFilterState: action.initDateFilterState, pendingContextFilterState: action.initContextFilterState, pendingTagFilterState: action.initTagFilterState, pendingActionErrorFilterState: action.initActionErrorFilterState, pendingAdvancedFilterState: action.initAdvancedFilterState, pageNumber: 1 });
4802
4877
  }
4803
4878
  case ActionType$6.UpdateDateFilterState: {
4804
- return Object.assign(Object.assign({}, state), { dateFilterState: action.dateFilterState });
4879
+ return Object.assign(Object.assign({}, state), { pendingDateFilterState: action.dateFilterState, userMadeFilterChange: true });
4805
4880
  }
4806
4881
  case ActionType$6.UpdateContextFilterState: {
4807
- return Object.assign(Object.assign({}, state), { contextFilterState: action.contextFilterState });
4882
+ return Object.assign(Object.assign({}, state), { pendingContextFilterState: action.contextFilterState, userMadeFilterChange: true });
4808
4883
  }
4809
4884
  case ActionType$6.UpdateTagFilterState: {
4810
- return Object.assign(Object.assign({}, state), { tagFilterState: action.tagFilterState });
4885
+ return Object.assign(Object.assign({}, state), { pendingTagFilterState: action.tagFilterState, userMadeFilterChange: true });
4811
4886
  }
4812
4887
  case ActionType$6.UpdateActionErrorFilterState: {
4813
- return Object.assign(Object.assign({}, state), { actionErrorFilterState: action.actionErrorFilterState });
4888
+ return Object.assign(Object.assign({}, state), { pendingActionErrorFilterState: action.actionErrorFilterState, userMadeFilterChange: true });
4814
4889
  }
4815
4890
  case ActionType$6.UpdateAdvancedFilterState: {
4816
- return Object.assign(Object.assign({}, state), { advancedFilterState: action.advancedFilterState });
4891
+ return Object.assign(Object.assign({}, state), { pendingAdvancedFilterState: action.advancedFilterState, userMadeFilterChange: true });
4892
+ }
4893
+ case ActionType$6.SetHasAnotherPage: {
4894
+ return Object.assign(Object.assign({}, state), { hasAnotherPage: action.hasAnotherPage });
4895
+ }
4896
+ case ActionType$6.SetNumPages: {
4897
+ return Object.assign(Object.assign({}, state), { numPages: action.numPages });
4898
+ }
4899
+ case ActionType$6.SetPageNumber: {
4900
+ return Object.assign(Object.assign({}, state), { pageNumber: action.pageNumber });
4901
+ }
4902
+ case ActionType$6.ResetUserMadeFilterChange: {
4903
+ return Object.assign(Object.assign({}, state), { userMadeFilterChange: false });
4817
4904
  }
4818
4905
  default: {
4819
4906
  return state;
@@ -4884,7 +4971,7 @@ const LogReviewer = (props) => {
4884
4971
  else {
4885
4972
  // Case: subcontexts exist
4886
4973
  initContextFilterState[context] = {};
4887
- Object.values(contextMap[context]).forEach((subcontext) => {
4974
+ Object.keys(contextMap[context]).forEach((subcontext) => {
4888
4975
  // Skip self ("_")
4889
4976
  if (subcontext === '_') {
4890
4977
  return;
@@ -4932,125 +5019,105 @@ const LogReviewer = (props) => {
4932
5019
  // Initial state
4933
5020
  const initialState = {
4934
5021
  loading: true,
4935
- logMap: {},
5022
+ logs: [],
4936
5023
  expandedFilterDrawer: undefined,
4937
- dateFilterState: initDateFilterState,
4938
- contextFilterState: initContextFilterState,
4939
- tagFilterState: initTagFilterState,
4940
- actionErrorFilterState: initActionErrorFilterState,
4941
- advancedFilterState: initAdvancedFilterState,
5024
+ pendingDateFilterState: initDateFilterState,
5025
+ pendingContextFilterState: initContextFilterState,
5026
+ pendingTagFilterState: initTagFilterState,
5027
+ pendingActionErrorFilterState: initActionErrorFilterState,
5028
+ pendingAdvancedFilterState: initAdvancedFilterState,
5029
+ pageNumber: 1,
5030
+ hasAnotherPage: false,
5031
+ numPages: 1,
5032
+ userMadeFilterChange: false,
4942
5033
  };
4943
5034
  // Initialize state
4944
5035
  const [state, dispatch] = React.useReducer(reducer$7, initialState);
4945
5036
  // Destructure common state
4946
- const { loading, logMap, expandedFilterDrawer, dateFilterState, contextFilterState, tagFilterState, actionErrorFilterState, advancedFilterState, } = state;
5037
+ const { loading, logs, expandedFilterDrawer, pendingDateFilterState, pendingContextFilterState, pendingTagFilterState, pendingActionErrorFilterState, pendingAdvancedFilterState, pageNumber, numPages, userMadeFilterChange, } = state;
5038
+ /* -------------- Refs -------------- */
5039
+ // Initialize refs
5040
+ const activeFiltersRef = React.useRef({
5041
+ dateFilterState: JSON.parse(JSON.stringify(pendingDateFilterState)),
5042
+ contextFilterState: JSON.parse(JSON.stringify(pendingContextFilterState)),
5043
+ tagFilterState: JSON.parse(JSON.stringify(pendingTagFilterState)),
5044
+ actionErrorFilterState: JSON.parse(JSON.stringify(pendingActionErrorFilterState)),
5045
+ advancedFilterState: JSON.parse(JSON.stringify(pendingAdvancedFilterState)),
5046
+ });
4947
5047
  /*------------------------------------------------------------------------*/
4948
5048
  /* ------------------------- Component Functions ------------------------ */
4949
5049
  /*------------------------------------------------------------------------*/
4950
5050
  /**
4951
- * Get the list of year/month combos that need to be loaded given a new
4952
- * start or end date and the existing logMap
4953
- * @author Gabe Abrams
4954
- * @param newDateFilterState the new date filter state
4955
- * @returns list of year/month combos that need to be loaded
4956
- */
4957
- const listMonthsToLoad = (newDateFilterState) => {
4958
- // List of year/month combos that need to be loaded
4959
- const toLoad = [];
4960
- // Loop through dates
4961
- let { year, month } = newDateFilterState.startDate;
4962
- while (
4963
- // Earlier year
4964
- (year < newDateFilterState.endDate.year)
4965
- // Current year but included month
4966
- || (year === newDateFilterState.endDate.year
4967
- && month <= newDateFilterState.endDate.month)) {
4968
- // Add to list if not already loaded
4969
- if (!logMap[year]
4970
- || !logMap[year][month]) {
4971
- toLoad.push({
4972
- year,
4973
- month,
4974
- });
4975
- }
4976
- // Increment
4977
- month += 1;
4978
- if (month > 12) {
4979
- month -= 12;
4980
- year += 1;
4981
- }
4982
- }
4983
- // Return
4984
- return toLoad;
4985
- };
4986
- /**
4987
- * Handle updated start/end dates (updates state, loads if necessary)
4988
- * @author Gabe Abrams
4989
- * @param newDateFilterState the new date filter state
5051
+ * Fetch logs from the server based on current filters
5052
+ * @author Yuen Ler Chow
5053
+ * @param opts object containing all arguments
5054
+ * @param opts.filters the filters to apply
5055
+ * @param opts.pageNum the page number to fetch
5056
+ * @param opts.filtersChanged if true, the filters have changed
4990
5057
  */
4991
- const handleDateRangeUpdated = (newDateFilterState) => __awaiter(void 0, void 0, void 0, function* () {
4992
- // Update state
4993
- dispatch({
4994
- type: ActionType$6.UpdateDateFilterState,
4995
- dateFilterState: newDateFilterState,
4996
- });
4997
- // Check which year/month combos we need to load
4998
- const toLoad = listMonthsToLoad(newDateFilterState);
4999
- // If nothing to load, finished
5000
- if (toLoad.length === 0) {
5001
- return;
5002
- }
5003
- // Start loading
5058
+ const fetchLogs = (opts) => __awaiter(void 0, void 0, void 0, function* () {
5059
+ const { filters, pageNum, filtersChanged, } = opts;
5004
5060
  dispatch({
5005
5061
  type: ActionType$6.StartLoading,
5006
5062
  });
5007
- // Load required months
5008
5063
  try {
5009
- for (let i = 0; i < toLoad.length; i++) {
5010
- // Destructure
5011
- const { year, month } = toLoad[i];
5012
- // Load
5013
- let logs = [];
5014
- let pageNumber = 1;
5015
- let hasAnotherPage = true;
5016
- while (hasAnotherPage) {
5017
- const response = yield visitServerEndpoint({
5018
- path: `${LOG_REVIEW_ROUTE_PATH_PREFIX}/years/${year}/months/${month}`,
5019
- method: 'GET',
5020
- params: {
5021
- pageNumber,
5022
- },
5023
- });
5024
- logs = logs.concat(response.items);
5025
- hasAnotherPage = response.hasAnotherPage;
5026
- pageNumber += 1;
5027
- }
5028
- // Add to map
5029
- if (!logMap[year]) {
5030
- logMap[year] = {};
5031
- }
5032
- logMap[year][month] = logs;
5064
+ // Send filters to the server
5065
+ let fetchedLogs = [];
5066
+ // Get logs from server
5067
+ const response = yield visitServerEndpoint({
5068
+ path: LOG_REVIEW_GET_LOGS_ROUTE,
5069
+ method: 'GET',
5070
+ params: {
5071
+ pageNumber: pageNum,
5072
+ filters,
5073
+ countDocuments: filtersChanged,
5074
+ },
5075
+ });
5076
+ fetchedLogs = fetchedLogs.concat(response.items);
5077
+ dispatch({
5078
+ type: ActionType$6.SetHasAnotherPage,
5079
+ hasAnotherPage: response.hasAnotherPage,
5080
+ });
5081
+ if (filtersChanged && response.numPages !== undefined) {
5082
+ dispatch({
5083
+ type: ActionType$6.SetNumPages,
5084
+ numPages: response.numPages,
5085
+ });
5033
5086
  }
5087
+ // Update logs in state
5088
+ dispatch({
5089
+ type: ActionType$6.FinishLoading,
5090
+ logs: fetchedLogs,
5091
+ });
5092
+ // Update page number
5093
+ dispatch({
5094
+ type: ActionType$6.SetPageNumber,
5095
+ pageNumber: pageNum,
5096
+ });
5034
5097
  }
5035
5098
  catch (err) {
5036
5099
  return showFatalError(err);
5037
5100
  }
5038
- // Finish loading
5039
- dispatch({
5040
- type: ActionType$6.FinishLoading,
5041
- logMap,
5042
- });
5043
5101
  });
5044
5102
  /*------------------------------------------------------------------------*/
5045
5103
  /* ------------------------- Lifecycle Functions ------------------------ */
5046
5104
  /*------------------------------------------------------------------------*/
5047
5105
  /**
5048
- * Mount
5049
- * @author Gabe Abrams
5106
+ * Fetch logs on mount
5107
+ * @author Yuen Ler Chow
5050
5108
  */
5051
5109
  React.useEffect(() => {
5052
- // Perform initial load
5053
- handleDateRangeUpdated(dateFilterState);
5110
+ fetchLogs({
5111
+ filters: {
5112
+ dateFilterState: pendingDateFilterState,
5113
+ contextFilterState: pendingContextFilterState,
5114
+ tagFilterState: pendingTagFilterState,
5115
+ actionErrorFilterState: pendingActionErrorFilterState,
5116
+ advancedFilterState: pendingAdvancedFilterState,
5117
+ },
5118
+ pageNum: 1,
5119
+ filtersChanged: true,
5120
+ });
5054
5121
  }, []);
5055
5122
  /*------------------------------------------------------------------------*/
5056
5123
  /* ------------------------------- Render ------------------------------- */
@@ -5058,718 +5125,534 @@ const LogReviewer = (props) => {
5058
5125
  /*----------------------------------------*/
5059
5126
  /* --------------- Main UI -------------- */
5060
5127
  /*----------------------------------------*/
5061
- // Body that will be filled with the contents of the panel
5062
- let body;
5063
- /* ------------- Loading ------------ */
5064
- if (loading) {
5065
- body = (React__default["default"].createElement("div", { className: "text-center p-5" },
5066
- React__default["default"].createElement(LoadingSpinner, null)));
5067
- }
5068
- /* ------------ Review UI ----------- */
5069
- if (!loading) {
5070
- /*----------------------------------------*/
5071
- /* --------------- Filters -------------- */
5072
- /*----------------------------------------*/
5073
- // Filter toggle
5074
- const filterToggles = (React__default["default"].createElement("div", { className: "LogReviewer-filter-toggles" },
5075
- React__default["default"].createElement("h3", { className: "m-0" }, "Filters:"),
5076
- React__default["default"].createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
5077
- 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: () => {
5078
- dispatch({
5079
- type: ActionType$6.ToggleFilterDrawer,
5080
- filterDrawer: FilterDrawer.Date,
5081
- });
5082
- } },
5083
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCalendar, className: "me-2" }),
5084
- "Date"),
5085
- 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: () => {
5086
- dispatch({
5087
- type: ActionType$6.ToggleFilterDrawer,
5088
- filterDrawer: FilterDrawer.Context,
5089
- });
5090
- } },
5091
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "me-2" }),
5092
- "Context"),
5093
- (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: () => {
5094
- dispatch({
5095
- type: ActionType$6.ToggleFilterDrawer,
5096
- filterDrawer: FilterDrawer.Tag,
5097
- });
5098
- } },
5099
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTag, className: "me-2" }),
5100
- "Tag")),
5101
- 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: () => {
5102
- dispatch({
5103
- type: ActionType$6.ToggleFilterDrawer,
5104
- filterDrawer: FilterDrawer.Action,
5105
- });
5106
- } },
5107
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faHammer, className: "me-2" }),
5108
- "Action"),
5109
- 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: () => {
5128
+ /*----------------------------------------*/
5129
+ /* ------------ Pagination -------------- */
5130
+ /*----------------------------------------*/
5131
+ const paginationControls = logs.length > 0 && (React__default["default"].createElement(Pagination, { currentPage: pageNumber, numPages: numPages, loading: loading, onPageChanged: (targetPage) => {
5132
+ const { current: activeFilters } = activeFiltersRef;
5133
+ fetchLogs({
5134
+ filters: {
5135
+ dateFilterState: activeFilters.dateFilterState,
5136
+ contextFilterState: activeFilters.contextFilterState,
5137
+ tagFilterState: activeFilters.tagFilterState,
5138
+ actionErrorFilterState: activeFilters.actionErrorFilterState,
5139
+ advancedFilterState: activeFilters.advancedFilterState,
5140
+ },
5141
+ pageNum: targetPage,
5142
+ filtersChanged: false,
5143
+ });
5144
+ } }));
5145
+ /*----------------------------------------*/
5146
+ /* --------------- Filters -------------- */
5147
+ /*----------------------------------------*/
5148
+ // Filter toggle
5149
+ const filterToggles = (React__default["default"].createElement("div", { className: "LogReviewer-filter-toggles" },
5150
+ React__default["default"].createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
5151
+ 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: () => {
5152
+ dispatch({
5153
+ type: ActionType$6.ToggleFilterDrawer,
5154
+ filterDrawer: FilterDrawer.Date,
5155
+ });
5156
+ } },
5157
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCalendar, className: "me-2" }),
5158
+ "Date"),
5159
+ 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: () => {
5160
+ dispatch({
5161
+ type: ActionType$6.ToggleFilterDrawer,
5162
+ filterDrawer: FilterDrawer.Context,
5163
+ });
5164
+ } },
5165
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "me-2" }),
5166
+ "Context"),
5167
+ (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: () => {
5168
+ dispatch({
5169
+ type: ActionType$6.ToggleFilterDrawer,
5170
+ filterDrawer: FilterDrawer.Tag,
5171
+ });
5172
+ } },
5173
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTag, className: "me-2" }),
5174
+ "Tag")),
5175
+ 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: () => {
5176
+ dispatch({
5177
+ type: ActionType$6.ToggleFilterDrawer,
5178
+ filterDrawer: FilterDrawer.Action,
5179
+ });
5180
+ } },
5181
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faHammer, className: "me-2" }),
5182
+ "Action"),
5183
+ 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: () => {
5184
+ dispatch({
5185
+ type: ActionType$6.ToggleFilterDrawer,
5186
+ filterDrawer: FilterDrawer.Advanced,
5187
+ });
5188
+ } },
5189
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faList, className: "me-2" }),
5190
+ "Advanced"),
5191
+ React__default["default"].createElement("button", { type: "button", id: "LogReviewer-reset-filters-button", className: "btn btn-light", "aria-label": "reset filters", onClick: () => {
5192
+ // Save active filters
5193
+ // Deep clone the initial filter states to avoid any reference issues
5194
+ // if the filter states are modified later
5195
+ activeFiltersRef.current = cloneDeep({
5196
+ dateFilterState: initDateFilterState,
5197
+ contextFilterState: initContextFilterState,
5198
+ tagFilterState: initTagFilterState,
5199
+ actionErrorFilterState: initActionErrorFilterState,
5200
+ advancedFilterState: initAdvancedFilterState,
5201
+ });
5202
+ fetchLogs({
5203
+ filters: {
5204
+ dateFilterState: initDateFilterState,
5205
+ contextFilterState: initContextFilterState,
5206
+ tagFilterState: initTagFilterState,
5207
+ actionErrorFilterState: initActionErrorFilterState,
5208
+ advancedFilterState: initAdvancedFilterState,
5209
+ },
5210
+ pageNum: 1,
5211
+ filtersChanged: true,
5212
+ });
5213
+ dispatch({
5214
+ type: ActionType$6.ResetFilters,
5215
+ initActionErrorFilterState,
5216
+ initAdvancedFilterState,
5217
+ initContextFilterState,
5218
+ initDateFilterState,
5219
+ initTagFilterState,
5220
+ });
5221
+ dispatch({
5222
+ type: ActionType$6.HideFilterDrawer,
5223
+ });
5224
+ } },
5225
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes }),
5226
+ ' ',
5227
+ "Reset"),
5228
+ userMadeFilterChange && (React__default["default"].createElement("button", { type: "button", id: "LogReviewer-submit-filters-button", className: "btn btn-primary ms-2", "aria-label": "submit filters", onClick: () => {
5229
+ dispatch({
5230
+ type: ActionType$6.HideFilterDrawer,
5231
+ });
5232
+ // Reset user made filter change indicator
5233
+ dispatch({
5234
+ type: ActionType$6.ResetUserMadeFilterChange,
5235
+ });
5236
+ // Save active filters
5237
+ // Deep clone the pending filter states to avoid any reference issues
5238
+ // if the filter states are modified later
5239
+ activeFiltersRef.current = cloneDeep({
5240
+ dateFilterState: pendingDateFilterState,
5241
+ contextFilterState: pendingContextFilterState,
5242
+ tagFilterState: pendingTagFilterState,
5243
+ actionErrorFilterState: pendingActionErrorFilterState,
5244
+ advancedFilterState: pendingAdvancedFilterState,
5245
+ });
5246
+ fetchLogs({
5247
+ filters: {
5248
+ dateFilterState: pendingDateFilterState,
5249
+ contextFilterState: pendingContextFilterState,
5250
+ tagFilterState: pendingTagFilterState,
5251
+ actionErrorFilterState: pendingActionErrorFilterState,
5252
+ advancedFilterState: pendingAdvancedFilterState,
5253
+ },
5254
+ pageNum: 1,
5255
+ filtersChanged: true,
5256
+ });
5257
+ } },
5258
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faSearch }),
5259
+ ' ',
5260
+ "Apply Filters")))));
5261
+ // Filter drawer
5262
+ let filterDrawer;
5263
+ if (expandedFilterDrawer) {
5264
+ if (expandedFilterDrawer === FilterDrawer.Date) {
5265
+ filterDrawer = (React__default["default"].createElement(TabBox, { title: "Date" },
5266
+ 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, dontAllowFuture: true, numMonthsToShow: 36, onChange: (month, day, year) => {
5267
+ const newDateFilterState = Object.assign(Object.assign({}, pendingDateFilterState), { startDate: { month, day, year } });
5110
5268
  dispatch({
5111
- type: ActionType$6.ToggleFilterDrawer,
5112
- filterDrawer: FilterDrawer.Advanced,
5269
+ type: ActionType$6.UpdateDateFilterState,
5270
+ dateFilterState: newDateFilterState,
5113
5271
  });
5114
- } },
5115
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faList, className: "me-2" }),
5116
- "Advanced"),
5117
- React__default["default"].createElement("button", { type: "button", id: "LogReviewer-reset-filters-button", className: "btn btn-light", "aria-label": "reset filters", onClick: () => {
5272
+ } }),
5273
+ ' ',
5274
+ "to",
5275
+ ' ',
5276
+ 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, dontAllowFuture: true, numMonthsToShow: 12, onChange: (month, day, year) => {
5277
+ if (year < pendingDateFilterState.startDate.year
5278
+ || (year === pendingDateFilterState.startDate.year
5279
+ && month < pendingDateFilterState.startDate.month)
5280
+ || (year === pendingDateFilterState.startDate.year
5281
+ && month === pendingDateFilterState.startDate.month
5282
+ && day < pendingDateFilterState.startDate.day)) {
5283
+ return alert('Invalid End Date', 'The end date cannot be before the start date.');
5284
+ }
5285
+ const newDateFilterState = Object.assign(Object.assign({}, pendingDateFilterState), { endDate: { month, day, year } });
5118
5286
  dispatch({
5119
- type: ActionType$6.ResetFilters,
5120
- initActionErrorFilterState,
5121
- initAdvancedFilterState,
5122
- initContextFilterState,
5123
- initDateFilterState,
5124
- initTagFilterState,
5287
+ type: ActionType$6.UpdateDateFilterState,
5288
+ dateFilterState: newDateFilterState,
5125
5289
  });
5126
- } },
5127
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes }),
5128
- ' ',
5129
- "Reset"))));
5130
- // Filter drawer
5131
- let filterDrawer;
5132
- if (expandedFilterDrawer) {
5133
- if (expandedFilterDrawer === FilterDrawer.Date) {
5134
- filterDrawer = (React__default["default"].createElement(TabBox, { title: "Date" },
5135
- 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) => {
5136
- dateFilterState.startDate = { month, day, year };
5137
- handleDateRangeUpdated(dateFilterState);
5138
- } }),
5139
- ' ',
5140
- "to",
5141
- ' ',
5142
- 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) => {
5143
- if (year < dateFilterState.startDate.year
5144
- || (year === dateFilterState.startDate.year
5145
- && month < dateFilterState.startDate.month)
5146
- || (year === dateFilterState.startDate.year
5147
- && month === dateFilterState.startDate.month
5148
- && day < dateFilterState.startDate.day)) {
5149
- return alert('Invalid Start Date', 'The start date cannot be before the end date.');
5150
- }
5151
- dateFilterState.endDate = { month, day, year };
5152
- handleDateRangeUpdated(dateFilterState);
5153
- } })));
5154
- }
5155
- else if (expandedFilterDrawer === FilterDrawer.Context) {
5156
- // Create item picker items
5157
- const builtInPickableItem = {
5158
- id: 'built-in-contexts',
5159
- name: 'Auto-logged',
5160
- isGroup: true,
5161
- children: [],
5162
- };
5163
- const pickableItems = [];
5164
- Object.keys(contextMap)
5165
- .forEach((context) => {
5166
- const value = contextMap[context];
5167
- if (typeof value === 'string') {
5168
- // No subcategories
5169
- const item = {
5170
- id: context,
5171
- name: genHumanReadableName(context),
5172
- isGroup: false,
5173
- checked: !!contextFilterState[context],
5174
- };
5175
- // Add built-in items to its own folder
5176
- const isBuiltIn = context in LogBuiltInMetadata.Context;
5177
- if (isBuiltIn) {
5178
- // Add to built-in pickable item
5179
- builtInPickableItem.children.push(item);
5180
- }
5181
- else {
5182
- // Add to pickable items list
5183
- pickableItems.push(item);
5184
- }
5185
- return;
5186
- }
5187
- // Has subcategories
5188
- const children = (Object.keys(value)
5189
- // Remove parent name
5190
- .filter((subcontext) => {
5191
- return subcontext !== '_';
5192
- })
5193
- // Create child pickable items
5194
- .map((subcontext) => {
5195
- return {
5196
- id: subcontext,
5197
- name: genHumanReadableName(subcontext),
5198
- isGroup: false,
5199
- checked: contextFilterState[context][subcontext],
5200
- };
5201
- }));
5290
+ } })));
5291
+ }
5292
+ else if (expandedFilterDrawer === FilterDrawer.Context) {
5293
+ // Create item picker items
5294
+ const builtInPickableItem = {
5295
+ id: 'built-in-contexts',
5296
+ name: 'Auto-logged',
5297
+ isGroup: true,
5298
+ children: [],
5299
+ };
5300
+ const pickableItems = [];
5301
+ Object.keys(contextMap)
5302
+ .forEach((context) => {
5303
+ const value = contextMap[context];
5304
+ if (typeof value === 'string') {
5305
+ // No subcategories
5202
5306
  const item = {
5203
5307
  id: context,
5204
5308
  name: genHumanReadableName(context),
5205
- isGroup: true,
5206
- children,
5309
+ isGroup: false,
5310
+ checked: !!pendingContextFilterState[context],
5207
5311
  };
5208
- pickableItems.push(item);
5209
- });
5210
- // Add built-in contexts to end ofl ist
5211
- pickableItems.push(builtInPickableItem);
5212
- // Create filter UI
5213
- filterDrawer = (React__default["default"].createElement(ItemPicker, { title: "Context", items: pickableItems, onChanged: (updatedItems) => {
5214
- // Update our state
5215
- updatedItems.forEach((pickableItem) => {
5216
- if (pickableItem.isGroup) {
5217
- // Has subcontexts
5218
- if (pickableItem.id === 'built-in-contexts') {
5219
- // Built-in
5220
- // Treat as if these were top-level contexts
5221
- pickableItem.children.forEach((subcontextItem) => {
5222
- contextFilterState[subcontextItem.id] = ('checked' in subcontextItem
5223
- && subcontextItem.checked);
5224
- });
5225
- }
5226
- else {
5227
- // Not built-in
5228
- pickableItem.children.forEach((subcontextItem) => {
5229
- if (!subcontextItem.isGroup) {
5230
- contextFilterState[pickableItem.id][subcontextItem.id] = (subcontextItem.checked);
5231
- }
5232
- });
5233
- }
5312
+ // Add built-in items to its own folder
5313
+ const isBuiltIn = context in LogBuiltInMetadata.Context;
5314
+ if (isBuiltIn) {
5315
+ // Add to built-in pickable item
5316
+ builtInPickableItem.children.push(item);
5317
+ }
5318
+ else {
5319
+ // Add to pickable items list
5320
+ pickableItems.push(item);
5321
+ }
5322
+ return;
5323
+ }
5324
+ // Has subcategories
5325
+ const children = (Object.keys(value)
5326
+ // Remove parent name
5327
+ .filter((subcontext) => {
5328
+ return (subcontext !== '_');
5329
+ })
5330
+ // Create child pickable items
5331
+ .map((subcontext) => {
5332
+ return {
5333
+ id: subcontext,
5334
+ name: genHumanReadableName(subcontext),
5335
+ isGroup: false,
5336
+ checked: pendingContextFilterState[context][subcontext],
5337
+ };
5338
+ }));
5339
+ const item = {
5340
+ id: context,
5341
+ name: genHumanReadableName(context),
5342
+ isGroup: true,
5343
+ children,
5344
+ };
5345
+ pickableItems.push(item);
5346
+ });
5347
+ // Add built-in contexts to end of list
5348
+ pickableItems.push(builtInPickableItem);
5349
+ // Create filter UI
5350
+ filterDrawer = (React__default["default"].createElement(ItemPicker, { title: "Context", items: pickableItems, onChanged: (updatedItems) => {
5351
+ // Update our state
5352
+ const newContextFilterState = Object.assign({}, pendingContextFilterState);
5353
+ updatedItems.forEach((pickableItem) => {
5354
+ if (pickableItem.isGroup) {
5355
+ // Has subcontexts
5356
+ if (pickableItem.id === 'built-in-contexts') {
5357
+ // Built-in
5358
+ // Treat as if these were top-level contexts
5359
+ pickableItem.children.forEach((subcontextItem) => {
5360
+ newContextFilterState[subcontextItem.id] = ('checked' in subcontextItem
5361
+ && subcontextItem.checked);
5362
+ });
5234
5363
  }
5235
5364
  else {
5236
- // No subcontexts
5237
- contextFilterState[pickableItem.id] = (pickableItem.checked);
5365
+ // Not built-in
5366
+ pickableItem.children.forEach((subcontextItem) => {
5367
+ if (!subcontextItem.isGroup) {
5368
+ newContextFilterState[pickableItem.id][subcontextItem.id] = (subcontextItem.checked);
5369
+ }
5370
+ });
5238
5371
  }
5239
- });
5240
- dispatch({
5241
- type: ActionType$6.UpdateContextFilterState,
5242
- contextFilterState,
5243
- });
5244
- } }));
5245
- }
5246
- else if (expandedFilterDrawer === FilterDrawer.Tag) {
5247
- // Create filter UI
5248
- filterDrawer = (React__default["default"].createElement(TabBox, { title: "Tags" },
5249
- 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."),
5250
- React__default["default"].createElement("div", { className: "d-flex gap-1 flex-wrap" }, Object.keys((_d = LogMetadata.Tag) !== null && _d !== void 0 ? _d : {})
5251
- .map((tag) => {
5252
- const description = genHumanReadableName(tag);
5253
- 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) => {
5254
- tagFilterState[tag] = checked;
5372
+ }
5373
+ else {
5374
+ // No subcontexts
5375
+ newContextFilterState[pickableItem.id] = (pickableItem.checked);
5376
+ }
5377
+ });
5378
+ // Update state
5379
+ dispatch({
5380
+ type: ActionType$6.UpdateContextFilterState,
5381
+ contextFilterState: newContextFilterState,
5382
+ });
5383
+ } }));
5384
+ }
5385
+ else if (expandedFilterDrawer === FilterDrawer.Tag) {
5386
+ // Create filter UI
5387
+ filterDrawer = (React__default["default"].createElement(TabBox, { title: "Tags" },
5388
+ 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."),
5389
+ React__default["default"].createElement("div", { className: "d-flex gap-1 flex-wrap" }, Object.keys((_d = LogMetadata.Tag) !== null && _d !== void 0 ? _d : {})
5390
+ .map((tag) => {
5391
+ const description = genHumanReadableName(tag);
5392
+ 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) => {
5393
+ const newTagFilterState = Object.assign(Object.assign({}, pendingTagFilterState), { [tag]: checked });
5394
+ dispatch({
5395
+ type: ActionType$6.UpdateTagFilterState,
5396
+ tagFilterState: newTagFilterState,
5397
+ });
5398
+ }, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }));
5399
+ }))));
5400
+ }
5401
+ else if (expandedFilterDrawer === FilterDrawer.Action) {
5402
+ // Create filter UI
5403
+ filterDrawer = (React__default["default"].createElement(React__default["default"].Fragment, null,
5404
+ React__default["default"].createElement(TabBox, { title: "Log Type" },
5405
+ React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-all", text: "All Logs", onSelected: () => {
5406
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { type: undefined });
5407
+ dispatch({
5408
+ type: ActionType$6.UpdateActionErrorFilterState,
5409
+ actionErrorFilterState: newActionErrorFilterState,
5410
+ });
5411
+ }, ariaLabel: "show logs of all types", selected: pendingActionErrorFilterState.type === undefined, unselectedVariant: Variant$1.Light }),
5412
+ React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-action-only", text: "Action Logs Only", onSelected: () => {
5413
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { type: LogType$1.Action });
5414
+ dispatch({
5415
+ type: ActionType$6.UpdateActionErrorFilterState,
5416
+ actionErrorFilterState: newActionErrorFilterState,
5417
+ });
5418
+ }, ariaLabel: "only show action logs", selected: pendingActionErrorFilterState.type === LogType$1.Action, unselectedVariant: Variant$1.Light }),
5419
+ React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-error-only", text: "Action Error Only", onSelected: () => {
5420
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { type: LogType$1.Error });
5421
+ dispatch({
5422
+ type: ActionType$6.UpdateActionErrorFilterState,
5423
+ actionErrorFilterState: newActionErrorFilterState,
5424
+ });
5425
+ }, ariaLabel: "only show error logs", selected: pendingActionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light })),
5426
+ (pendingActionErrorFilterState.type === undefined
5427
+ || pendingActionErrorFilterState.type === LogType$1.Action) && (React__default["default"].createElement(TabBox, { title: "Action Log Details" },
5428
+ React__default["default"].createElement(ButtonInputGroup, { label: "Action", className: "mb-2", wrapButtonsAndAddGaps: true }, Object.keys(LogAction$1)
5429
+ .map((action) => {
5430
+ const description = genHumanReadableName(action);
5431
+ 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) => {
5432
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { action: Object.assign(Object.assign({}, pendingActionErrorFilterState.action), { [action]: checked }) });
5255
5433
  dispatch({
5256
- type: ActionType$6.UpdateTagFilterState,
5257
- tagFilterState,
5434
+ type: ActionType$6.UpdateActionErrorFilterState,
5435
+ actionErrorFilterState: newActionErrorFilterState,
5258
5436
  });
5259
- } }));
5260
- }))));
5261
- }
5262
- else if (expandedFilterDrawer === FilterDrawer.Action) {
5263
- // Create filter UI
5264
- filterDrawer = (React__default["default"].createElement(React__default["default"].Fragment, null,
5265
- React__default["default"].createElement(TabBox, { title: "Log Type" },
5266
- React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-all", text: "All Logs", onSelected: () => {
5267
- actionErrorFilterState.type = undefined;
5437
+ }, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }));
5438
+ })),
5439
+ React__default["default"].createElement(ButtonInputGroup, { label: "Target", wrapButtonsAndAddGaps: true }, Object.keys(targetMap)
5440
+ .map((target) => {
5441
+ const description = genHumanReadableName(target);
5442
+ 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) => {
5443
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { target: Object.assign(Object.assign({}, pendingActionErrorFilterState.target), { [target]: checked }) });
5268
5444
  dispatch({
5269
5445
  type: ActionType$6.UpdateActionErrorFilterState,
5270
- actionErrorFilterState,
5446
+ actionErrorFilterState: newActionErrorFilterState,
5271
5447
  });
5272
- }, ariaLabel: "show logs of all types", selected: actionErrorFilterState.type === undefined }),
5273
- React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-action-only", text: "Action Logs Only", onSelected: () => {
5274
- actionErrorFilterState.type = LogType$1.Action;
5448
+ }, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }));
5449
+ })))),
5450
+ (pendingActionErrorFilterState.type === undefined
5451
+ || pendingActionErrorFilterState.type === LogType$1.Error) && (React__default["default"].createElement(TabBox, { title: "Error Log Details" },
5452
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5453
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Error Message"),
5454
+ 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) => {
5455
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { errorMessage: e.target.value });
5275
5456
  dispatch({
5276
5457
  type: ActionType$6.UpdateActionErrorFilterState,
5277
- actionErrorFilterState,
5458
+ actionErrorFilterState: newActionErrorFilterState,
5278
5459
  });
5279
- }, ariaLabel: "only show action logs", selected: actionErrorFilterState.type === LogType$1.Action }),
5280
- React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-error-only", text: "Action Error Only", onSelected: () => {
5281
- actionErrorFilterState.type = LogType$1.Error;
5460
+ } })),
5461
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5462
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Error Code"),
5463
+ 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) => {
5464
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { errorCode: ((e.target.value)
5465
+ .trim()
5466
+ .toUpperCase()) });
5282
5467
  dispatch({
5283
5468
  type: ActionType$6.UpdateActionErrorFilterState,
5284
- actionErrorFilterState,
5469
+ actionErrorFilterState: newActionErrorFilterState,
5285
5470
  });
5286
- }, ariaLabel: "only show error logs", selected: actionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true })),
5287
- (actionErrorFilterState.type === undefined
5288
- || actionErrorFilterState.type === LogType$1.Action) && (React__default["default"].createElement(TabBox, { title: "Action Log Details" },
5289
- React__default["default"].createElement(ButtonInputGroup, { label: "Action", className: "mb-2", wrapButtonsAndAddGaps: true }, Object.keys(LogAction$1)
5290
- .map((action) => {
5291
- const description = genHumanReadableName(action);
5292
- 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) => {
5293
- actionErrorFilterState.action[action] = checked;
5294
- dispatch({
5295
- type: ActionType$6.UpdateActionErrorFilterState,
5296
- actionErrorFilterState,
5297
- });
5298
- } }));
5299
- })),
5300
- React__default["default"].createElement(ButtonInputGroup, { label: "Target", wrapButtonsAndAddGaps: true }, Object.keys(targetMap)
5301
- .map((target) => {
5302
- const description = genHumanReadableName(target);
5303
- 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) => {
5304
- actionErrorFilterState.target[target] = checked;
5305
- dispatch({
5306
- type: ActionType$6.UpdateActionErrorFilterState,
5307
- actionErrorFilterState,
5308
- });
5309
- } }));
5310
- })))),
5311
- (actionErrorFilterState.type === undefined
5312
- || actionErrorFilterState.type === LogType$1.Error) && (React__default["default"].createElement(TabBox, { title: "Error Log Details" },
5313
- React__default["default"].createElement("div", { className: "input-group mb-2" },
5314
- React__default["default"].createElement("span", { className: "input-group-text" }, "Error Message"),
5315
- 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) => {
5316
- actionErrorFilterState.errorMessage = e.target.value;
5317
- dispatch({
5318
- type: ActionType$6.UpdateActionErrorFilterState,
5319
- actionErrorFilterState,
5320
- });
5321
- } })),
5322
- React__default["default"].createElement("div", { className: "input-group mb-2" },
5323
- React__default["default"].createElement("span", { className: "input-group-text" }, "Error Code"),
5324
- 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) => {
5325
- actionErrorFilterState.errorCode = ((e.target.value)
5326
- .trim()
5327
- .toUpperCase());
5328
- dispatch({
5329
- type: ActionType$6.UpdateActionErrorFilterState,
5330
- actionErrorFilterState,
5331
- });
5332
- } }))))));
5333
- }
5334
- else if (expandedFilterDrawer === FilterDrawer.Advanced) {
5335
- // Create advanced filter ui
5336
- filterDrawer = (React__default["default"].createElement(React__default["default"].Fragment, null,
5337
- React__default["default"].createElement(TabBox, { title: "User" },
5338
- React__default["default"].createElement("div", { className: "input-group mb-2" },
5339
- React__default["default"].createElement("span", { className: "input-group-text" }, "User First Name"),
5340
- 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) => {
5341
- advancedFilterState.userFirstName = e.target.value;
5342
- dispatch({
5343
- type: ActionType$6.UpdateAdvancedFilterState,
5344
- advancedFilterState,
5345
- });
5346
- } })),
5347
- React__default["default"].createElement("div", { className: "input-group mb-2" },
5348
- React__default["default"].createElement("span", { className: "input-group-text" }, "User Last Name"),
5349
- 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) => {
5350
- advancedFilterState.userLastName = e.target.value;
5351
- dispatch({
5352
- type: ActionType$6.UpdateAdvancedFilterState,
5353
- advancedFilterState,
5354
- });
5355
- } })),
5356
- React__default["default"].createElement("div", { className: "input-group mb-2" },
5357
- React__default["default"].createElement("span", { className: "input-group-text" }, "User Email"),
5358
- 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) => {
5359
- advancedFilterState.userEmail = ((e.target.value)
5360
- .trim());
5361
- dispatch({
5362
- type: ActionType$6.UpdateAdvancedFilterState,
5363
- advancedFilterState,
5364
- });
5365
- } })),
5366
- React__default["default"].createElement("div", { className: "input-group mb-2" },
5367
- React__default["default"].createElement("span", { className: "input-group-text" }, "User Canvas Id"),
5368
- 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) => {
5369
- const { value } = e.target;
5370
- // Only update if value contains only numbers
5371
- if (/^\d+$/.test(value)) {
5372
- advancedFilterState.userId = ((e.target.value)
5373
- .trim());
5374
- }
5375
- dispatch({
5376
- type: ActionType$6.UpdateAdvancedFilterState,
5377
- advancedFilterState,
5378
- });
5379
- } })),
5380
- React__default["default"].createElement(ButtonInputGroup, { label: "Role" },
5381
- React__default["default"].createElement(CheckboxButton, { text: "Students", onChanged: (checked) => {
5382
- advancedFilterState.includeLearners = checked;
5383
- dispatch({
5384
- type: ActionType$6.UpdateAdvancedFilterState,
5385
- advancedFilterState,
5386
- });
5387
- }, checked: advancedFilterState.includeLearners, ariaLabel: "show logs from students" }),
5388
- React__default["default"].createElement(CheckboxButton, { text: "Teaching Team Members", onChanged: (checked) => {
5389
- advancedFilterState.includeTTMs = checked;
5471
+ } }))))));
5472
+ }
5473
+ else if (expandedFilterDrawer === FilterDrawer.Advanced) {
5474
+ // Create advanced filter ui
5475
+ filterDrawer = (React__default["default"].createElement(React__default["default"].Fragment, null,
5476
+ React__default["default"].createElement(TabBox, { title: "User" },
5477
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5478
+ React__default["default"].createElement("span", { className: "input-group-text" }, "User First Name"),
5479
+ 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) => {
5480
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userFirstName: e.target.value });
5481
+ dispatch({
5482
+ type: ActionType$6.UpdateAdvancedFilterState,
5483
+ advancedFilterState: newAdvancedFilterState,
5484
+ });
5485
+ } })),
5486
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5487
+ React__default["default"].createElement("span", { className: "input-group-text" }, "User Last Name"),
5488
+ 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) => {
5489
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userLastName: e.target.value });
5490
+ dispatch({
5491
+ type: ActionType$6.UpdateAdvancedFilterState,
5492
+ advancedFilterState: newAdvancedFilterState,
5493
+ });
5494
+ } })),
5495
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5496
+ React__default["default"].createElement("span", { className: "input-group-text" }, "User Email"),
5497
+ 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) => {
5498
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userEmail: ((e.target.value)
5499
+ .trim()) });
5500
+ dispatch({
5501
+ type: ActionType$6.UpdateAdvancedFilterState,
5502
+ advancedFilterState: newAdvancedFilterState,
5503
+ });
5504
+ } })),
5505
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5506
+ React__default["default"].createElement("span", { className: "input-group-text" }, "User Canvas Id"),
5507
+ 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) => {
5508
+ const { value } = e.target;
5509
+ // Only update if value contains only numbers
5510
+ if (/^\d+$/.test(value) || value === '') {
5511
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userId: ((e.target.value)
5512
+ .trim()) });
5390
5513
  dispatch({
5391
5514
  type: ActionType$6.UpdateAdvancedFilterState,
5392
- advancedFilterState,
5515
+ advancedFilterState: newAdvancedFilterState,
5393
5516
  });
5394
- }, checked: advancedFilterState.includeTTMs, ariaLabel: "show logs from teaching team members" }),
5395
- React__default["default"].createElement(CheckboxButton, { text: "Admins", onChanged: (checked) => {
5396
- advancedFilterState.includeAdmins = checked;
5517
+ }
5518
+ } })),
5519
+ React__default["default"].createElement(ButtonInputGroup, { label: "Role" },
5520
+ React__default["default"].createElement(CheckboxButton, { text: "Students", onChanged: (checked) => {
5521
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { includeLearners: checked });
5522
+ dispatch({
5523
+ type: ActionType$6.UpdateAdvancedFilterState,
5524
+ advancedFilterState: newAdvancedFilterState,
5525
+ });
5526
+ }, checked: pendingAdvancedFilterState.includeLearners, ariaLabel: "show logs from students", checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }),
5527
+ React__default["default"].createElement(CheckboxButton, { text: "Teaching Team Members", onChanged: (checked) => {
5528
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { includeTTMs: checked });
5529
+ dispatch({
5530
+ type: ActionType$6.UpdateAdvancedFilterState,
5531
+ advancedFilterState: newAdvancedFilterState,
5532
+ });
5533
+ }, checked: pendingAdvancedFilterState.includeTTMs, ariaLabel: "show logs from teaching team members", checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }),
5534
+ React__default["default"].createElement(CheckboxButton, { text: "Admins", onChanged: (checked) => {
5535
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { includeAdmins: checked });
5536
+ dispatch({
5537
+ type: ActionType$6.UpdateAdvancedFilterState,
5538
+ advancedFilterState: newAdvancedFilterState,
5539
+ });
5540
+ }, checked: pendingAdvancedFilterState.includeAdmins, ariaLabel: "show logs from admins", checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }))),
5541
+ React__default["default"].createElement(TabBox, { title: "Course" },
5542
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5543
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Course Name"),
5544
+ 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) => {
5545
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { courseName: e.target.value });
5546
+ dispatch({
5547
+ type: ActionType$6.UpdateAdvancedFilterState,
5548
+ advancedFilterState: newAdvancedFilterState,
5549
+ });
5550
+ } })),
5551
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5552
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Course Canvas Id"),
5553
+ 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) => {
5554
+ const { value } = e.target;
5555
+ // Only update if value contains only numbers
5556
+ if (/^\d+$/.test(value) || value === '') {
5557
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { courseId: ((e.target.value)
5558
+ .trim()) });
5397
5559
  dispatch({
5398
5560
  type: ActionType$6.UpdateAdvancedFilterState,
5399
- advancedFilterState,
5561
+ advancedFilterState: newAdvancedFilterState,
5400
5562
  });
5401
- }, checked: advancedFilterState.includeAdmins, ariaLabel: "show logs from admins" }))),
5402
- React__default["default"].createElement(TabBox, { title: "Course" },
5563
+ }
5564
+ } }))),
5565
+ React__default["default"].createElement(TabBox, { title: "Device" },
5566
+ React__default["default"].createElement(ButtonInputGroup, { label: "Device Type" },
5567
+ React__default["default"].createElement(RadioButton, { text: "All Devices", ariaLabel: "show logs from all devices", selected: pendingAdvancedFilterState.isMobile === undefined, onSelected: () => {
5568
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { isMobile: undefined });
5569
+ dispatch({
5570
+ type: ActionType$6.UpdateAdvancedFilterState,
5571
+ advancedFilterState: newAdvancedFilterState,
5572
+ });
5573
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5574
+ React__default["default"].createElement(RadioButton, { text: "Mobile Only", ariaLabel: "show logs from mobile devices", selected: pendingAdvancedFilterState.isMobile === true, onSelected: () => {
5575
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { isMobile: true });
5576
+ dispatch({
5577
+ type: ActionType$6.UpdateAdvancedFilterState,
5578
+ advancedFilterState: newAdvancedFilterState,
5579
+ });
5580
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5581
+ React__default["default"].createElement(RadioButton, { text: "Desktop Only", ariaLabel: "show logs from desktop devices", selected: pendingAdvancedFilterState.isMobile === false, onSelected: () => {
5582
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { isMobile: false });
5583
+ dispatch({
5584
+ type: ActionType$6.UpdateAdvancedFilterState,
5585
+ advancedFilterState: newAdvancedFilterState,
5586
+ });
5587
+ }, noMarginOnRight: true, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }))),
5588
+ React__default["default"].createElement(TabBox, { title: "Source" },
5589
+ React__default["default"].createElement(ButtonInputGroup, { label: "Source Type" },
5590
+ React__default["default"].createElement(RadioButton, { text: "Both", ariaLabel: "show logs from all sources", selected: pendingAdvancedFilterState.source === undefined, onSelected: () => {
5591
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { source: undefined });
5592
+ dispatch({
5593
+ type: ActionType$6.UpdateAdvancedFilterState,
5594
+ advancedFilterState: newAdvancedFilterState,
5595
+ });
5596
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5597
+ React__default["default"].createElement(RadioButton, { text: "Client Only", ariaLabel: "show logs from client source", selected: pendingAdvancedFilterState.source === LogSource$1.Client, onSelected: () => {
5598
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { source: LogSource$1.Client });
5599
+ dispatch({
5600
+ type: ActionType$6.UpdateAdvancedFilterState,
5601
+ advancedFilterState: newAdvancedFilterState,
5602
+ });
5603
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5604
+ React__default["default"].createElement(RadioButton, { text: "Server Only", ariaLabel: "show logs from server source", selected: pendingAdvancedFilterState.source === LogSource$1.Server, onSelected: () => {
5605
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { source: LogSource$1.Server });
5606
+ dispatch({
5607
+ type: ActionType$6.UpdateAdvancedFilterState,
5608
+ advancedFilterState: newAdvancedFilterState,
5609
+ });
5610
+ }, noMarginOnRight: true, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light })),
5611
+ pendingAdvancedFilterState.source !== LogSource$1.Client && (React__default["default"].createElement("div", { className: "mt-2" },
5403
5612
  React__default["default"].createElement("div", { className: "input-group mb-2" },
5404
- React__default["default"].createElement("span", { className: "input-group-text" }, "Course Name"),
5405
- 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) => {
5406
- advancedFilterState.courseName = e.target.value;
5613
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Path"),
5614
+ 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) => {
5615
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { routePath: ((e.target.value)
5616
+ .trim()) });
5407
5617
  dispatch({
5408
5618
  type: ActionType$6.UpdateAdvancedFilterState,
5409
- advancedFilterState,
5619
+ advancedFilterState: newAdvancedFilterState,
5410
5620
  });
5411
5621
  } })),
5412
5622
  React__default["default"].createElement("div", { className: "input-group mb-2" },
5413
- React__default["default"].createElement("span", { className: "input-group-text" }, "Course Canvas Id"),
5414
- 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) => {
5415
- const { value } = e.target;
5416
- // Only update if value contains only numbers
5417
- if (/^\d+$/.test(value)) {
5418
- advancedFilterState.courseId = ((e.target.value)
5419
- .trim());
5420
- }
5421
- dispatch({
5422
- type: ActionType$6.UpdateAdvancedFilterState,
5423
- advancedFilterState,
5424
- });
5425
- } }))),
5426
- React__default["default"].createElement(TabBox, { title: "Device" },
5427
- React__default["default"].createElement(ButtonInputGroup, { label: "Device Type" },
5428
- React__default["default"].createElement(RadioButton, { text: "All Devices", ariaLabel: "show logs from all devices", selected: advancedFilterState.isMobile === undefined, onSelected: () => {
5429
- advancedFilterState.isMobile = undefined;
5430
- dispatch({
5431
- type: ActionType$6.UpdateAdvancedFilterState,
5432
- advancedFilterState,
5433
- });
5434
- } }),
5435
- React__default["default"].createElement(RadioButton, { text: "Mobile Only", ariaLabel: "show logs from mobile devices", selected: advancedFilterState.isMobile === true, onSelected: () => {
5436
- advancedFilterState.isMobile = true;
5437
- dispatch({
5438
- type: ActionType$6.UpdateAdvancedFilterState,
5439
- advancedFilterState,
5440
- });
5441
- } }),
5442
- React__default["default"].createElement(RadioButton, { text: "Desktop Only", ariaLabel: "show logs from desktop devices", selected: advancedFilterState.isMobile === false, onSelected: () => {
5443
- advancedFilterState.isMobile = false;
5444
- dispatch({
5445
- type: ActionType$6.UpdateAdvancedFilterState,
5446
- advancedFilterState,
5447
- });
5448
- }, noMarginOnRight: true }))),
5449
- React__default["default"].createElement(TabBox, { title: "Source" },
5450
- React__default["default"].createElement(ButtonInputGroup, { label: "Source Type" },
5451
- React__default["default"].createElement(RadioButton, { text: "Both", ariaLabel: "show logs from all sources", selected: advancedFilterState.source === undefined, onSelected: () => {
5452
- advancedFilterState.source = undefined;
5453
- dispatch({
5454
- type: ActionType$6.UpdateAdvancedFilterState,
5455
- advancedFilterState,
5456
- });
5457
- } }),
5458
- React__default["default"].createElement(RadioButton, { text: "Client Only", ariaLabel: "show logs from client source", selected: advancedFilterState.source === LogSource$1.Client, onSelected: () => {
5459
- advancedFilterState.source = LogSource$1.Client;
5460
- dispatch({
5461
- type: ActionType$6.UpdateAdvancedFilterState,
5462
- advancedFilterState,
5463
- });
5464
- } }),
5465
- React__default["default"].createElement(RadioButton, { text: "Server Only", ariaLabel: "show logs from server source", selected: advancedFilterState.source === LogSource$1.Server, onSelected: () => {
5466
- advancedFilterState.source = LogSource$1.Server;
5623
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Template"),
5624
+ 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) => {
5625
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { routeTemplate: ((e.target.value)
5626
+ .trim()) });
5467
5627
  dispatch({
5468
5628
  type: ActionType$6.UpdateAdvancedFilterState,
5469
- advancedFilterState,
5629
+ advancedFilterState: newAdvancedFilterState,
5470
5630
  });
5471
- }, noMarginOnRight: true })),
5472
- advancedFilterState.source !== LogSource$1.Client && (React__default["default"].createElement("div", { className: "mt-2" },
5473
- React__default["default"].createElement("div", { className: "input-group mb-2" },
5474
- React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Path"),
5475
- 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) => {
5476
- advancedFilterState.courseName = ((e.target.value)
5477
- .trim());
5478
- dispatch({
5479
- type: ActionType$6.UpdateAdvancedFilterState,
5480
- advancedFilterState,
5481
- });
5482
- } })),
5483
- React__default["default"].createElement("div", { className: "input-group mb-2" },
5484
- React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Template"),
5485
- 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) => {
5486
- advancedFilterState.courseName = ((e.target.value)
5487
- .trim());
5488
- dispatch({
5489
- type: ActionType$6.UpdateAdvancedFilterState,
5490
- advancedFilterState,
5491
- });
5492
- } })))))));
5493
- }
5631
+ } })))))));
5494
5632
  }
5495
- // Filters UI
5496
- const filters = (React__default["default"].createElement(React__default["default"].Fragment, null,
5497
- filterToggles,
5498
- filterDrawer && (React__default["default"].createElement(Drawer, { customBackgroundColor: "#eee" }, filterDrawer))));
5499
- // Actually filter the logs
5500
- // > Perform filters
5501
- const logs = [];
5502
- Object.keys(logMap).forEach((year) => {
5503
- Object.keys(logMap[year]).forEach((month) => {
5504
- logMap[year][month].forEach((log) => {
5505
- /* ----------- Date Filter ---------- */
5506
- var _a;
5507
- // Before start date
5508
- if (
5509
- // Previous year
5510
- log.year < dateFilterState.startDate.year
5511
- // Same year, earlier month
5512
- || ((log.year === dateFilterState.startDate.year)
5513
- && (log.month < dateFilterState.startDate.month))
5514
- // Same year, same month, earlier day
5515
- || ((log.year === dateFilterState.startDate.year)
5516
- && (log.month === dateFilterState.startDate.month)
5517
- && (log.day < dateFilterState.startDate.day))) {
5518
- return;
5519
- }
5520
- // After end date
5521
- if (
5522
- // Later year
5523
- log.year > dateFilterState.endDate.year
5524
- // Same year, later month
5525
- || ((log.year === dateFilterState.endDate.year)
5526
- && (log.month > dateFilterState.endDate.month))
5527
- // Same year, same month, later day
5528
- || ((log.year === dateFilterState.endDate.year)
5529
- && (log.month === dateFilterState.endDate.month)
5530
- && (log.day > dateFilterState.endDate.day))) {
5531
- return;
5532
- }
5533
- /* --------- Context Filter --------- */
5534
- // Context doesn't match
5535
- if (
5536
- // Whole context is deselected
5537
- contextFilterState[log.context] === false
5538
- // None of the subcontexts are selected
5539
- || (
5540
- // Has subcontexts
5541
- typeof contextFilterState[log.context] !== 'boolean'
5542
- // None of the subcontexts are selected
5543
- && Object.values((_a = contextFilterState[log.context]) !== null && _a !== void 0 ? _a : {})
5544
- .every((isSelected) => {
5545
- return !isSelected;
5546
- }))) {
5547
- return;
5548
- }
5549
- // Subcontext doesn't match
5550
- if (
5551
- // Log context is not "uncategorized" (no point in further filters)
5552
- log.context !== LogBuiltInMetadata.Context.Uncategorized
5553
- // Log has a subcontext
5554
- && log.subcontext
5555
- // Context has subcontexts
5556
- && (contextFilterState[log.context]
5557
- && contextFilterState[log.context] !== false
5558
- && contextFilterState[log.context] !== true)
5559
- // Subcontext is not selected
5560
- && !contextFilterState[log.context][log.subcontext]) {
5561
- return;
5562
- }
5563
- /* -------------- Tags -------------- */
5564
- // No tags match
5565
- if (
5566
- // At least one tag is required
5567
- Object.values(tagFilterState)
5568
- .filter((isSelected) => {
5569
- return isSelected;
5570
- })
5571
- .length > 0
5572
- // No tags match
5573
- && log.tags.every((tag) => {
5574
- return !tagFilterState[tag];
5575
- })) {
5576
- return;
5577
- }
5578
- /* ------- Actions and Errors ------- */
5579
- // Log type doesn't match
5580
- if (
5581
- // Filter won't allow all types
5582
- actionErrorFilterState.type !== undefined
5583
- // Log type doesn't match
5584
- && actionErrorFilterState.type !== log.type) {
5585
- return;
5586
- }
5587
- // Filter errors
5588
- if (log.type === LogType$1.Error) {
5589
- // Message doesn't match
5590
- if (
5591
- // Message exists
5592
- log.errorMessage
5593
- // Message filter exists
5594
- && actionErrorFilterState.errorMessage.trim().length > 0
5595
- // Message doesn't match
5596
- && log.errorMessage.toLowerCase().includes(actionErrorFilterState.errorMessage.trim().toLowerCase())) {
5597
- return;
5598
- }
5599
- // Code doesn't match
5600
- if (
5601
- // Code exists
5602
- log.errorCode
5603
- // Code filter exists
5604
- && actionErrorFilterState.errorCode.trim().length > 0
5605
- // Code doesn't match
5606
- && log.errorCode.toUpperCase().includes(actionErrorFilterState.errorCode.trim().toUpperCase())) {
5607
- return;
5608
- }
5609
- }
5610
- // Filter actions
5611
- if (log.type === LogType$1.Action) {
5612
- // Target isn't selected
5613
- if (
5614
- // Target exists
5615
- log.target
5616
- // Target isn't selected
5617
- && !actionErrorFilterState.target[log.target]) {
5618
- return;
5619
- }
5620
- // Action
5621
- if (
5622
- // Action exists
5623
- log.action
5624
- // Action isn't selected
5625
- && !actionErrorFilterState.action[log.action]) {
5626
- return;
5627
- }
5628
- }
5629
- /* --------- Advanced Filter -------- */
5630
- // First name doesn't match
5631
- if (
5632
- // First name exists
5633
- log.userFirstName
5634
- // First name query doesn't match
5635
- && !log.userFirstName.toLowerCase().includes(advancedFilterState.userFirstName.toLowerCase().trim())) {
5636
- return;
5637
- }
5638
- // Last name doesn't match
5639
- if (
5640
- // Last name exists
5641
- log.userLastName
5642
- // Last name query doesn't match
5643
- && !log.userLastName.toLowerCase().includes(advancedFilterState.userLastName.toLowerCase().trim())) {
5644
- return;
5645
- }
5646
- // Email doesn't match
5647
- if (
5648
- // Email exists
5649
- log.userEmail
5650
- // Email query doesn't match
5651
- && !log.userEmail.toLowerCase().includes(advancedFilterState.userEmail.toLowerCase().trim())) {
5652
- return;
5653
- }
5654
- // User id doesn't match
5655
- if (
5656
- // User id exists
5657
- log.userId
5658
- // User id doesn't match
5659
- && !String(log.userId).includes(advancedFilterState.userId.trim())) {
5660
- return;
5661
- }
5662
- // Learner not allowed
5663
- if (
5664
- // User is a learner
5665
- log.isLearner
5666
- // Learners aren't included
5667
- && !advancedFilterState.includeLearners) {
5668
- return;
5669
- }
5670
- // TTM not allowed
5671
- if (
5672
- // User is a ttm
5673
- log.isTTM
5674
- // TTMs aren't included
5675
- && !advancedFilterState.includeTTMs) {
5676
- return;
5677
- }
5678
- // Admin not allowed
5679
- if (
5680
- // User is an admin
5681
- log.isAdmin
5682
- // Admins aren't included
5683
- && !advancedFilterState.includeAdmins) {
5684
- return;
5685
- }
5686
- // Course Id doesn't match
5687
- if (
5688
- // Course Id exists
5689
- log.courseId
5690
- // Course Id doesn't match
5691
- && !String(log.courseId).includes(advancedFilterState.courseId.trim())) {
5692
- return;
5693
- }
5694
- // Course name doesn't match
5695
- if (
5696
- // Course name exists
5697
- log.courseName
5698
- // Course name doesn't match
5699
- && !String(log.courseName).includes(advancedFilterState.courseName.trim())) {
5700
- return;
5701
- }
5702
- // Mobile filter doesn't match
5703
- if (
5704
- // Mobile filter exists
5705
- advancedFilterState.isMobile !== undefined
5706
- // Device info exists
5707
- && log.device
5708
- // Mobile filter doesn't match
5709
- && (advancedFilterState.isMobile !== log.device.isMobile)) {
5710
- return;
5711
- }
5712
- // Log source doesn't match
5713
- if (
5714
- // Source filter exists
5715
- advancedFilterState.source !== undefined
5716
- // Source info exists
5717
- && log.source
5718
- // Source filter doesn't match
5719
- && (advancedFilterState.source !== log.source)) {
5720
- return;
5721
- }
5722
- // Route path doesn't match (Only for server source)
5723
- if (
5724
- // Source is server
5725
- (log.source === LogSource$1.Server)
5726
- // Route path is being filtered
5727
- && (advancedFilterState.routePath.trim().length)
5728
- // Route path doesn't match
5729
- && !(log.routePath.includes(advancedFilterState.routePath.trim()))) {
5730
- return;
5731
- }
5732
- // Route template doesn't match (Only for server source)
5733
- if (
5734
- // Source is server
5735
- (log.source === LogSource$1.Server)
5736
- // Route template is being filtered
5737
- && (advancedFilterState.routeTemplate.trim().length)
5738
- // Route template doesn't match
5739
- && !(log.routeTemplate.includes(advancedFilterState.routeTemplate.trim()))) {
5740
- return;
5741
- }
5742
- /* -------------- Done -------------- */
5743
- // Made it past all filters. Add to the list
5744
- logs.push(log);
5745
- });
5746
- });
5747
- });
5748
- /*----------------------------------------*/
5749
- /* ---------------- Data ---------------- */
5750
- /*----------------------------------------*/
5751
- // Nothing to show notice
5752
- const noLogsNotice = (logs.length === 0
5753
- ? (React__default["default"].createElement("div", { className: "alert alert-warning text-center mt-2" },
5754
- React__default["default"].createElement("h4", { className: "m-1" }, "No Logs to Show"),
5755
- React__default["default"].createElement("div", null, "Either your filters are too strict or no matching logs have been created yet.")))
5756
- : undefined);
5757
- // Create intelliTable
5758
- const dataTable = (React__default["default"].createElement(IntelliTable, { title: "Matching Logs:", csvName: `Logs from ${getHumanReadableDate()}`, id: "logs", data: logs, columns: columns }));
5759
- // Main body
5760
- body = (React__default["default"].createElement(React__default["default"].Fragment, null,
5761
- filters,
5762
- React__default["default"].createElement("div", { className: "mt-2" },
5763
- dataTable,
5764
- noLogsNotice)));
5765
5633
  }
5634
+ // Filters UI
5635
+ const filters = (React__default["default"].createElement(React__default["default"].Fragment, null,
5636
+ filterToggles,
5637
+ filterDrawer && (React__default["default"].createElement(Drawer, { customBackgroundColor: "#eee" }, filterDrawer))));
5638
+ // Body that will be filled with the contents of the panel
5639
+ const body = (React__default["default"].createElement(React__default["default"].Fragment, null,
5640
+ loading && (React__default["default"].createElement("div", { className: "text-center p-5" },
5641
+ React__default["default"].createElement(LoadingSpinner, null))),
5642
+ !loading && (React__default["default"].createElement("div", null, filters)),
5643
+ !loading && logs.length === 0 && (React__default["default"].createElement("div", { className: "alert alert-warning text-center mt-2" },
5644
+ React__default["default"].createElement("h4", { className: "m-1" }, "No Logs to Show"),
5645
+ React__default["default"].createElement("div", null, "Either your filters are too strict or no matching logs have been created yet."))),
5646
+ React__default["default"].createElement("div", { className: loading || logs.length === 0 ? 'd-none' : undefined },
5647
+ React__default["default"].createElement(IntelliTable, { title: "Matching Logs", csvName: `Logs from ${getHumanReadableDate()}`, id: "logs", data: logs, columns: columns })),
5648
+ !loading && logs.length > 0 && paginationControls));
5766
5649
  /* ---------- Wrap in Modal --------- */
5767
5650
  return (React__default["default"].createElement("div", { className: "LogReviewer-outer-container" },
5768
5651
  React__default["default"].createElement("style", null, style$2),
5769
5652
  React__default["default"].createElement("div", { className: "LogReviewer-inner-container" },
5770
5653
  React__default["default"].createElement("div", { className: "LogReviewer-header" },
5771
5654
  React__default["default"].createElement("div", { className: "LogReviewer-header-title" },
5772
- React__default["default"].createElement("h3", { className: "text-center m-0" }, "Log Review Dashboard")),
5655
+ React__default["default"].createElement("h3", { className: "text-center" }, "Log Review Dashboard")),
5773
5656
  React__default["default"].createElement("div", { style: { width: 0 } },
5774
5657
  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 },
5775
5658
  React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes })))),
@@ -15445,6 +15328,12 @@ const HOUR_IN_MS = 3600000;
15445
15328
  */
15446
15329
  const DAY_IN_MS = 86400000;
15447
15330
 
15331
+ /**
15332
+ * Path of the route for storing client-side logs
15333
+ * @author Gabe Abrams
15334
+ */
15335
+ const LOG_REVIEW_ROUTE_PATH_PREFIX = `/admin${ROUTE_PATH_PREFIX}/logs`;
15336
+
15448
15337
  /**
15449
15338
  * Route for checking the status of the current user's
15450
15339
  * access to log review
@@ -16508,6 +16397,7 @@ exports.ErrorWithCode = ErrorWithCode;
16508
16397
  exports.HOUR_IN_MS = HOUR_IN_MS;
16509
16398
  exports.IntelliTable = IntelliTable;
16510
16399
  exports.ItemPicker = ItemPicker;
16400
+ exports.LOG_REVIEW_GET_LOGS_ROUTE = LOG_REVIEW_GET_LOGS_ROUTE;
16511
16401
  exports.LOG_REVIEW_ROUTE_PATH_PREFIX = LOG_REVIEW_ROUTE_PATH_PREFIX;
16512
16402
  exports.LOG_REVIEW_STATUS_ROUTE = LOG_REVIEW_STATUS_ROUTE;
16513
16403
  exports.LOG_ROUTE_PATH = LOG_ROUTE_PATH;
@@ -16543,6 +16433,7 @@ exports.avg = avg;
16543
16433
  exports.canReviewLogs = canReviewLogs;
16544
16434
  exports.capitalize = capitalize;
16545
16435
  exports.ceilToNumDecimals = ceilToNumDecimals;
16436
+ exports.cloneDeep = cloneDeep;
16546
16437
  exports.combineClassNames = combineClassNames;
16547
16438
  exports.compareArraysByProp = compareArraysByProp;
16548
16439
  exports.confirm = confirm;