dce-reactkit 4.0.1 → 4.1.0

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 (37) 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/constants/LOG_REVIEW_GET_LOGS_ROUTE.ts +9 -0
  30. package/src/helpers/cloneDeep.ts +11 -0
  31. package/src/index.ts +6 -0
  32. package/src/types/LogReviewerFilterState/ActionErrorFilterState.ts +24 -0
  33. package/src/types/LogReviewerFilterState/AdvancedFilterState.ts +36 -0
  34. package/src/types/LogReviewerFilterState/ContextFilterState.ts +16 -0
  35. package/src/types/LogReviewerFilterState/DateFilterState.ts +26 -0
  36. package/src/types/LogReviewerFilterState/TagFilterState.ts +9 -0
  37. 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.
@@ -3650,10 +3652,10 @@ const getHumanReadableDate = (dateOrTimestamp) => {
3650
3652
  };
3651
3653
 
3652
3654
  /**
3653
- * Path of the route for storing client-side logs
3655
+ * Path of the route for getting logs for log review
3654
3656
  * @author Gabe Abrams
3655
3657
  */
3656
- const LOG_REVIEW_ROUTE_PATH_PREFIX = `/admin${ROUTE_PATH_PREFIX}/logs`;
3658
+ const LOG_REVIEW_GET_LOGS_ROUTE = `/admin${ROUTE_PATH_PREFIX}/logs`;
3657
3659
 
3658
3660
  /**
3659
3661
  * Source of a log event
@@ -4258,6 +4260,71 @@ const IntelliTable = (props) => {
4258
4260
  } }, table)));
4259
4261
  };
4260
4262
 
4263
+ // Import React
4264
+ /*------------------------------------------------------------------------*/
4265
+ /* ------------------------------ Component ----------------------------- */
4266
+ /*------------------------------------------------------------------------*/
4267
+ const Pagination = (props) => {
4268
+ /*------------------------------------------------------------------------*/
4269
+ /* -------------------------------- Setup ------------------------------- */
4270
+ /*------------------------------------------------------------------------*/
4271
+ /* -------------- Props ------------- */
4272
+ // Destructure props
4273
+ const { currentPage, numPages, loading = false, onPageChanged, } = props;
4274
+ /*------------------------------------------------------------------------*/
4275
+ /* ------------------------------- Render ------------------------------- */
4276
+ /*------------------------------------------------------------------------*/
4277
+ // Compute pages to display
4278
+ const pages = [];
4279
+ const delta = 2; // how many pages to show on either side of current
4280
+ let start = Math.max(1, currentPage - delta);
4281
+ let end = Math.min(numPages, currentPage + delta);
4282
+ // If we are too close to the beginning or end, shift the window.
4283
+ if (currentPage - delta < 1) {
4284
+ end = Math.min(numPages, end + (1 - (currentPage - delta)));
4285
+ }
4286
+ if (currentPage + delta > numPages) {
4287
+ start = Math.max(1, start - ((currentPage + delta) - numPages));
4288
+ }
4289
+ for (let i = start; i <= end; i++) {
4290
+ pages.push(i);
4291
+ }
4292
+ // Render
4293
+ return (React__default["default"].createElement("nav", { "aria-label": "Page navigation for logs", className: "mt-3" },
4294
+ React__default["default"].createElement("ul", { className: "pagination justify-content-center" },
4295
+ React__default["default"].createElement("li", { className: `page-item ${(currentPage <= 1 || loading) ? 'disabled' : ''}` },
4296
+ 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" },
4297
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faArrowLeft }),
4298
+ ' ',
4299
+ "Prev")),
4300
+ (currentPage > 3 && pages[0] !== 1) && (React__default["default"].createElement("li", { className: "page-item" },
4301
+ React__default["default"].createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(1); }, disabled: loading, "aria-label": "Go to page 1" }, "1"))),
4302
+ currentPage > 4 && (React__default["default"].createElement("li", { className: "page-item disabled" },
4303
+ React__default["default"].createElement("span", { className: "page-link" }, "..."))),
4304
+ pages.map((pageNum) => {
4305
+ return (React__default["default"].createElement("li", { key: pageNum, className: `page-item ${pageNum === currentPage ? 'active' : ''}` },
4306
+ React__default["default"].createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(pageNum); }, disabled: loading, "aria-label": `Go to page ${pageNum}` }, pageNum)));
4307
+ }),
4308
+ currentPage < numPages - 3 && (React__default["default"].createElement("li", { className: "page-item disabled" },
4309
+ React__default["default"].createElement("span", { className: "page-link" }, "..."))),
4310
+ (currentPage < numPages - 2
4311
+ && pages[pages.length - 1] !== numPages) && (React__default["default"].createElement("li", { className: "page-item" },
4312
+ React__default["default"].createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(numPages); }, disabled: loading, "aria-label": "Go to last page" }, numPages))),
4313
+ React__default["default"].createElement("li", { className: `page-item ${(currentPage >= numPages || loading) ? 'disabled' : ''}` },
4314
+ 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" },
4315
+ "Next",
4316
+ ' ',
4317
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faArrowRight }))))));
4318
+ };
4319
+
4320
+ /**
4321
+ * Deeply clones an object
4322
+ * @author Yuen Ler Chow
4323
+ * @param obj the object to clone
4324
+ * @returns a deep clone of the object
4325
+ */
4326
+ const cloneDeep = clone__default["default"];
4327
+
4261
4328
  /**
4262
4329
  * Log reviewer panel that allows users (must be approved admins) to
4263
4330
  * review logs written by dce-reactkit
@@ -4613,7 +4680,7 @@ var ActionType$6;
4613
4680
  (function (ActionType) {
4614
4681
  // Show the loading bar
4615
4682
  ActionType["StartLoading"] = "start-loading";
4616
- // Finish loading one or more months of logs
4683
+ // Finish loading logs
4617
4684
  ActionType["FinishLoading"] = "finish-loading";
4618
4685
  // Reset filters to initial values
4619
4686
  ActionType["ResetFilters"] = "reset-filters";
@@ -4631,6 +4698,14 @@ var ActionType$6;
4631
4698
  ActionType["UpdateActionErrorFilterState"] = "update-action-error-filter-state";
4632
4699
  // Update the advanced filter state
4633
4700
  ActionType["UpdateAdvancedFilterState"] = "update-advanced-filter-state";
4701
+ // Set has another page
4702
+ ActionType["SetHasAnotherPage"] = "set-has-another-page";
4703
+ // Set the number of pages
4704
+ ActionType["SetNumPages"] = "set-num-pages";
4705
+ // Set page number
4706
+ ActionType["SetPageNumber"] = "set-page-number";
4707
+ // Reset user made filter change indicator
4708
+ ActionType["ResetUserMadeFilterChange"] = "reset-user-made-filter-change";
4634
4709
  })(ActionType$6 || (ActionType$6 = {}));
4635
4710
  /**
4636
4711
  * Reducer that executes actions
@@ -4644,7 +4719,7 @@ const reducer$7 = (state, action) => {
4644
4719
  return Object.assign(Object.assign({}, state), { loading: true });
4645
4720
  }
4646
4721
  case ActionType$6.FinishLoading: {
4647
- return Object.assign(Object.assign({}, state), { loading: false, logMap: action.logMap });
4722
+ return Object.assign(Object.assign({}, state), { loading: false, logs: action.logs });
4648
4723
  }
4649
4724
  case ActionType$6.ToggleFilterDrawer: {
4650
4725
  return Object.assign(Object.assign({}, state), { expandedFilterDrawer: (state.expandedFilterDrawer === action.filterDrawer
@@ -4655,22 +4730,34 @@ const reducer$7 = (state, action) => {
4655
4730
  return Object.assign(Object.assign({}, state), { expandedFilterDrawer: undefined });
4656
4731
  }
4657
4732
  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 });
4733
+ return Object.assign(Object.assign({}, state), { pendingDateFilterState: action.initDateFilterState, pendingContextFilterState: action.initContextFilterState, pendingTagFilterState: action.initTagFilterState, pendingActionErrorFilterState: action.initActionErrorFilterState, pendingAdvancedFilterState: action.initAdvancedFilterState, pageNumber: 1 });
4659
4734
  }
4660
4735
  case ActionType$6.UpdateDateFilterState: {
4661
- return Object.assign(Object.assign({}, state), { dateFilterState: action.dateFilterState });
4736
+ return Object.assign(Object.assign({}, state), { pendingDateFilterState: action.dateFilterState, userMadeFilterChange: true });
4662
4737
  }
4663
4738
  case ActionType$6.UpdateContextFilterState: {
4664
- return Object.assign(Object.assign({}, state), { contextFilterState: action.contextFilterState });
4739
+ return Object.assign(Object.assign({}, state), { pendingContextFilterState: action.contextFilterState, userMadeFilterChange: true });
4665
4740
  }
4666
4741
  case ActionType$6.UpdateTagFilterState: {
4667
- return Object.assign(Object.assign({}, state), { tagFilterState: action.tagFilterState });
4742
+ return Object.assign(Object.assign({}, state), { pendingTagFilterState: action.tagFilterState, userMadeFilterChange: true });
4668
4743
  }
4669
4744
  case ActionType$6.UpdateActionErrorFilterState: {
4670
- return Object.assign(Object.assign({}, state), { actionErrorFilterState: action.actionErrorFilterState });
4745
+ return Object.assign(Object.assign({}, state), { pendingActionErrorFilterState: action.actionErrorFilterState, userMadeFilterChange: true });
4671
4746
  }
4672
4747
  case ActionType$6.UpdateAdvancedFilterState: {
4673
- return Object.assign(Object.assign({}, state), { advancedFilterState: action.advancedFilterState });
4748
+ return Object.assign(Object.assign({}, state), { pendingAdvancedFilterState: action.advancedFilterState, userMadeFilterChange: true });
4749
+ }
4750
+ case ActionType$6.SetHasAnotherPage: {
4751
+ return Object.assign(Object.assign({}, state), { hasAnotherPage: action.hasAnotherPage });
4752
+ }
4753
+ case ActionType$6.SetNumPages: {
4754
+ return Object.assign(Object.assign({}, state), { numPages: action.numPages });
4755
+ }
4756
+ case ActionType$6.SetPageNumber: {
4757
+ return Object.assign(Object.assign({}, state), { pageNumber: action.pageNumber });
4758
+ }
4759
+ case ActionType$6.ResetUserMadeFilterChange: {
4760
+ return Object.assign(Object.assign({}, state), { userMadeFilterChange: false });
4674
4761
  }
4675
4762
  default: {
4676
4763
  return state;
@@ -4741,7 +4828,7 @@ const LogReviewer = (props) => {
4741
4828
  else {
4742
4829
  // Case: subcontexts exist
4743
4830
  initContextFilterState[context] = {};
4744
- Object.values(contextMap[context]).forEach((subcontext) => {
4831
+ Object.keys(contextMap[context]).forEach((subcontext) => {
4745
4832
  // Skip self ("_")
4746
4833
  if (subcontext === '_') {
4747
4834
  return;
@@ -4789,125 +4876,105 @@ const LogReviewer = (props) => {
4789
4876
  // Initial state
4790
4877
  const initialState = {
4791
4878
  loading: true,
4792
- logMap: {},
4879
+ logs: [],
4793
4880
  expandedFilterDrawer: undefined,
4794
- dateFilterState: initDateFilterState,
4795
- contextFilterState: initContextFilterState,
4796
- tagFilterState: initTagFilterState,
4797
- actionErrorFilterState: initActionErrorFilterState,
4798
- advancedFilterState: initAdvancedFilterState,
4881
+ pendingDateFilterState: initDateFilterState,
4882
+ pendingContextFilterState: initContextFilterState,
4883
+ pendingTagFilterState: initTagFilterState,
4884
+ pendingActionErrorFilterState: initActionErrorFilterState,
4885
+ pendingAdvancedFilterState: initAdvancedFilterState,
4886
+ pageNumber: 1,
4887
+ hasAnotherPage: false,
4888
+ numPages: 1,
4889
+ userMadeFilterChange: false,
4799
4890
  };
4800
4891
  // Initialize state
4801
4892
  const [state, dispatch] = React.useReducer(reducer$7, initialState);
4802
4893
  // Destructure common state
4803
- const { loading, logMap, expandedFilterDrawer, dateFilterState, contextFilterState, tagFilterState, actionErrorFilterState, advancedFilterState, } = state;
4894
+ const { loading, logs, expandedFilterDrawer, pendingDateFilterState, pendingContextFilterState, pendingTagFilterState, pendingActionErrorFilterState, pendingAdvancedFilterState, pageNumber, numPages, userMadeFilterChange, } = state;
4895
+ /* -------------- Refs -------------- */
4896
+ // Initialize refs
4897
+ const activeFiltersRef = React.useRef({
4898
+ dateFilterState: JSON.parse(JSON.stringify(pendingDateFilterState)),
4899
+ contextFilterState: JSON.parse(JSON.stringify(pendingContextFilterState)),
4900
+ tagFilterState: JSON.parse(JSON.stringify(pendingTagFilterState)),
4901
+ actionErrorFilterState: JSON.parse(JSON.stringify(pendingActionErrorFilterState)),
4902
+ advancedFilterState: JSON.parse(JSON.stringify(pendingAdvancedFilterState)),
4903
+ });
4804
4904
  /*------------------------------------------------------------------------*/
4805
4905
  /* ------------------------- Component Functions ------------------------ */
4806
4906
  /*------------------------------------------------------------------------*/
4807
4907
  /**
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
4908
+ * Fetch logs from the server based on current filters
4909
+ * @author Yuen Ler Chow
4910
+ * @param opts object containing all arguments
4911
+ * @param opts.filters the filters to apply
4912
+ * @param opts.pageNum the page number to fetch
4913
+ * @param opts.filtersChanged if true, the filters have changed
4847
4914
  */
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
4915
+ const fetchLogs = (opts) => __awaiter(void 0, void 0, void 0, function* () {
4916
+ const { filters, pageNum, filtersChanged, } = opts;
4861
4917
  dispatch({
4862
4918
  type: ActionType$6.StartLoading,
4863
4919
  });
4864
- // Load required months
4865
4920
  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;
4921
+ // Send filters to the server
4922
+ let fetchedLogs = [];
4923
+ // Get logs from server
4924
+ const response = yield visitServerEndpoint({
4925
+ path: LOG_REVIEW_GET_LOGS_ROUTE,
4926
+ method: 'GET',
4927
+ params: {
4928
+ pageNumber: pageNum,
4929
+ filters,
4930
+ countDocuments: filtersChanged,
4931
+ },
4932
+ });
4933
+ fetchedLogs = fetchedLogs.concat(response.items);
4934
+ dispatch({
4935
+ type: ActionType$6.SetHasAnotherPage,
4936
+ hasAnotherPage: response.hasAnotherPage,
4937
+ });
4938
+ if (filtersChanged && response.numPages !== undefined) {
4939
+ dispatch({
4940
+ type: ActionType$6.SetNumPages,
4941
+ numPages: response.numPages,
4942
+ });
4890
4943
  }
4944
+ // Update logs in state
4945
+ dispatch({
4946
+ type: ActionType$6.FinishLoading,
4947
+ logs: fetchedLogs,
4948
+ });
4949
+ // Update page number
4950
+ dispatch({
4951
+ type: ActionType$6.SetPageNumber,
4952
+ pageNumber: pageNum,
4953
+ });
4891
4954
  }
4892
4955
  catch (err) {
4893
4956
  return showFatalError(err);
4894
4957
  }
4895
- // Finish loading
4896
- dispatch({
4897
- type: ActionType$6.FinishLoading,
4898
- logMap,
4899
- });
4900
4958
  });
4901
4959
  /*------------------------------------------------------------------------*/
4902
4960
  /* ------------------------- Lifecycle Functions ------------------------ */
4903
4961
  /*------------------------------------------------------------------------*/
4904
4962
  /**
4905
- * Mount
4906
- * @author Gabe Abrams
4963
+ * Fetch logs on mount
4964
+ * @author Yuen Ler Chow
4907
4965
  */
4908
4966
  React.useEffect(() => {
4909
- // Perform initial load
4910
- handleDateRangeUpdated(dateFilterState);
4967
+ fetchLogs({
4968
+ filters: {
4969
+ dateFilterState: pendingDateFilterState,
4970
+ contextFilterState: pendingContextFilterState,
4971
+ tagFilterState: pendingTagFilterState,
4972
+ actionErrorFilterState: pendingActionErrorFilterState,
4973
+ advancedFilterState: pendingAdvancedFilterState,
4974
+ },
4975
+ pageNum: 1,
4976
+ filtersChanged: true,
4977
+ });
4911
4978
  }, []);
4912
4979
  /*------------------------------------------------------------------------*/
4913
4980
  /* ------------------------------- Render ------------------------------- */
@@ -4915,718 +4982,534 @@ const LogReviewer = (props) => {
4915
4982
  /*----------------------------------------*/
4916
4983
  /* --------------- Main UI -------------- */
4917
4984
  /*----------------------------------------*/
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: () => {
4985
+ /*----------------------------------------*/
4986
+ /* ------------ Pagination -------------- */
4987
+ /*----------------------------------------*/
4988
+ const paginationControls = logs.length > 0 && (React__default["default"].createElement(Pagination, { currentPage: pageNumber, numPages: numPages, loading: loading, onPageChanged: (targetPage) => {
4989
+ const { current: activeFilters } = activeFiltersRef;
4990
+ fetchLogs({
4991
+ filters: {
4992
+ dateFilterState: activeFilters.dateFilterState,
4993
+ contextFilterState: activeFilters.contextFilterState,
4994
+ tagFilterState: activeFilters.tagFilterState,
4995
+ actionErrorFilterState: activeFilters.actionErrorFilterState,
4996
+ advancedFilterState: activeFilters.advancedFilterState,
4997
+ },
4998
+ pageNum: targetPage,
4999
+ filtersChanged: false,
5000
+ });
5001
+ } }));
5002
+ /*----------------------------------------*/
5003
+ /* --------------- Filters -------------- */
5004
+ /*----------------------------------------*/
5005
+ // Filter toggle
5006
+ const filterToggles = (React__default["default"].createElement("div", { className: "LogReviewer-filter-toggles" },
5007
+ React__default["default"].createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
5008
+ 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: () => {
5009
+ dispatch({
5010
+ type: ActionType$6.ToggleFilterDrawer,
5011
+ filterDrawer: FilterDrawer.Date,
5012
+ });
5013
+ } },
5014
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCalendar, className: "me-2" }),
5015
+ "Date"),
5016
+ 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: () => {
5017
+ dispatch({
5018
+ type: ActionType$6.ToggleFilterDrawer,
5019
+ filterDrawer: FilterDrawer.Context,
5020
+ });
5021
+ } },
5022
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "me-2" }),
5023
+ "Context"),
5024
+ (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: () => {
5025
+ dispatch({
5026
+ type: ActionType$6.ToggleFilterDrawer,
5027
+ filterDrawer: FilterDrawer.Tag,
5028
+ });
5029
+ } },
5030
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTag, className: "me-2" }),
5031
+ "Tag")),
5032
+ 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: () => {
5033
+ dispatch({
5034
+ type: ActionType$6.ToggleFilterDrawer,
5035
+ filterDrawer: FilterDrawer.Action,
5036
+ });
5037
+ } },
5038
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faHammer, className: "me-2" }),
5039
+ "Action"),
5040
+ 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: () => {
5041
+ dispatch({
5042
+ type: ActionType$6.ToggleFilterDrawer,
5043
+ filterDrawer: FilterDrawer.Advanced,
5044
+ });
5045
+ } },
5046
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faList, className: "me-2" }),
5047
+ "Advanced"),
5048
+ React__default["default"].createElement("button", { type: "button", id: "LogReviewer-reset-filters-button", className: "btn btn-light", "aria-label": "reset filters", onClick: () => {
5049
+ // Save active filters
5050
+ // Deep clone the initial filter states to avoid any reference issues
5051
+ // if the filter states are modified later
5052
+ activeFiltersRef.current = cloneDeep({
5053
+ dateFilterState: initDateFilterState,
5054
+ contextFilterState: initContextFilterState,
5055
+ tagFilterState: initTagFilterState,
5056
+ actionErrorFilterState: initActionErrorFilterState,
5057
+ advancedFilterState: initAdvancedFilterState,
5058
+ });
5059
+ fetchLogs({
5060
+ filters: {
5061
+ dateFilterState: initDateFilterState,
5062
+ contextFilterState: initContextFilterState,
5063
+ tagFilterState: initTagFilterState,
5064
+ actionErrorFilterState: initActionErrorFilterState,
5065
+ advancedFilterState: initAdvancedFilterState,
5066
+ },
5067
+ pageNum: 1,
5068
+ filtersChanged: true,
5069
+ });
5070
+ dispatch({
5071
+ type: ActionType$6.ResetFilters,
5072
+ initActionErrorFilterState,
5073
+ initAdvancedFilterState,
5074
+ initContextFilterState,
5075
+ initDateFilterState,
5076
+ initTagFilterState,
5077
+ });
5078
+ dispatch({
5079
+ type: ActionType$6.HideFilterDrawer,
5080
+ });
5081
+ } },
5082
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes }),
5083
+ ' ',
5084
+ "Reset"),
5085
+ userMadeFilterChange && (React__default["default"].createElement("button", { type: "button", id: "LogReviewer-submit-filters-button", className: "btn btn-primary ms-2", "aria-label": "submit filters", onClick: () => {
5086
+ dispatch({
5087
+ type: ActionType$6.HideFilterDrawer,
5088
+ });
5089
+ // Reset user made filter change indicator
5090
+ dispatch({
5091
+ type: ActionType$6.ResetUserMadeFilterChange,
5092
+ });
5093
+ // Save active filters
5094
+ // Deep clone the pending filter states to avoid any reference issues
5095
+ // if the filter states are modified later
5096
+ activeFiltersRef.current = cloneDeep({
5097
+ dateFilterState: pendingDateFilterState,
5098
+ contextFilterState: pendingContextFilterState,
5099
+ tagFilterState: pendingTagFilterState,
5100
+ actionErrorFilterState: pendingActionErrorFilterState,
5101
+ advancedFilterState: pendingAdvancedFilterState,
5102
+ });
5103
+ fetchLogs({
5104
+ filters: {
5105
+ dateFilterState: pendingDateFilterState,
5106
+ contextFilterState: pendingContextFilterState,
5107
+ tagFilterState: pendingTagFilterState,
5108
+ actionErrorFilterState: pendingActionErrorFilterState,
5109
+ advancedFilterState: pendingAdvancedFilterState,
5110
+ },
5111
+ pageNum: 1,
5112
+ filtersChanged: true,
5113
+ });
5114
+ } },
5115
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faSearch }),
5116
+ ' ',
5117
+ "Apply Filters")))));
5118
+ // Filter drawer
5119
+ let filterDrawer;
5120
+ if (expandedFilterDrawer) {
5121
+ if (expandedFilterDrawer === FilterDrawer.Date) {
5122
+ filterDrawer = (React__default["default"].createElement(TabBox, { title: "Date" },
5123
+ 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) => {
5124
+ const newDateFilterState = Object.assign(Object.assign({}, pendingDateFilterState), { startDate: { month, day, year } });
4967
5125
  dispatch({
4968
- type: ActionType$6.ToggleFilterDrawer,
4969
- filterDrawer: FilterDrawer.Advanced,
5126
+ type: ActionType$6.UpdateDateFilterState,
5127
+ dateFilterState: newDateFilterState,
4970
5128
  });
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: () => {
5129
+ } }),
5130
+ ' ',
5131
+ "to",
5132
+ ' ',
5133
+ 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) => {
5134
+ if (year < pendingDateFilterState.startDate.year
5135
+ || (year === pendingDateFilterState.startDate.year
5136
+ && month < pendingDateFilterState.startDate.month)
5137
+ || (year === pendingDateFilterState.startDate.year
5138
+ && month === pendingDateFilterState.startDate.month
5139
+ && day < pendingDateFilterState.startDate.day)) {
5140
+ return alert('Invalid End Date', 'The end date cannot be before the start date.');
5141
+ }
5142
+ const newDateFilterState = Object.assign(Object.assign({}, pendingDateFilterState), { endDate: { month, day, year } });
4975
5143
  dispatch({
4976
- type: ActionType$6.ResetFilters,
4977
- initActionErrorFilterState,
4978
- initAdvancedFilterState,
4979
- initContextFilterState,
4980
- initDateFilterState,
4981
- initTagFilterState,
5144
+ type: ActionType$6.UpdateDateFilterState,
5145
+ dateFilterState: newDateFilterState,
4982
5146
  });
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
- }));
5147
+ } })));
5148
+ }
5149
+ else if (expandedFilterDrawer === FilterDrawer.Context) {
5150
+ // Create item picker items
5151
+ const builtInPickableItem = {
5152
+ id: 'built-in-contexts',
5153
+ name: 'Auto-logged',
5154
+ isGroup: true,
5155
+ children: [],
5156
+ };
5157
+ const pickableItems = [];
5158
+ Object.keys(contextMap)
5159
+ .forEach((context) => {
5160
+ const value = contextMap[context];
5161
+ if (typeof value === 'string') {
5162
+ // No subcategories
5059
5163
  const item = {
5060
5164
  id: context,
5061
5165
  name: genHumanReadableName(context),
5062
- isGroup: true,
5063
- children,
5166
+ isGroup: false,
5167
+ checked: !!pendingContextFilterState[context],
5064
5168
  };
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
- }
5169
+ // Add built-in items to its own folder
5170
+ const isBuiltIn = context in LogBuiltInMetadata.Context;
5171
+ if (isBuiltIn) {
5172
+ // Add to built-in pickable item
5173
+ builtInPickableItem.children.push(item);
5174
+ }
5175
+ else {
5176
+ // Add to pickable items list
5177
+ pickableItems.push(item);
5178
+ }
5179
+ return;
5180
+ }
5181
+ // Has subcategories
5182
+ const children = (Object.keys(value)
5183
+ // Remove parent name
5184
+ .filter((subcontext) => {
5185
+ return (subcontext !== '_');
5186
+ })
5187
+ // Create child pickable items
5188
+ .map((subcontext) => {
5189
+ return {
5190
+ id: subcontext,
5191
+ name: genHumanReadableName(subcontext),
5192
+ isGroup: false,
5193
+ checked: pendingContextFilterState[context][subcontext],
5194
+ };
5195
+ }));
5196
+ const item = {
5197
+ id: context,
5198
+ name: genHumanReadableName(context),
5199
+ isGroup: true,
5200
+ children,
5201
+ };
5202
+ pickableItems.push(item);
5203
+ });
5204
+ // Add built-in contexts to end of list
5205
+ pickableItems.push(builtInPickableItem);
5206
+ // Create filter UI
5207
+ filterDrawer = (React__default["default"].createElement(ItemPicker, { title: "Context", items: pickableItems, onChanged: (updatedItems) => {
5208
+ // Update our state
5209
+ const newContextFilterState = Object.assign({}, pendingContextFilterState);
5210
+ updatedItems.forEach((pickableItem) => {
5211
+ if (pickableItem.isGroup) {
5212
+ // Has subcontexts
5213
+ if (pickableItem.id === 'built-in-contexts') {
5214
+ // Built-in
5215
+ // Treat as if these were top-level contexts
5216
+ pickableItem.children.forEach((subcontextItem) => {
5217
+ newContextFilterState[subcontextItem.id] = ('checked' in subcontextItem
5218
+ && subcontextItem.checked);
5219
+ });
5091
5220
  }
5092
5221
  else {
5093
- // No subcontexts
5094
- contextFilterState[pickableItem.id] = (pickableItem.checked);
5222
+ // Not built-in
5223
+ pickableItem.children.forEach((subcontextItem) => {
5224
+ if (!subcontextItem.isGroup) {
5225
+ newContextFilterState[pickableItem.id][subcontextItem.id] = (subcontextItem.checked);
5226
+ }
5227
+ });
5095
5228
  }
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;
5229
+ }
5230
+ else {
5231
+ // No subcontexts
5232
+ newContextFilterState[pickableItem.id] = (pickableItem.checked);
5233
+ }
5234
+ });
5235
+ // Update state
5236
+ dispatch({
5237
+ type: ActionType$6.UpdateContextFilterState,
5238
+ contextFilterState: newContextFilterState,
5239
+ });
5240
+ } }));
5241
+ }
5242
+ else if (expandedFilterDrawer === FilterDrawer.Tag) {
5243
+ // Create filter UI
5244
+ filterDrawer = (React__default["default"].createElement(TabBox, { title: "Tags" },
5245
+ 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."),
5246
+ React__default["default"].createElement("div", { className: "d-flex gap-1 flex-wrap" }, Object.keys((_d = LogMetadata.Tag) !== null && _d !== void 0 ? _d : {})
5247
+ .map((tag) => {
5248
+ const description = genHumanReadableName(tag);
5249
+ 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) => {
5250
+ const newTagFilterState = Object.assign(Object.assign({}, pendingTagFilterState), { [tag]: checked });
5251
+ dispatch({
5252
+ type: ActionType$6.UpdateTagFilterState,
5253
+ tagFilterState: newTagFilterState,
5254
+ });
5255
+ }, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }));
5256
+ }))));
5257
+ }
5258
+ else if (expandedFilterDrawer === FilterDrawer.Action) {
5259
+ // Create filter UI
5260
+ filterDrawer = (React__default["default"].createElement(React__default["default"].Fragment, null,
5261
+ React__default["default"].createElement(TabBox, { title: "Log Type" },
5262
+ React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-all", text: "All Logs", onSelected: () => {
5263
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { type: undefined });
5264
+ dispatch({
5265
+ type: ActionType$6.UpdateActionErrorFilterState,
5266
+ actionErrorFilterState: newActionErrorFilterState,
5267
+ });
5268
+ }, ariaLabel: "show logs of all types", selected: pendingActionErrorFilterState.type === undefined, unselectedVariant: Variant$1.Light }),
5269
+ React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-action-only", text: "Action Logs Only", onSelected: () => {
5270
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { type: LogType$1.Action });
5271
+ dispatch({
5272
+ type: ActionType$6.UpdateActionErrorFilterState,
5273
+ actionErrorFilterState: newActionErrorFilterState,
5274
+ });
5275
+ }, ariaLabel: "only show action logs", selected: pendingActionErrorFilterState.type === LogType$1.Action, unselectedVariant: Variant$1.Light }),
5276
+ React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-error-only", text: "Action Error Only", onSelected: () => {
5277
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { type: LogType$1.Error });
5278
+ dispatch({
5279
+ type: ActionType$6.UpdateActionErrorFilterState,
5280
+ actionErrorFilterState: newActionErrorFilterState,
5281
+ });
5282
+ }, ariaLabel: "only show error logs", selected: pendingActionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light })),
5283
+ (pendingActionErrorFilterState.type === undefined
5284
+ || pendingActionErrorFilterState.type === LogType$1.Action) && (React__default["default"].createElement(TabBox, { title: "Action Log Details" },
5285
+ React__default["default"].createElement(ButtonInputGroup, { label: "Action", className: "mb-2", wrapButtonsAndAddGaps: true }, Object.keys(LogAction$1)
5286
+ .map((action) => {
5287
+ const description = genHumanReadableName(action);
5288
+ 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) => {
5289
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { action: Object.assign(Object.assign({}, pendingActionErrorFilterState.action), { [action]: checked }) });
5112
5290
  dispatch({
5113
- type: ActionType$6.UpdateTagFilterState,
5114
- tagFilterState,
5291
+ type: ActionType$6.UpdateActionErrorFilterState,
5292
+ actionErrorFilterState: newActionErrorFilterState,
5115
5293
  });
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;
5294
+ }, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }));
5295
+ })),
5296
+ React__default["default"].createElement(ButtonInputGroup, { label: "Target", wrapButtonsAndAddGaps: true }, Object.keys(targetMap)
5297
+ .map((target) => {
5298
+ const description = genHumanReadableName(target);
5299
+ 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) => {
5300
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { target: Object.assign(Object.assign({}, pendingActionErrorFilterState.target), { [target]: checked }) });
5125
5301
  dispatch({
5126
5302
  type: ActionType$6.UpdateActionErrorFilterState,
5127
- actionErrorFilterState,
5303
+ actionErrorFilterState: newActionErrorFilterState,
5128
5304
  });
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;
5305
+ }, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }));
5306
+ })))),
5307
+ (pendingActionErrorFilterState.type === undefined
5308
+ || pendingActionErrorFilterState.type === LogType$1.Error) && (React__default["default"].createElement(TabBox, { title: "Error Log Details" },
5309
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5310
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Error Message"),
5311
+ 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) => {
5312
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { errorMessage: e.target.value });
5132
5313
  dispatch({
5133
5314
  type: ActionType$6.UpdateActionErrorFilterState,
5134
- actionErrorFilterState,
5315
+ actionErrorFilterState: newActionErrorFilterState,
5135
5316
  });
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;
5317
+ } })),
5318
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5319
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Error Code"),
5320
+ 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) => {
5321
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { errorCode: ((e.target.value)
5322
+ .trim()
5323
+ .toUpperCase()) });
5139
5324
  dispatch({
5140
5325
  type: ActionType$6.UpdateActionErrorFilterState,
5141
- actionErrorFilterState,
5326
+ actionErrorFilterState: newActionErrorFilterState,
5142
5327
  });
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;
5328
+ } }))))));
5329
+ }
5330
+ else if (expandedFilterDrawer === FilterDrawer.Advanced) {
5331
+ // Create advanced filter ui
5332
+ filterDrawer = (React__default["default"].createElement(React__default["default"].Fragment, null,
5333
+ React__default["default"].createElement(TabBox, { title: "User" },
5334
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5335
+ React__default["default"].createElement("span", { className: "input-group-text" }, "User First Name"),
5336
+ 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) => {
5337
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userFirstName: e.target.value });
5338
+ dispatch({
5339
+ type: ActionType$6.UpdateAdvancedFilterState,
5340
+ advancedFilterState: newAdvancedFilterState,
5341
+ });
5342
+ } })),
5343
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5344
+ React__default["default"].createElement("span", { className: "input-group-text" }, "User Last Name"),
5345
+ 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) => {
5346
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userLastName: e.target.value });
5347
+ dispatch({
5348
+ type: ActionType$6.UpdateAdvancedFilterState,
5349
+ advancedFilterState: newAdvancedFilterState,
5350
+ });
5351
+ } })),
5352
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5353
+ React__default["default"].createElement("span", { className: "input-group-text" }, "User Email"),
5354
+ 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) => {
5355
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userEmail: ((e.target.value)
5356
+ .trim()) });
5357
+ dispatch({
5358
+ type: ActionType$6.UpdateAdvancedFilterState,
5359
+ advancedFilterState: newAdvancedFilterState,
5360
+ });
5361
+ } })),
5362
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5363
+ React__default["default"].createElement("span", { className: "input-group-text" }, "User Canvas Id"),
5364
+ 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) => {
5365
+ const { value } = e.target;
5366
+ // Only update if value contains only numbers
5367
+ if (/^\d+$/.test(value) || value === '') {
5368
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userId: ((e.target.value)
5369
+ .trim()) });
5247
5370
  dispatch({
5248
5371
  type: ActionType$6.UpdateAdvancedFilterState,
5249
- advancedFilterState,
5372
+ advancedFilterState: newAdvancedFilterState,
5250
5373
  });
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;
5374
+ }
5375
+ } })),
5376
+ React__default["default"].createElement(ButtonInputGroup, { label: "Role" },
5377
+ React__default["default"].createElement(CheckboxButton, { text: "Students", onChanged: (checked) => {
5378
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { includeLearners: checked });
5379
+ dispatch({
5380
+ type: ActionType$6.UpdateAdvancedFilterState,
5381
+ advancedFilterState: newAdvancedFilterState,
5382
+ });
5383
+ }, checked: pendingAdvancedFilterState.includeLearners, ariaLabel: "show logs from students", checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }),
5384
+ React__default["default"].createElement(CheckboxButton, { text: "Teaching Team Members", onChanged: (checked) => {
5385
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { includeTTMs: checked });
5386
+ dispatch({
5387
+ type: ActionType$6.UpdateAdvancedFilterState,
5388
+ advancedFilterState: newAdvancedFilterState,
5389
+ });
5390
+ }, checked: pendingAdvancedFilterState.includeTTMs, ariaLabel: "show logs from teaching team members", checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }),
5391
+ React__default["default"].createElement(CheckboxButton, { text: "Admins", onChanged: (checked) => {
5392
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { includeAdmins: checked });
5393
+ dispatch({
5394
+ type: ActionType$6.UpdateAdvancedFilterState,
5395
+ advancedFilterState: newAdvancedFilterState,
5396
+ });
5397
+ }, checked: pendingAdvancedFilterState.includeAdmins, ariaLabel: "show logs from admins", checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }))),
5398
+ React__default["default"].createElement(TabBox, { title: "Course" },
5399
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5400
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Course Name"),
5401
+ 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) => {
5402
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { courseName: e.target.value });
5403
+ dispatch({
5404
+ type: ActionType$6.UpdateAdvancedFilterState,
5405
+ advancedFilterState: newAdvancedFilterState,
5406
+ });
5407
+ } })),
5408
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5409
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Course Canvas Id"),
5410
+ 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) => {
5411
+ const { value } = e.target;
5412
+ // Only update if value contains only numbers
5413
+ if (/^\d+$/.test(value) || value === '') {
5414
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { courseId: ((e.target.value)
5415
+ .trim()) });
5254
5416
  dispatch({
5255
5417
  type: ActionType$6.UpdateAdvancedFilterState,
5256
- advancedFilterState,
5418
+ advancedFilterState: newAdvancedFilterState,
5257
5419
  });
5258
- }, checked: advancedFilterState.includeAdmins, ariaLabel: "show logs from admins" }))),
5259
- React__default["default"].createElement(TabBox, { title: "Course" },
5420
+ }
5421
+ } }))),
5422
+ React__default["default"].createElement(TabBox, { title: "Device" },
5423
+ React__default["default"].createElement(ButtonInputGroup, { label: "Device Type" },
5424
+ React__default["default"].createElement(RadioButton, { text: "All Devices", ariaLabel: "show logs from all devices", selected: pendingAdvancedFilterState.isMobile === undefined, onSelected: () => {
5425
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { isMobile: undefined });
5426
+ dispatch({
5427
+ type: ActionType$6.UpdateAdvancedFilterState,
5428
+ advancedFilterState: newAdvancedFilterState,
5429
+ });
5430
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5431
+ React__default["default"].createElement(RadioButton, { text: "Mobile Only", ariaLabel: "show logs from mobile devices", selected: pendingAdvancedFilterState.isMobile === true, onSelected: () => {
5432
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { isMobile: true });
5433
+ dispatch({
5434
+ type: ActionType$6.UpdateAdvancedFilterState,
5435
+ advancedFilterState: newAdvancedFilterState,
5436
+ });
5437
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5438
+ React__default["default"].createElement(RadioButton, { text: "Desktop Only", ariaLabel: "show logs from desktop devices", selected: pendingAdvancedFilterState.isMobile === false, onSelected: () => {
5439
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { isMobile: false });
5440
+ dispatch({
5441
+ type: ActionType$6.UpdateAdvancedFilterState,
5442
+ advancedFilterState: newAdvancedFilterState,
5443
+ });
5444
+ }, noMarginOnRight: true, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }))),
5445
+ React__default["default"].createElement(TabBox, { title: "Source" },
5446
+ React__default["default"].createElement(ButtonInputGroup, { label: "Source Type" },
5447
+ React__default["default"].createElement(RadioButton, { text: "Both", ariaLabel: "show logs from all sources", selected: pendingAdvancedFilterState.source === undefined, onSelected: () => {
5448
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { source: undefined });
5449
+ dispatch({
5450
+ type: ActionType$6.UpdateAdvancedFilterState,
5451
+ advancedFilterState: newAdvancedFilterState,
5452
+ });
5453
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5454
+ React__default["default"].createElement(RadioButton, { text: "Client Only", ariaLabel: "show logs from client source", selected: pendingAdvancedFilterState.source === LogSource$1.Client, onSelected: () => {
5455
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { source: LogSource$1.Client });
5456
+ dispatch({
5457
+ type: ActionType$6.UpdateAdvancedFilterState,
5458
+ advancedFilterState: newAdvancedFilterState,
5459
+ });
5460
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5461
+ React__default["default"].createElement(RadioButton, { text: "Server Only", ariaLabel: "show logs from server source", selected: pendingAdvancedFilterState.source === LogSource$1.Server, onSelected: () => {
5462
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { source: LogSource$1.Server });
5463
+ dispatch({
5464
+ type: ActionType$6.UpdateAdvancedFilterState,
5465
+ advancedFilterState: newAdvancedFilterState,
5466
+ });
5467
+ }, noMarginOnRight: true, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light })),
5468
+ pendingAdvancedFilterState.source !== LogSource$1.Client && (React__default["default"].createElement("div", { className: "mt-2" },
5260
5469
  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;
5470
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Path"),
5471
+ 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) => {
5472
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { routePath: ((e.target.value)
5473
+ .trim()) });
5264
5474
  dispatch({
5265
5475
  type: ActionType$6.UpdateAdvancedFilterState,
5266
- advancedFilterState,
5476
+ advancedFilterState: newAdvancedFilterState,
5267
5477
  });
5268
5478
  } })),
5269
5479
  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;
5317
- dispatch({
5318
- type: ActionType$6.UpdateAdvancedFilterState,
5319
- advancedFilterState,
5320
- });
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;
5480
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Template"),
5481
+ 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) => {
5482
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { routeTemplate: ((e.target.value)
5483
+ .trim()) });
5324
5484
  dispatch({
5325
5485
  type: ActionType$6.UpdateAdvancedFilterState,
5326
- advancedFilterState,
5486
+ advancedFilterState: newAdvancedFilterState,
5327
5487
  });
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
- }
5488
+ } })))))));
5351
5489
  }
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
5490
  }
5491
+ // Filters UI
5492
+ const filters = (React__default["default"].createElement(React__default["default"].Fragment, null,
5493
+ filterToggles,
5494
+ filterDrawer && (React__default["default"].createElement(Drawer, { customBackgroundColor: "#eee" }, filterDrawer))));
5495
+ // Body that will be filled with the contents of the panel
5496
+ const body = (React__default["default"].createElement(React__default["default"].Fragment, null,
5497
+ loading && (React__default["default"].createElement("div", { className: "text-center p-5" },
5498
+ React__default["default"].createElement(LoadingSpinner, null))),
5499
+ !loading && (React__default["default"].createElement("div", null, filters)),
5500
+ !loading && logs.length === 0 && (React__default["default"].createElement("div", { className: "alert alert-warning text-center mt-2" },
5501
+ React__default["default"].createElement("h4", { className: "m-1" }, "No Logs to Show"),
5502
+ React__default["default"].createElement("div", null, "Either your filters are too strict or no matching logs have been created yet."))),
5503
+ React__default["default"].createElement("div", { className: loading || logs.length === 0 ? 'd-none' : undefined },
5504
+ React__default["default"].createElement(IntelliTable, { title: "Matching Logs", csvName: `Logs from ${getHumanReadableDate()}`, id: "logs", data: logs, columns: columns })),
5505
+ !loading && logs.length > 0 && paginationControls));
5623
5506
  /* ---------- Wrap in Modal --------- */
5624
5507
  return (React__default["default"].createElement("div", { className: "LogReviewer-outer-container" },
5625
5508
  React__default["default"].createElement("style", null, style$2),
5626
5509
  React__default["default"].createElement("div", { className: "LogReviewer-inner-container" },
5627
5510
  React__default["default"].createElement("div", { className: "LogReviewer-header" },
5628
5511
  React__default["default"].createElement("div", { className: "LogReviewer-header-title" },
5629
- React__default["default"].createElement("h3", { className: "text-center m-0" }, "Log Review Dashboard")),
5512
+ React__default["default"].createElement("h3", { className: "text-center" }, "Log Review Dashboard")),
5630
5513
  React__default["default"].createElement("div", { style: { width: 0 } },
5631
5514
  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
5515
  React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes })))),
@@ -15302,6 +15185,12 @@ const HOUR_IN_MS = 3600000;
15302
15185
  */
15303
15186
  const DAY_IN_MS = 86400000;
15304
15187
 
15188
+ /**
15189
+ * Path of the route for storing client-side logs
15190
+ * @author Gabe Abrams
15191
+ */
15192
+ const LOG_REVIEW_ROUTE_PATH_PREFIX = `/admin${ROUTE_PATH_PREFIX}/logs`;
15193
+
15305
15194
  /**
15306
15195
  * Route for checking the status of the current user's
15307
15196
  * access to log review
@@ -16365,6 +16254,7 @@ exports.ErrorWithCode = ErrorWithCode;
16365
16254
  exports.HOUR_IN_MS = HOUR_IN_MS;
16366
16255
  exports.IntelliTable = IntelliTable;
16367
16256
  exports.ItemPicker = ItemPicker;
16257
+ exports.LOG_REVIEW_GET_LOGS_ROUTE = LOG_REVIEW_GET_LOGS_ROUTE;
16368
16258
  exports.LOG_REVIEW_ROUTE_PATH_PREFIX = LOG_REVIEW_ROUTE_PATH_PREFIX;
16369
16259
  exports.LOG_REVIEW_STATUS_ROUTE = LOG_REVIEW_STATUS_ROUTE;
16370
16260
  exports.LOG_ROUTE_PATH = LOG_ROUTE_PATH;
@@ -16400,6 +16290,7 @@ exports.avg = avg;
16400
16290
  exports.canReviewLogs = canReviewLogs;
16401
16291
  exports.capitalize = capitalize;
16402
16292
  exports.ceilToNumDecimals = ceilToNumDecimals;
16293
+ exports.cloneDeep = cloneDeep;
16403
16294
  exports.combineClassNames = combineClassNames;
16404
16295
  exports.compareArraysByProp = compareArraysByProp;
16405
16296
  exports.confirm = confirm;