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/esm/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import * as React from 'react';
2
2
  import React__default, { useState, useRef, useEffect, useReducer, forwardRef, useContext, useLayoutEffect, createContext, useMemo, useCallback, Component, Fragment } from 'react';
3
- import { faExclamationTriangle, faCircle, faHourglassEnd, faDotCircle, faCheckSquare, faHourglass, faClipboard, faChevronDown, faChevronRight, faCloudDownloadAlt, faMinus, faCheckCircle, faXmarkCircle, faSort, faSortDown, faSortUp, faCalendar, faTag, faHammer, faList, faTimes, faSave, faTrash, faCog, faPlus } from '@fortawesome/free-solid-svg-icons';
3
+ import { faExclamationTriangle, faCircle, faHourglassEnd, faDotCircle, faCheckSquare, faHourglass, faClipboard, faChevronDown, faChevronRight, faCloudDownloadAlt, faMinus, faCheckCircle, faXmarkCircle, faSort, faSortDown, faSortUp, faArrowLeft, faArrowRight, faCalendar, faTag, faHammer, faList, faTimes, faSearch, faSave, faTrash, faCog, faPlus } from '@fortawesome/free-solid-svg-icons';
4
4
  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
5
5
  import { faCircle as faCircle$1, faSquareMinus, faSquare } from '@fortawesome/free-regular-svg-icons';
6
+ import clone from 'nanoclone';
6
7
 
7
8
  /*! *****************************************************************************
8
9
  Copyright (c) Microsoft Corporation.
@@ -3767,10 +3768,10 @@ const getHumanReadableDate = (dateOrTimestamp) => {
3767
3768
  };
3768
3769
 
3769
3770
  /**
3770
- * Path of the route for storing client-side logs
3771
+ * Path of the route for getting logs for log review
3771
3772
  * @author Gabe Abrams
3772
3773
  */
3773
- const LOG_REVIEW_ROUTE_PATH_PREFIX = `/admin${ROUTE_PATH_PREFIX}/logs`;
3774
+ const LOG_REVIEW_GET_LOGS_ROUTE = `/admin${ROUTE_PATH_PREFIX}/logs`;
3774
3775
 
3775
3776
  /**
3776
3777
  * Source of a log event
@@ -4375,6 +4376,71 @@ const IntelliTable = (props) => {
4375
4376
  } }, table)));
4376
4377
  };
4377
4378
 
4379
+ // Import React
4380
+ /*------------------------------------------------------------------------*/
4381
+ /* ------------------------------ Component ----------------------------- */
4382
+ /*------------------------------------------------------------------------*/
4383
+ const Pagination = (props) => {
4384
+ /*------------------------------------------------------------------------*/
4385
+ /* -------------------------------- Setup ------------------------------- */
4386
+ /*------------------------------------------------------------------------*/
4387
+ /* -------------- Props ------------- */
4388
+ // Destructure props
4389
+ const { currentPage, numPages, loading = false, onPageChanged, } = props;
4390
+ /*------------------------------------------------------------------------*/
4391
+ /* ------------------------------- Render ------------------------------- */
4392
+ /*------------------------------------------------------------------------*/
4393
+ // Compute pages to display
4394
+ const pages = [];
4395
+ const delta = 2; // how many pages to show on either side of current
4396
+ let start = Math.max(1, currentPage - delta);
4397
+ let end = Math.min(numPages, currentPage + delta);
4398
+ // If we are too close to the beginning or end, shift the window.
4399
+ if (currentPage - delta < 1) {
4400
+ end = Math.min(numPages, end + (1 - (currentPage - delta)));
4401
+ }
4402
+ if (currentPage + delta > numPages) {
4403
+ start = Math.max(1, start - ((currentPage + delta) - numPages));
4404
+ }
4405
+ for (let i = start; i <= end; i++) {
4406
+ pages.push(i);
4407
+ }
4408
+ // Render
4409
+ return (React__default.createElement("nav", { "aria-label": "Page navigation for logs", className: "mt-3" },
4410
+ React__default.createElement("ul", { className: "pagination justify-content-center" },
4411
+ React__default.createElement("li", { className: `page-item ${(currentPage <= 1 || loading) ? 'disabled' : ''}` },
4412
+ React__default.createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(currentPage - 1); }, disabled: currentPage <= 1 || loading, "aria-label": "Go to previous page" },
4413
+ React__default.createElement(FontAwesomeIcon, { icon: faArrowLeft }),
4414
+ ' ',
4415
+ "Prev")),
4416
+ (currentPage > 3 && pages[0] !== 1) && (React__default.createElement("li", { className: "page-item" },
4417
+ React__default.createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(1); }, disabled: loading, "aria-label": "Go to page 1" }, "1"))),
4418
+ currentPage > 4 && (React__default.createElement("li", { className: "page-item disabled" },
4419
+ React__default.createElement("span", { className: "page-link" }, "..."))),
4420
+ pages.map((pageNum) => {
4421
+ return (React__default.createElement("li", { key: pageNum, className: `page-item ${pageNum === currentPage ? 'active' : ''}` },
4422
+ React__default.createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(pageNum); }, disabled: loading, "aria-label": `Go to page ${pageNum}` }, pageNum)));
4423
+ }),
4424
+ currentPage < numPages - 3 && (React__default.createElement("li", { className: "page-item disabled" },
4425
+ React__default.createElement("span", { className: "page-link" }, "..."))),
4426
+ (currentPage < numPages - 2
4427
+ && pages[pages.length - 1] !== numPages) && (React__default.createElement("li", { className: "page-item" },
4428
+ React__default.createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(numPages); }, disabled: loading, "aria-label": "Go to last page" }, numPages))),
4429
+ React__default.createElement("li", { className: `page-item ${(currentPage >= numPages || loading) ? 'disabled' : ''}` },
4430
+ React__default.createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(currentPage + 1); }, disabled: currentPage >= numPages || loading, "aria-label": "Go to next page" },
4431
+ "Next",
4432
+ ' ',
4433
+ React__default.createElement(FontAwesomeIcon, { icon: faArrowRight }))))));
4434
+ };
4435
+
4436
+ /**
4437
+ * Deeply clones an object
4438
+ * @author Yuen Ler Chow
4439
+ * @param obj the object to clone
4440
+ * @returns a deep clone of the object
4441
+ */
4442
+ const cloneDeep = clone;
4443
+
4378
4444
  /**
4379
4445
  * Log reviewer panel that allows users (must be approved admins) to
4380
4446
  * review logs written by dce-reactkit
@@ -4730,7 +4796,7 @@ var ActionType$6;
4730
4796
  (function (ActionType) {
4731
4797
  // Show the loading bar
4732
4798
  ActionType["StartLoading"] = "start-loading";
4733
- // Finish loading one or more months of logs
4799
+ // Finish loading logs
4734
4800
  ActionType["FinishLoading"] = "finish-loading";
4735
4801
  // Reset filters to initial values
4736
4802
  ActionType["ResetFilters"] = "reset-filters";
@@ -4748,6 +4814,14 @@ var ActionType$6;
4748
4814
  ActionType["UpdateActionErrorFilterState"] = "update-action-error-filter-state";
4749
4815
  // Update the advanced filter state
4750
4816
  ActionType["UpdateAdvancedFilterState"] = "update-advanced-filter-state";
4817
+ // Set has another page
4818
+ ActionType["SetHasAnotherPage"] = "set-has-another-page";
4819
+ // Set the number of pages
4820
+ ActionType["SetNumPages"] = "set-num-pages";
4821
+ // Set page number
4822
+ ActionType["SetPageNumber"] = "set-page-number";
4823
+ // Reset user made filter change indicator
4824
+ ActionType["ResetUserMadeFilterChange"] = "reset-user-made-filter-change";
4751
4825
  })(ActionType$6 || (ActionType$6 = {}));
4752
4826
  /**
4753
4827
  * Reducer that executes actions
@@ -4761,7 +4835,7 @@ const reducer$7 = (state, action) => {
4761
4835
  return Object.assign(Object.assign({}, state), { loading: true });
4762
4836
  }
4763
4837
  case ActionType$6.FinishLoading: {
4764
- return Object.assign(Object.assign({}, state), { loading: false, logMap: action.logMap });
4838
+ return Object.assign(Object.assign({}, state), { loading: false, logs: action.logs });
4765
4839
  }
4766
4840
  case ActionType$6.ToggleFilterDrawer: {
4767
4841
  return Object.assign(Object.assign({}, state), { expandedFilterDrawer: (state.expandedFilterDrawer === action.filterDrawer
@@ -4772,22 +4846,34 @@ const reducer$7 = (state, action) => {
4772
4846
  return Object.assign(Object.assign({}, state), { expandedFilterDrawer: undefined });
4773
4847
  }
4774
4848
  case ActionType$6.ResetFilters: {
4775
- return Object.assign(Object.assign({}, state), { dateFilterState: action.initDateFilterState, contextFilterState: action.initContextFilterState, tagFilterState: action.initTagFilterState, actionErrorFilterState: action.initActionErrorFilterState, advancedFilterState: action.initAdvancedFilterState });
4849
+ return Object.assign(Object.assign({}, state), { pendingDateFilterState: action.initDateFilterState, pendingContextFilterState: action.initContextFilterState, pendingTagFilterState: action.initTagFilterState, pendingActionErrorFilterState: action.initActionErrorFilterState, pendingAdvancedFilterState: action.initAdvancedFilterState, pageNumber: 1 });
4776
4850
  }
4777
4851
  case ActionType$6.UpdateDateFilterState: {
4778
- return Object.assign(Object.assign({}, state), { dateFilterState: action.dateFilterState });
4852
+ return Object.assign(Object.assign({}, state), { pendingDateFilterState: action.dateFilterState, userMadeFilterChange: true });
4779
4853
  }
4780
4854
  case ActionType$6.UpdateContextFilterState: {
4781
- return Object.assign(Object.assign({}, state), { contextFilterState: action.contextFilterState });
4855
+ return Object.assign(Object.assign({}, state), { pendingContextFilterState: action.contextFilterState, userMadeFilterChange: true });
4782
4856
  }
4783
4857
  case ActionType$6.UpdateTagFilterState: {
4784
- return Object.assign(Object.assign({}, state), { tagFilterState: action.tagFilterState });
4858
+ return Object.assign(Object.assign({}, state), { pendingTagFilterState: action.tagFilterState, userMadeFilterChange: true });
4785
4859
  }
4786
4860
  case ActionType$6.UpdateActionErrorFilterState: {
4787
- return Object.assign(Object.assign({}, state), { actionErrorFilterState: action.actionErrorFilterState });
4861
+ return Object.assign(Object.assign({}, state), { pendingActionErrorFilterState: action.actionErrorFilterState, userMadeFilterChange: true });
4788
4862
  }
4789
4863
  case ActionType$6.UpdateAdvancedFilterState: {
4790
- return Object.assign(Object.assign({}, state), { advancedFilterState: action.advancedFilterState });
4864
+ return Object.assign(Object.assign({}, state), { pendingAdvancedFilterState: action.advancedFilterState, userMadeFilterChange: true });
4865
+ }
4866
+ case ActionType$6.SetHasAnotherPage: {
4867
+ return Object.assign(Object.assign({}, state), { hasAnotherPage: action.hasAnotherPage });
4868
+ }
4869
+ case ActionType$6.SetNumPages: {
4870
+ return Object.assign(Object.assign({}, state), { numPages: action.numPages });
4871
+ }
4872
+ case ActionType$6.SetPageNumber: {
4873
+ return Object.assign(Object.assign({}, state), { pageNumber: action.pageNumber });
4874
+ }
4875
+ case ActionType$6.ResetUserMadeFilterChange: {
4876
+ return Object.assign(Object.assign({}, state), { userMadeFilterChange: false });
4791
4877
  }
4792
4878
  default: {
4793
4879
  return state;
@@ -4858,7 +4944,7 @@ const LogReviewer = (props) => {
4858
4944
  else {
4859
4945
  // Case: subcontexts exist
4860
4946
  initContextFilterState[context] = {};
4861
- Object.values(contextMap[context]).forEach((subcontext) => {
4947
+ Object.keys(contextMap[context]).forEach((subcontext) => {
4862
4948
  // Skip self ("_")
4863
4949
  if (subcontext === '_') {
4864
4950
  return;
@@ -4906,125 +4992,105 @@ const LogReviewer = (props) => {
4906
4992
  // Initial state
4907
4993
  const initialState = {
4908
4994
  loading: true,
4909
- logMap: {},
4995
+ logs: [],
4910
4996
  expandedFilterDrawer: undefined,
4911
- dateFilterState: initDateFilterState,
4912
- contextFilterState: initContextFilterState,
4913
- tagFilterState: initTagFilterState,
4914
- actionErrorFilterState: initActionErrorFilterState,
4915
- advancedFilterState: initAdvancedFilterState,
4997
+ pendingDateFilterState: initDateFilterState,
4998
+ pendingContextFilterState: initContextFilterState,
4999
+ pendingTagFilterState: initTagFilterState,
5000
+ pendingActionErrorFilterState: initActionErrorFilterState,
5001
+ pendingAdvancedFilterState: initAdvancedFilterState,
5002
+ pageNumber: 1,
5003
+ hasAnotherPage: false,
5004
+ numPages: 1,
5005
+ userMadeFilterChange: false,
4916
5006
  };
4917
5007
  // Initialize state
4918
5008
  const [state, dispatch] = useReducer(reducer$7, initialState);
4919
5009
  // Destructure common state
4920
- const { loading, logMap, expandedFilterDrawer, dateFilterState, contextFilterState, tagFilterState, actionErrorFilterState, advancedFilterState, } = state;
5010
+ const { loading, logs, expandedFilterDrawer, pendingDateFilterState, pendingContextFilterState, pendingTagFilterState, pendingActionErrorFilterState, pendingAdvancedFilterState, pageNumber, numPages, userMadeFilterChange, } = state;
5011
+ /* -------------- Refs -------------- */
5012
+ // Initialize refs
5013
+ const activeFiltersRef = useRef({
5014
+ dateFilterState: JSON.parse(JSON.stringify(pendingDateFilterState)),
5015
+ contextFilterState: JSON.parse(JSON.stringify(pendingContextFilterState)),
5016
+ tagFilterState: JSON.parse(JSON.stringify(pendingTagFilterState)),
5017
+ actionErrorFilterState: JSON.parse(JSON.stringify(pendingActionErrorFilterState)),
5018
+ advancedFilterState: JSON.parse(JSON.stringify(pendingAdvancedFilterState)),
5019
+ });
4921
5020
  /*------------------------------------------------------------------------*/
4922
5021
  /* ------------------------- Component Functions ------------------------ */
4923
5022
  /*------------------------------------------------------------------------*/
4924
5023
  /**
4925
- * Get the list of year/month combos that need to be loaded given a new
4926
- * start or end date and the existing logMap
4927
- * @author Gabe Abrams
4928
- * @param newDateFilterState the new date filter state
4929
- * @returns list of year/month combos that need to be loaded
4930
- */
4931
- const listMonthsToLoad = (newDateFilterState) => {
4932
- // List of year/month combos that need to be loaded
4933
- const toLoad = [];
4934
- // Loop through dates
4935
- let { year, month } = newDateFilterState.startDate;
4936
- while (
4937
- // Earlier year
4938
- (year < newDateFilterState.endDate.year)
4939
- // Current year but included month
4940
- || (year === newDateFilterState.endDate.year
4941
- && month <= newDateFilterState.endDate.month)) {
4942
- // Add to list if not already loaded
4943
- if (!logMap[year]
4944
- || !logMap[year][month]) {
4945
- toLoad.push({
4946
- year,
4947
- month,
4948
- });
4949
- }
4950
- // Increment
4951
- month += 1;
4952
- if (month > 12) {
4953
- month -= 12;
4954
- year += 1;
4955
- }
4956
- }
4957
- // Return
4958
- return toLoad;
4959
- };
4960
- /**
4961
- * Handle updated start/end dates (updates state, loads if necessary)
4962
- * @author Gabe Abrams
4963
- * @param newDateFilterState the new date filter state
5024
+ * Fetch logs from the server based on current filters
5025
+ * @author Yuen Ler Chow
5026
+ * @param opts object containing all arguments
5027
+ * @param opts.filters the filters to apply
5028
+ * @param opts.pageNum the page number to fetch
5029
+ * @param opts.filtersChanged if true, the filters have changed
4964
5030
  */
4965
- const handleDateRangeUpdated = (newDateFilterState) => __awaiter(void 0, void 0, void 0, function* () {
4966
- // Update state
4967
- dispatch({
4968
- type: ActionType$6.UpdateDateFilterState,
4969
- dateFilterState: newDateFilterState,
4970
- });
4971
- // Check which year/month combos we need to load
4972
- const toLoad = listMonthsToLoad(newDateFilterState);
4973
- // If nothing to load, finished
4974
- if (toLoad.length === 0) {
4975
- return;
4976
- }
4977
- // Start loading
5031
+ const fetchLogs = (opts) => __awaiter(void 0, void 0, void 0, function* () {
5032
+ const { filters, pageNum, filtersChanged, } = opts;
4978
5033
  dispatch({
4979
5034
  type: ActionType$6.StartLoading,
4980
5035
  });
4981
- // Load required months
4982
5036
  try {
4983
- for (let i = 0; i < toLoad.length; i++) {
4984
- // Destructure
4985
- const { year, month } = toLoad[i];
4986
- // Load
4987
- let logs = [];
4988
- let pageNumber = 1;
4989
- let hasAnotherPage = true;
4990
- while (hasAnotherPage) {
4991
- const response = yield visitServerEndpoint({
4992
- path: `${LOG_REVIEW_ROUTE_PATH_PREFIX}/years/${year}/months/${month}`,
4993
- method: 'GET',
4994
- params: {
4995
- pageNumber,
4996
- },
4997
- });
4998
- logs = logs.concat(response.items);
4999
- hasAnotherPage = response.hasAnotherPage;
5000
- pageNumber += 1;
5001
- }
5002
- // Add to map
5003
- if (!logMap[year]) {
5004
- logMap[year] = {};
5005
- }
5006
- logMap[year][month] = logs;
5037
+ // Send filters to the server
5038
+ let fetchedLogs = [];
5039
+ // Get logs from server
5040
+ const response = yield visitServerEndpoint({
5041
+ path: LOG_REVIEW_GET_LOGS_ROUTE,
5042
+ method: 'GET',
5043
+ params: {
5044
+ pageNumber: pageNum,
5045
+ filters,
5046
+ countDocuments: filtersChanged,
5047
+ },
5048
+ });
5049
+ fetchedLogs = fetchedLogs.concat(response.items);
5050
+ dispatch({
5051
+ type: ActionType$6.SetHasAnotherPage,
5052
+ hasAnotherPage: response.hasAnotherPage,
5053
+ });
5054
+ if (filtersChanged && response.numPages !== undefined) {
5055
+ dispatch({
5056
+ type: ActionType$6.SetNumPages,
5057
+ numPages: response.numPages,
5058
+ });
5007
5059
  }
5060
+ // Update logs in state
5061
+ dispatch({
5062
+ type: ActionType$6.FinishLoading,
5063
+ logs: fetchedLogs,
5064
+ });
5065
+ // Update page number
5066
+ dispatch({
5067
+ type: ActionType$6.SetPageNumber,
5068
+ pageNumber: pageNum,
5069
+ });
5008
5070
  }
5009
5071
  catch (err) {
5010
5072
  return showFatalError(err);
5011
5073
  }
5012
- // Finish loading
5013
- dispatch({
5014
- type: ActionType$6.FinishLoading,
5015
- logMap,
5016
- });
5017
5074
  });
5018
5075
  /*------------------------------------------------------------------------*/
5019
5076
  /* ------------------------- Lifecycle Functions ------------------------ */
5020
5077
  /*------------------------------------------------------------------------*/
5021
5078
  /**
5022
- * Mount
5023
- * @author Gabe Abrams
5079
+ * Fetch logs on mount
5080
+ * @author Yuen Ler Chow
5024
5081
  */
5025
5082
  useEffect(() => {
5026
- // Perform initial load
5027
- handleDateRangeUpdated(dateFilterState);
5083
+ fetchLogs({
5084
+ filters: {
5085
+ dateFilterState: pendingDateFilterState,
5086
+ contextFilterState: pendingContextFilterState,
5087
+ tagFilterState: pendingTagFilterState,
5088
+ actionErrorFilterState: pendingActionErrorFilterState,
5089
+ advancedFilterState: pendingAdvancedFilterState,
5090
+ },
5091
+ pageNum: 1,
5092
+ filtersChanged: true,
5093
+ });
5028
5094
  }, []);
5029
5095
  /*------------------------------------------------------------------------*/
5030
5096
  /* ------------------------------- Render ------------------------------- */
@@ -5032,718 +5098,534 @@ const LogReviewer = (props) => {
5032
5098
  /*----------------------------------------*/
5033
5099
  /* --------------- Main UI -------------- */
5034
5100
  /*----------------------------------------*/
5035
- // Body that will be filled with the contents of the panel
5036
- let body;
5037
- /* ------------- Loading ------------ */
5038
- if (loading) {
5039
- body = (React__default.createElement("div", { className: "text-center p-5" },
5040
- React__default.createElement(LoadingSpinner, null)));
5041
- }
5042
- /* ------------ Review UI ----------- */
5043
- if (!loading) {
5044
- /*----------------------------------------*/
5045
- /* --------------- Filters -------------- */
5046
- /*----------------------------------------*/
5047
- // Filter toggle
5048
- const filterToggles = (React__default.createElement("div", { className: "LogReviewer-filter-toggles" },
5049
- React__default.createElement("h3", { className: "m-0" }, "Filters:"),
5050
- React__default.createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
5051
- React__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: () => {
5052
- dispatch({
5053
- type: ActionType$6.ToggleFilterDrawer,
5054
- filterDrawer: FilterDrawer.Date,
5055
- });
5056
- } },
5057
- React__default.createElement(FontAwesomeIcon, { icon: faCalendar, className: "me-2" }),
5058
- "Date"),
5059
- React__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: () => {
5060
- dispatch({
5061
- type: ActionType$6.ToggleFilterDrawer,
5062
- filterDrawer: FilterDrawer.Context,
5063
- });
5064
- } },
5065
- React__default.createElement(FontAwesomeIcon, { icon: faCircle, className: "me-2" }),
5066
- "Context"),
5067
- (LogMetadata.Tag && Object.keys(LogMetadata.Tag).length > 0) && (React__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: () => {
5068
- dispatch({
5069
- type: ActionType$6.ToggleFilterDrawer,
5070
- filterDrawer: FilterDrawer.Tag,
5071
- });
5072
- } },
5073
- React__default.createElement(FontAwesomeIcon, { icon: faTag, className: "me-2" }),
5074
- "Tag")),
5075
- React__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: () => {
5076
- dispatch({
5077
- type: ActionType$6.ToggleFilterDrawer,
5078
- filterDrawer: FilterDrawer.Action,
5079
- });
5080
- } },
5081
- React__default.createElement(FontAwesomeIcon, { icon: faHammer, className: "me-2" }),
5082
- "Action"),
5083
- React__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: () => {
5101
+ /*----------------------------------------*/
5102
+ /* ------------ Pagination -------------- */
5103
+ /*----------------------------------------*/
5104
+ const paginationControls = logs.length > 0 && (React__default.createElement(Pagination, { currentPage: pageNumber, numPages: numPages, loading: loading, onPageChanged: (targetPage) => {
5105
+ const { current: activeFilters } = activeFiltersRef;
5106
+ fetchLogs({
5107
+ filters: {
5108
+ dateFilterState: activeFilters.dateFilterState,
5109
+ contextFilterState: activeFilters.contextFilterState,
5110
+ tagFilterState: activeFilters.tagFilterState,
5111
+ actionErrorFilterState: activeFilters.actionErrorFilterState,
5112
+ advancedFilterState: activeFilters.advancedFilterState,
5113
+ },
5114
+ pageNum: targetPage,
5115
+ filtersChanged: false,
5116
+ });
5117
+ } }));
5118
+ /*----------------------------------------*/
5119
+ /* --------------- Filters -------------- */
5120
+ /*----------------------------------------*/
5121
+ // Filter toggle
5122
+ const filterToggles = (React__default.createElement("div", { className: "LogReviewer-filter-toggles" },
5123
+ React__default.createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
5124
+ React__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: () => {
5125
+ dispatch({
5126
+ type: ActionType$6.ToggleFilterDrawer,
5127
+ filterDrawer: FilterDrawer.Date,
5128
+ });
5129
+ } },
5130
+ React__default.createElement(FontAwesomeIcon, { icon: faCalendar, className: "me-2" }),
5131
+ "Date"),
5132
+ React__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: () => {
5133
+ dispatch({
5134
+ type: ActionType$6.ToggleFilterDrawer,
5135
+ filterDrawer: FilterDrawer.Context,
5136
+ });
5137
+ } },
5138
+ React__default.createElement(FontAwesomeIcon, { icon: faCircle, className: "me-2" }),
5139
+ "Context"),
5140
+ (LogMetadata.Tag && Object.keys(LogMetadata.Tag).length > 0) && (React__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: () => {
5141
+ dispatch({
5142
+ type: ActionType$6.ToggleFilterDrawer,
5143
+ filterDrawer: FilterDrawer.Tag,
5144
+ });
5145
+ } },
5146
+ React__default.createElement(FontAwesomeIcon, { icon: faTag, className: "me-2" }),
5147
+ "Tag")),
5148
+ React__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: () => {
5149
+ dispatch({
5150
+ type: ActionType$6.ToggleFilterDrawer,
5151
+ filterDrawer: FilterDrawer.Action,
5152
+ });
5153
+ } },
5154
+ React__default.createElement(FontAwesomeIcon, { icon: faHammer, className: "me-2" }),
5155
+ "Action"),
5156
+ React__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: () => {
5157
+ dispatch({
5158
+ type: ActionType$6.ToggleFilterDrawer,
5159
+ filterDrawer: FilterDrawer.Advanced,
5160
+ });
5161
+ } },
5162
+ React__default.createElement(FontAwesomeIcon, { icon: faList, className: "me-2" }),
5163
+ "Advanced"),
5164
+ React__default.createElement("button", { type: "button", id: "LogReviewer-reset-filters-button", className: "btn btn-light", "aria-label": "reset filters", onClick: () => {
5165
+ // Save active filters
5166
+ // Deep clone the initial filter states to avoid any reference issues
5167
+ // if the filter states are modified later
5168
+ activeFiltersRef.current = cloneDeep({
5169
+ dateFilterState: initDateFilterState,
5170
+ contextFilterState: initContextFilterState,
5171
+ tagFilterState: initTagFilterState,
5172
+ actionErrorFilterState: initActionErrorFilterState,
5173
+ advancedFilterState: initAdvancedFilterState,
5174
+ });
5175
+ fetchLogs({
5176
+ filters: {
5177
+ dateFilterState: initDateFilterState,
5178
+ contextFilterState: initContextFilterState,
5179
+ tagFilterState: initTagFilterState,
5180
+ actionErrorFilterState: initActionErrorFilterState,
5181
+ advancedFilterState: initAdvancedFilterState,
5182
+ },
5183
+ pageNum: 1,
5184
+ filtersChanged: true,
5185
+ });
5186
+ dispatch({
5187
+ type: ActionType$6.ResetFilters,
5188
+ initActionErrorFilterState,
5189
+ initAdvancedFilterState,
5190
+ initContextFilterState,
5191
+ initDateFilterState,
5192
+ initTagFilterState,
5193
+ });
5194
+ dispatch({
5195
+ type: ActionType$6.HideFilterDrawer,
5196
+ });
5197
+ } },
5198
+ React__default.createElement(FontAwesomeIcon, { icon: faTimes }),
5199
+ ' ',
5200
+ "Reset"),
5201
+ userMadeFilterChange && (React__default.createElement("button", { type: "button", id: "LogReviewer-submit-filters-button", className: "btn btn-primary ms-2", "aria-label": "submit filters", onClick: () => {
5202
+ dispatch({
5203
+ type: ActionType$6.HideFilterDrawer,
5204
+ });
5205
+ // Reset user made filter change indicator
5206
+ dispatch({
5207
+ type: ActionType$6.ResetUserMadeFilterChange,
5208
+ });
5209
+ // Save active filters
5210
+ // Deep clone the pending filter states to avoid any reference issues
5211
+ // if the filter states are modified later
5212
+ activeFiltersRef.current = cloneDeep({
5213
+ dateFilterState: pendingDateFilterState,
5214
+ contextFilterState: pendingContextFilterState,
5215
+ tagFilterState: pendingTagFilterState,
5216
+ actionErrorFilterState: pendingActionErrorFilterState,
5217
+ advancedFilterState: pendingAdvancedFilterState,
5218
+ });
5219
+ fetchLogs({
5220
+ filters: {
5221
+ dateFilterState: pendingDateFilterState,
5222
+ contextFilterState: pendingContextFilterState,
5223
+ tagFilterState: pendingTagFilterState,
5224
+ actionErrorFilterState: pendingActionErrorFilterState,
5225
+ advancedFilterState: pendingAdvancedFilterState,
5226
+ },
5227
+ pageNum: 1,
5228
+ filtersChanged: true,
5229
+ });
5230
+ } },
5231
+ React__default.createElement(FontAwesomeIcon, { icon: faSearch }),
5232
+ ' ',
5233
+ "Apply Filters")))));
5234
+ // Filter drawer
5235
+ let filterDrawer;
5236
+ if (expandedFilterDrawer) {
5237
+ if (expandedFilterDrawer === FilterDrawer.Date) {
5238
+ filterDrawer = (React__default.createElement(TabBox, { title: "Date" },
5239
+ React__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) => {
5240
+ const newDateFilterState = Object.assign(Object.assign({}, pendingDateFilterState), { startDate: { month, day, year } });
5084
5241
  dispatch({
5085
- type: ActionType$6.ToggleFilterDrawer,
5086
- filterDrawer: FilterDrawer.Advanced,
5242
+ type: ActionType$6.UpdateDateFilterState,
5243
+ dateFilterState: newDateFilterState,
5087
5244
  });
5088
- } },
5089
- React__default.createElement(FontAwesomeIcon, { icon: faList, className: "me-2" }),
5090
- "Advanced"),
5091
- React__default.createElement("button", { type: "button", id: "LogReviewer-reset-filters-button", className: "btn btn-light", "aria-label": "reset filters", onClick: () => {
5245
+ } }),
5246
+ ' ',
5247
+ "to",
5248
+ ' ',
5249
+ React__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) => {
5250
+ if (year < pendingDateFilterState.startDate.year
5251
+ || (year === pendingDateFilterState.startDate.year
5252
+ && month < pendingDateFilterState.startDate.month)
5253
+ || (year === pendingDateFilterState.startDate.year
5254
+ && month === pendingDateFilterState.startDate.month
5255
+ && day < pendingDateFilterState.startDate.day)) {
5256
+ return alert('Invalid End Date', 'The end date cannot be before the start date.');
5257
+ }
5258
+ const newDateFilterState = Object.assign(Object.assign({}, pendingDateFilterState), { endDate: { month, day, year } });
5092
5259
  dispatch({
5093
- type: ActionType$6.ResetFilters,
5094
- initActionErrorFilterState,
5095
- initAdvancedFilterState,
5096
- initContextFilterState,
5097
- initDateFilterState,
5098
- initTagFilterState,
5260
+ type: ActionType$6.UpdateDateFilterState,
5261
+ dateFilterState: newDateFilterState,
5099
5262
  });
5100
- } },
5101
- React__default.createElement(FontAwesomeIcon, { icon: faTimes }),
5102
- ' ',
5103
- "Reset"))));
5104
- // Filter drawer
5105
- let filterDrawer;
5106
- if (expandedFilterDrawer) {
5107
- if (expandedFilterDrawer === FilterDrawer.Date) {
5108
- filterDrawer = (React__default.createElement(TabBox, { title: "Date" },
5109
- React__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) => {
5110
- dateFilterState.startDate = { month, day, year };
5111
- handleDateRangeUpdated(dateFilterState);
5112
- } }),
5113
- ' ',
5114
- "to",
5115
- ' ',
5116
- React__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) => {
5117
- if (year < dateFilterState.startDate.year
5118
- || (year === dateFilterState.startDate.year
5119
- && month < dateFilterState.startDate.month)
5120
- || (year === dateFilterState.startDate.year
5121
- && month === dateFilterState.startDate.month
5122
- && day < dateFilterState.startDate.day)) {
5123
- return alert('Invalid Start Date', 'The start date cannot be before the end date.');
5124
- }
5125
- dateFilterState.endDate = { month, day, year };
5126
- handleDateRangeUpdated(dateFilterState);
5127
- } })));
5128
- }
5129
- else if (expandedFilterDrawer === FilterDrawer.Context) {
5130
- // Create item picker items
5131
- const builtInPickableItem = {
5132
- id: 'built-in-contexts',
5133
- name: 'Auto-logged',
5134
- isGroup: true,
5135
- children: [],
5136
- };
5137
- const pickableItems = [];
5138
- Object.keys(contextMap)
5139
- .forEach((context) => {
5140
- const value = contextMap[context];
5141
- if (typeof value === 'string') {
5142
- // No subcategories
5143
- const item = {
5144
- id: context,
5145
- name: genHumanReadableName(context),
5146
- isGroup: false,
5147
- checked: !!contextFilterState[context],
5148
- };
5149
- // Add built-in items to its own folder
5150
- const isBuiltIn = context in LogBuiltInMetadata.Context;
5151
- if (isBuiltIn) {
5152
- // Add to built-in pickable item
5153
- builtInPickableItem.children.push(item);
5154
- }
5155
- else {
5156
- // Add to pickable items list
5157
- pickableItems.push(item);
5158
- }
5159
- return;
5160
- }
5161
- // Has subcategories
5162
- const children = (Object.keys(value)
5163
- // Remove parent name
5164
- .filter((subcontext) => {
5165
- return subcontext !== '_';
5166
- })
5167
- // Create child pickable items
5168
- .map((subcontext) => {
5169
- return {
5170
- id: subcontext,
5171
- name: genHumanReadableName(subcontext),
5172
- isGroup: false,
5173
- checked: contextFilterState[context][subcontext],
5174
- };
5175
- }));
5263
+ } })));
5264
+ }
5265
+ else if (expandedFilterDrawer === FilterDrawer.Context) {
5266
+ // Create item picker items
5267
+ const builtInPickableItem = {
5268
+ id: 'built-in-contexts',
5269
+ name: 'Auto-logged',
5270
+ isGroup: true,
5271
+ children: [],
5272
+ };
5273
+ const pickableItems = [];
5274
+ Object.keys(contextMap)
5275
+ .forEach((context) => {
5276
+ const value = contextMap[context];
5277
+ if (typeof value === 'string') {
5278
+ // No subcategories
5176
5279
  const item = {
5177
5280
  id: context,
5178
5281
  name: genHumanReadableName(context),
5179
- isGroup: true,
5180
- children,
5282
+ isGroup: false,
5283
+ checked: !!pendingContextFilterState[context],
5181
5284
  };
5182
- pickableItems.push(item);
5183
- });
5184
- // Add built-in contexts to end ofl ist
5185
- pickableItems.push(builtInPickableItem);
5186
- // Create filter UI
5187
- filterDrawer = (React__default.createElement(ItemPicker, { title: "Context", items: pickableItems, onChanged: (updatedItems) => {
5188
- // Update our state
5189
- updatedItems.forEach((pickableItem) => {
5190
- if (pickableItem.isGroup) {
5191
- // Has subcontexts
5192
- if (pickableItem.id === 'built-in-contexts') {
5193
- // Built-in
5194
- // Treat as if these were top-level contexts
5195
- pickableItem.children.forEach((subcontextItem) => {
5196
- contextFilterState[subcontextItem.id] = ('checked' in subcontextItem
5197
- && subcontextItem.checked);
5198
- });
5199
- }
5200
- else {
5201
- // Not built-in
5202
- pickableItem.children.forEach((subcontextItem) => {
5203
- if (!subcontextItem.isGroup) {
5204
- contextFilterState[pickableItem.id][subcontextItem.id] = (subcontextItem.checked);
5205
- }
5206
- });
5207
- }
5285
+ // Add built-in items to its own folder
5286
+ const isBuiltIn = context in LogBuiltInMetadata.Context;
5287
+ if (isBuiltIn) {
5288
+ // Add to built-in pickable item
5289
+ builtInPickableItem.children.push(item);
5290
+ }
5291
+ else {
5292
+ // Add to pickable items list
5293
+ pickableItems.push(item);
5294
+ }
5295
+ return;
5296
+ }
5297
+ // Has subcategories
5298
+ const children = (Object.keys(value)
5299
+ // Remove parent name
5300
+ .filter((subcontext) => {
5301
+ return (subcontext !== '_');
5302
+ })
5303
+ // Create child pickable items
5304
+ .map((subcontext) => {
5305
+ return {
5306
+ id: subcontext,
5307
+ name: genHumanReadableName(subcontext),
5308
+ isGroup: false,
5309
+ checked: pendingContextFilterState[context][subcontext],
5310
+ };
5311
+ }));
5312
+ const item = {
5313
+ id: context,
5314
+ name: genHumanReadableName(context),
5315
+ isGroup: true,
5316
+ children,
5317
+ };
5318
+ pickableItems.push(item);
5319
+ });
5320
+ // Add built-in contexts to end of list
5321
+ pickableItems.push(builtInPickableItem);
5322
+ // Create filter UI
5323
+ filterDrawer = (React__default.createElement(ItemPicker, { title: "Context", items: pickableItems, onChanged: (updatedItems) => {
5324
+ // Update our state
5325
+ const newContextFilterState = Object.assign({}, pendingContextFilterState);
5326
+ updatedItems.forEach((pickableItem) => {
5327
+ if (pickableItem.isGroup) {
5328
+ // Has subcontexts
5329
+ if (pickableItem.id === 'built-in-contexts') {
5330
+ // Built-in
5331
+ // Treat as if these were top-level contexts
5332
+ pickableItem.children.forEach((subcontextItem) => {
5333
+ newContextFilterState[subcontextItem.id] = ('checked' in subcontextItem
5334
+ && subcontextItem.checked);
5335
+ });
5208
5336
  }
5209
5337
  else {
5210
- // No subcontexts
5211
- contextFilterState[pickableItem.id] = (pickableItem.checked);
5338
+ // Not built-in
5339
+ pickableItem.children.forEach((subcontextItem) => {
5340
+ if (!subcontextItem.isGroup) {
5341
+ newContextFilterState[pickableItem.id][subcontextItem.id] = (subcontextItem.checked);
5342
+ }
5343
+ });
5212
5344
  }
5213
- });
5214
- dispatch({
5215
- type: ActionType$6.UpdateContextFilterState,
5216
- contextFilterState,
5217
- });
5218
- } }));
5219
- }
5220
- else if (expandedFilterDrawer === FilterDrawer.Tag) {
5221
- // Create filter UI
5222
- filterDrawer = (React__default.createElement(TabBox, { title: "Tags" },
5223
- React__default.createElement("div", null, "If any tags are selected, logs must contain at least one (but not necessarily all) of the selected tags."),
5224
- React__default.createElement("div", { className: "d-flex gap-1 flex-wrap" }, Object.keys((_d = LogMetadata.Tag) !== null && _d !== void 0 ? _d : {})
5225
- .map((tag) => {
5226
- const description = genHumanReadableName(tag);
5227
- return (React__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) => {
5228
- tagFilterState[tag] = checked;
5345
+ }
5346
+ else {
5347
+ // No subcontexts
5348
+ newContextFilterState[pickableItem.id] = (pickableItem.checked);
5349
+ }
5350
+ });
5351
+ // Update state
5352
+ dispatch({
5353
+ type: ActionType$6.UpdateContextFilterState,
5354
+ contextFilterState: newContextFilterState,
5355
+ });
5356
+ } }));
5357
+ }
5358
+ else if (expandedFilterDrawer === FilterDrawer.Tag) {
5359
+ // Create filter UI
5360
+ filterDrawer = (React__default.createElement(TabBox, { title: "Tags" },
5361
+ React__default.createElement("div", null, "If any tags are selected, logs must contain at least one (but not necessarily all) of the selected tags."),
5362
+ React__default.createElement("div", { className: "d-flex gap-1 flex-wrap" }, Object.keys((_d = LogMetadata.Tag) !== null && _d !== void 0 ? _d : {})
5363
+ .map((tag) => {
5364
+ const description = genHumanReadableName(tag);
5365
+ return (React__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) => {
5366
+ const newTagFilterState = Object.assign(Object.assign({}, pendingTagFilterState), { [tag]: checked });
5367
+ dispatch({
5368
+ type: ActionType$6.UpdateTagFilterState,
5369
+ tagFilterState: newTagFilterState,
5370
+ });
5371
+ }, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }));
5372
+ }))));
5373
+ }
5374
+ else if (expandedFilterDrawer === FilterDrawer.Action) {
5375
+ // Create filter UI
5376
+ filterDrawer = (React__default.createElement(React__default.Fragment, null,
5377
+ React__default.createElement(TabBox, { title: "Log Type" },
5378
+ React__default.createElement(RadioButton, { id: "LogReviewer-type-all", text: "All Logs", onSelected: () => {
5379
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { type: undefined });
5380
+ dispatch({
5381
+ type: ActionType$6.UpdateActionErrorFilterState,
5382
+ actionErrorFilterState: newActionErrorFilterState,
5383
+ });
5384
+ }, ariaLabel: "show logs of all types", selected: pendingActionErrorFilterState.type === undefined, unselectedVariant: Variant$1.Light }),
5385
+ React__default.createElement(RadioButton, { id: "LogReviewer-type-action-only", text: "Action Logs Only", onSelected: () => {
5386
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { type: LogType$1.Action });
5387
+ dispatch({
5388
+ type: ActionType$6.UpdateActionErrorFilterState,
5389
+ actionErrorFilterState: newActionErrorFilterState,
5390
+ });
5391
+ }, ariaLabel: "only show action logs", selected: pendingActionErrorFilterState.type === LogType$1.Action, unselectedVariant: Variant$1.Light }),
5392
+ React__default.createElement(RadioButton, { id: "LogReviewer-type-error-only", text: "Action Error Only", onSelected: () => {
5393
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { type: LogType$1.Error });
5394
+ dispatch({
5395
+ type: ActionType$6.UpdateActionErrorFilterState,
5396
+ actionErrorFilterState: newActionErrorFilterState,
5397
+ });
5398
+ }, ariaLabel: "only show error logs", selected: pendingActionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light })),
5399
+ (pendingActionErrorFilterState.type === undefined
5400
+ || pendingActionErrorFilterState.type === LogType$1.Action) && (React__default.createElement(TabBox, { title: "Action Log Details" },
5401
+ React__default.createElement(ButtonInputGroup, { label: "Action", className: "mb-2", wrapButtonsAndAddGaps: true }, Object.keys(LogAction$1)
5402
+ .map((action) => {
5403
+ const description = genHumanReadableName(action);
5404
+ return (React__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) => {
5405
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { action: Object.assign(Object.assign({}, pendingActionErrorFilterState.action), { [action]: checked }) });
5229
5406
  dispatch({
5230
- type: ActionType$6.UpdateTagFilterState,
5231
- tagFilterState,
5407
+ type: ActionType$6.UpdateActionErrorFilterState,
5408
+ actionErrorFilterState: newActionErrorFilterState,
5232
5409
  });
5233
- } }));
5234
- }))));
5235
- }
5236
- else if (expandedFilterDrawer === FilterDrawer.Action) {
5237
- // Create filter UI
5238
- filterDrawer = (React__default.createElement(React__default.Fragment, null,
5239
- React__default.createElement(TabBox, { title: "Log Type" },
5240
- React__default.createElement(RadioButton, { id: "LogReviewer-type-all", text: "All Logs", onSelected: () => {
5241
- actionErrorFilterState.type = undefined;
5410
+ }, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }));
5411
+ })),
5412
+ React__default.createElement(ButtonInputGroup, { label: "Target", wrapButtonsAndAddGaps: true }, Object.keys(targetMap)
5413
+ .map((target) => {
5414
+ const description = genHumanReadableName(target);
5415
+ return (React__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) => {
5416
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { target: Object.assign(Object.assign({}, pendingActionErrorFilterState.target), { [target]: checked }) });
5242
5417
  dispatch({
5243
5418
  type: ActionType$6.UpdateActionErrorFilterState,
5244
- actionErrorFilterState,
5419
+ actionErrorFilterState: newActionErrorFilterState,
5245
5420
  });
5246
- }, ariaLabel: "show logs of all types", selected: actionErrorFilterState.type === undefined }),
5247
- React__default.createElement(RadioButton, { id: "LogReviewer-type-action-only", text: "Action Logs Only", onSelected: () => {
5248
- actionErrorFilterState.type = LogType$1.Action;
5421
+ }, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }));
5422
+ })))),
5423
+ (pendingActionErrorFilterState.type === undefined
5424
+ || pendingActionErrorFilterState.type === LogType$1.Error) && (React__default.createElement(TabBox, { title: "Error Log Details" },
5425
+ React__default.createElement("div", { className: "input-group mb-2" },
5426
+ React__default.createElement("span", { className: "input-group-text" }, "Error Message"),
5427
+ React__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) => {
5428
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { errorMessage: e.target.value });
5249
5429
  dispatch({
5250
5430
  type: ActionType$6.UpdateActionErrorFilterState,
5251
- actionErrorFilterState,
5431
+ actionErrorFilterState: newActionErrorFilterState,
5252
5432
  });
5253
- }, ariaLabel: "only show action logs", selected: actionErrorFilterState.type === LogType$1.Action }),
5254
- React__default.createElement(RadioButton, { id: "LogReviewer-type-error-only", text: "Action Error Only", onSelected: () => {
5255
- actionErrorFilterState.type = LogType$1.Error;
5433
+ } })),
5434
+ React__default.createElement("div", { className: "input-group mb-2" },
5435
+ React__default.createElement("span", { className: "input-group-text" }, "Error Code"),
5436
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for error code", value: pendingActionErrorFilterState.errorCode, placeholder: "e.g. GC22", onChange: (e) => {
5437
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { errorCode: ((e.target.value)
5438
+ .trim()
5439
+ .toUpperCase()) });
5256
5440
  dispatch({
5257
5441
  type: ActionType$6.UpdateActionErrorFilterState,
5258
- actionErrorFilterState,
5442
+ actionErrorFilterState: newActionErrorFilterState,
5259
5443
  });
5260
- }, ariaLabel: "only show error logs", selected: actionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true })),
5261
- (actionErrorFilterState.type === undefined
5262
- || actionErrorFilterState.type === LogType$1.Action) && (React__default.createElement(TabBox, { title: "Action Log Details" },
5263
- React__default.createElement(ButtonInputGroup, { label: "Action", className: "mb-2", wrapButtonsAndAddGaps: true }, Object.keys(LogAction$1)
5264
- .map((action) => {
5265
- const description = genHumanReadableName(action);
5266
- return (React__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) => {
5267
- actionErrorFilterState.action[action] = checked;
5268
- dispatch({
5269
- type: ActionType$6.UpdateActionErrorFilterState,
5270
- actionErrorFilterState,
5271
- });
5272
- } }));
5273
- })),
5274
- React__default.createElement(ButtonInputGroup, { label: "Target", wrapButtonsAndAddGaps: true }, Object.keys(targetMap)
5275
- .map((target) => {
5276
- const description = genHumanReadableName(target);
5277
- return (React__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) => {
5278
- actionErrorFilterState.target[target] = checked;
5279
- dispatch({
5280
- type: ActionType$6.UpdateActionErrorFilterState,
5281
- actionErrorFilterState,
5282
- });
5283
- } }));
5284
- })))),
5285
- (actionErrorFilterState.type === undefined
5286
- || actionErrorFilterState.type === LogType$1.Error) && (React__default.createElement(TabBox, { title: "Error Log Details" },
5287
- React__default.createElement("div", { className: "input-group mb-2" },
5288
- React__default.createElement("span", { className: "input-group-text" }, "Error Message"),
5289
- React__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) => {
5290
- actionErrorFilterState.errorMessage = e.target.value;
5291
- dispatch({
5292
- type: ActionType$6.UpdateActionErrorFilterState,
5293
- actionErrorFilterState,
5294
- });
5295
- } })),
5296
- React__default.createElement("div", { className: "input-group mb-2" },
5297
- React__default.createElement("span", { className: "input-group-text" }, "Error Code"),
5298
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for error code", value: actionErrorFilterState.errorCode, placeholder: "e.g. GC22", onChange: (e) => {
5299
- actionErrorFilterState.errorCode = ((e.target.value)
5300
- .trim()
5301
- .toUpperCase());
5302
- dispatch({
5303
- type: ActionType$6.UpdateActionErrorFilterState,
5304
- actionErrorFilterState,
5305
- });
5306
- } }))))));
5307
- }
5308
- else if (expandedFilterDrawer === FilterDrawer.Advanced) {
5309
- // Create advanced filter ui
5310
- filterDrawer = (React__default.createElement(React__default.Fragment, null,
5311
- React__default.createElement(TabBox, { title: "User" },
5312
- React__default.createElement("div", { className: "input-group mb-2" },
5313
- React__default.createElement("span", { className: "input-group-text" }, "User First Name"),
5314
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user first name", value: advancedFilterState.userFirstName, placeholder: "e.g. Divardo", onChange: (e) => {
5315
- advancedFilterState.userFirstName = e.target.value;
5316
- dispatch({
5317
- type: ActionType$6.UpdateAdvancedFilterState,
5318
- advancedFilterState,
5319
- });
5320
- } })),
5321
- React__default.createElement("div", { className: "input-group mb-2" },
5322
- React__default.createElement("span", { className: "input-group-text" }, "User Last Name"),
5323
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user last name", value: advancedFilterState.userLastName, placeholder: "e.g. Calicci", onChange: (e) => {
5324
- advancedFilterState.userLastName = e.target.value;
5325
- dispatch({
5326
- type: ActionType$6.UpdateAdvancedFilterState,
5327
- advancedFilterState,
5328
- });
5329
- } })),
5330
- React__default.createElement("div", { className: "input-group mb-2" },
5331
- React__default.createElement("span", { className: "input-group-text" }, "User Email"),
5332
- React__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) => {
5333
- advancedFilterState.userEmail = ((e.target.value)
5334
- .trim());
5335
- dispatch({
5336
- type: ActionType$6.UpdateAdvancedFilterState,
5337
- advancedFilterState,
5338
- });
5339
- } })),
5340
- React__default.createElement("div", { className: "input-group mb-2" },
5341
- React__default.createElement("span", { className: "input-group-text" }, "User Canvas Id"),
5342
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user canvas id", value: advancedFilterState.userId, placeholder: "e.g. 104985", onChange: (e) => {
5343
- const { value } = e.target;
5344
- // Only update if value contains only numbers
5345
- if (/^\d+$/.test(value)) {
5346
- advancedFilterState.userId = ((e.target.value)
5347
- .trim());
5348
- }
5349
- dispatch({
5350
- type: ActionType$6.UpdateAdvancedFilterState,
5351
- advancedFilterState,
5352
- });
5353
- } })),
5354
- React__default.createElement(ButtonInputGroup, { label: "Role" },
5355
- React__default.createElement(CheckboxButton, { text: "Students", onChanged: (checked) => {
5356
- advancedFilterState.includeLearners = checked;
5357
- dispatch({
5358
- type: ActionType$6.UpdateAdvancedFilterState,
5359
- advancedFilterState,
5360
- });
5361
- }, checked: advancedFilterState.includeLearners, ariaLabel: "show logs from students" }),
5362
- React__default.createElement(CheckboxButton, { text: "Teaching Team Members", onChanged: (checked) => {
5363
- advancedFilterState.includeTTMs = checked;
5444
+ } }))))));
5445
+ }
5446
+ else if (expandedFilterDrawer === FilterDrawer.Advanced) {
5447
+ // Create advanced filter ui
5448
+ filterDrawer = (React__default.createElement(React__default.Fragment, null,
5449
+ React__default.createElement(TabBox, { title: "User" },
5450
+ React__default.createElement("div", { className: "input-group mb-2" },
5451
+ React__default.createElement("span", { className: "input-group-text" }, "User First Name"),
5452
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user first name", value: pendingAdvancedFilterState.userFirstName, placeholder: "e.g. Divardo", onChange: (e) => {
5453
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userFirstName: e.target.value });
5454
+ dispatch({
5455
+ type: ActionType$6.UpdateAdvancedFilterState,
5456
+ advancedFilterState: newAdvancedFilterState,
5457
+ });
5458
+ } })),
5459
+ React__default.createElement("div", { className: "input-group mb-2" },
5460
+ React__default.createElement("span", { className: "input-group-text" }, "User Last Name"),
5461
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user last name", value: pendingAdvancedFilterState.userLastName, placeholder: "e.g. Calicci", onChange: (e) => {
5462
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userLastName: e.target.value });
5463
+ dispatch({
5464
+ type: ActionType$6.UpdateAdvancedFilterState,
5465
+ advancedFilterState: newAdvancedFilterState,
5466
+ });
5467
+ } })),
5468
+ React__default.createElement("div", { className: "input-group mb-2" },
5469
+ React__default.createElement("span", { className: "input-group-text" }, "User Email"),
5470
+ React__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) => {
5471
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userEmail: ((e.target.value)
5472
+ .trim()) });
5473
+ dispatch({
5474
+ type: ActionType$6.UpdateAdvancedFilterState,
5475
+ advancedFilterState: newAdvancedFilterState,
5476
+ });
5477
+ } })),
5478
+ React__default.createElement("div", { className: "input-group mb-2" },
5479
+ React__default.createElement("span", { className: "input-group-text" }, "User Canvas Id"),
5480
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user canvas id", value: pendingAdvancedFilterState.userId, placeholder: "e.g. 104985", onChange: (e) => {
5481
+ const { value } = e.target;
5482
+ // Only update if value contains only numbers
5483
+ if (/^\d+$/.test(value) || value === '') {
5484
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userId: ((e.target.value)
5485
+ .trim()) });
5364
5486
  dispatch({
5365
5487
  type: ActionType$6.UpdateAdvancedFilterState,
5366
- advancedFilterState,
5488
+ advancedFilterState: newAdvancedFilterState,
5367
5489
  });
5368
- }, checked: advancedFilterState.includeTTMs, ariaLabel: "show logs from teaching team members" }),
5369
- React__default.createElement(CheckboxButton, { text: "Admins", onChanged: (checked) => {
5370
- advancedFilterState.includeAdmins = checked;
5490
+ }
5491
+ } })),
5492
+ React__default.createElement(ButtonInputGroup, { label: "Role" },
5493
+ React__default.createElement(CheckboxButton, { text: "Students", onChanged: (checked) => {
5494
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { includeLearners: checked });
5495
+ dispatch({
5496
+ type: ActionType$6.UpdateAdvancedFilterState,
5497
+ advancedFilterState: newAdvancedFilterState,
5498
+ });
5499
+ }, checked: pendingAdvancedFilterState.includeLearners, ariaLabel: "show logs from students", checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }),
5500
+ React__default.createElement(CheckboxButton, { text: "Teaching Team Members", onChanged: (checked) => {
5501
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { includeTTMs: checked });
5502
+ dispatch({
5503
+ type: ActionType$6.UpdateAdvancedFilterState,
5504
+ advancedFilterState: newAdvancedFilterState,
5505
+ });
5506
+ }, checked: pendingAdvancedFilterState.includeTTMs, ariaLabel: "show logs from teaching team members", checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }),
5507
+ React__default.createElement(CheckboxButton, { text: "Admins", onChanged: (checked) => {
5508
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { includeAdmins: checked });
5509
+ dispatch({
5510
+ type: ActionType$6.UpdateAdvancedFilterState,
5511
+ advancedFilterState: newAdvancedFilterState,
5512
+ });
5513
+ }, checked: pendingAdvancedFilterState.includeAdmins, ariaLabel: "show logs from admins", checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }))),
5514
+ React__default.createElement(TabBox, { title: "Course" },
5515
+ React__default.createElement("div", { className: "input-group mb-2" },
5516
+ React__default.createElement("span", { className: "input-group-text" }, "Course Name"),
5517
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for course name", value: pendingAdvancedFilterState.courseName, placeholder: "e.g. GLC 200", onChange: (e) => {
5518
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { courseName: e.target.value });
5519
+ dispatch({
5520
+ type: ActionType$6.UpdateAdvancedFilterState,
5521
+ advancedFilterState: newAdvancedFilterState,
5522
+ });
5523
+ } })),
5524
+ React__default.createElement("div", { className: "input-group mb-2" },
5525
+ React__default.createElement("span", { className: "input-group-text" }, "Course Canvas Id"),
5526
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for course canvas id", value: pendingAdvancedFilterState.courseId, placeholder: "e.g. 15948", onChange: (e) => {
5527
+ const { value } = e.target;
5528
+ // Only update if value contains only numbers
5529
+ if (/^\d+$/.test(value) || value === '') {
5530
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { courseId: ((e.target.value)
5531
+ .trim()) });
5371
5532
  dispatch({
5372
5533
  type: ActionType$6.UpdateAdvancedFilterState,
5373
- advancedFilterState,
5534
+ advancedFilterState: newAdvancedFilterState,
5374
5535
  });
5375
- }, checked: advancedFilterState.includeAdmins, ariaLabel: "show logs from admins" }))),
5376
- React__default.createElement(TabBox, { title: "Course" },
5536
+ }
5537
+ } }))),
5538
+ React__default.createElement(TabBox, { title: "Device" },
5539
+ React__default.createElement(ButtonInputGroup, { label: "Device Type" },
5540
+ React__default.createElement(RadioButton, { text: "All Devices", ariaLabel: "show logs from all devices", selected: pendingAdvancedFilterState.isMobile === undefined, onSelected: () => {
5541
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { isMobile: undefined });
5542
+ dispatch({
5543
+ type: ActionType$6.UpdateAdvancedFilterState,
5544
+ advancedFilterState: newAdvancedFilterState,
5545
+ });
5546
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5547
+ React__default.createElement(RadioButton, { text: "Mobile Only", ariaLabel: "show logs from mobile devices", selected: pendingAdvancedFilterState.isMobile === true, onSelected: () => {
5548
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { isMobile: true });
5549
+ dispatch({
5550
+ type: ActionType$6.UpdateAdvancedFilterState,
5551
+ advancedFilterState: newAdvancedFilterState,
5552
+ });
5553
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5554
+ React__default.createElement(RadioButton, { text: "Desktop Only", ariaLabel: "show logs from desktop devices", selected: pendingAdvancedFilterState.isMobile === false, onSelected: () => {
5555
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { isMobile: false });
5556
+ dispatch({
5557
+ type: ActionType$6.UpdateAdvancedFilterState,
5558
+ advancedFilterState: newAdvancedFilterState,
5559
+ });
5560
+ }, noMarginOnRight: true, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }))),
5561
+ React__default.createElement(TabBox, { title: "Source" },
5562
+ React__default.createElement(ButtonInputGroup, { label: "Source Type" },
5563
+ React__default.createElement(RadioButton, { text: "Both", ariaLabel: "show logs from all sources", selected: pendingAdvancedFilterState.source === undefined, onSelected: () => {
5564
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { source: undefined });
5565
+ dispatch({
5566
+ type: ActionType$6.UpdateAdvancedFilterState,
5567
+ advancedFilterState: newAdvancedFilterState,
5568
+ });
5569
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5570
+ React__default.createElement(RadioButton, { text: "Client Only", ariaLabel: "show logs from client source", selected: pendingAdvancedFilterState.source === LogSource$1.Client, onSelected: () => {
5571
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { source: LogSource$1.Client });
5572
+ dispatch({
5573
+ type: ActionType$6.UpdateAdvancedFilterState,
5574
+ advancedFilterState: newAdvancedFilterState,
5575
+ });
5576
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5577
+ React__default.createElement(RadioButton, { text: "Server Only", ariaLabel: "show logs from server source", selected: pendingAdvancedFilterState.source === LogSource$1.Server, onSelected: () => {
5578
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { source: LogSource$1.Server });
5579
+ dispatch({
5580
+ type: ActionType$6.UpdateAdvancedFilterState,
5581
+ advancedFilterState: newAdvancedFilterState,
5582
+ });
5583
+ }, noMarginOnRight: true, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light })),
5584
+ pendingAdvancedFilterState.source !== LogSource$1.Client && (React__default.createElement("div", { className: "mt-2" },
5377
5585
  React__default.createElement("div", { className: "input-group mb-2" },
5378
- React__default.createElement("span", { className: "input-group-text" }, "Course Name"),
5379
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for course name", value: advancedFilterState.courseName, placeholder: "e.g. GLC 200", onChange: (e) => {
5380
- advancedFilterState.courseName = e.target.value;
5586
+ React__default.createElement("span", { className: "input-group-text" }, "Server Route Path"),
5587
+ React__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) => {
5588
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { routePath: ((e.target.value)
5589
+ .trim()) });
5381
5590
  dispatch({
5382
5591
  type: ActionType$6.UpdateAdvancedFilterState,
5383
- advancedFilterState,
5592
+ advancedFilterState: newAdvancedFilterState,
5384
5593
  });
5385
5594
  } })),
5386
5595
  React__default.createElement("div", { className: "input-group mb-2" },
5387
- React__default.createElement("span", { className: "input-group-text" }, "Course Canvas Id"),
5388
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for course canvas id", value: advancedFilterState.courseId, placeholder: "e.g. 15948", onChange: (e) => {
5389
- const { value } = e.target;
5390
- // Only update if value contains only numbers
5391
- if (/^\d+$/.test(value)) {
5392
- advancedFilterState.courseId = ((e.target.value)
5393
- .trim());
5394
- }
5395
- dispatch({
5396
- type: ActionType$6.UpdateAdvancedFilterState,
5397
- advancedFilterState,
5398
- });
5399
- } }))),
5400
- React__default.createElement(TabBox, { title: "Device" },
5401
- React__default.createElement(ButtonInputGroup, { label: "Device Type" },
5402
- React__default.createElement(RadioButton, { text: "All Devices", ariaLabel: "show logs from all devices", selected: advancedFilterState.isMobile === undefined, onSelected: () => {
5403
- advancedFilterState.isMobile = undefined;
5404
- dispatch({
5405
- type: ActionType$6.UpdateAdvancedFilterState,
5406
- advancedFilterState,
5407
- });
5408
- } }),
5409
- React__default.createElement(RadioButton, { text: "Mobile Only", ariaLabel: "show logs from mobile devices", selected: advancedFilterState.isMobile === true, onSelected: () => {
5410
- advancedFilterState.isMobile = true;
5411
- dispatch({
5412
- type: ActionType$6.UpdateAdvancedFilterState,
5413
- advancedFilterState,
5414
- });
5415
- } }),
5416
- React__default.createElement(RadioButton, { text: "Desktop Only", ariaLabel: "show logs from desktop devices", selected: advancedFilterState.isMobile === false, onSelected: () => {
5417
- advancedFilterState.isMobile = false;
5418
- dispatch({
5419
- type: ActionType$6.UpdateAdvancedFilterState,
5420
- advancedFilterState,
5421
- });
5422
- }, noMarginOnRight: true }))),
5423
- React__default.createElement(TabBox, { title: "Source" },
5424
- React__default.createElement(ButtonInputGroup, { label: "Source Type" },
5425
- React__default.createElement(RadioButton, { text: "Both", ariaLabel: "show logs from all sources", selected: advancedFilterState.source === undefined, onSelected: () => {
5426
- advancedFilterState.source = undefined;
5427
- dispatch({
5428
- type: ActionType$6.UpdateAdvancedFilterState,
5429
- advancedFilterState,
5430
- });
5431
- } }),
5432
- React__default.createElement(RadioButton, { text: "Client Only", ariaLabel: "show logs from client source", selected: advancedFilterState.source === LogSource$1.Client, onSelected: () => {
5433
- advancedFilterState.source = LogSource$1.Client;
5434
- dispatch({
5435
- type: ActionType$6.UpdateAdvancedFilterState,
5436
- advancedFilterState,
5437
- });
5438
- } }),
5439
- React__default.createElement(RadioButton, { text: "Server Only", ariaLabel: "show logs from server source", selected: advancedFilterState.source === LogSource$1.Server, onSelected: () => {
5440
- advancedFilterState.source = LogSource$1.Server;
5596
+ React__default.createElement("span", { className: "input-group-text" }, "Server Route Template"),
5597
+ React__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) => {
5598
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { routeTemplate: ((e.target.value)
5599
+ .trim()) });
5441
5600
  dispatch({
5442
5601
  type: ActionType$6.UpdateAdvancedFilterState,
5443
- advancedFilterState,
5602
+ advancedFilterState: newAdvancedFilterState,
5444
5603
  });
5445
- }, noMarginOnRight: true })),
5446
- advancedFilterState.source !== LogSource$1.Client && (React__default.createElement("div", { className: "mt-2" },
5447
- React__default.createElement("div", { className: "input-group mb-2" },
5448
- React__default.createElement("span", { className: "input-group-text" }, "Server Route Path"),
5449
- React__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) => {
5450
- advancedFilterState.courseName = ((e.target.value)
5451
- .trim());
5452
- dispatch({
5453
- type: ActionType$6.UpdateAdvancedFilterState,
5454
- advancedFilterState,
5455
- });
5456
- } })),
5457
- React__default.createElement("div", { className: "input-group mb-2" },
5458
- React__default.createElement("span", { className: "input-group-text" }, "Server Route Template"),
5459
- React__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) => {
5460
- advancedFilterState.courseName = ((e.target.value)
5461
- .trim());
5462
- dispatch({
5463
- type: ActionType$6.UpdateAdvancedFilterState,
5464
- advancedFilterState,
5465
- });
5466
- } })))))));
5467
- }
5604
+ } })))))));
5468
5605
  }
5469
- // Filters UI
5470
- const filters = (React__default.createElement(React__default.Fragment, null,
5471
- filterToggles,
5472
- filterDrawer && (React__default.createElement(Drawer, { customBackgroundColor: "#eee" }, filterDrawer))));
5473
- // Actually filter the logs
5474
- // > Perform filters
5475
- const logs = [];
5476
- Object.keys(logMap).forEach((year) => {
5477
- Object.keys(logMap[year]).forEach((month) => {
5478
- logMap[year][month].forEach((log) => {
5479
- /* ----------- Date Filter ---------- */
5480
- var _a;
5481
- // Before start date
5482
- if (
5483
- // Previous year
5484
- log.year < dateFilterState.startDate.year
5485
- // Same year, earlier month
5486
- || ((log.year === dateFilterState.startDate.year)
5487
- && (log.month < dateFilterState.startDate.month))
5488
- // Same year, same month, earlier day
5489
- || ((log.year === dateFilterState.startDate.year)
5490
- && (log.month === dateFilterState.startDate.month)
5491
- && (log.day < dateFilterState.startDate.day))) {
5492
- return;
5493
- }
5494
- // After end date
5495
- if (
5496
- // Later year
5497
- log.year > dateFilterState.endDate.year
5498
- // Same year, later month
5499
- || ((log.year === dateFilterState.endDate.year)
5500
- && (log.month > dateFilterState.endDate.month))
5501
- // Same year, same month, later day
5502
- || ((log.year === dateFilterState.endDate.year)
5503
- && (log.month === dateFilterState.endDate.month)
5504
- && (log.day > dateFilterState.endDate.day))) {
5505
- return;
5506
- }
5507
- /* --------- Context Filter --------- */
5508
- // Context doesn't match
5509
- if (
5510
- // Whole context is deselected
5511
- contextFilterState[log.context] === false
5512
- // None of the subcontexts are selected
5513
- || (
5514
- // Has subcontexts
5515
- typeof contextFilterState[log.context] !== 'boolean'
5516
- // None of the subcontexts are selected
5517
- && Object.values((_a = contextFilterState[log.context]) !== null && _a !== void 0 ? _a : {})
5518
- .every((isSelected) => {
5519
- return !isSelected;
5520
- }))) {
5521
- return;
5522
- }
5523
- // Subcontext doesn't match
5524
- if (
5525
- // Log context is not "uncategorized" (no point in further filters)
5526
- log.context !== LogBuiltInMetadata.Context.Uncategorized
5527
- // Log has a subcontext
5528
- && log.subcontext
5529
- // Context has subcontexts
5530
- && (contextFilterState[log.context]
5531
- && contextFilterState[log.context] !== false
5532
- && contextFilterState[log.context] !== true)
5533
- // Subcontext is not selected
5534
- && !contextFilterState[log.context][log.subcontext]) {
5535
- return;
5536
- }
5537
- /* -------------- Tags -------------- */
5538
- // No tags match
5539
- if (
5540
- // At least one tag is required
5541
- Object.values(tagFilterState)
5542
- .filter((isSelected) => {
5543
- return isSelected;
5544
- })
5545
- .length > 0
5546
- // No tags match
5547
- && log.tags.every((tag) => {
5548
- return !tagFilterState[tag];
5549
- })) {
5550
- return;
5551
- }
5552
- /* ------- Actions and Errors ------- */
5553
- // Log type doesn't match
5554
- if (
5555
- // Filter won't allow all types
5556
- actionErrorFilterState.type !== undefined
5557
- // Log type doesn't match
5558
- && actionErrorFilterState.type !== log.type) {
5559
- return;
5560
- }
5561
- // Filter errors
5562
- if (log.type === LogType$1.Error) {
5563
- // Message doesn't match
5564
- if (
5565
- // Message exists
5566
- log.errorMessage
5567
- // Message filter exists
5568
- && actionErrorFilterState.errorMessage.trim().length > 0
5569
- // Message doesn't match
5570
- && log.errorMessage.toLowerCase().includes(actionErrorFilterState.errorMessage.trim().toLowerCase())) {
5571
- return;
5572
- }
5573
- // Code doesn't match
5574
- if (
5575
- // Code exists
5576
- log.errorCode
5577
- // Code filter exists
5578
- && actionErrorFilterState.errorCode.trim().length > 0
5579
- // Code doesn't match
5580
- && log.errorCode.toUpperCase().includes(actionErrorFilterState.errorCode.trim().toUpperCase())) {
5581
- return;
5582
- }
5583
- }
5584
- // Filter actions
5585
- if (log.type === LogType$1.Action) {
5586
- // Target isn't selected
5587
- if (
5588
- // Target exists
5589
- log.target
5590
- // Target isn't selected
5591
- && !actionErrorFilterState.target[log.target]) {
5592
- return;
5593
- }
5594
- // Action
5595
- if (
5596
- // Action exists
5597
- log.action
5598
- // Action isn't selected
5599
- && !actionErrorFilterState.action[log.action]) {
5600
- return;
5601
- }
5602
- }
5603
- /* --------- Advanced Filter -------- */
5604
- // First name doesn't match
5605
- if (
5606
- // First name exists
5607
- log.userFirstName
5608
- // First name query doesn't match
5609
- && !log.userFirstName.toLowerCase().includes(advancedFilterState.userFirstName.toLowerCase().trim())) {
5610
- return;
5611
- }
5612
- // Last name doesn't match
5613
- if (
5614
- // Last name exists
5615
- log.userLastName
5616
- // Last name query doesn't match
5617
- && !log.userLastName.toLowerCase().includes(advancedFilterState.userLastName.toLowerCase().trim())) {
5618
- return;
5619
- }
5620
- // Email doesn't match
5621
- if (
5622
- // Email exists
5623
- log.userEmail
5624
- // Email query doesn't match
5625
- && !log.userEmail.toLowerCase().includes(advancedFilterState.userEmail.toLowerCase().trim())) {
5626
- return;
5627
- }
5628
- // User id doesn't match
5629
- if (
5630
- // User id exists
5631
- log.userId
5632
- // User id doesn't match
5633
- && !String(log.userId).includes(advancedFilterState.userId.trim())) {
5634
- return;
5635
- }
5636
- // Learner not allowed
5637
- if (
5638
- // User is a learner
5639
- log.isLearner
5640
- // Learners aren't included
5641
- && !advancedFilterState.includeLearners) {
5642
- return;
5643
- }
5644
- // TTM not allowed
5645
- if (
5646
- // User is a ttm
5647
- log.isTTM
5648
- // TTMs aren't included
5649
- && !advancedFilterState.includeTTMs) {
5650
- return;
5651
- }
5652
- // Admin not allowed
5653
- if (
5654
- // User is an admin
5655
- log.isAdmin
5656
- // Admins aren't included
5657
- && !advancedFilterState.includeAdmins) {
5658
- return;
5659
- }
5660
- // Course Id doesn't match
5661
- if (
5662
- // Course Id exists
5663
- log.courseId
5664
- // Course Id doesn't match
5665
- && !String(log.courseId).includes(advancedFilterState.courseId.trim())) {
5666
- return;
5667
- }
5668
- // Course name doesn't match
5669
- if (
5670
- // Course name exists
5671
- log.courseName
5672
- // Course name doesn't match
5673
- && !String(log.courseName).includes(advancedFilterState.courseName.trim())) {
5674
- return;
5675
- }
5676
- // Mobile filter doesn't match
5677
- if (
5678
- // Mobile filter exists
5679
- advancedFilterState.isMobile !== undefined
5680
- // Device info exists
5681
- && log.device
5682
- // Mobile filter doesn't match
5683
- && (advancedFilterState.isMobile !== log.device.isMobile)) {
5684
- return;
5685
- }
5686
- // Log source doesn't match
5687
- if (
5688
- // Source filter exists
5689
- advancedFilterState.source !== undefined
5690
- // Source info exists
5691
- && log.source
5692
- // Source filter doesn't match
5693
- && (advancedFilterState.source !== log.source)) {
5694
- return;
5695
- }
5696
- // Route path doesn't match (Only for server source)
5697
- if (
5698
- // Source is server
5699
- (log.source === LogSource$1.Server)
5700
- // Route path is being filtered
5701
- && (advancedFilterState.routePath.trim().length)
5702
- // Route path doesn't match
5703
- && !(log.routePath.includes(advancedFilterState.routePath.trim()))) {
5704
- return;
5705
- }
5706
- // Route template doesn't match (Only for server source)
5707
- if (
5708
- // Source is server
5709
- (log.source === LogSource$1.Server)
5710
- // Route template is being filtered
5711
- && (advancedFilterState.routeTemplate.trim().length)
5712
- // Route template doesn't match
5713
- && !(log.routeTemplate.includes(advancedFilterState.routeTemplate.trim()))) {
5714
- return;
5715
- }
5716
- /* -------------- Done -------------- */
5717
- // Made it past all filters. Add to the list
5718
- logs.push(log);
5719
- });
5720
- });
5721
- });
5722
- /*----------------------------------------*/
5723
- /* ---------------- Data ---------------- */
5724
- /*----------------------------------------*/
5725
- // Nothing to show notice
5726
- const noLogsNotice = (logs.length === 0
5727
- ? (React__default.createElement("div", { className: "alert alert-warning text-center mt-2" },
5728
- React__default.createElement("h4", { className: "m-1" }, "No Logs to Show"),
5729
- React__default.createElement("div", null, "Either your filters are too strict or no matching logs have been created yet.")))
5730
- : undefined);
5731
- // Create intelliTable
5732
- const dataTable = (React__default.createElement(IntelliTable, { title: "Matching Logs:", csvName: `Logs from ${getHumanReadableDate()}`, id: "logs", data: logs, columns: columns }));
5733
- // Main body
5734
- body = (React__default.createElement(React__default.Fragment, null,
5735
- filters,
5736
- React__default.createElement("div", { className: "mt-2" },
5737
- dataTable,
5738
- noLogsNotice)));
5739
5606
  }
5607
+ // Filters UI
5608
+ const filters = (React__default.createElement(React__default.Fragment, null,
5609
+ filterToggles,
5610
+ filterDrawer && (React__default.createElement(Drawer, { customBackgroundColor: "#eee" }, filterDrawer))));
5611
+ // Body that will be filled with the contents of the panel
5612
+ const body = (React__default.createElement(React__default.Fragment, null,
5613
+ loading && (React__default.createElement("div", { className: "text-center p-5" },
5614
+ React__default.createElement(LoadingSpinner, null))),
5615
+ !loading && (React__default.createElement("div", null, filters)),
5616
+ !loading && logs.length === 0 && (React__default.createElement("div", { className: "alert alert-warning text-center mt-2" },
5617
+ React__default.createElement("h4", { className: "m-1" }, "No Logs to Show"),
5618
+ React__default.createElement("div", null, "Either your filters are too strict or no matching logs have been created yet."))),
5619
+ React__default.createElement("div", { className: loading || logs.length === 0 ? 'd-none' : undefined },
5620
+ React__default.createElement(IntelliTable, { title: "Matching Logs", csvName: `Logs from ${getHumanReadableDate()}`, id: "logs", data: logs, columns: columns })),
5621
+ !loading && logs.length > 0 && paginationControls));
5740
5622
  /* ---------- Wrap in Modal --------- */
5741
5623
  return (React__default.createElement("div", { className: "LogReviewer-outer-container" },
5742
5624
  React__default.createElement("style", null, style$2),
5743
5625
  React__default.createElement("div", { className: "LogReviewer-inner-container" },
5744
5626
  React__default.createElement("div", { className: "LogReviewer-header" },
5745
5627
  React__default.createElement("div", { className: "LogReviewer-header-title" },
5746
- React__default.createElement("h3", { className: "text-center m-0" }, "Log Review Dashboard")),
5628
+ React__default.createElement("h3", { className: "text-center" }, "Log Review Dashboard")),
5747
5629
  React__default.createElement("div", { style: { width: 0 } },
5748
5630
  React__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 },
5749
5631
  React__default.createElement(FontAwesomeIcon, { icon: faTimes })))),
@@ -15419,6 +15301,12 @@ const HOUR_IN_MS = 3600000;
15419
15301
  */
15420
15302
  const DAY_IN_MS = 86400000;
15421
15303
 
15304
+ /**
15305
+ * Path of the route for storing client-side logs
15306
+ * @author Gabe Abrams
15307
+ */
15308
+ const LOG_REVIEW_ROUTE_PATH_PREFIX = `/admin${ROUTE_PATH_PREFIX}/logs`;
15309
+
15422
15310
  /**
15423
15311
  * Route for checking the status of the current user's
15424
15312
  * access to log review
@@ -16463,5 +16351,5 @@ var DayOfWeek;
16463
16351
  })(DayOfWeek || (DayOfWeek = {}));
16464
16352
  var DayOfWeek$1 = DayOfWeek;
16465
16353
 
16466
- export { AppWrapper, AutoscrollToBottomContainer, ButtonInputGroup, CSVDownloadButton, CheckboxButton, CopiableBox, DAY_IN_MS, DBEntryFieldType$1 as DBEntryFieldType, DBEntryManagerPanel, DayOfWeek$1 as DayOfWeek, Drawer, Dropdown, DropdownItemType$1 as DropdownItemType, DynamicWord, ErrorBox, ErrorWithCode, HOUR_IN_MS, IntelliTable, ItemPicker, LOG_REVIEW_ROUTE_PATH_PREFIX, LOG_REVIEW_STATUS_ROUTE, LOG_ROUTE_PATH, LoadingSpinner, LogAction$1 as LogAction, LogBuiltInMetadata, LogLevel$1 as LogLevel, LogReviewer, LogSource$1 as LogSource, LogType$1 as LogType, MINUTE_IN_MS, Modal, ModalButtonType$1 as ModalButtonType, ModalSize$1 as ModalSize, ModalType$1 as ModalType, MultiSwitch, ParamType$1 as ParamType, PopFailureMark, PopPendingMark, PopSuccessMark, RadioButton, ReactKitErrorCode$1 as ReactKitErrorCode, SimpleDateChooser, SimpleTimeChooser, TabBox, ToggleSwitch, Tooltip, Variant$1 as Variant, abbreviate, addFatalErrorHandler, alert, avg, canReviewLogs, capitalize, ceilToNumDecimals, combineClassNames, compareArraysByProp, confirm, everyAsync, extractProp, filterAsync, floorToNumDecimals, forEachAsync, forceNumIntoBounds, genCSV, genCommaList, getHumanReadableDate, getLocalTimeInfo, getMonthName, getOrdinal, getPartOfDay, getTimeInfoInET, getWordCount, idify, initClient, isMobileOrTablet, leaveToURL, logClientEvent, makeLinksClickable, mapAsync, onlyKeepLetters, padDecimalZeros, padZerosLeft, parallelLimit, prefixWithAOrAn, prompt, roundToNumDecimals, setClientEventMetadataPopulator, showFatalError, shuffleArray, someAsync, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, useForceRender, validateEmail, validatePhoneNumber, validateString, visitServerEndpoint, waitMs };
16354
+ export { AppWrapper, AutoscrollToBottomContainer, ButtonInputGroup, CSVDownloadButton, CheckboxButton, CopiableBox, DAY_IN_MS, DBEntryFieldType$1 as DBEntryFieldType, DBEntryManagerPanel, DayOfWeek$1 as DayOfWeek, Drawer, Dropdown, DropdownItemType$1 as DropdownItemType, DynamicWord, ErrorBox, ErrorWithCode, HOUR_IN_MS, IntelliTable, ItemPicker, LOG_REVIEW_GET_LOGS_ROUTE, LOG_REVIEW_ROUTE_PATH_PREFIX, LOG_REVIEW_STATUS_ROUTE, LOG_ROUTE_PATH, LoadingSpinner, LogAction$1 as LogAction, LogBuiltInMetadata, LogLevel$1 as LogLevel, LogReviewer, LogSource$1 as LogSource, LogType$1 as LogType, MINUTE_IN_MS, Modal, ModalButtonType$1 as ModalButtonType, ModalSize$1 as ModalSize, ModalType$1 as ModalType, MultiSwitch, ParamType$1 as ParamType, PopFailureMark, PopPendingMark, PopSuccessMark, RadioButton, ReactKitErrorCode$1 as ReactKitErrorCode, SimpleDateChooser, SimpleTimeChooser, TabBox, ToggleSwitch, Tooltip, Variant$1 as Variant, abbreviate, addFatalErrorHandler, alert, avg, canReviewLogs, capitalize, ceilToNumDecimals, cloneDeep, combineClassNames, compareArraysByProp, confirm, everyAsync, extractProp, filterAsync, floorToNumDecimals, forEachAsync, forceNumIntoBounds, genCSV, genCommaList, getHumanReadableDate, getLocalTimeInfo, getMonthName, getOrdinal, getPartOfDay, getTimeInfoInET, getWordCount, idify, initClient, isMobileOrTablet, leaveToURL, logClientEvent, makeLinksClickable, mapAsync, onlyKeepLetters, padDecimalZeros, padZerosLeft, parallelLimit, prefixWithAOrAn, prompt, roundToNumDecimals, setClientEventMetadataPopulator, showFatalError, shuffleArray, someAsync, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, useForceRender, validateEmail, validatePhoneNumber, validateString, visitServerEndpoint, waitMs };
16467
16355
  //# sourceMappingURL=index.js.map