dce-reactkit 3.2.2-beta.2 → 3.2.2-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +357 -198
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/ButtonInputGroup.d.ts +1 -0
- package/dist/cjs/types/components/ItemPicker/NestableItemList.d.ts +1 -0
- package/dist/cjs/types/components/ItemPicker/index.d.ts +1 -0
- package/dist/cjs/types/index.d.ts +2 -1
- package/dist/cjs/types/server/initServer.d.ts +1 -1
- package/dist/cjs/types/types/LogBuiltInMetadata.d.ts +1 -1
- package/dist/esm/index.js +357 -199
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/ButtonInputGroup.d.ts +1 -0
- package/dist/esm/types/components/ItemPicker/NestableItemList.d.ts +1 -0
- package/dist/esm/types/components/ItemPicker/index.d.ts +1 -0
- package/dist/esm/types/index.d.ts +2 -1
- package/dist/esm/types/server/initServer.d.ts +1 -1
- package/dist/esm/types/types/LogBuiltInMetadata.d.ts +1 -1
- package/dist/index.d.ts +12 -3
- package/package.json +1 -1
- package/src/components/ButtonInputGroup.tsx +4 -1
- package/src/components/IntelliTable.tsx +4 -4
- package/src/components/ItemPicker/NestableItemList.tsx +32 -14
- package/src/components/ItemPicker/index.tsx +4 -0
- package/src/components/LogReviewer.tsx +281 -131
- package/src/components/SimpleDateChooser.tsx +20 -7
- package/src/constants/LOG_REVIEW_STATUS_ROUTE.ts +1 -1
- package/src/helpers/genRouteHandler.ts +1 -1
- package/src/helpers/logClientEvent.tsx +6 -1
- package/src/index.ts +2 -0
- package/src/server/initServer.ts +93 -73
- package/src/types/LogBuiltInMetadata.ts +5 -5
package/dist/cjs/index.js
CHANGED
|
@@ -519,17 +519,29 @@ const LOG_ROUTE_PATH = `${ROUTE_PATH_PREFIX}/log`;
|
|
|
519
519
|
const LogBuiltInMetadata = {
|
|
520
520
|
// Contexts
|
|
521
521
|
Context: {
|
|
522
|
-
Uncategorized: '
|
|
523
|
-
ServerRenderedErrorPage: '
|
|
524
|
-
ServerEndpointError: '
|
|
525
|
-
ClientFatalError: '
|
|
522
|
+
Uncategorized: 'Uncategorized',
|
|
523
|
+
ServerRenderedErrorPage: 'ServerRenderedErrorPage',
|
|
524
|
+
ServerEndpointError: 'ServerEndpointError',
|
|
525
|
+
ClientFatalError: 'ClientFatalError',
|
|
526
526
|
},
|
|
527
527
|
// Targets
|
|
528
528
|
Target: {
|
|
529
|
-
|
|
529
|
+
NoTarget: 'NoTarget',
|
|
530
530
|
},
|
|
531
531
|
};
|
|
532
532
|
|
|
533
|
+
/**
|
|
534
|
+
* Allowed log levels
|
|
535
|
+
* @author Gabe Abrams
|
|
536
|
+
*/
|
|
537
|
+
var LogLevel;
|
|
538
|
+
(function (LogLevel) {
|
|
539
|
+
LogLevel["Warn"] = "Warn";
|
|
540
|
+
LogLevel["Info"] = "Info";
|
|
541
|
+
LogLevel["Debug"] = "Debug";
|
|
542
|
+
})(LogLevel || (LogLevel = {}));
|
|
543
|
+
var LogLevel$1 = LogLevel;
|
|
544
|
+
|
|
533
545
|
// Keep track of whether or not session expiry has already been handled
|
|
534
546
|
let sessionAlreadyExpired = false;
|
|
535
547
|
/*------------------------------------------------------------------------*/
|
|
@@ -648,7 +660,7 @@ const visitServerEndpoint = (opts) => __awaiter(void 0, void 0, void 0, function
|
|
|
648
660
|
* @author Gabe Abrams
|
|
649
661
|
*/
|
|
650
662
|
const logClientEvent = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
651
|
-
var _a, _b, _c, _d, _e, _f;
|
|
663
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
652
664
|
return visitServerEndpoint({
|
|
653
665
|
path: LOG_ROUTE_PATH,
|
|
654
666
|
method: 'POST',
|
|
@@ -657,8 +669,9 @@ const logClientEvent = (opts) => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
657
669
|
? opts.context
|
|
658
670
|
: ((_b = ((_a = opts.context) !== null && _a !== void 0 ? _a : {})._) !== null && _b !== void 0 ? _b : LogBuiltInMetadata.Context.Uncategorized)),
|
|
659
671
|
subcontext: ((_c = opts.subcontext) !== null && _c !== void 0 ? _c : LogBuiltInMetadata.Context.Uncategorized),
|
|
660
|
-
|
|
661
|
-
|
|
672
|
+
level: ((_d = opts.level) !== null && _d !== void 0 ? _d : LogLevel$1.Info),
|
|
673
|
+
tags: JSON.stringify((_e = opts.tags) !== null && _e !== void 0 ? _e : []),
|
|
674
|
+
metadata: JSON.stringify((_f = opts.metadata) !== null && _f !== void 0 ? _f : {}),
|
|
662
675
|
errorMessage: (opts.error
|
|
663
676
|
? opts.error.message
|
|
664
677
|
: undefined),
|
|
@@ -669,7 +682,7 @@ const logClientEvent = (opts) => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
669
682
|
? opts.error.stack
|
|
670
683
|
: undefined),
|
|
671
684
|
target: (opts.action
|
|
672
|
-
? ((
|
|
685
|
+
? ((_g = opts.target) !== null && _g !== void 0 ? _g : LogBuiltInMetadata.Target.NoTarget)
|
|
673
686
|
: undefined),
|
|
674
687
|
action: (opts.action
|
|
675
688
|
? opts.action
|
|
@@ -1175,14 +1188,14 @@ const ButtonInputGroup = (props) => {
|
|
|
1175
1188
|
/*------------------------------------------------------------------------*/
|
|
1176
1189
|
/* -------------- Props ------------- */
|
|
1177
1190
|
// Destructure all props
|
|
1178
|
-
const { label, minLabelWidth, children, } = props;
|
|
1191
|
+
const { label, minLabelWidth, children, className, } = props;
|
|
1179
1192
|
/*------------------------------------------------------------------------*/
|
|
1180
1193
|
/* Render */
|
|
1181
1194
|
/*------------------------------------------------------------------------*/
|
|
1182
1195
|
/*----------------------------------------*/
|
|
1183
1196
|
/* Main UI */
|
|
1184
1197
|
/*----------------------------------------*/
|
|
1185
|
-
return (React__default["default"].createElement("div", { className:
|
|
1198
|
+
return (React__default["default"].createElement("div", { className: `input-group ${className !== null && className !== void 0 ? className : ''}` },
|
|
1186
1199
|
React__default["default"].createElement("div", { className: "input-group-prepend d-flex w-100" },
|
|
1187
1200
|
React__default["default"].createElement("span", { className: "input-group-text", style: {
|
|
1188
1201
|
minWidth: (minLabelWidth !== null && minLabelWidth !== void 0 ? minLabelWidth : undefined),
|
|
@@ -1352,12 +1365,25 @@ const SimpleDateChooser = (props) => {
|
|
|
1352
1365
|
// Figure out which days are allowed
|
|
1353
1366
|
const days = [];
|
|
1354
1367
|
const numDaysInMonth = (new Date(year, month, 0)).getDate();
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1368
|
+
if (chooseFromPast) {
|
|
1369
|
+
// Past selection
|
|
1370
|
+
const numDaysToAdd = ((month === today.month)
|
|
1371
|
+
? today.day // Current month, only add up to today
|
|
1372
|
+
: numDaysInMonth // Past month, add all days
|
|
1373
|
+
);
|
|
1374
|
+
for (let day = 1; day <= numDaysToAdd; day++) {
|
|
1375
|
+
days.push(day);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
else {
|
|
1379
|
+
// Future selection: add all remaining days of the month
|
|
1380
|
+
const firstDay = (month === today.month
|
|
1381
|
+
? today.day // Current month: start at current date
|
|
1382
|
+
: 1 // Future month: start at beginning of month
|
|
1383
|
+
);
|
|
1384
|
+
for (let day = firstDay; day <= numDaysInMonth; day++) {
|
|
1385
|
+
days.push(day);
|
|
1386
|
+
}
|
|
1361
1387
|
}
|
|
1362
1388
|
choices.push({
|
|
1363
1389
|
choiceName: `${monthName} ${year}`,
|
|
@@ -1898,24 +1924,25 @@ const CopiableBox = (props) => {
|
|
|
1898
1924
|
/**
|
|
1899
1925
|
* Reusable nested item picker
|
|
1900
1926
|
* @author Yuen Ler Chow
|
|
1927
|
+
* @author Gabe Abrams
|
|
1901
1928
|
*/
|
|
1902
1929
|
/* ------------- Actions ------------ */
|
|
1903
1930
|
// Types of actions
|
|
1904
1931
|
var ActionType$2;
|
|
1905
1932
|
(function (ActionType) {
|
|
1906
|
-
// Toggle whether
|
|
1907
|
-
ActionType["
|
|
1933
|
+
// Toggle whether a child are being shown
|
|
1934
|
+
ActionType["ToggleChild"] = "toggle-child";
|
|
1908
1935
|
})(ActionType$2 || (ActionType$2 = {}));
|
|
1909
1936
|
/**
|
|
1910
1937
|
* Reducer that executes actions
|
|
1911
|
-
* @author
|
|
1938
|
+
* @author Gabe Abrams
|
|
1912
1939
|
* @param state current state
|
|
1913
1940
|
* @param action action to execute
|
|
1914
1941
|
*/
|
|
1915
1942
|
const reducer$2 = (state, action) => {
|
|
1916
1943
|
switch (action.type) {
|
|
1917
|
-
case ActionType$2.
|
|
1918
|
-
return {
|
|
1944
|
+
case ActionType$2.ToggleChild: {
|
|
1945
|
+
return Object.assign(Object.assign({}, state), { childExpanded: Object.assign(Object.assign({}, state.childExpanded), { [String(action.id)]: !state.childExpanded[String(action.id)] }) });
|
|
1919
1946
|
}
|
|
1920
1947
|
default: {
|
|
1921
1948
|
return state;
|
|
@@ -1933,14 +1960,19 @@ const NestableItemList = (props) => {
|
|
|
1933
1960
|
// Destructure all props
|
|
1934
1961
|
const { items, onChanged, } = props;
|
|
1935
1962
|
/* -------------- State ------------- */
|
|
1963
|
+
// Create initial map of child expanded booleans
|
|
1964
|
+
const initChildExpanded = {};
|
|
1965
|
+
items.forEach((item) => {
|
|
1966
|
+
initChildExpanded[String(item.id)] = false;
|
|
1967
|
+
});
|
|
1936
1968
|
// Initial state
|
|
1937
1969
|
const initialState = {
|
|
1938
|
-
|
|
1970
|
+
childExpanded: initChildExpanded,
|
|
1939
1971
|
};
|
|
1940
1972
|
// Initialize state
|
|
1941
1973
|
const [state, dispatch] = React.useReducer(reducer$2, initialState);
|
|
1942
1974
|
// Destructure common state
|
|
1943
|
-
const {
|
|
1975
|
+
const { childExpanded, } = state;
|
|
1944
1976
|
/*------------------------------------------------------------------------*/
|
|
1945
1977
|
/* Component Functions */
|
|
1946
1978
|
/*------------------------------------------------------------------------*/
|
|
@@ -2028,14 +2060,15 @@ const NestableItemList = (props) => {
|
|
|
2028
2060
|
backgroundColor: 'transparent',
|
|
2029
2061
|
}, type: "button", onClick: () => {
|
|
2030
2062
|
dispatch({
|
|
2031
|
-
type: ActionType$2.
|
|
2063
|
+
type: ActionType$2.ToggleChild,
|
|
2064
|
+
id: item.id,
|
|
2032
2065
|
});
|
|
2033
|
-
}, "aria-label": `${
|
|
2034
|
-
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon:
|
|
2066
|
+
}, "aria-label": `${childExpanded[item.id] ? 'Hide' : 'Show'} items in ${item.name}` },
|
|
2067
|
+
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: childExpanded[item.id] ? freeSolidSvgIcons.faChevronDown : freeSolidSvgIcons.faChevronRight })))),
|
|
2035
2068
|
React__default["default"].createElement(CheckboxButton, { className: `NestableItemList-CheckboxButton-${item.id}`, text: item.name, checked: item.isGroup ? allChecked(item.children) : item.checked, dashed: item.isGroup ? !noneChecked(item.children) : false, onChanged: (checked) => {
|
|
2036
2069
|
onChanged(changeChecked(item.id, checked, items));
|
|
2037
2070
|
}, ariaLabel: `Select ${item.name}`, checkedVariant: Variant$1.Light }),
|
|
2038
|
-
item.isGroup &&
|
|
2071
|
+
(item.isGroup && childExpanded[item.id]) && (React__default["default"].createElement("div", { className: "NestableItemList-children-container", style: {
|
|
2039
2072
|
paddingLeft: '2.2rem',
|
|
2040
2073
|
} },
|
|
2041
2074
|
React__default["default"].createElement(NestableItemList, { items: item.children, onChanged: (updatedItems) => {
|
|
@@ -2057,7 +2090,7 @@ const ItemPicker = (props) => {
|
|
|
2057
2090
|
/*------------------------------------------------------------------------*/
|
|
2058
2091
|
/* -------------- Props ------------- */
|
|
2059
2092
|
// Destructure all props
|
|
2060
|
-
const { title, items, onChanged, } = props;
|
|
2093
|
+
const { title, items, onChanged, noBottomMargin, } = props;
|
|
2061
2094
|
/*------------------------------------------------------------------------*/
|
|
2062
2095
|
/* Component Functions */
|
|
2063
2096
|
/*------------------------------------------------------------------------*/
|
|
@@ -2067,7 +2100,7 @@ const ItemPicker = (props) => {
|
|
|
2067
2100
|
/*----------------------------------------*/
|
|
2068
2101
|
/* Main UI */
|
|
2069
2102
|
/*----------------------------------------*/
|
|
2070
|
-
return (React__default["default"].createElement(TabBox, { title: title },
|
|
2103
|
+
return (React__default["default"].createElement(TabBox, { title: title, noBottomMargin: noBottomMargin },
|
|
2071
2104
|
React__default["default"].createElement("div", { style: { overflowX: 'auto' } },
|
|
2072
2105
|
React__default["default"].createElement(NestableItemList, { items: items, onChanged: onChanged }))));
|
|
2073
2106
|
};
|
|
@@ -2384,9 +2417,9 @@ const IntelliTable = (props) => {
|
|
|
2384
2417
|
// Create the cell UI
|
|
2385
2418
|
return (React__default["default"].createElement("th", { key: column.param, scope: "col", id: `IntelliTable-${id}-header-${column.param}` },
|
|
2386
2419
|
React__default["default"].createElement("div", { className: "d-flex align-items-center justify-content-center flex-row h-100" },
|
|
2387
|
-
React__default["default"].createElement("
|
|
2420
|
+
React__default["default"].createElement("span", { className: "text-nowrap" }, column.title),
|
|
2388
2421
|
React__default["default"].createElement("div", null,
|
|
2389
|
-
React__default["default"].createElement("button", { type: "button", className: "btn btn-light", "aria-label": sortButtonAriaLabel, onClick: () => {
|
|
2422
|
+
React__default["default"].createElement("button", { type: "button", className: "btn btn-light btn-sm ms-1", "aria-label": sortButtonAriaLabel, onClick: () => {
|
|
2390
2423
|
dispatch({
|
|
2391
2424
|
type: ActionType$1.ToggleSortColumn,
|
|
2392
2425
|
param: column.param,
|
|
@@ -2534,7 +2567,7 @@ const IntelliTable = (props) => {
|
|
|
2534
2567
|
React__default["default"].createElement("h3", { className: "m-0" }, title),
|
|
2535
2568
|
React__default["default"].createElement("div", { className: "flex-grow-1 text-end" },
|
|
2536
2569
|
React__default["default"].createElement(CSVDownloadButton, { "aria-label": `download data as csv for ${title}`, id: `IntelliTable-${id}-download-as-csv`, filename: `${title}.csv`, csv: csv }),
|
|
2537
|
-
React__default["default"].createElement("button", { type: "button", className: "btn btn-secondary", "aria-label": `show panel for customizing which columns show in table ${title}`, id: `IntelliTable-${id}-show-column-customization-modal`, onClick: () => {
|
|
2570
|
+
React__default["default"].createElement("button", { type: "button", className: "btn btn-secondary ms-2", "aria-label": `show panel for customizing which columns show in table ${title}`, id: `IntelliTable-${id}-show-column-customization-modal`, onClick: () => {
|
|
2538
2571
|
dispatch({
|
|
2539
2572
|
type: ActionType$1.ToggleColVisCusModalVisibility,
|
|
2540
2573
|
});
|
|
@@ -2598,6 +2631,11 @@ const style = `
|
|
|
2598
2631
|
border: 0.05rem solid black;
|
|
2599
2632
|
border-radius: 0.5rem;
|
|
2600
2633
|
overflow: hidden;
|
|
2634
|
+
padding: 0.7rem;
|
|
2635
|
+
|
|
2636
|
+
/* Solid background */
|
|
2637
|
+
background-color: white;
|
|
2638
|
+
color: black;
|
|
2601
2639
|
|
|
2602
2640
|
/* Place contents in flex column */
|
|
2603
2641
|
flex-direction: column;
|
|
@@ -2624,6 +2662,27 @@ const style = `
|
|
|
2624
2662
|
/* Vertical scroll */
|
|
2625
2663
|
overflow-y: auto;
|
|
2626
2664
|
}
|
|
2665
|
+
|
|
2666
|
+
.LogReviewer-header-close-button {
|
|
2667
|
+
border: 0 !important;
|
|
2668
|
+
background-color: transparent !important;
|
|
2669
|
+
padding-top: 0 !important;
|
|
2670
|
+
padding-bottom: 0 !important;
|
|
2671
|
+
padding-right: 1em !important;
|
|
2672
|
+
margin: 0 !important;
|
|
2673
|
+
color: #444 !important;
|
|
2674
|
+
|
|
2675
|
+
right: 0 !important;
|
|
2676
|
+
position: absolute !important;
|
|
2677
|
+
}
|
|
2678
|
+
.LogReviewer-header-close-button:hover {
|
|
2679
|
+
border: 0 !important;
|
|
2680
|
+
background-color: transparent !important;
|
|
2681
|
+
padding-top: 0 !important;
|
|
2682
|
+
padding-bottom: 0 !important;
|
|
2683
|
+
margin: 0 !important;
|
|
2684
|
+
color: #000 !important;
|
|
2685
|
+
}
|
|
2627
2686
|
`;
|
|
2628
2687
|
/*------------------------------------------------------------------------*/
|
|
2629
2688
|
/* Static Functions */
|
|
@@ -2643,7 +2702,7 @@ const genHumanReadableName = (machineReadableName) => {
|
|
|
2643
2702
|
// Uppercase! Add a space before
|
|
2644
2703
|
humanReadableName += ' ';
|
|
2645
2704
|
}
|
|
2646
|
-
humanReadableName +=
|
|
2705
|
+
humanReadableName += char;
|
|
2647
2706
|
});
|
|
2648
2707
|
// Trim and return
|
|
2649
2708
|
return humanReadableName.trim();
|
|
@@ -2705,7 +2764,19 @@ const reducer = (state, action) => {
|
|
|
2705
2764
|
return Object.assign(Object.assign({}, state), { contextFilterState: action.contextFilterState });
|
|
2706
2765
|
}
|
|
2707
2766
|
case ActionType.UpdateTagFilterState: {
|
|
2708
|
-
|
|
2767
|
+
const { tagFilterState } = action;
|
|
2768
|
+
// Select all if every tag is deselected
|
|
2769
|
+
const numTagsSelected = (Object.values(tagFilterState)
|
|
2770
|
+
.filter((isSelected) => {
|
|
2771
|
+
return isSelected;
|
|
2772
|
+
})
|
|
2773
|
+
.length);
|
|
2774
|
+
if (numTagsSelected === 0) {
|
|
2775
|
+
Object.keys(tagFilterState).forEach((tag) => {
|
|
2776
|
+
tagFilterState[tag] = true;
|
|
2777
|
+
});
|
|
2778
|
+
}
|
|
2779
|
+
return Object.assign(Object.assign({}, state), { tagFilterState });
|
|
2709
2780
|
}
|
|
2710
2781
|
case ActionType.UpdateActionErrorFilterState: {
|
|
2711
2782
|
return Object.assign(Object.assign({}, state), { actionErrorFilterState: action.actionErrorFilterState });
|
|
@@ -2725,10 +2796,33 @@ const LogReviewer = (props) => {
|
|
|
2725
2796
|
/*------------------------------------------------------------------------*/
|
|
2726
2797
|
/* Setup */
|
|
2727
2798
|
/*------------------------------------------------------------------------*/
|
|
2728
|
-
var _a, _b, _c, _d, _e;
|
|
2799
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
2729
2800
|
/* -------------- Props ------------- */
|
|
2730
2801
|
// Destructure props
|
|
2731
2802
|
const { LogMetadata, onClose, } = props;
|
|
2803
|
+
// Add built-in LogMetadata
|
|
2804
|
+
// > Add "uncategorized" subcontext to each context
|
|
2805
|
+
Object.keys((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {}).forEach((context) => {
|
|
2806
|
+
if (LogMetadata.Context
|
|
2807
|
+
// Context has children already
|
|
2808
|
+
&& typeof LogMetadata.Context[context] !== 'string') {
|
|
2809
|
+
LogMetadata.Context[context][LogBuiltInMetadata.Context.Uncategorized] = (LogBuiltInMetadata.Context.Uncategorized);
|
|
2810
|
+
}
|
|
2811
|
+
});
|
|
2812
|
+
// > Add built-in contexts
|
|
2813
|
+
LogMetadata.Context = ((_b = LogMetadata.Context) !== null && _b !== void 0 ? _b : {});
|
|
2814
|
+
Object.keys(LogBuiltInMetadata.Context).forEach((context) => {
|
|
2815
|
+
if (LogMetadata.Context) {
|
|
2816
|
+
LogMetadata.Context[context] = context;
|
|
2817
|
+
}
|
|
2818
|
+
});
|
|
2819
|
+
// > Add built-in targets
|
|
2820
|
+
LogMetadata.Target = ((_c = LogMetadata.Target) !== null && _c !== void 0 ? _c : {});
|
|
2821
|
+
Object.keys(LogBuiltInMetadata.Target).forEach((target) => {
|
|
2822
|
+
if (LogMetadata.Target) {
|
|
2823
|
+
LogMetadata.Target[target] = target;
|
|
2824
|
+
}
|
|
2825
|
+
});
|
|
2732
2826
|
/* -------------- State ------------- */
|
|
2733
2827
|
// Create initial date filter state
|
|
2734
2828
|
const today = getTimeInfoInET();
|
|
@@ -2748,7 +2842,7 @@ const LogReviewer = (props) => {
|
|
|
2748
2842
|
};
|
|
2749
2843
|
// Create initial context filter state
|
|
2750
2844
|
const initContextFilterState = {};
|
|
2751
|
-
Object.keys((
|
|
2845
|
+
Object.keys((_d = LogMetadata.Context) !== null && _d !== void 0 ? _d : {}).forEach((context) => {
|
|
2752
2846
|
var _a, _b;
|
|
2753
2847
|
const contextValue = ((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {})[context];
|
|
2754
2848
|
if (typeof contextValue === 'string') {
|
|
@@ -2766,7 +2860,7 @@ const LogReviewer = (props) => {
|
|
|
2766
2860
|
});
|
|
2767
2861
|
// Create initial tag filter state
|
|
2768
2862
|
const initTagFilterState = {};
|
|
2769
|
-
Object.values((
|
|
2863
|
+
Object.values((_e = LogMetadata.Tag) !== null && _e !== void 0 ? _e : {}).forEach((tagValue) => {
|
|
2770
2864
|
initTagFilterState[tagValue] = true;
|
|
2771
2865
|
});
|
|
2772
2866
|
// Create advanced filter state
|
|
@@ -2793,12 +2887,16 @@ const LogReviewer = (props) => {
|
|
|
2793
2887
|
target: {},
|
|
2794
2888
|
action: {},
|
|
2795
2889
|
};
|
|
2796
|
-
Object.values((
|
|
2890
|
+
Object.values((_f = LogMetadata.Target) !== null && _f !== void 0 ? _f : {}).forEach((target) => {
|
|
2797
2891
|
initActionErrorFilterState.target[target] = true;
|
|
2798
2892
|
});
|
|
2799
2893
|
Object.values(LogAction$1).forEach((action) => {
|
|
2800
2894
|
initActionErrorFilterState.action[action] = true;
|
|
2801
2895
|
});
|
|
2896
|
+
// Add built-in targets
|
|
2897
|
+
Object.values(LogBuiltInMetadata.Target).forEach((target) => {
|
|
2898
|
+
initActionErrorFilterState.target[target] = true;
|
|
2899
|
+
});
|
|
2802
2900
|
// Initial state
|
|
2803
2901
|
const initialState = {
|
|
2804
2902
|
loading: true,
|
|
@@ -2832,15 +2930,18 @@ const LogReviewer = (props) => {
|
|
|
2832
2930
|
let month = newDateFilterState.startDate.month;
|
|
2833
2931
|
while (
|
|
2834
2932
|
// Earlier year
|
|
2835
|
-
(year
|
|
2933
|
+
(year < newDateFilterState.endDate.year)
|
|
2836
2934
|
// Current year but included month
|
|
2837
2935
|
|| (year === newDateFilterState.endDate.year
|
|
2838
2936
|
&& month <= newDateFilterState.endDate.month)) {
|
|
2839
|
-
// Add to list
|
|
2840
|
-
|
|
2841
|
-
year
|
|
2842
|
-
|
|
2843
|
-
|
|
2937
|
+
// Add to list if not already loaded
|
|
2938
|
+
if (!logMap[year]
|
|
2939
|
+
|| !logMap[year][month]) {
|
|
2940
|
+
toLoad.push({
|
|
2941
|
+
year,
|
|
2942
|
+
month,
|
|
2943
|
+
});
|
|
2944
|
+
}
|
|
2844
2945
|
// Increment
|
|
2845
2946
|
month += 1;
|
|
2846
2947
|
if (month > 12) {
|
|
@@ -2864,6 +2965,7 @@ const LogReviewer = (props) => {
|
|
|
2864
2965
|
});
|
|
2865
2966
|
// Check which year/month combos we need to load
|
|
2866
2967
|
const toLoad = listMonthsToLoad(newDateFilterState);
|
|
2968
|
+
console.log('Filter update:', newDateFilterState, toLoad, logMap);
|
|
2867
2969
|
// If nothing to load, finished
|
|
2868
2970
|
if (toLoad.length === 0) {
|
|
2869
2971
|
return;
|
|
@@ -2928,10 +3030,10 @@ const LogReviewer = (props) => {
|
|
|
2928
3030
|
/* Filters */
|
|
2929
3031
|
/*----------------------------------------*/
|
|
2930
3032
|
// Filter toggle
|
|
2931
|
-
const filterToggles = (React__default["default"].createElement("div", { className: "LogReviewer-filter-toggles
|
|
3033
|
+
const filterToggles = (React__default["default"].createElement("div", { className: "LogReviewer-filter-toggles" },
|
|
2932
3034
|
React__default["default"].createElement("h3", { className: "m-0" }, "Filters:"),
|
|
2933
|
-
React__default["default"].createElement("div", { className: "LogReviewer-filter-toggle-buttons" },
|
|
2934
|
-
React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-date-filter-drawer", className: `btn btn-${FilterDrawer.Date === expandedFilterDrawer} me-2`, "aria-label": "toggle date filter drawer", onClick: () => {
|
|
3035
|
+
React__default["default"].createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
|
|
3036
|
+
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: () => {
|
|
2935
3037
|
dispatch({
|
|
2936
3038
|
type: ActionType.ToggleFilterDrawer,
|
|
2937
3039
|
filterDrawer: FilterDrawer.Date,
|
|
@@ -2939,7 +3041,7 @@ const LogReviewer = (props) => {
|
|
|
2939
3041
|
} },
|
|
2940
3042
|
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCalendar, className: "me-2" }),
|
|
2941
3043
|
"Date"),
|
|
2942
|
-
React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-context-filter-drawer", className: `btn btn-${FilterDrawer.Context === expandedFilterDrawer} me-2`, "aria-label": "toggle context filter drawer", onClick: () => {
|
|
3044
|
+
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: () => {
|
|
2943
3045
|
dispatch({
|
|
2944
3046
|
type: ActionType.ToggleFilterDrawer,
|
|
2945
3047
|
filterDrawer: FilterDrawer.Context,
|
|
@@ -2947,15 +3049,15 @@ const LogReviewer = (props) => {
|
|
|
2947
3049
|
} },
|
|
2948
3050
|
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "me-2" }),
|
|
2949
3051
|
"Context"),
|
|
2950
|
-
React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-tag-filter-drawer", className: `btn btn-${FilterDrawer.Tag === expandedFilterDrawer} me-2`, "aria-label": "toggle tag filter drawer", onClick: () => {
|
|
3052
|
+
(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: () => {
|
|
2951
3053
|
dispatch({
|
|
2952
3054
|
type: ActionType.ToggleFilterDrawer,
|
|
2953
3055
|
filterDrawer: FilterDrawer.Tag,
|
|
2954
3056
|
});
|
|
2955
3057
|
} },
|
|
2956
3058
|
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTag, className: "me-2" }),
|
|
2957
|
-
"Tag"),
|
|
2958
|
-
React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-action-filter-drawer", className: `btn btn-${FilterDrawer.Action === expandedFilterDrawer} me-2`, "aria-label": "toggle action and error filter drawer", onClick: () => {
|
|
3059
|
+
"Tag")),
|
|
3060
|
+
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: () => {
|
|
2959
3061
|
dispatch({
|
|
2960
3062
|
type: ActionType.ToggleFilterDrawer,
|
|
2961
3063
|
filterDrawer: FilterDrawer.Action,
|
|
@@ -2963,7 +3065,7 @@ const LogReviewer = (props) => {
|
|
|
2963
3065
|
} },
|
|
2964
3066
|
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faHammer, className: "me-2" }),
|
|
2965
3067
|
"Action"),
|
|
2966
|
-
React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-advanced-filter-drawer", className: `btn btn-${FilterDrawer.Advanced === expandedFilterDrawer}`, "aria-label": "toggle advanced filter drawer", onClick: () => {
|
|
3068
|
+
React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-advanced-filter-drawer", className: `btn btn-${FilterDrawer.Advanced === expandedFilterDrawer ? 'warning' : 'light'}`, "aria-label": "toggle advanced filter drawer", onClick: () => {
|
|
2967
3069
|
dispatch({
|
|
2968
3070
|
type: ActionType.ToggleFilterDrawer,
|
|
2969
3071
|
filterDrawer: FilterDrawer.Advanced,
|
|
@@ -2976,25 +3078,29 @@ const LogReviewer = (props) => {
|
|
|
2976
3078
|
if (expandedFilterDrawer) {
|
|
2977
3079
|
if (expandedFilterDrawer === FilterDrawer.Date) {
|
|
2978
3080
|
filterDrawer = (React__default["default"].createElement(TabBox, { title: "Date" },
|
|
2979
|
-
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, onChange: (month, day, year) => {
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
dateFilterState: Object.assign(Object.assign({}, dateFilterState), { startDate: { month, day, year } }),
|
|
2983
|
-
});
|
|
3081
|
+
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, chooseFromPast: true, numMonthsToShow: 12, onChange: (month, day, year) => {
|
|
3082
|
+
dateFilterState.startDate = { month, day, year };
|
|
3083
|
+
handleDateRangeUpdated(dateFilterState);
|
|
2984
3084
|
} }),
|
|
2985
3085
|
' ',
|
|
2986
3086
|
"to",
|
|
2987
3087
|
' ',
|
|
2988
|
-
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, onChange: (month, day, year) => {
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
3088
|
+
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, chooseFromPast: true, numMonthsToShow: 12, onChange: (month, day, year) => {
|
|
3089
|
+
if (year < dateFilterState.startDate.year
|
|
3090
|
+
|| (year === dateFilterState.startDate.year
|
|
3091
|
+
&& month < dateFilterState.startDate.month)
|
|
3092
|
+
|| (year === dateFilterState.startDate.year
|
|
3093
|
+
&& month === dateFilterState.startDate.month
|
|
3094
|
+
&& day < dateFilterState.startDate.day)) {
|
|
3095
|
+
return alert$1('Invalid Start Date', 'The start date cannot be before the end date.');
|
|
3096
|
+
}
|
|
3097
|
+
dateFilterState.endDate = { month, day, year };
|
|
3098
|
+
handleDateRangeUpdated(dateFilterState);
|
|
2993
3099
|
} })));
|
|
2994
3100
|
}
|
|
2995
3101
|
else if (expandedFilterDrawer === FilterDrawer.Context) {
|
|
2996
3102
|
// Create item picker items
|
|
2997
|
-
const pickableItems = (Object.keys((
|
|
3103
|
+
const pickableItems = (Object.keys((_g = LogMetadata.Context) !== null && _g !== void 0 ? _g : {})
|
|
2998
3104
|
.map((context) => {
|
|
2999
3105
|
var _a;
|
|
3000
3106
|
const value = ((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {})[context];
|
|
@@ -3015,7 +3121,7 @@ const LogReviewer = (props) => {
|
|
|
3015
3121
|
})
|
|
3016
3122
|
.map((subcontext) => {
|
|
3017
3123
|
return {
|
|
3018
|
-
id:
|
|
3124
|
+
id: subcontext,
|
|
3019
3125
|
name: genHumanReadableName(subcontext),
|
|
3020
3126
|
isGroup: false,
|
|
3021
3127
|
checked: !!value[subcontext],
|
|
@@ -3023,7 +3129,7 @@ const LogReviewer = (props) => {
|
|
|
3023
3129
|
}));
|
|
3024
3130
|
const item = {
|
|
3025
3131
|
id: context,
|
|
3026
|
-
name: context,
|
|
3132
|
+
name: genHumanReadableName(context),
|
|
3027
3133
|
isGroup: true,
|
|
3028
3134
|
children,
|
|
3029
3135
|
};
|
|
@@ -3031,6 +3137,8 @@ const LogReviewer = (props) => {
|
|
|
3031
3137
|
}));
|
|
3032
3138
|
// Create filter UI
|
|
3033
3139
|
filterDrawer = (React__default["default"].createElement(ItemPicker, { title: "Context", items: pickableItems, onChanged: (updatedItems) => {
|
|
3140
|
+
console.log('Items before:', pickableItems);
|
|
3141
|
+
console.log('Updated Items:', updatedItems);
|
|
3034
3142
|
// Update our state
|
|
3035
3143
|
updatedItems.forEach((pickableItem) => {
|
|
3036
3144
|
if (pickableItem.isGroup) {
|
|
@@ -3087,7 +3195,7 @@ const LogReviewer = (props) => {
|
|
|
3087
3195
|
}, ariaLabel: "only show error logs", selected: actionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true })),
|
|
3088
3196
|
(actionErrorFilterState.type === undefined
|
|
3089
3197
|
|| actionErrorFilterState.type === LogType$1.Action) && (React__default["default"].createElement(TabBox, { title: "Action Log Details" },
|
|
3090
|
-
React__default["default"].createElement(ButtonInputGroup, { label: "Action" }, Object.keys(LogAction$1)
|
|
3198
|
+
React__default["default"].createElement(ButtonInputGroup, { label: "Action", className: "mb-2" }, Object.keys(LogAction$1)
|
|
3091
3199
|
.map((action, i) => {
|
|
3092
3200
|
const description = genHumanReadableName(action);
|
|
3093
3201
|
return (React__default["default"].createElement(CheckboxButton, { id: `LogReviewer-action-${action}-checkbox`, text: description, ariaLabel: `include logs with action type "${description}" in results`, noMarginOnRight: i === Object.keys(LogAction$1).length - 1, onChanged: (checked) => {
|
|
@@ -3098,18 +3206,20 @@ const LogReviewer = (props) => {
|
|
|
3098
3206
|
});
|
|
3099
3207
|
} }));
|
|
3100
3208
|
})),
|
|
3101
|
-
React__default["default"].createElement(ButtonInputGroup, { label: "Target" },
|
|
3102
|
-
.
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3209
|
+
React__default["default"].createElement(ButtonInputGroup, { label: "Target" },
|
|
3210
|
+
(Object.keys((_h = LogMetadata.Target) !== null && _h !== void 0 ? _h : {}).length === 0) && (React__default["default"].createElement("div", null, "This app does not have any targets yet.")),
|
|
3211
|
+
Object.keys((_j = LogMetadata.Target) !== null && _j !== void 0 ? _j : {})
|
|
3212
|
+
.map((target, i) => {
|
|
3213
|
+
var _a;
|
|
3214
|
+
const description = genHumanReadableName(target);
|
|
3215
|
+
return (React__default["default"].createElement(CheckboxButton, { id: `LogReviewer-target-${target}-checkbox`, text: description, ariaLabel: `include logs with target "${description}" in results`, onChanged: (checked) => {
|
|
3216
|
+
actionErrorFilterState.target[target] = checked;
|
|
3217
|
+
dispatch({
|
|
3218
|
+
type: ActionType.UpdateActionErrorFilterState,
|
|
3219
|
+
actionErrorFilterState,
|
|
3220
|
+
});
|
|
3221
|
+
}, noMarginOnRight: i === Object.keys((_a = LogMetadata.Target) !== null && _a !== void 0 ? _a : {}).length - 1 }));
|
|
3222
|
+
})))),
|
|
3113
3223
|
(actionErrorFilterState.type === undefined
|
|
3114
3224
|
|| actionErrorFilterState.type === LogType$1.Error) && (React__default["default"].createElement(TabBox, { title: "Error Log Details" },
|
|
3115
3225
|
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
@@ -3271,26 +3381,27 @@ const LogReviewer = (props) => {
|
|
|
3271
3381
|
advancedFilterState,
|
|
3272
3382
|
});
|
|
3273
3383
|
}, noMarginOnRight: true })),
|
|
3274
|
-
React__default["default"].createElement("div", { className: "
|
|
3275
|
-
React__default["default"].createElement("
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
.
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
React__default["default"].createElement("
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
.
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3384
|
+
advancedFilterState.source !== LogSource$1.Client && (React__default["default"].createElement("div", { className: "mt-2" },
|
|
3385
|
+
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3386
|
+
React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Path"),
|
|
3387
|
+
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for server route path", placeholder: "e.g. /api/ttm/courses/12345", value: advancedFilterState.routePath, onChange: (e) => {
|
|
3388
|
+
advancedFilterState.courseName = ((e.target.value)
|
|
3389
|
+
.trim());
|
|
3390
|
+
dispatch({
|
|
3391
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
3392
|
+
advancedFilterState,
|
|
3393
|
+
});
|
|
3394
|
+
} })),
|
|
3395
|
+
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3396
|
+
React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Template"),
|
|
3397
|
+
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) => {
|
|
3398
|
+
advancedFilterState.courseName = ((e.target.value)
|
|
3399
|
+
.trim());
|
|
3400
|
+
dispatch({
|
|
3401
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
3402
|
+
advancedFilterState,
|
|
3403
|
+
});
|
|
3404
|
+
} })))))));
|
|
3294
3405
|
}
|
|
3295
3406
|
}
|
|
3296
3407
|
// Filters UI
|
|
@@ -3301,10 +3412,11 @@ const LogReviewer = (props) => {
|
|
|
3301
3412
|
// > Perform filters
|
|
3302
3413
|
const logs = [];
|
|
3303
3414
|
Object.keys(logMap).forEach((year) => {
|
|
3304
|
-
Object.keys(logMap).forEach((month) => {
|
|
3415
|
+
Object.keys(logMap[year]).forEach((month) => {
|
|
3305
3416
|
logMap[year][month].forEach((log) => {
|
|
3306
|
-
/* ----------- Date Filter ---------- */
|
|
3307
3417
|
var _a;
|
|
3418
|
+
/* ----------- Date Filter ---------- */
|
|
3419
|
+
console.log('Log:', log);
|
|
3308
3420
|
// Before start date
|
|
3309
3421
|
if (
|
|
3310
3422
|
// Previous year
|
|
@@ -3316,6 +3428,7 @@ const LogReviewer = (props) => {
|
|
|
3316
3428
|
|| ((log.year === dateFilterState.startDate.year)
|
|
3317
3429
|
&& (log.month === dateFilterState.startDate.month)
|
|
3318
3430
|
&& (log.day < dateFilterState.startDate.day))) {
|
|
3431
|
+
console.log('RULED OUT: START');
|
|
3319
3432
|
return;
|
|
3320
3433
|
}
|
|
3321
3434
|
// After end date
|
|
@@ -3329,6 +3442,7 @@ const LogReviewer = (props) => {
|
|
|
3329
3442
|
|| ((log.year === dateFilterState.endDate.year)
|
|
3330
3443
|
&& (log.month === dateFilterState.endDate.month)
|
|
3331
3444
|
&& (log.day > dateFilterState.endDate.day))) {
|
|
3445
|
+
console.log('RULED OUT: END');
|
|
3332
3446
|
return;
|
|
3333
3447
|
}
|
|
3334
3448
|
/* --------- Context Filter --------- */
|
|
@@ -3341,29 +3455,30 @@ const LogReviewer = (props) => {
|
|
|
3341
3455
|
.every((isSelected) => {
|
|
3342
3456
|
return !isSelected;
|
|
3343
3457
|
}))) {
|
|
3458
|
+
console.log('RULED OUT: CONTEXT');
|
|
3344
3459
|
return;
|
|
3345
3460
|
}
|
|
3346
3461
|
// Subcontext doesn't match
|
|
3347
3462
|
if (
|
|
3348
|
-
// Log
|
|
3349
|
-
log.
|
|
3463
|
+
// Log context is not "uncategorized" (no point in further filters)
|
|
3464
|
+
log.context !== LogBuiltInMetadata.Context.Uncategorized
|
|
3465
|
+
// Log has a subcontext
|
|
3466
|
+
&& log.subcontext
|
|
3350
3467
|
// Context has subcontexts
|
|
3351
3468
|
&& (contextFilterState[log.context]
|
|
3352
3469
|
&& contextFilterState[log.context] !== false
|
|
3353
3470
|
&& contextFilterState[log.context] !== true)
|
|
3354
3471
|
// Subcontext is not selected
|
|
3355
3472
|
&& !contextFilterState[log.context][log.subcontext]) {
|
|
3473
|
+
console.log('RULED OUT: SUBCONTEXT');
|
|
3356
3474
|
return;
|
|
3357
3475
|
}
|
|
3358
3476
|
/* -------------- Tags -------------- */
|
|
3359
3477
|
// No tags match
|
|
3360
|
-
if (
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
&& (log.tags.every((tag) => {
|
|
3365
|
-
return !tagFilterState[tag];
|
|
3366
|
-
}))) {
|
|
3478
|
+
if (log.tags.every((tag) => {
|
|
3479
|
+
return !tagFilterState[tag];
|
|
3480
|
+
})) {
|
|
3481
|
+
console.log('RULED OUT: TAGS');
|
|
3367
3482
|
return;
|
|
3368
3483
|
}
|
|
3369
3484
|
/* ------- Actions and Errors ------- */
|
|
@@ -3373,6 +3488,7 @@ const LogReviewer = (props) => {
|
|
|
3373
3488
|
actionErrorFilterState.type !== undefined
|
|
3374
3489
|
// Log type doesn't match
|
|
3375
3490
|
&& actionErrorFilterState.type !== log.type) {
|
|
3491
|
+
console.log('RULED OUT: TYPE');
|
|
3376
3492
|
return;
|
|
3377
3493
|
}
|
|
3378
3494
|
// Filter errors
|
|
@@ -3385,6 +3501,7 @@ const LogReviewer = (props) => {
|
|
|
3385
3501
|
&& actionErrorFilterState.errorMessage.trim().length > 0
|
|
3386
3502
|
// Message doesn't match
|
|
3387
3503
|
&& log.errorMessage.toLowerCase().includes(actionErrorFilterState.errorMessage.trim().toLowerCase())) {
|
|
3504
|
+
console.log('RULED OUT: ERROR MESSAGE');
|
|
3388
3505
|
return;
|
|
3389
3506
|
}
|
|
3390
3507
|
// Code doesn't match
|
|
@@ -3395,6 +3512,7 @@ const LogReviewer = (props) => {
|
|
|
3395
3512
|
&& actionErrorFilterState.errorCode.trim().length > 0
|
|
3396
3513
|
// Code doesn't match
|
|
3397
3514
|
&& log.errorCode.toUpperCase().includes(actionErrorFilterState.errorCode.trim().toUpperCase())) {
|
|
3515
|
+
console.log('RULED OUT: ERROR CODE');
|
|
3398
3516
|
return;
|
|
3399
3517
|
}
|
|
3400
3518
|
}
|
|
@@ -3406,6 +3524,7 @@ const LogReviewer = (props) => {
|
|
|
3406
3524
|
log.target
|
|
3407
3525
|
// Target isn't selected
|
|
3408
3526
|
&& !actionErrorFilterState.target[log.target]) {
|
|
3527
|
+
console.log('RULED OUT: TARGET');
|
|
3409
3528
|
return;
|
|
3410
3529
|
}
|
|
3411
3530
|
// Action
|
|
@@ -3414,6 +3533,7 @@ const LogReviewer = (props) => {
|
|
|
3414
3533
|
log.action
|
|
3415
3534
|
// Action isn't selected
|
|
3416
3535
|
&& !actionErrorFilterState.action[log.action]) {
|
|
3536
|
+
console.log('RULED OUT: ACTION');
|
|
3417
3537
|
return;
|
|
3418
3538
|
}
|
|
3419
3539
|
}
|
|
@@ -3424,6 +3544,7 @@ const LogReviewer = (props) => {
|
|
|
3424
3544
|
log.userFirstName
|
|
3425
3545
|
// First name query doesn't match
|
|
3426
3546
|
&& !log.userFirstName.toLowerCase().includes(advancedFilterState.userFirstName.toLowerCase().trim())) {
|
|
3547
|
+
console.log('RULED OUT: FIRST');
|
|
3427
3548
|
return;
|
|
3428
3549
|
}
|
|
3429
3550
|
// Last name doesn't match
|
|
@@ -3432,6 +3553,7 @@ const LogReviewer = (props) => {
|
|
|
3432
3553
|
log.userLastName
|
|
3433
3554
|
// Last name query doesn't match
|
|
3434
3555
|
&& !log.userLastName.toLowerCase().includes(advancedFilterState.userLastName.toLowerCase().trim())) {
|
|
3556
|
+
console.log('RULED OUT: LAST');
|
|
3435
3557
|
return;
|
|
3436
3558
|
}
|
|
3437
3559
|
// Email doesn't match
|
|
@@ -3440,6 +3562,7 @@ const LogReviewer = (props) => {
|
|
|
3440
3562
|
log.userEmail
|
|
3441
3563
|
// Email query doesn't match
|
|
3442
3564
|
&& !log.userEmail.toLowerCase().includes(advancedFilterState.userEmail.toLowerCase().trim())) {
|
|
3565
|
+
console.log('RULED OUT: EMAIL');
|
|
3443
3566
|
return;
|
|
3444
3567
|
}
|
|
3445
3568
|
// User id doesn't match
|
|
@@ -3448,6 +3571,7 @@ const LogReviewer = (props) => {
|
|
|
3448
3571
|
log.userId
|
|
3449
3572
|
// User id doesn't match
|
|
3450
3573
|
&& !String(log.userId).includes(advancedFilterState.userId.trim())) {
|
|
3574
|
+
console.log('RULED OUT: USER ID');
|
|
3451
3575
|
return;
|
|
3452
3576
|
}
|
|
3453
3577
|
// Learner not allowed
|
|
@@ -3456,6 +3580,7 @@ const LogReviewer = (props) => {
|
|
|
3456
3580
|
log.isLearner
|
|
3457
3581
|
// Learners aren't included
|
|
3458
3582
|
&& !advancedFilterState.includeLearners) {
|
|
3583
|
+
console.log('RULED OUT: LEARNER');
|
|
3459
3584
|
return;
|
|
3460
3585
|
}
|
|
3461
3586
|
// TTM not allowed
|
|
@@ -3464,6 +3589,7 @@ const LogReviewer = (props) => {
|
|
|
3464
3589
|
log.isTTM
|
|
3465
3590
|
// TTMs aren't included
|
|
3466
3591
|
&& !advancedFilterState.includeTTMs) {
|
|
3592
|
+
console.log('RULED OUT: TTM');
|
|
3467
3593
|
return;
|
|
3468
3594
|
}
|
|
3469
3595
|
// Admin not allowed
|
|
@@ -3472,6 +3598,7 @@ const LogReviewer = (props) => {
|
|
|
3472
3598
|
log.isAdmin
|
|
3473
3599
|
// Admins aren't included
|
|
3474
3600
|
&& !advancedFilterState.includeAdmins) {
|
|
3601
|
+
console.log('RULED OUT: ADMIN');
|
|
3475
3602
|
return;
|
|
3476
3603
|
}
|
|
3477
3604
|
// Course Id doesn't match
|
|
@@ -3480,6 +3607,7 @@ const LogReviewer = (props) => {
|
|
|
3480
3607
|
log.courseId
|
|
3481
3608
|
// Course Id doesn't match
|
|
3482
3609
|
&& !String(log.courseId).includes(advancedFilterState.courseId.trim())) {
|
|
3610
|
+
console.log('RULED OUT: COURSE ID');
|
|
3483
3611
|
return;
|
|
3484
3612
|
}
|
|
3485
3613
|
// Course name doesn't match
|
|
@@ -3488,6 +3616,7 @@ const LogReviewer = (props) => {
|
|
|
3488
3616
|
log.courseName
|
|
3489
3617
|
// Course name doesn't match
|
|
3490
3618
|
&& !String(log.courseName).includes(advancedFilterState.courseName.trim())) {
|
|
3619
|
+
console.log('RULED OUT: COURSE NAME');
|
|
3491
3620
|
return;
|
|
3492
3621
|
}
|
|
3493
3622
|
// Mobile filter doesn't match
|
|
@@ -3498,6 +3627,7 @@ const LogReviewer = (props) => {
|
|
|
3498
3627
|
&& log.device
|
|
3499
3628
|
// Mobile filter doesn't match
|
|
3500
3629
|
&& (advancedFilterState.isMobile === log.device.isMobile)) {
|
|
3630
|
+
console.log('RULED OUT: MOBILE');
|
|
3501
3631
|
return;
|
|
3502
3632
|
}
|
|
3503
3633
|
// Log source doesn't match
|
|
@@ -3508,6 +3638,7 @@ const LogReviewer = (props) => {
|
|
|
3508
3638
|
&& log.source
|
|
3509
3639
|
// Source filter doesn't match
|
|
3510
3640
|
&& (advancedFilterState.source !== log.source)) {
|
|
3641
|
+
console.log('RULED OUT: SOURCE');
|
|
3511
3642
|
return;
|
|
3512
3643
|
}
|
|
3513
3644
|
// Route path doesn't match (Only for server source)
|
|
@@ -3518,6 +3649,7 @@ const LogReviewer = (props) => {
|
|
|
3518
3649
|
&& (advancedFilterState.routePath.trim().length)
|
|
3519
3650
|
// Route path doesn't match
|
|
3520
3651
|
&& !(log.routePath.includes(advancedFilterState.routePath.trim()))) {
|
|
3652
|
+
console.log('RULED OUT: PATH');
|
|
3521
3653
|
return;
|
|
3522
3654
|
}
|
|
3523
3655
|
// Route template doesn't match (Only for server source)
|
|
@@ -3528,6 +3660,7 @@ const LogReviewer = (props) => {
|
|
|
3528
3660
|
&& (advancedFilterState.routeTemplate.trim().length)
|
|
3529
3661
|
// Route template doesn't match
|
|
3530
3662
|
&& !(log.routeTemplate.includes(advancedFilterState.routeTemplate.trim()))) {
|
|
3663
|
+
console.log('RULED OUT: TEMPLATE');
|
|
3531
3664
|
return;
|
|
3532
3665
|
}
|
|
3533
3666
|
/* -------------- Done -------------- */
|
|
@@ -3727,11 +3860,17 @@ const LogReviewer = (props) => {
|
|
|
3727
3860
|
},
|
|
3728
3861
|
];
|
|
3729
3862
|
// Create intelliTable
|
|
3730
|
-
const dataTable = (
|
|
3863
|
+
const dataTable = (logs.length === 0
|
|
3864
|
+
? (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
3865
|
+
React__default["default"].createElement("h3", { className: "m-0" }, "Matching Logs:"),
|
|
3866
|
+
React__default["default"].createElement("div", { className: "alert alert-warning text-center" },
|
|
3867
|
+
React__default["default"].createElement("h4", { className: "m-1" }, "No Logs to Show"),
|
|
3868
|
+
React__default["default"].createElement("div", null, "Either your filters are too strict or no matching logs have been created yet."))))
|
|
3869
|
+
: (React__default["default"].createElement(IntelliTable, { title: "Matching Logs:", id: "logs", data: logs, columns: columns })));
|
|
3731
3870
|
// Main body
|
|
3732
3871
|
body = (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
3733
3872
|
filters,
|
|
3734
|
-
dataTable));
|
|
3873
|
+
React__default["default"].createElement("div", { className: "mt-2" }, dataTable)));
|
|
3735
3874
|
}
|
|
3736
3875
|
/* ---------- Wrap in Modal --------- */
|
|
3737
3876
|
return (React__default["default"].createElement("div", { className: "LogReviewer-outer-container" },
|
|
@@ -3739,14 +3878,10 @@ const LogReviewer = (props) => {
|
|
|
3739
3878
|
React__default["default"].createElement("div", { className: "LogReviewer-inner-container" },
|
|
3740
3879
|
React__default["default"].createElement("div", { className: "LogReviewer-header" },
|
|
3741
3880
|
React__default["default"].createElement("div", { className: "LogReviewer-header-title" },
|
|
3742
|
-
React__default["default"].createElement("
|
|
3743
|
-
React__default["default"].createElement("
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
padding: 0,
|
|
3747
|
-
margin: 0,
|
|
3748
|
-
} },
|
|
3749
|
-
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes }))),
|
|
3881
|
+
React__default["default"].createElement("h3", { className: "text-center m-0" }, "Log Review Dashboard")),
|
|
3882
|
+
React__default["default"].createElement("div", { style: { width: 0 } },
|
|
3883
|
+
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 },
|
|
3884
|
+
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes })))),
|
|
3750
3885
|
React__default["default"].createElement("div", { className: "LogReviewer-contents" }, body))));
|
|
3751
3886
|
};
|
|
3752
3887
|
|
|
@@ -3892,7 +4027,7 @@ const padZerosLeft = (num, numDigits) => {
|
|
|
3892
4027
|
* access to log review
|
|
3893
4028
|
* @author Gabe Abrams
|
|
3894
4029
|
*/
|
|
3895
|
-
const LOG_REVIEW_STATUS_ROUTE =
|
|
4030
|
+
const LOG_REVIEW_STATUS_ROUTE = `${ROUTE_PATH_PREFIX}/logs/access_allowed`;
|
|
3896
4031
|
|
|
3897
4032
|
// Stored copy of caccl functions
|
|
3898
4033
|
let _cacclGetLaunchInfo;
|
|
@@ -3934,7 +4069,7 @@ const internalGetLogCollection = () => {
|
|
|
3934
4069
|
* @param opts.getLaunchInfo CACCL LTI's get launch info function
|
|
3935
4070
|
* @param [opts.logCollection] mongo collection from dce-mango to use for
|
|
3936
4071
|
* storing logs. If none is included, logs are written to the console
|
|
3937
|
-
* @param [opts.logReviewAdmins=all
|
|
4072
|
+
* @param [opts.logReviewAdmins=all] info on which admins can review
|
|
3938
4073
|
* logs from the client. If not included, all Canvas admins are allowed to
|
|
3939
4074
|
* review logs. If null, no Canvas admins are allowed to review logs.
|
|
3940
4075
|
* If an array of Canvas userIds (numbers), only Canvas admins with those
|
|
@@ -4017,71 +4152,81 @@ const initServer = (opts) => {
|
|
|
4017
4152
|
/*----------------------------------------*/
|
|
4018
4153
|
/* Log Reviewer */
|
|
4019
4154
|
/*----------------------------------------*/
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4155
|
+
/**
|
|
4156
|
+
* Check if a given user is allowed to review logs
|
|
4157
|
+
* @author Gabe Abrams
|
|
4158
|
+
* @param userId the id of the user
|
|
4159
|
+
* @param isAdmin if true, the user is an admin
|
|
4160
|
+
* @returns true if the user can review logs
|
|
4161
|
+
*/
|
|
4162
|
+
const canReviewLogs = (userId, isAdmin) => __awaiter(void 0, void 0, void 0, function* () {
|
|
4163
|
+
// Immediately deny access if user is not an admin
|
|
4164
|
+
if (!isAdmin) {
|
|
4165
|
+
return false;
|
|
4166
|
+
}
|
|
4167
|
+
// If all admins are allowed, we're done
|
|
4168
|
+
if (!opts.logReviewAdmins) {
|
|
4169
|
+
return true;
|
|
4170
|
+
}
|
|
4171
|
+
// Do a dynamic check
|
|
4172
|
+
try {
|
|
4173
|
+
// Array of userIds
|
|
4174
|
+
if (Array.isArray(opts.logReviewAdmins)) {
|
|
4175
|
+
return opts.logReviewAdmins.some((allowedId) => {
|
|
4176
|
+
return (userId === allowedId);
|
|
4177
|
+
});
|
|
4039
4178
|
}
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4179
|
+
// Must be a collection
|
|
4180
|
+
const matches = yield opts.logReviewAdmins.find({ userId });
|
|
4181
|
+
// Make sure at least one entry matches
|
|
4182
|
+
return matches.length > 0;
|
|
4183
|
+
}
|
|
4184
|
+
catch (err) {
|
|
4185
|
+
// If an error occurred, simply return false
|
|
4186
|
+
return false;
|
|
4187
|
+
}
|
|
4188
|
+
});
|
|
4189
|
+
/**
|
|
4190
|
+
* Check if the current user has access to logs
|
|
4191
|
+
* @author Gabe Abrams
|
|
4192
|
+
* @returns {boolean} true if user has access
|
|
4193
|
+
*/
|
|
4194
|
+
opts.app.get(LOG_REVIEW_STATUS_ROUTE, genRouteHandler({
|
|
4195
|
+
handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
4196
|
+
const { userId, isAdmin } = params;
|
|
4197
|
+
const canReview = yield canReviewLogs(userId, isAdmin);
|
|
4198
|
+
return canReview;
|
|
4199
|
+
}),
|
|
4200
|
+
}));
|
|
4201
|
+
/**
|
|
4202
|
+
* Get all logs for a certain month
|
|
4203
|
+
* @author Gabe Abrams
|
|
4204
|
+
* @param {number} year the year to query (e.g. 2022)
|
|
4205
|
+
* @param {number} month the month to query (e.g. 1 = January)
|
|
4206
|
+
* @returns {Log[]} list of logs from the given month
|
|
4207
|
+
*/
|
|
4208
|
+
opts.app.get(`${LOG_REVIEW_ROUTE_PATH_PREFIX}/years/:year/months/:month`, genRouteHandler({
|
|
4209
|
+
paramTypes: {
|
|
4210
|
+
year: ParamType$1.Int,
|
|
4211
|
+
month: ParamType$1.Int,
|
|
4212
|
+
},
|
|
4213
|
+
handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
4214
|
+
// Get user info
|
|
4215
|
+
const { year, month, userId, isAdmin, } = params;
|
|
4216
|
+
// Validate user
|
|
4217
|
+
const canReview = yield canReviewLogs(userId, isAdmin);
|
|
4218
|
+
if (!canReview) {
|
|
4219
|
+
throw new ErrorWithCode('You cannot access this resource because you do not have the appropriate permissions.', ReactKitErrorCode$1.NotAllowedToReviewLogs);
|
|
4043
4220
|
}
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
const canReview = yield canReviewLogs(userId);
|
|
4054
|
-
return canReview;
|
|
4055
|
-
}),
|
|
4056
|
-
}));
|
|
4057
|
-
/**
|
|
4058
|
-
* Get all logs for a certain month
|
|
4059
|
-
* @author Gabe Abrams
|
|
4060
|
-
* @param {number} year the year to query (e.g. 2022)
|
|
4061
|
-
* @param {number} month the month to query (e.g. 1 = January)
|
|
4062
|
-
* @returns {Log[]} list of logs from the given month
|
|
4063
|
-
*/
|
|
4064
|
-
opts.app.post(`${LOG_REVIEW_ROUTE_PATH_PREFIX}/years/:year/months/:month`, genRouteHandler({
|
|
4065
|
-
paramTypes: {
|
|
4066
|
-
year: ParamType$1.Int,
|
|
4067
|
-
month: ParamType$1.Int,
|
|
4068
|
-
},
|
|
4069
|
-
handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
4070
|
-
// Get user info
|
|
4071
|
-
const { userId } = params;
|
|
4072
|
-
// Validate user
|
|
4073
|
-
// isAdmin is already checked because path starts with '/admin'
|
|
4074
|
-
const canReview = yield canReviewLogs(userId);
|
|
4075
|
-
if (!canReview) {
|
|
4076
|
-
throw new ErrorWithCode('You cannot access this resource because you do not have the appropriate permissions.', ReactKitErrorCode$1.NotAllowedToReviewLogs);
|
|
4077
|
-
}
|
|
4078
|
-
// Query for logs
|
|
4079
|
-
const logs = yield _logCollection.find({ userId });
|
|
4080
|
-
// Return logs
|
|
4081
|
-
return logs;
|
|
4082
|
-
}),
|
|
4083
|
-
}));
|
|
4084
|
-
}
|
|
4221
|
+
// Query for logs
|
|
4222
|
+
const logs = yield _logCollection.find({
|
|
4223
|
+
year,
|
|
4224
|
+
month,
|
|
4225
|
+
});
|
|
4226
|
+
// Return logs
|
|
4227
|
+
return logs;
|
|
4228
|
+
}),
|
|
4229
|
+
}));
|
|
4085
4230
|
};
|
|
4086
4231
|
|
|
4087
4232
|
// Import shared types
|
|
@@ -4381,18 +4526,6 @@ const parseUserAgent = (userAgent) => {
|
|
|
4381
4526
|
};
|
|
4382
4527
|
};
|
|
4383
4528
|
|
|
4384
|
-
/**
|
|
4385
|
-
* Allowed log levels
|
|
4386
|
-
* @author Gabe Abrams
|
|
4387
|
-
*/
|
|
4388
|
-
var LogLevel;
|
|
4389
|
-
(function (LogLevel) {
|
|
4390
|
-
LogLevel["Warn"] = "Warn";
|
|
4391
|
-
LogLevel["Info"] = "Info";
|
|
4392
|
-
LogLevel["Debug"] = "Debug";
|
|
4393
|
-
})(LogLevel || (LogLevel = {}));
|
|
4394
|
-
var LogLevel$1 = LogLevel;
|
|
4395
|
-
|
|
4396
4529
|
/**
|
|
4397
4530
|
* Generate an express API route handler
|
|
4398
4531
|
* @author Gabe Abrams
|
|
@@ -4765,7 +4898,7 @@ const genRouteHandler = (opts) => {
|
|
|
4765
4898
|
}
|
|
4766
4899
|
: {
|
|
4767
4900
|
type: LogType$1.Action,
|
|
4768
|
-
target: ((_m = opts.target) !== null && _m !== void 0 ? _m : LogBuiltInMetadata.Target.
|
|
4901
|
+
target: ((_m = opts.target) !== null && _m !== void 0 ? _m : LogBuiltInMetadata.Target.NoTarget),
|
|
4769
4902
|
action: ((_o = opts.action) !== null && _o !== void 0 ? _o : LogAction$1.Unknown),
|
|
4770
4903
|
});
|
|
4771
4904
|
// Source-specific info
|
|
@@ -5132,6 +5265,31 @@ const initLogCollection = (Collection) => {
|
|
|
5132
5265
|
});
|
|
5133
5266
|
};
|
|
5134
5267
|
|
|
5268
|
+
// Cache user's ability
|
|
5269
|
+
let canReview = undefined;
|
|
5270
|
+
/**
|
|
5271
|
+
* Check if the current user can review logs
|
|
5272
|
+
* @author Gabe Abrams
|
|
5273
|
+
* @returns true if current user can review logs
|
|
5274
|
+
*/
|
|
5275
|
+
const canReviewLogs = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
5276
|
+
// If cached, use that value
|
|
5277
|
+
if (canReview !== undefined) {
|
|
5278
|
+
return canReview;
|
|
5279
|
+
}
|
|
5280
|
+
// Ask on server
|
|
5281
|
+
try {
|
|
5282
|
+
canReview = !!(yield visitServerEndpoint({
|
|
5283
|
+
path: LOG_REVIEW_STATUS_ROUTE,
|
|
5284
|
+
method: 'GET',
|
|
5285
|
+
}));
|
|
5286
|
+
}
|
|
5287
|
+
catch (err) {
|
|
5288
|
+
canReview = false;
|
|
5289
|
+
}
|
|
5290
|
+
return canReview;
|
|
5291
|
+
});
|
|
5292
|
+
|
|
5135
5293
|
/**
|
|
5136
5294
|
* Days of the week
|
|
5137
5295
|
* @author Gabe Abrams
|
|
@@ -5184,6 +5342,7 @@ exports.Variant = Variant$1;
|
|
|
5184
5342
|
exports.abbreviate = abbreviate;
|
|
5185
5343
|
exports.alert = alert$1;
|
|
5186
5344
|
exports.avg = avg;
|
|
5345
|
+
exports.canReviewLogs = canReviewLogs;
|
|
5187
5346
|
exports.ceilToNumDecimals = ceilToNumDecimals;
|
|
5188
5347
|
exports.confirm = confirm;
|
|
5189
5348
|
exports.floorToNumDecimals = floorToNumDecimals;
|