dce-reactkit 3.2.2-beta.5 → 3.2.2-beta.50
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 +728 -433
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/ButtonInputGroup.d.ts +1 -0
- package/dist/cjs/types/components/Drawer.d.ts +1 -0
- package/dist/cjs/types/components/IntelliTable.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/types/LogAction.d.ts +16 -16
- package/dist/cjs/types/types/LogBuiltInMetadata.d.ts +1 -1
- package/dist/cjs/types/types/LogMetadataContextMap.d.ts +11 -0
- package/dist/cjs/types/types/LogMetadataTagMap.d.ts +8 -0
- package/dist/cjs/types/types/LogMetadataTargetMap.d.ts +8 -0
- package/dist/cjs/types/types/LogMetadataType.d.ts +6 -12
- package/dist/esm/index.js +729 -434
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/ButtonInputGroup.d.ts +1 -0
- package/dist/esm/types/components/Drawer.d.ts +1 -0
- package/dist/esm/types/components/IntelliTable.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/types/LogAction.d.ts +16 -16
- package/dist/esm/types/types/LogBuiltInMetadata.d.ts +1 -1
- package/dist/esm/types/types/LogMetadataContextMap.d.ts +11 -0
- package/dist/esm/types/types/LogMetadataTagMap.d.ts +8 -0
- package/dist/esm/types/types/LogMetadataTargetMap.d.ts +8 -0
- package/dist/esm/types/types/LogMetadataType.d.ts +6 -12
- package/dist/index.d.ts +51 -29
- package/package.json +1 -1
- package/src/components/ButtonInputGroup.tsx +5 -2
- package/src/components/CSVDownloadButton.tsx +2 -2
- package/src/components/Drawer.tsx +9 -1
- package/src/components/IntelliTable.tsx +181 -26
- package/src/components/ItemPicker/NestableItemList.tsx +32 -14
- package/src/components/ItemPicker/index.tsx +4 -0
- package/src/components/LogReviewer.tsx +635 -406
- package/src/components/SimpleDateChooser.tsx +27 -18
- package/src/helpers/genCSV.ts +22 -4
- package/src/helpers/genRouteHandler.ts +2 -2
- package/src/helpers/logClientEvent.tsx +1 -1
- package/src/server/initServer.ts +10 -3
- package/src/types/LogAction.ts +16 -16
- package/src/types/LogBuiltInMetadata.ts +5 -5
- package/src/types/LogMetadataContextMap.ts +15 -0
- package/src/types/LogMetadataTagMap.ts +9 -0
- package/src/types/LogMetadataTargetMap.ts +9 -0
- package/src/types/LogMetadataType.ts +8 -15
package/dist/esm/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState, useRef, useEffect, useReducer } from 'react';
|
|
2
2
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
3
|
-
import { faExclamationTriangle, faCircle, faDotCircle, faCheckSquare, faHourglass, faClipboard, faChevronDown, faChevronRight, faCloudDownloadAlt, faCheckCircle, faXmarkCircle,
|
|
3
|
+
import { faExclamationTriangle, faCircle, faDotCircle, faCheckSquare, faHourglass, faClipboard, faChevronDown, faChevronRight, faCloudDownloadAlt, faMinus, faCheckCircle, faXmarkCircle, faSort, faSortDown, faSortUp, faCalendar, faTag, faHammer, faList, faTimes } from '@fortawesome/free-solid-svg-icons';
|
|
4
4
|
import { faCircle as faCircle$1, faSquareMinus, faSquare } from '@fortawesome/free-regular-svg-icons';
|
|
5
5
|
|
|
6
6
|
/******************************************************************************
|
|
@@ -511,14 +511,14 @@ const LOG_ROUTE_PATH = `${ROUTE_PATH_PREFIX}/log`;
|
|
|
511
511
|
const LogBuiltInMetadata = {
|
|
512
512
|
// Contexts
|
|
513
513
|
Context: {
|
|
514
|
-
Uncategorized: '
|
|
515
|
-
ServerRenderedErrorPage: '
|
|
516
|
-
ServerEndpointError: '
|
|
517
|
-
ClientFatalError: '
|
|
514
|
+
Uncategorized: 'Uncategorized',
|
|
515
|
+
ServerRenderedErrorPage: 'ServerRenderedErrorPage',
|
|
516
|
+
ServerEndpointError: 'ServerEndpointError',
|
|
517
|
+
ClientFatalError: 'ClientFatalError',
|
|
518
518
|
},
|
|
519
519
|
// Targets
|
|
520
520
|
Target: {
|
|
521
|
-
|
|
521
|
+
NoTarget: 'NoTarget',
|
|
522
522
|
},
|
|
523
523
|
};
|
|
524
524
|
|
|
@@ -674,7 +674,7 @@ const logClientEvent = (opts) => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
674
674
|
? opts.error.stack
|
|
675
675
|
: undefined),
|
|
676
676
|
target: (opts.action
|
|
677
|
-
? ((_g = opts.target) !== null && _g !== void 0 ? _g : LogBuiltInMetadata.Target.
|
|
677
|
+
? ((_g = opts.target) !== null && _g !== void 0 ? _g : LogBuiltInMetadata.Target.NoTarget)
|
|
678
678
|
: undefined),
|
|
679
679
|
action: (opts.action
|
|
680
680
|
? opts.action
|
|
@@ -1180,21 +1180,21 @@ const ButtonInputGroup = (props) => {
|
|
|
1180
1180
|
/*------------------------------------------------------------------------*/
|
|
1181
1181
|
/* -------------- Props ------------- */
|
|
1182
1182
|
// Destructure all props
|
|
1183
|
-
const { label, minLabelWidth, children, } = props;
|
|
1183
|
+
const { label, minLabelWidth, children, className, } = props;
|
|
1184
1184
|
/*------------------------------------------------------------------------*/
|
|
1185
1185
|
/* Render */
|
|
1186
1186
|
/*------------------------------------------------------------------------*/
|
|
1187
1187
|
/*----------------------------------------*/
|
|
1188
1188
|
/* Main UI */
|
|
1189
1189
|
/*----------------------------------------*/
|
|
1190
|
-
return (React.createElement("div", { className:
|
|
1190
|
+
return (React.createElement("div", { className: `input-group ${className !== null && className !== void 0 ? className : ''}` },
|
|
1191
1191
|
React.createElement("div", { className: "input-group-prepend d-flex w-100" },
|
|
1192
1192
|
React.createElement("span", { className: "input-group-text", style: {
|
|
1193
1193
|
minWidth: (minLabelWidth !== null && minLabelWidth !== void 0 ? minLabelWidth : undefined),
|
|
1194
1194
|
borderTopRightRadius: 0,
|
|
1195
1195
|
borderBottomRightRadius: 0,
|
|
1196
1196
|
} }, label),
|
|
1197
|
-
React.createElement("span", { className: "input-group-text flex-grow-1 rounded-right", style: {
|
|
1197
|
+
React.createElement("span", { className: "input-group-text flex-grow-1 rounded-right d-flex flex-wrap", style: {
|
|
1198
1198
|
backgroundColor: 'white',
|
|
1199
1199
|
borderTopLeftRadius: 0,
|
|
1200
1200
|
borderBottomLeftRadius: 0,
|
|
@@ -1339,7 +1339,7 @@ const SimpleDateChooser = (props) => {
|
|
|
1339
1339
|
let startMonth = today.month;
|
|
1340
1340
|
if (chooseFromPast) {
|
|
1341
1341
|
startMonth -= Math.max(0, numMonthsToShow - 1);
|
|
1342
|
-
|
|
1342
|
+
while (startMonth <= 0) {
|
|
1343
1343
|
startMonth += 12;
|
|
1344
1344
|
startYear -= 1;
|
|
1345
1345
|
}
|
|
@@ -1347,22 +1347,35 @@ const SimpleDateChooser = (props) => {
|
|
|
1347
1347
|
for (let i = 0; i < numMonthsToShow; i++) {
|
|
1348
1348
|
// Get month and year info
|
|
1349
1349
|
const unmoddedMonth = (startMonth + i);
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1350
|
+
let month = unmoddedMonth;
|
|
1351
|
+
while (month > 12) {
|
|
1352
|
+
month -= 12;
|
|
1353
|
+
}
|
|
1353
1354
|
const monthName = getMonthName(month).full;
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
: startYear);
|
|
1355
|
+
// Year is start year +1 for each 12 months
|
|
1356
|
+
const year = (startYear + (Math.floor(unmoddedMonth / 12)));
|
|
1357
1357
|
// Figure out which days are allowed
|
|
1358
1358
|
const days = [];
|
|
1359
1359
|
const numDaysInMonth = (new Date(year, month, 0)).getDate();
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1360
|
+
if (chooseFromPast) {
|
|
1361
|
+
// Past selection
|
|
1362
|
+
const numDaysToAdd = ((month === today.month)
|
|
1363
|
+
? today.day // Current month, only add up to today
|
|
1364
|
+
: numDaysInMonth // Past month, add all days
|
|
1365
|
+
);
|
|
1366
|
+
for (let day = 1; day <= numDaysToAdd; day++) {
|
|
1367
|
+
days.push(day);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
else {
|
|
1371
|
+
// Future selection: add all remaining days of the month
|
|
1372
|
+
const firstDay = (month === today.month
|
|
1373
|
+
? today.day // Current month: start at current date
|
|
1374
|
+
: 1 // Future month: start at beginning of month
|
|
1375
|
+
);
|
|
1376
|
+
for (let day = firstDay; day <= numDaysInMonth; day++) {
|
|
1377
|
+
days.push(day);
|
|
1378
|
+
}
|
|
1366
1379
|
}
|
|
1367
1380
|
choices.push({
|
|
1368
1381
|
choiceName: `${monthName} ${year}`,
|
|
@@ -1433,14 +1446,16 @@ const Drawer = (props) => {
|
|
|
1433
1446
|
/*------------------------------------------------------------------------*/
|
|
1434
1447
|
/* -------------- Props ------------- */
|
|
1435
1448
|
// Destructure all props
|
|
1436
|
-
const { children, } = props;
|
|
1449
|
+
const { customBackgroundColor, children, } = props;
|
|
1437
1450
|
/*------------------------------------------------------------------------*/
|
|
1438
1451
|
/* Render */
|
|
1439
1452
|
/*------------------------------------------------------------------------*/
|
|
1440
1453
|
/*----------------------------------------*/
|
|
1441
1454
|
/* Main UI */
|
|
1442
1455
|
/*----------------------------------------*/
|
|
1443
|
-
return (React.createElement("div", { className: "Drawer-container"
|
|
1456
|
+
return (React.createElement("div", { className: "Drawer-container", style: {
|
|
1457
|
+
backgroundColor: (customBackgroundColor !== null && customBackgroundColor !== void 0 ? customBackgroundColor : undefined),
|
|
1458
|
+
} },
|
|
1444
1459
|
React.createElement("style", null, style$4),
|
|
1445
1460
|
children));
|
|
1446
1461
|
};
|
|
@@ -1903,24 +1918,25 @@ const CopiableBox = (props) => {
|
|
|
1903
1918
|
/**
|
|
1904
1919
|
* Reusable nested item picker
|
|
1905
1920
|
* @author Yuen Ler Chow
|
|
1921
|
+
* @author Gabe Abrams
|
|
1906
1922
|
*/
|
|
1907
1923
|
/* ------------- Actions ------------ */
|
|
1908
1924
|
// Types of actions
|
|
1909
1925
|
var ActionType$2;
|
|
1910
1926
|
(function (ActionType) {
|
|
1911
|
-
// Toggle whether
|
|
1912
|
-
ActionType["
|
|
1927
|
+
// Toggle whether a child are being shown
|
|
1928
|
+
ActionType["ToggleChild"] = "toggle-child";
|
|
1913
1929
|
})(ActionType$2 || (ActionType$2 = {}));
|
|
1914
1930
|
/**
|
|
1915
1931
|
* Reducer that executes actions
|
|
1916
|
-
* @author
|
|
1932
|
+
* @author Gabe Abrams
|
|
1917
1933
|
* @param state current state
|
|
1918
1934
|
* @param action action to execute
|
|
1919
1935
|
*/
|
|
1920
1936
|
const reducer$2 = (state, action) => {
|
|
1921
1937
|
switch (action.type) {
|
|
1922
|
-
case ActionType$2.
|
|
1923
|
-
return {
|
|
1938
|
+
case ActionType$2.ToggleChild: {
|
|
1939
|
+
return Object.assign(Object.assign({}, state), { childExpanded: Object.assign(Object.assign({}, state.childExpanded), { [String(action.id)]: !state.childExpanded[String(action.id)] }) });
|
|
1924
1940
|
}
|
|
1925
1941
|
default: {
|
|
1926
1942
|
return state;
|
|
@@ -1938,14 +1954,19 @@ const NestableItemList = (props) => {
|
|
|
1938
1954
|
// Destructure all props
|
|
1939
1955
|
const { items, onChanged, } = props;
|
|
1940
1956
|
/* -------------- State ------------- */
|
|
1957
|
+
// Create initial map of child expanded booleans
|
|
1958
|
+
const initChildExpanded = {};
|
|
1959
|
+
items.forEach((item) => {
|
|
1960
|
+
initChildExpanded[String(item.id)] = false;
|
|
1961
|
+
});
|
|
1941
1962
|
// Initial state
|
|
1942
1963
|
const initialState = {
|
|
1943
|
-
|
|
1964
|
+
childExpanded: initChildExpanded,
|
|
1944
1965
|
};
|
|
1945
1966
|
// Initialize state
|
|
1946
1967
|
const [state, dispatch] = useReducer(reducer$2, initialState);
|
|
1947
1968
|
// Destructure common state
|
|
1948
|
-
const {
|
|
1969
|
+
const { childExpanded, } = state;
|
|
1949
1970
|
/*------------------------------------------------------------------------*/
|
|
1950
1971
|
/* Component Functions */
|
|
1951
1972
|
/*------------------------------------------------------------------------*/
|
|
@@ -2033,14 +2054,15 @@ const NestableItemList = (props) => {
|
|
|
2033
2054
|
backgroundColor: 'transparent',
|
|
2034
2055
|
}, type: "button", onClick: () => {
|
|
2035
2056
|
dispatch({
|
|
2036
|
-
type: ActionType$2.
|
|
2057
|
+
type: ActionType$2.ToggleChild,
|
|
2058
|
+
id: item.id,
|
|
2037
2059
|
});
|
|
2038
|
-
}, "aria-label": `${
|
|
2039
|
-
React.createElement(FontAwesomeIcon, { icon:
|
|
2060
|
+
}, "aria-label": `${childExpanded[item.id] ? 'Hide' : 'Show'} items in ${item.name}` },
|
|
2061
|
+
React.createElement(FontAwesomeIcon, { icon: childExpanded[item.id] ? faChevronDown : faChevronRight })))),
|
|
2040
2062
|
React.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) => {
|
|
2041
2063
|
onChanged(changeChecked(item.id, checked, items));
|
|
2042
2064
|
}, ariaLabel: `Select ${item.name}`, checkedVariant: Variant$1.Light }),
|
|
2043
|
-
item.isGroup &&
|
|
2065
|
+
(item.isGroup && childExpanded[item.id]) && (React.createElement("div", { className: "NestableItemList-children-container", style: {
|
|
2044
2066
|
paddingLeft: '2.2rem',
|
|
2045
2067
|
} },
|
|
2046
2068
|
React.createElement(NestableItemList, { items: item.children, onChanged: (updatedItems) => {
|
|
@@ -2062,7 +2084,7 @@ const ItemPicker = (props) => {
|
|
|
2062
2084
|
/*------------------------------------------------------------------------*/
|
|
2063
2085
|
/* -------------- Props ------------- */
|
|
2064
2086
|
// Destructure all props
|
|
2065
|
-
const { title, items, onChanged, } = props;
|
|
2087
|
+
const { title, items, onChanged, noBottomMargin, } = props;
|
|
2066
2088
|
/*------------------------------------------------------------------------*/
|
|
2067
2089
|
/* Component Functions */
|
|
2068
2090
|
/*------------------------------------------------------------------------*/
|
|
@@ -2072,11 +2094,34 @@ const ItemPicker = (props) => {
|
|
|
2072
2094
|
/*----------------------------------------*/
|
|
2073
2095
|
/* Main UI */
|
|
2074
2096
|
/*----------------------------------------*/
|
|
2075
|
-
return (React.createElement(TabBox, { title: title },
|
|
2097
|
+
return (React.createElement(TabBox, { title: title, noBottomMargin: noBottomMargin },
|
|
2076
2098
|
React.createElement("div", { style: { overflowX: 'auto' } },
|
|
2077
2099
|
React.createElement(NestableItemList, { items: items, onChanged: onChanged }))));
|
|
2078
2100
|
};
|
|
2079
2101
|
|
|
2102
|
+
// Import shared helpers
|
|
2103
|
+
/**
|
|
2104
|
+
* Get a human-readable description of a date (all in ET)
|
|
2105
|
+
* @author Gabe Abrams
|
|
2106
|
+
* @param [dateOrTimestamp=today] the date or timestamp for the date to describe
|
|
2107
|
+
* @returns human-readable description of the date
|
|
2108
|
+
*/
|
|
2109
|
+
const getHumanReadableDate = (dateOrTimestamp) => {
|
|
2110
|
+
// Get the date info
|
|
2111
|
+
const { month, day, year, } = getTimeInfoInET(dateOrTimestamp);
|
|
2112
|
+
const currYear = getTimeInfoInET().year;
|
|
2113
|
+
// Get the short month description
|
|
2114
|
+
const monthName = getMonthName(month).short;
|
|
2115
|
+
// Create start of description
|
|
2116
|
+
let description = `${monthName} ${day}${getOrdinal(day)}`;
|
|
2117
|
+
// Add on year if it's different
|
|
2118
|
+
if (year !== currYear) {
|
|
2119
|
+
description += ` ${year}`;
|
|
2120
|
+
}
|
|
2121
|
+
// Return description
|
|
2122
|
+
return description;
|
|
2123
|
+
};
|
|
2124
|
+
|
|
2080
2125
|
/**
|
|
2081
2126
|
* Path of the route for storing client-side logs
|
|
2082
2127
|
* @author Gabe Abrams
|
|
@@ -2116,37 +2161,37 @@ var LogType$1 = LogType;
|
|
|
2116
2161
|
var LogAction;
|
|
2117
2162
|
(function (LogAction) {
|
|
2118
2163
|
// Target was opened by the user (it was not on screen, but now it is)
|
|
2119
|
-
LogAction["Open"] = "
|
|
2164
|
+
LogAction["Open"] = "Open";
|
|
2120
2165
|
// Target was closed by the user (it was on screen, but now it is not)
|
|
2121
|
-
LogAction["Close"] = "
|
|
2166
|
+
LogAction["Close"] = "Close";
|
|
2122
2167
|
// Target was cancelled by the user (it was on closed without saving)
|
|
2123
|
-
LogAction["Cancel"] = "
|
|
2168
|
+
LogAction["Cancel"] = "Cancel";
|
|
2124
2169
|
// Target was expanded by the user (it always remains on screen, but size was changed)
|
|
2125
|
-
LogAction["Expand"] = "
|
|
2170
|
+
LogAction["Expand"] = "Expand";
|
|
2126
2171
|
// Target was collapsed by the user (it always remains on screen, but size was changed)
|
|
2127
|
-
LogAction["Collapse"] = "
|
|
2172
|
+
LogAction["Collapse"] = "Collapse";
|
|
2128
2173
|
// Target was viewed by the user (only for items that are not opened or closed, those must use Open/Close actions)
|
|
2129
|
-
LogAction["View"] = "
|
|
2174
|
+
LogAction["View"] = "View";
|
|
2130
2175
|
// Target interrupted the user (popup, dialog, validation message, etc. appeared without user prompting)
|
|
2131
|
-
LogAction["Interrupt"] = "
|
|
2176
|
+
LogAction["Interrupt"] = "Interrupt";
|
|
2132
2177
|
// Target was created by the user (it did not exist before)
|
|
2133
|
-
LogAction["Create"] = "
|
|
2178
|
+
LogAction["Create"] = "Create";
|
|
2134
2179
|
// Target was edited by the user (it existed and was changed)
|
|
2135
|
-
LogAction["Edit"] = "
|
|
2180
|
+
LogAction["Edit"] = "Edit";
|
|
2136
2181
|
// Target was deleted by the user (it existed and now it doesn't)
|
|
2137
|
-
LogAction["Delete"] = "
|
|
2182
|
+
LogAction["Delete"] = "Delete";
|
|
2138
2183
|
// Target was added by the user (it already existed and was added to another place)
|
|
2139
|
-
LogAction["Add"] = "
|
|
2184
|
+
LogAction["Add"] = "Add";
|
|
2140
2185
|
// Target was removed by the user (it was removed from something but still exists)
|
|
2141
|
-
LogAction["Remove"] = "
|
|
2186
|
+
LogAction["Remove"] = "Remove";
|
|
2142
2187
|
// Target was activated by the user (click, check, tap, keypress, etc.)
|
|
2143
|
-
LogAction["Activate"] = "
|
|
2188
|
+
LogAction["Activate"] = "Activate";
|
|
2144
2189
|
// Target was deactivated by the user (click away, uncheck, tap outside of, tab away, etc.)
|
|
2145
|
-
LogAction["Deactivate"] = "
|
|
2190
|
+
LogAction["Deactivate"] = "Deactivate";
|
|
2146
2191
|
// User showed interest in a target (hover, peek, etc.)
|
|
2147
|
-
LogAction["Peek"] = "
|
|
2192
|
+
LogAction["Peek"] = "Peek";
|
|
2148
2193
|
// Unknown action
|
|
2149
|
-
LogAction["Unknown"] = "
|
|
2194
|
+
LogAction["Unknown"] = "Unknown";
|
|
2150
2195
|
})(LogAction || (LogAction = {}));
|
|
2151
2196
|
var LogAction$1 = LogAction;
|
|
2152
2197
|
|
|
@@ -2188,12 +2233,12 @@ const roundToNumDecimals = (num, numDecimals) => {
|
|
|
2188
2233
|
* @returns escaped cell text
|
|
2189
2234
|
*/
|
|
2190
2235
|
const escapeCellText = (text) => {
|
|
2191
|
-
if (!text.includes(',')) {
|
|
2236
|
+
if (!String(text).includes(',')) {
|
|
2192
2237
|
// No need to escape
|
|
2193
|
-
return text;
|
|
2238
|
+
return String(text);
|
|
2194
2239
|
}
|
|
2195
2240
|
// Perform escape
|
|
2196
|
-
return `"${text.replace(/"/g, '""')}`;
|
|
2241
|
+
return `"${String(text).replace(/"/g, '""')}`;
|
|
2197
2242
|
};
|
|
2198
2243
|
/**
|
|
2199
2244
|
* Generate a CSV file
|
|
@@ -2212,9 +2257,26 @@ const genCSV = (data, columns) => {
|
|
|
2212
2257
|
.join(','));
|
|
2213
2258
|
// Add each row
|
|
2214
2259
|
data.forEach((datum) => {
|
|
2260
|
+
csv += '\n';
|
|
2215
2261
|
csv += (columns
|
|
2216
2262
|
.map((column) => {
|
|
2217
|
-
|
|
2263
|
+
let contents;
|
|
2264
|
+
const cell = datum[column.param];
|
|
2265
|
+
if (typeof cell === 'string'
|
|
2266
|
+
|| typeof cell === 'number') {
|
|
2267
|
+
contents = String(cell);
|
|
2268
|
+
}
|
|
2269
|
+
else if (typeof cell === 'undefined'
|
|
2270
|
+
|| cell === null) {
|
|
2271
|
+
contents = '';
|
|
2272
|
+
}
|
|
2273
|
+
else if (typeof cell === 'object') {
|
|
2274
|
+
contents = JSON.stringify(cell);
|
|
2275
|
+
}
|
|
2276
|
+
else {
|
|
2277
|
+
contents = '';
|
|
2278
|
+
}
|
|
2279
|
+
return escapeCellText(contents);
|
|
2218
2280
|
})
|
|
2219
2281
|
.join(','));
|
|
2220
2282
|
});
|
|
@@ -2243,11 +2305,12 @@ const CSVDownloadButton = (props) => {
|
|
|
2243
2305
|
/* Main UI */
|
|
2244
2306
|
/*----------------------------------------*/
|
|
2245
2307
|
// Render the button
|
|
2246
|
-
return (React.createElement("a", { id: id, download: filename, href: `data:application/octet-stream,${csv}`, className: `CSVDownloadButton-button ${className !== null && className !== void 0 ? className : 'btn btn-secondary'}`, "aria-label": (ariaLabel
|
|
2308
|
+
return (React.createElement("a", { id: id, download: filename, href: `data:application/octet-stream,${encodeURIComponent(csv)}`, className: `CSVDownloadButton-button ${className !== null && className !== void 0 ? className : 'btn btn-secondary'}`, "aria-label": (ariaLabel
|
|
2247
2309
|
? `Click to download ${filename}`
|
|
2248
2310
|
: ariaLabel), style: style, onClick: onClick },
|
|
2249
2311
|
!children && (React.createElement(React.Fragment, null,
|
|
2250
|
-
React.createElement(FontAwesomeIcon, { icon: faCloudDownloadAlt
|
|
2312
|
+
React.createElement(FontAwesomeIcon, { icon: faCloudDownloadAlt }),
|
|
2313
|
+
' ',
|
|
2251
2314
|
"Download CSV")),
|
|
2252
2315
|
children));
|
|
2253
2316
|
};
|
|
@@ -2271,9 +2334,13 @@ var ActionType$1;
|
|
|
2271
2334
|
// Toggle sort column param
|
|
2272
2335
|
ActionType["ToggleSortColumn"] = "toggle-sort-column";
|
|
2273
2336
|
// Toggle the visibility of a column
|
|
2274
|
-
ActionType["
|
|
2337
|
+
ActionType["UpdateColumnVisibility"] = "update-column-visibility";
|
|
2275
2338
|
// Toggle the column visibility customization modal
|
|
2276
2339
|
ActionType["ToggleColVisCusModalVisibility"] = "toggle-col-vis-cus-modal-visibility";
|
|
2340
|
+
// Show all columns
|
|
2341
|
+
ActionType["ShowAllColumns"] = "show-all-columns";
|
|
2342
|
+
// Hide all columns
|
|
2343
|
+
ActionType["HideAllColumns"] = "hide-all-columns";
|
|
2277
2344
|
})(ActionType$1 || (ActionType$1 = {}));
|
|
2278
2345
|
/**
|
|
2279
2346
|
* Reducer that executes actions
|
|
@@ -2295,14 +2362,28 @@ const reducer$1 = (state, action) => {
|
|
|
2295
2362
|
// Stop sorting by column
|
|
2296
2363
|
return Object.assign(Object.assign({}, state), { sortColumnParam: undefined, sortType: SortType.Ascending });
|
|
2297
2364
|
}
|
|
2298
|
-
case ActionType$1.
|
|
2365
|
+
case ActionType$1.UpdateColumnVisibility: {
|
|
2299
2366
|
const { columnVisibilityMap } = state;
|
|
2300
|
-
columnVisibilityMap[action.param] =
|
|
2367
|
+
columnVisibilityMap[action.param] = action.visible;
|
|
2301
2368
|
return Object.assign(Object.assign({}, state), { columnVisibilityMap });
|
|
2302
2369
|
}
|
|
2303
2370
|
case ActionType$1.ToggleColVisCusModalVisibility: {
|
|
2304
2371
|
return Object.assign(Object.assign({}, state), { columnVisibilityCustomizationModalVisible: !state.columnVisibilityCustomizationModalVisible });
|
|
2305
2372
|
}
|
|
2373
|
+
case ActionType$1.ShowAllColumns: {
|
|
2374
|
+
const { columnVisibilityMap } = state;
|
|
2375
|
+
Object.keys(columnVisibilityMap).forEach((param) => {
|
|
2376
|
+
columnVisibilityMap[param] = true;
|
|
2377
|
+
});
|
|
2378
|
+
return Object.assign(Object.assign({}, state), { columnVisibilityMap });
|
|
2379
|
+
}
|
|
2380
|
+
case ActionType$1.HideAllColumns: {
|
|
2381
|
+
const { columnVisibilityMap } = state;
|
|
2382
|
+
Object.keys(columnVisibilityMap).forEach((param) => {
|
|
2383
|
+
columnVisibilityMap[param] = false;
|
|
2384
|
+
});
|
|
2385
|
+
return Object.assign(Object.assign({}, state), { columnVisibilityMap });
|
|
2386
|
+
}
|
|
2306
2387
|
default: {
|
|
2307
2388
|
return state;
|
|
2308
2389
|
}
|
|
@@ -2318,7 +2399,18 @@ const IntelliTable = (props) => {
|
|
|
2318
2399
|
var _a;
|
|
2319
2400
|
/* -------------- Props ------------- */
|
|
2320
2401
|
// Destructure all props
|
|
2321
|
-
const { title, id,
|
|
2402
|
+
const { title, id, columns, } = props;
|
|
2403
|
+
// Get data, show empty row if none
|
|
2404
|
+
const data = ((props.data && props.data.length > 0)
|
|
2405
|
+
? props.data
|
|
2406
|
+
: [{ id: 'empty-row' }]);
|
|
2407
|
+
// Get CSV filename
|
|
2408
|
+
let filename = `${title}.csv`;
|
|
2409
|
+
if (props.csvName) {
|
|
2410
|
+
filename = (props.csvName.endsWith('.csv')
|
|
2411
|
+
? props.csvName
|
|
2412
|
+
: `${props.csvName}.csv`);
|
|
2413
|
+
}
|
|
2322
2414
|
/* -------------- State ------------- */
|
|
2323
2415
|
// Initial state
|
|
2324
2416
|
const initColumnVisibilityMap = {};
|
|
@@ -2335,21 +2427,49 @@ const IntelliTable = (props) => {
|
|
|
2335
2427
|
const [state, dispatch] = useReducer(reducer$1, initialState);
|
|
2336
2428
|
// Destructure common state
|
|
2337
2429
|
const { sortColumnParam, sortType, columnVisibilityMap, columnVisibilityCustomizationModalVisible, } = state;
|
|
2430
|
+
/*------------------------------------------------------------------------*/
|
|
2431
|
+
/* Render */
|
|
2432
|
+
/*------------------------------------------------------------------------*/
|
|
2433
|
+
/*----------------------------------------*/
|
|
2434
|
+
/* Modal */
|
|
2435
|
+
/*----------------------------------------*/
|
|
2436
|
+
// Modal that may be defined
|
|
2437
|
+
let modal;
|
|
2338
2438
|
/* ------- Col Vis Customization Modal ------ */
|
|
2339
2439
|
if (columnVisibilityCustomizationModalVisible) {
|
|
2340
2440
|
// Create modal
|
|
2341
|
-
(React.createElement(Modal, { type: ModalType$1.Okay, title: "Choose columns to show:", onClose: () => {
|
|
2441
|
+
modal = (React.createElement(Modal, { type: ModalType$1.Okay, title: "Choose columns to show:", onClose: () => {
|
|
2442
|
+
const noColumnsSelected = (Object.values(columnVisibilityMap)
|
|
2443
|
+
.every((isSelected) => {
|
|
2444
|
+
return !isSelected;
|
|
2445
|
+
}));
|
|
2446
|
+
if (noColumnsSelected) {
|
|
2447
|
+
return alert$1('Choose at least one column', 'To continue, you have to choose at least one column to show');
|
|
2448
|
+
}
|
|
2342
2449
|
dispatch({
|
|
2343
2450
|
type: ActionType$1.ToggleColVisCusModalVisibility,
|
|
2344
2451
|
});
|
|
2345
|
-
}
|
|
2346
|
-
|
|
2452
|
+
}, okayVariant: Variant$1.Light },
|
|
2453
|
+
columns.map((column) => {
|
|
2454
|
+
return (React.createElement(CheckboxButton, { key: column.param, id: `IntelliTable-${id}-toggle-visibility-${column.param}`, className: "mb-2", text: column.title, onChanged: (checked) => {
|
|
2455
|
+
dispatch({
|
|
2456
|
+
type: ActionType$1.UpdateColumnVisibility,
|
|
2457
|
+
param: column.param,
|
|
2458
|
+
visible: checked,
|
|
2459
|
+
});
|
|
2460
|
+
}, checked: columnVisibilityMap[column.param], ariaLabel: `show "${column.title}" column in the ${title} table`, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Secondary }));
|
|
2461
|
+
}),
|
|
2462
|
+
React.createElement("div", { className: "mt-3" }, "Or you can:"),
|
|
2463
|
+
React.createElement("button", { type: "button", id: `IntelliTable-${id}-select-all-columns`, className: "btn btn-secondary me-2", "aria-label": `show all columns in the ${title} table`, onClick: () => {
|
|
2464
|
+
dispatch({
|
|
2465
|
+
type: ActionType$1.ShowAllColumns,
|
|
2466
|
+
});
|
|
2467
|
+
} }, "Select All"),
|
|
2468
|
+
React.createElement("button", { type: "button", id: `IntelliTable-${id}-select-none-columns`, className: "btn btn-secondary", "aria-label": `hide all columns in the ${title} table`, onClick: () => {
|
|
2347
2469
|
dispatch({
|
|
2348
|
-
type: ActionType$1.
|
|
2349
|
-
param: column.param,
|
|
2470
|
+
type: ActionType$1.HideAllColumns,
|
|
2350
2471
|
});
|
|
2351
|
-
}
|
|
2352
|
-
})));
|
|
2472
|
+
} }, "Deselect All")));
|
|
2353
2473
|
}
|
|
2354
2474
|
/*----------------------------------------*/
|
|
2355
2475
|
/* Main UI */
|
|
@@ -2363,6 +2483,7 @@ const IntelliTable = (props) => {
|
|
|
2363
2483
|
// Custom info based on current sort type
|
|
2364
2484
|
let sortButtonAriaLabel;
|
|
2365
2485
|
let sortIcon = faSort;
|
|
2486
|
+
let sortingByThisColumn = false;
|
|
2366
2487
|
if (!sortColumnParam) {
|
|
2367
2488
|
// Not being sorted yet
|
|
2368
2489
|
sortButtonAriaLabel = `sort ascending by ${column.title}`;
|
|
@@ -2370,6 +2491,7 @@ const IntelliTable = (props) => {
|
|
|
2370
2491
|
}
|
|
2371
2492
|
else if (column.param === sortColumnParam) {
|
|
2372
2493
|
// Already sorted by this column
|
|
2494
|
+
sortingByThisColumn = true;
|
|
2373
2495
|
if (sortType === SortType.Ascending) {
|
|
2374
2496
|
// Sorted ascending
|
|
2375
2497
|
sortButtonAriaLabel = `sort descending by ${column.title}`;
|
|
@@ -2387,11 +2509,14 @@ const IntelliTable = (props) => {
|
|
|
2387
2509
|
sortIcon = faSort;
|
|
2388
2510
|
}
|
|
2389
2511
|
// Create the cell UI
|
|
2390
|
-
return (React.createElement("th", { key: column.param, scope: "col", id: `IntelliTable-${id}-header-${column.param}
|
|
2391
|
-
|
|
2392
|
-
|
|
2512
|
+
return (React.createElement("th", { key: column.param, scope: "col", id: `IntelliTable-${id}-header-${column.param}`, className: "text-start", style: {
|
|
2513
|
+
borderRight: '0.05rem solid #555',
|
|
2514
|
+
borderLeft: '0.05rem solid #555',
|
|
2515
|
+
} },
|
|
2516
|
+
React.createElement("div", { className: "d-flex align-items-center justify-content-start flex-row h-100" },
|
|
2517
|
+
React.createElement("span", { className: "text-nowrap" }, column.title),
|
|
2393
2518
|
React.createElement("div", null,
|
|
2394
|
-
React.createElement("button", { type: "button", className:
|
|
2519
|
+
React.createElement("button", { type: "button", id: `IntelliTable-${id}-sort-by-${column.param}-button`, className: `btn btn-${sortingByThisColumn ? 'light' : 'secondary'} btn-sm ms-1 ps-1 pe-1 pt-0 pb-0`, "aria-label": sortButtonAriaLabel, onClick: () => {
|
|
2395
2520
|
dispatch({
|
|
2396
2521
|
type: ActionType$1.ToggleSortColumn,
|
|
2397
2522
|
param: column.param,
|
|
@@ -2409,8 +2534,28 @@ const IntelliTable = (props) => {
|
|
|
2409
2534
|
const descending = (sortType === SortType.Descending);
|
|
2410
2535
|
if (sortColumnParam) {
|
|
2411
2536
|
sortedData.sort((a, b) => {
|
|
2537
|
+
var _a, _b;
|
|
2412
2538
|
const aVal = a[sortColumnParam];
|
|
2413
2539
|
const bVal = b[sortColumnParam];
|
|
2540
|
+
// Tiebreaker sort by timestamp, most recent first (used if tied)
|
|
2541
|
+
const tiebreaker = (((_a = b.timestamp) !== null && _a !== void 0 ? _a : 0)
|
|
2542
|
+
- ((_b = a.timestamp) !== null && _b !== void 0 ? _b : 0));
|
|
2543
|
+
// Auto-sort undefined and null to end of list
|
|
2544
|
+
if ((aVal === undefined || aVal === null)
|
|
2545
|
+
|| (bVal === undefined || bVal === null)) {
|
|
2546
|
+
// At least one was undefined
|
|
2547
|
+
if ((aVal === undefined || aVal === null)
|
|
2548
|
+
&& (bVal === undefined || bVal === null)) {
|
|
2549
|
+
// Both undefined
|
|
2550
|
+
return tiebreaker;
|
|
2551
|
+
}
|
|
2552
|
+
if (aVal === undefined || aVal === null) {
|
|
2553
|
+
// First was undefined
|
|
2554
|
+
return 1;
|
|
2555
|
+
}
|
|
2556
|
+
// Second was undefined
|
|
2557
|
+
return -1;
|
|
2558
|
+
}
|
|
2414
2559
|
// Sort differently based on the data type
|
|
2415
2560
|
// > Boolean
|
|
2416
2561
|
if (paramType === ParamType$1.Boolean) {
|
|
@@ -2420,7 +2565,7 @@ const IntelliTable = (props) => {
|
|
|
2420
2565
|
if (!aVal && bVal) {
|
|
2421
2566
|
return (descending ? 1 : -1);
|
|
2422
2567
|
}
|
|
2423
|
-
return
|
|
2568
|
+
return tiebreaker;
|
|
2424
2569
|
}
|
|
2425
2570
|
// > Number
|
|
2426
2571
|
if (paramType === ParamType$1.Int
|
|
@@ -2432,12 +2577,12 @@ const IntelliTable = (props) => {
|
|
|
2432
2577
|
// > String
|
|
2433
2578
|
if (paramType === ParamType$1.String) {
|
|
2434
2579
|
if (aVal < bVal) {
|
|
2435
|
-
return (descending ?
|
|
2580
|
+
return (descending ? 1 : -1);
|
|
2436
2581
|
}
|
|
2437
2582
|
if (aVal > bVal) {
|
|
2438
|
-
return (descending ? 1 :
|
|
2583
|
+
return (descending ? -1 : 1);
|
|
2439
2584
|
}
|
|
2440
|
-
return
|
|
2585
|
+
return tiebreaker;
|
|
2441
2586
|
}
|
|
2442
2587
|
// > JSON
|
|
2443
2588
|
if (paramType === ParamType$1.JSON) {
|
|
@@ -2452,7 +2597,7 @@ const IntelliTable = (props) => {
|
|
|
2452
2597
|
: (aSize - bSize));
|
|
2453
2598
|
}
|
|
2454
2599
|
// No sort
|
|
2455
|
-
return
|
|
2600
|
+
return tiebreaker;
|
|
2456
2601
|
});
|
|
2457
2602
|
}
|
|
2458
2603
|
// Table body
|
|
@@ -2477,9 +2622,12 @@ const IntelliTable = (props) => {
|
|
|
2477
2622
|
const noValue = (value === undefined
|
|
2478
2623
|
|| value === null);
|
|
2479
2624
|
visibleValue = (noValue
|
|
2480
|
-
? (React.createElement(FontAwesomeIcon, { icon:
|
|
2481
|
-
: (React.createElement(FontAwesomeIcon, { icon: faXmarkCircle })));
|
|
2625
|
+
? (React.createElement(FontAwesomeIcon, { icon: faMinus }))
|
|
2626
|
+
: (React.createElement(FontAwesomeIcon, { icon: fullValue ? faCheckCircle : faXmarkCircle })));
|
|
2482
2627
|
title = (fullValue ? 'True' : 'False');
|
|
2628
|
+
if (noValue) {
|
|
2629
|
+
title = 'Empty Cell';
|
|
2630
|
+
}
|
|
2483
2631
|
}
|
|
2484
2632
|
else if (column.type === ParamType$1.Int) {
|
|
2485
2633
|
fullValue = Number.parseInt(value, 10);
|
|
@@ -2488,6 +2636,9 @@ const IntelliTable = (props) => {
|
|
|
2488
2636
|
? (React.createElement(FontAwesomeIcon, { icon: faMinus }))
|
|
2489
2637
|
: fullValue);
|
|
2490
2638
|
title = String(fullValue);
|
|
2639
|
+
if (noValue) {
|
|
2640
|
+
title = 'Empty Cell';
|
|
2641
|
+
}
|
|
2491
2642
|
}
|
|
2492
2643
|
else if (column.type === ParamType$1.Float) {
|
|
2493
2644
|
fullValue = Number.parseFloat(value);
|
|
@@ -2496,14 +2647,22 @@ const IntelliTable = (props) => {
|
|
|
2496
2647
|
? (React.createElement(FontAwesomeIcon, { icon: faMinus }))
|
|
2497
2648
|
: roundToNumDecimals(fullValue, 2));
|
|
2498
2649
|
title = String(fullValue);
|
|
2650
|
+
if (noValue) {
|
|
2651
|
+
title = 'Empty Cell';
|
|
2652
|
+
}
|
|
2499
2653
|
}
|
|
2500
2654
|
else if (column.type === ParamType$1.String) {
|
|
2501
2655
|
fullValue = String(value).trim();
|
|
2502
|
-
const noValue = (value
|
|
2656
|
+
const noValue = (value === undefined
|
|
2657
|
+
|| value === null
|
|
2658
|
+
|| String(fullValue).trim().length === 0);
|
|
2503
2659
|
visibleValue = (noValue
|
|
2504
2660
|
? (React.createElement(FontAwesomeIcon, { icon: faMinus }))
|
|
2505
2661
|
: fullValue);
|
|
2506
2662
|
title = `"${value}"`;
|
|
2663
|
+
if (noValue) {
|
|
2664
|
+
title = 'Empty Cell';
|
|
2665
|
+
}
|
|
2507
2666
|
}
|
|
2508
2667
|
else if (column.type === ParamType$1.JSON) {
|
|
2509
2668
|
fullValue = JSON.stringify(value);
|
|
@@ -2513,9 +2672,13 @@ const IntelliTable = (props) => {
|
|
|
2513
2672
|
visibleValue = (noValue
|
|
2514
2673
|
? (React.createElement(FontAwesomeIcon, { icon: faMinus }))
|
|
2515
2674
|
: fullValue);
|
|
2675
|
+
title = "JSON Object";
|
|
2516
2676
|
}
|
|
2517
2677
|
// Create UI
|
|
2518
|
-
return (React.createElement("td", { key: `${datum.id}-${column.param}`, title: title
|
|
2678
|
+
return (React.createElement("td", { key: `${datum.id}-${column.param}`, title: title, style: {
|
|
2679
|
+
borderRight: '0.05rem solid #555',
|
|
2680
|
+
borderLeft: '0.05rem solid #555',
|
|
2681
|
+
} }, visibleValue));
|
|
2519
2682
|
}));
|
|
2520
2683
|
// Add cells to a row
|
|
2521
2684
|
return (React.createElement("tr", { key: datum.id }, cells));
|
|
@@ -2532,14 +2695,17 @@ const IntelliTable = (props) => {
|
|
|
2532
2695
|
})
|
|
2533
2696
|
.length);
|
|
2534
2697
|
// Build CSV
|
|
2535
|
-
const csv = genCSV(data, columns)
|
|
2698
|
+
const csv = genCSV(data, columns.filter((column) => {
|
|
2699
|
+
return columnVisibilityMap[column.param];
|
|
2700
|
+
}));
|
|
2536
2701
|
// Build main UI
|
|
2537
2702
|
return (React.createElement("div", { className: `IntelliTable-container-${id}` },
|
|
2538
|
-
|
|
2703
|
+
modal,
|
|
2704
|
+
React.createElement("div", { className: "d-flex align-items-center justify-content-start" },
|
|
2539
2705
|
React.createElement("h3", { className: "m-0" }, title),
|
|
2540
2706
|
React.createElement("div", { className: "flex-grow-1 text-end" },
|
|
2541
|
-
React.createElement(CSVDownloadButton, { "aria-label": `download data as csv for ${title}`, id: `IntelliTable-${id}-download-as-csv`, filename:
|
|
2542
|
-
React.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: () => {
|
|
2707
|
+
React.createElement(CSVDownloadButton, { "aria-label": `download data as csv for ${title}`, id: `IntelliTable-${id}-download-as-csv`, filename: filename, csv: csv }),
|
|
2708
|
+
React.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: () => {
|
|
2543
2709
|
dispatch({
|
|
2544
2710
|
type: ActionType$1.ToggleColVisCusModalVisibility,
|
|
2545
2711
|
});
|
|
@@ -2551,7 +2717,7 @@ const IntelliTable = (props) => {
|
|
|
2551
2717
|
numHiddenCols,
|
|
2552
2718
|
' ',
|
|
2553
2719
|
"hidden)"))))),
|
|
2554
|
-
React.createElement("div", { className: `IntelliTable-table-${id}`, style: {
|
|
2720
|
+
React.createElement("div", { className: `IntelliTable-table-${id} mt-2`, style: {
|
|
2555
2721
|
overflowX: 'auto',
|
|
2556
2722
|
} }, table)));
|
|
2557
2723
|
};
|
|
@@ -2603,6 +2769,11 @@ const style = `
|
|
|
2603
2769
|
border: 0.05rem solid black;
|
|
2604
2770
|
border-radius: 0.5rem;
|
|
2605
2771
|
overflow: hidden;
|
|
2772
|
+
padding: 0.7rem;
|
|
2773
|
+
|
|
2774
|
+
/* Solid background */
|
|
2775
|
+
background-color: white;
|
|
2776
|
+
color: black;
|
|
2606
2777
|
|
|
2607
2778
|
/* Place contents in flex column */
|
|
2608
2779
|
flex-direction: column;
|
|
@@ -2629,8 +2800,218 @@ const style = `
|
|
|
2629
2800
|
/* Vertical scroll */
|
|
2630
2801
|
overflow-y: auto;
|
|
2631
2802
|
}
|
|
2803
|
+
|
|
2804
|
+
.LogReviewer-header-close-button {
|
|
2805
|
+
border: 0 !important;
|
|
2806
|
+
background-color: transparent !important;
|
|
2807
|
+
padding-top: 0 !important;
|
|
2808
|
+
padding-bottom: 0 !important;
|
|
2809
|
+
padding-right: 1em !important;
|
|
2810
|
+
margin: 0 !important;
|
|
2811
|
+
color: #444 !important;
|
|
2812
|
+
|
|
2813
|
+
right: 0 !important;
|
|
2814
|
+
position: absolute !important;
|
|
2815
|
+
}
|
|
2816
|
+
.LogReviewer-header-close-button:hover {
|
|
2817
|
+
border: 0 !important;
|
|
2818
|
+
background-color: transparent !important;
|
|
2819
|
+
padding-top: 0 !important;
|
|
2820
|
+
padding-bottom: 0 !important;
|
|
2821
|
+
margin: 0 !important;
|
|
2822
|
+
color: #000 !important;
|
|
2823
|
+
}
|
|
2632
2824
|
`;
|
|
2633
2825
|
/*------------------------------------------------------------------------*/
|
|
2826
|
+
/* Constants */
|
|
2827
|
+
/*------------------------------------------------------------------------*/
|
|
2828
|
+
const columns = [
|
|
2829
|
+
{
|
|
2830
|
+
title: 'First Name',
|
|
2831
|
+
param: 'userFirstName',
|
|
2832
|
+
type: ParamType$1.String,
|
|
2833
|
+
},
|
|
2834
|
+
{
|
|
2835
|
+
title: 'Last Name',
|
|
2836
|
+
param: 'userLastName',
|
|
2837
|
+
type: ParamType$1.String,
|
|
2838
|
+
},
|
|
2839
|
+
{
|
|
2840
|
+
title: 'Email',
|
|
2841
|
+
param: 'userEmail',
|
|
2842
|
+
type: ParamType$1.String,
|
|
2843
|
+
},
|
|
2844
|
+
{
|
|
2845
|
+
title: 'Canvas Id',
|
|
2846
|
+
param: 'userId',
|
|
2847
|
+
type: ParamType$1.Int,
|
|
2848
|
+
},
|
|
2849
|
+
{
|
|
2850
|
+
title: 'Student?',
|
|
2851
|
+
param: 'isLearner',
|
|
2852
|
+
type: ParamType$1.Boolean,
|
|
2853
|
+
},
|
|
2854
|
+
{
|
|
2855
|
+
title: 'Teaching Staff?',
|
|
2856
|
+
param: 'isTTM',
|
|
2857
|
+
type: ParamType$1.Boolean,
|
|
2858
|
+
startsHidden: true,
|
|
2859
|
+
},
|
|
2860
|
+
{
|
|
2861
|
+
title: 'Admin?',
|
|
2862
|
+
param: 'isAdmin',
|
|
2863
|
+
type: ParamType$1.Boolean,
|
|
2864
|
+
startsHidden: true,
|
|
2865
|
+
},
|
|
2866
|
+
{
|
|
2867
|
+
title: 'Course Canvas Id',
|
|
2868
|
+
param: 'courseId',
|
|
2869
|
+
type: ParamType$1.Int,
|
|
2870
|
+
startsHidden: true,
|
|
2871
|
+
},
|
|
2872
|
+
{
|
|
2873
|
+
title: 'Course Name',
|
|
2874
|
+
param: 'courseName',
|
|
2875
|
+
type: ParamType$1.String,
|
|
2876
|
+
},
|
|
2877
|
+
{
|
|
2878
|
+
title: 'Browser Name',
|
|
2879
|
+
param: 'browser.name',
|
|
2880
|
+
type: ParamType$1.String,
|
|
2881
|
+
startsHidden: true,
|
|
2882
|
+
},
|
|
2883
|
+
{
|
|
2884
|
+
title: 'Browser Version',
|
|
2885
|
+
param: 'browser.version',
|
|
2886
|
+
type: ParamType$1.String,
|
|
2887
|
+
startsHidden: true,
|
|
2888
|
+
},
|
|
2889
|
+
{
|
|
2890
|
+
title: 'OS',
|
|
2891
|
+
param: 'device.os',
|
|
2892
|
+
type: ParamType$1.String,
|
|
2893
|
+
startsHidden: true,
|
|
2894
|
+
},
|
|
2895
|
+
{
|
|
2896
|
+
title: 'Mobile?',
|
|
2897
|
+
param: 'device.isMobile',
|
|
2898
|
+
type: ParamType$1.Boolean,
|
|
2899
|
+
startsHidden: true,
|
|
2900
|
+
},
|
|
2901
|
+
{
|
|
2902
|
+
title: 'Year',
|
|
2903
|
+
param: 'year',
|
|
2904
|
+
type: ParamType$1.Int,
|
|
2905
|
+
},
|
|
2906
|
+
{
|
|
2907
|
+
title: 'Month',
|
|
2908
|
+
param: 'month',
|
|
2909
|
+
type: ParamType$1.Int,
|
|
2910
|
+
},
|
|
2911
|
+
{
|
|
2912
|
+
title: 'Day',
|
|
2913
|
+
param: 'day',
|
|
2914
|
+
type: ParamType$1.Int,
|
|
2915
|
+
},
|
|
2916
|
+
{
|
|
2917
|
+
title: 'Hour',
|
|
2918
|
+
param: 'hour',
|
|
2919
|
+
type: ParamType$1.Int,
|
|
2920
|
+
},
|
|
2921
|
+
{
|
|
2922
|
+
title: 'Minute',
|
|
2923
|
+
param: 'minute',
|
|
2924
|
+
type: ParamType$1.Int,
|
|
2925
|
+
startsHidden: true,
|
|
2926
|
+
},
|
|
2927
|
+
{
|
|
2928
|
+
title: 'Timestamp',
|
|
2929
|
+
param: 'timestamp',
|
|
2930
|
+
type: ParamType$1.Int,
|
|
2931
|
+
startsHidden: true,
|
|
2932
|
+
},
|
|
2933
|
+
{
|
|
2934
|
+
title: 'Context',
|
|
2935
|
+
param: 'context',
|
|
2936
|
+
type: ParamType$1.String,
|
|
2937
|
+
},
|
|
2938
|
+
{
|
|
2939
|
+
title: 'Subcontext',
|
|
2940
|
+
param: 'subcontext',
|
|
2941
|
+
type: ParamType$1.String,
|
|
2942
|
+
},
|
|
2943
|
+
{
|
|
2944
|
+
title: 'Tags',
|
|
2945
|
+
param: 'tags',
|
|
2946
|
+
type: ParamType$1.JSON,
|
|
2947
|
+
startsHidden: true,
|
|
2948
|
+
},
|
|
2949
|
+
{
|
|
2950
|
+
title: 'Log Level',
|
|
2951
|
+
param: 'level',
|
|
2952
|
+
type: ParamType$1.String,
|
|
2953
|
+
startsHidden: true,
|
|
2954
|
+
},
|
|
2955
|
+
{
|
|
2956
|
+
title: 'Metadata',
|
|
2957
|
+
param: 'metadata',
|
|
2958
|
+
type: ParamType$1.JSON,
|
|
2959
|
+
startsHidden: true,
|
|
2960
|
+
},
|
|
2961
|
+
{
|
|
2962
|
+
title: 'Source',
|
|
2963
|
+
param: 'source',
|
|
2964
|
+
type: ParamType$1.String,
|
|
2965
|
+
},
|
|
2966
|
+
{
|
|
2967
|
+
title: 'Server Route Path',
|
|
2968
|
+
param: 'routePath',
|
|
2969
|
+
type: ParamType$1.String,
|
|
2970
|
+
startsHidden: true,
|
|
2971
|
+
},
|
|
2972
|
+
{
|
|
2973
|
+
title: 'Server Route Template',
|
|
2974
|
+
param: 'routeTemplate',
|
|
2975
|
+
type: ParamType$1.String,
|
|
2976
|
+
startsHidden: true,
|
|
2977
|
+
},
|
|
2978
|
+
{
|
|
2979
|
+
title: 'Type',
|
|
2980
|
+
param: 'type',
|
|
2981
|
+
type: ParamType$1.String,
|
|
2982
|
+
},
|
|
2983
|
+
{
|
|
2984
|
+
title: 'Error Message',
|
|
2985
|
+
param: 'errorMessage',
|
|
2986
|
+
type: ParamType$1.String,
|
|
2987
|
+
startsHidden: true,
|
|
2988
|
+
},
|
|
2989
|
+
{
|
|
2990
|
+
title: 'Error Code',
|
|
2991
|
+
param: 'errorCode',
|
|
2992
|
+
type: ParamType$1.String,
|
|
2993
|
+
startsHidden: true,
|
|
2994
|
+
},
|
|
2995
|
+
{
|
|
2996
|
+
title: 'Error Stack',
|
|
2997
|
+
param: 'errorStack',
|
|
2998
|
+
type: ParamType$1.String,
|
|
2999
|
+
startsHidden: true,
|
|
3000
|
+
},
|
|
3001
|
+
{
|
|
3002
|
+
title: 'Action Target',
|
|
3003
|
+
param: 'target',
|
|
3004
|
+
type: ParamType$1.String,
|
|
3005
|
+
startsHidden: true,
|
|
3006
|
+
},
|
|
3007
|
+
{
|
|
3008
|
+
title: 'Action Type',
|
|
3009
|
+
param: 'action',
|
|
3010
|
+
type: ParamType$1.String,
|
|
3011
|
+
startsHidden: true,
|
|
3012
|
+
},
|
|
3013
|
+
];
|
|
3014
|
+
/*------------------------------------------------------------------------*/
|
|
2634
3015
|
/* Static Functions */
|
|
2635
3016
|
/*------------------------------------------------------------------------*/
|
|
2636
3017
|
/**
|
|
@@ -2648,7 +3029,7 @@ const genHumanReadableName = (machineReadableName) => {
|
|
|
2648
3029
|
// Uppercase! Add a space before
|
|
2649
3030
|
humanReadableName += ' ';
|
|
2650
3031
|
}
|
|
2651
|
-
humanReadableName +=
|
|
3032
|
+
humanReadableName += char;
|
|
2652
3033
|
});
|
|
2653
3034
|
// Trim and return
|
|
2654
3035
|
return humanReadableName.trim();
|
|
@@ -2710,7 +3091,19 @@ const reducer = (state, action) => {
|
|
|
2710
3091
|
return Object.assign(Object.assign({}, state), { contextFilterState: action.contextFilterState });
|
|
2711
3092
|
}
|
|
2712
3093
|
case ActionType.UpdateTagFilterState: {
|
|
2713
|
-
|
|
3094
|
+
const { tagFilterState } = action;
|
|
3095
|
+
// Select all if every tag is deselected
|
|
3096
|
+
const numTagsSelected = (Object.values(tagFilterState)
|
|
3097
|
+
.filter((isSelected) => {
|
|
3098
|
+
return isSelected;
|
|
3099
|
+
})
|
|
3100
|
+
.length);
|
|
3101
|
+
if (numTagsSelected === 0) {
|
|
3102
|
+
Object.keys(tagFilterState).forEach((tag) => {
|
|
3103
|
+
tagFilterState[tag] = true;
|
|
3104
|
+
});
|
|
3105
|
+
}
|
|
3106
|
+
return Object.assign(Object.assign({}, state), { tagFilterState });
|
|
2714
3107
|
}
|
|
2715
3108
|
case ActionType.UpdateActionErrorFilterState: {
|
|
2716
3109
|
return Object.assign(Object.assign({}, state), { actionErrorFilterState: action.actionErrorFilterState });
|
|
@@ -2730,10 +3123,35 @@ const LogReviewer = (props) => {
|
|
|
2730
3123
|
/*------------------------------------------------------------------------*/
|
|
2731
3124
|
/* Setup */
|
|
2732
3125
|
/*------------------------------------------------------------------------*/
|
|
2733
|
-
var _a, _b, _c, _d, _e;
|
|
3126
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2734
3127
|
/* -------------- Props ------------- */
|
|
2735
3128
|
// Destructure props
|
|
2736
3129
|
const { LogMetadata, onClose, } = props;
|
|
3130
|
+
// Create complete map of contexts
|
|
3131
|
+
const contextMap = {};
|
|
3132
|
+
[
|
|
3133
|
+
((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {}),
|
|
3134
|
+
LogBuiltInMetadata.Context,
|
|
3135
|
+
].forEach((partialContextMap) => {
|
|
3136
|
+
Object.keys(partialContextMap).forEach((context) => {
|
|
3137
|
+
// Add context
|
|
3138
|
+
contextMap[context] = context;
|
|
3139
|
+
// If context has children, add an "uncategorized" subcontext
|
|
3140
|
+
if (typeof contextMap[context] !== 'string') {
|
|
3141
|
+
contextMap[context][LogBuiltInMetadata.Context.Uncategorized] = (LogBuiltInMetadata.Context.Uncategorized);
|
|
3142
|
+
}
|
|
3143
|
+
});
|
|
3144
|
+
});
|
|
3145
|
+
// Create complete map of targets
|
|
3146
|
+
const targetMap = {};
|
|
3147
|
+
[
|
|
3148
|
+
((_b = LogMetadata.Target) !== null && _b !== void 0 ? _b : {}),
|
|
3149
|
+
LogBuiltInMetadata.Target,
|
|
3150
|
+
].forEach((partialTargetMap) => {
|
|
3151
|
+
Object.keys(partialTargetMap).forEach((target) => {
|
|
3152
|
+
targetMap[target] = target;
|
|
3153
|
+
});
|
|
3154
|
+
});
|
|
2737
3155
|
/* -------------- State ------------- */
|
|
2738
3156
|
// Create initial date filter state
|
|
2739
3157
|
const today = getTimeInfoInET();
|
|
@@ -2753,26 +3171,30 @@ const LogReviewer = (props) => {
|
|
|
2753
3171
|
};
|
|
2754
3172
|
// Create initial context filter state
|
|
2755
3173
|
const initContextFilterState = {};
|
|
2756
|
-
Object.keys((
|
|
3174
|
+
Object.keys((_c = LogMetadata.Context) !== null && _c !== void 0 ? _c : {}).forEach((context) => {
|
|
2757
3175
|
var _a, _b;
|
|
2758
3176
|
const contextValue = ((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {})[context];
|
|
2759
3177
|
if (typeof contextValue === 'string') {
|
|
2760
|
-
// Case: no subcontexts
|
|
3178
|
+
// Case: no subcontexts, init as checked
|
|
2761
3179
|
initContextFilterState[contextValue] = true;
|
|
2762
3180
|
}
|
|
2763
3181
|
else {
|
|
2764
3182
|
// Case: subcontexts exist
|
|
2765
|
-
initContextFilterState[
|
|
3183
|
+
initContextFilterState[context] = {};
|
|
2766
3184
|
Object.values(((_b = LogMetadata.Context) !== null && _b !== void 0 ? _b : {})[context]).forEach((subcontext) => {
|
|
2767
|
-
|
|
2768
|
-
|
|
3185
|
+
// Skip self ("_")
|
|
3186
|
+
if (subcontext === '_') {
|
|
3187
|
+
return;
|
|
3188
|
+
}
|
|
3189
|
+
// Initialize as checked
|
|
3190
|
+
initContextFilterState[context][subcontext] = true;
|
|
2769
3191
|
});
|
|
2770
3192
|
}
|
|
2771
3193
|
});
|
|
2772
3194
|
// Create initial tag filter state
|
|
2773
3195
|
const initTagFilterState = {};
|
|
2774
|
-
Object.
|
|
2775
|
-
initTagFilterState[
|
|
3196
|
+
Object.keys((_d = LogMetadata.Tag) !== null && _d !== void 0 ? _d : {}).forEach((tag) => {
|
|
3197
|
+
initTagFilterState[tag] = false;
|
|
2776
3198
|
});
|
|
2777
3199
|
// Create advanced filter state
|
|
2778
3200
|
const initAdvancedFilterState = {
|
|
@@ -2798,12 +3220,16 @@ const LogReviewer = (props) => {
|
|
|
2798
3220
|
target: {},
|
|
2799
3221
|
action: {},
|
|
2800
3222
|
};
|
|
2801
|
-
Object.values((
|
|
3223
|
+
Object.values((_e = LogMetadata.Target) !== null && _e !== void 0 ? _e : {}).forEach((target) => {
|
|
2802
3224
|
initActionErrorFilterState.target[target] = true;
|
|
2803
3225
|
});
|
|
2804
3226
|
Object.values(LogAction$1).forEach((action) => {
|
|
2805
3227
|
initActionErrorFilterState.action[action] = true;
|
|
2806
3228
|
});
|
|
3229
|
+
// Add built-in targets
|
|
3230
|
+
Object.values(LogBuiltInMetadata.Target).forEach((target) => {
|
|
3231
|
+
initActionErrorFilterState.target[target] = true;
|
|
3232
|
+
});
|
|
2807
3233
|
// Initial state
|
|
2808
3234
|
const initialState = {
|
|
2809
3235
|
loading: true,
|
|
@@ -2837,15 +3263,18 @@ const LogReviewer = (props) => {
|
|
|
2837
3263
|
let month = newDateFilterState.startDate.month;
|
|
2838
3264
|
while (
|
|
2839
3265
|
// Earlier year
|
|
2840
|
-
(year
|
|
3266
|
+
(year < newDateFilterState.endDate.year)
|
|
2841
3267
|
// Current year but included month
|
|
2842
3268
|
|| (year === newDateFilterState.endDate.year
|
|
2843
3269
|
&& month <= newDateFilterState.endDate.month)) {
|
|
2844
|
-
// Add to list
|
|
2845
|
-
|
|
2846
|
-
year
|
|
2847
|
-
|
|
2848
|
-
|
|
3270
|
+
// Add to list if not already loaded
|
|
3271
|
+
if (!logMap[year]
|
|
3272
|
+
|| !logMap[year][month]) {
|
|
3273
|
+
toLoad.push({
|
|
3274
|
+
year,
|
|
3275
|
+
month,
|
|
3276
|
+
});
|
|
3277
|
+
}
|
|
2849
3278
|
// Increment
|
|
2850
3279
|
month += 1;
|
|
2851
3280
|
if (month > 12) {
|
|
@@ -2933,10 +3362,10 @@ const LogReviewer = (props) => {
|
|
|
2933
3362
|
/* Filters */
|
|
2934
3363
|
/*----------------------------------------*/
|
|
2935
3364
|
// Filter toggle
|
|
2936
|
-
const filterToggles = (React.createElement("div", { className: "LogReviewer-filter-toggles
|
|
3365
|
+
const filterToggles = (React.createElement("div", { className: "LogReviewer-filter-toggles" },
|
|
2937
3366
|
React.createElement("h3", { className: "m-0" }, "Filters:"),
|
|
2938
|
-
React.createElement("div", { className: "LogReviewer-filter-toggle-buttons" },
|
|
2939
|
-
React.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: () => {
|
|
3367
|
+
React.createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
|
|
3368
|
+
React.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: () => {
|
|
2940
3369
|
dispatch({
|
|
2941
3370
|
type: ActionType.ToggleFilterDrawer,
|
|
2942
3371
|
filterDrawer: FilterDrawer.Date,
|
|
@@ -2944,7 +3373,7 @@ const LogReviewer = (props) => {
|
|
|
2944
3373
|
} },
|
|
2945
3374
|
React.createElement(FontAwesomeIcon, { icon: faCalendar, className: "me-2" }),
|
|
2946
3375
|
"Date"),
|
|
2947
|
-
React.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: () => {
|
|
3376
|
+
React.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: () => {
|
|
2948
3377
|
dispatch({
|
|
2949
3378
|
type: ActionType.ToggleFilterDrawer,
|
|
2950
3379
|
filterDrawer: FilterDrawer.Context,
|
|
@@ -2952,15 +3381,15 @@ const LogReviewer = (props) => {
|
|
|
2952
3381
|
} },
|
|
2953
3382
|
React.createElement(FontAwesomeIcon, { icon: faCircle, className: "me-2" }),
|
|
2954
3383
|
"Context"),
|
|
2955
|
-
React.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: () => {
|
|
3384
|
+
(LogMetadata.Tag && Object.keys(LogMetadata.Tag).length > 0) && (React.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: () => {
|
|
2956
3385
|
dispatch({
|
|
2957
3386
|
type: ActionType.ToggleFilterDrawer,
|
|
2958
3387
|
filterDrawer: FilterDrawer.Tag,
|
|
2959
3388
|
});
|
|
2960
3389
|
} },
|
|
2961
3390
|
React.createElement(FontAwesomeIcon, { icon: faTag, className: "me-2" }),
|
|
2962
|
-
"Tag"),
|
|
2963
|
-
React.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: () => {
|
|
3391
|
+
"Tag")),
|
|
3392
|
+
React.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: () => {
|
|
2964
3393
|
dispatch({
|
|
2965
3394
|
type: ActionType.ToggleFilterDrawer,
|
|
2966
3395
|
filterDrawer: FilterDrawer.Action,
|
|
@@ -2968,41 +3397,64 @@ const LogReviewer = (props) => {
|
|
|
2968
3397
|
} },
|
|
2969
3398
|
React.createElement(FontAwesomeIcon, { icon: faHammer, className: "me-2" }),
|
|
2970
3399
|
"Action"),
|
|
2971
|
-
React.createElement("button", { type: "button", id: "LogReviewer-toggle-advanced-filter-drawer", className: `btn btn-${FilterDrawer.Advanced === expandedFilterDrawer}`, "aria-label": "toggle advanced filter drawer", onClick: () => {
|
|
3400
|
+
React.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: () => {
|
|
2972
3401
|
dispatch({
|
|
2973
3402
|
type: ActionType.ToggleFilterDrawer,
|
|
2974
3403
|
filterDrawer: FilterDrawer.Advanced,
|
|
2975
3404
|
});
|
|
2976
3405
|
} },
|
|
2977
3406
|
React.createElement(FontAwesomeIcon, { icon: faList, className: "me-2" }),
|
|
2978
|
-
"Advanced")
|
|
3407
|
+
"Advanced"),
|
|
3408
|
+
React.createElement("button", { type: "button", id: "LogReviewer-reset-filters-button", className: "btn btn-light", "aria-label": "reset filters", onClick: () => {
|
|
3409
|
+
dispatch({
|
|
3410
|
+
type: ActionType.ResetFilters,
|
|
3411
|
+
initActionErrorFilterState,
|
|
3412
|
+
initAdvancedFilterState,
|
|
3413
|
+
initContextFilterState,
|
|
3414
|
+
initDateFilterState,
|
|
3415
|
+
initTagFilterState,
|
|
3416
|
+
});
|
|
3417
|
+
} },
|
|
3418
|
+
React.createElement(FontAwesomeIcon, { icon: faTimes }),
|
|
3419
|
+
' ',
|
|
3420
|
+
"Reset"))));
|
|
2979
3421
|
// Filter drawer
|
|
2980
3422
|
let filterDrawer;
|
|
2981
3423
|
if (expandedFilterDrawer) {
|
|
2982
3424
|
if (expandedFilterDrawer === FilterDrawer.Date) {
|
|
2983
3425
|
filterDrawer = (React.createElement(TabBox, { title: "Date" },
|
|
2984
|
-
React.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) => {
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
dateFilterState: Object.assign(Object.assign({}, dateFilterState), { startDate: { month, day, year } }),
|
|
2988
|
-
});
|
|
3426
|
+
React.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: 36, onChange: (month, day, year) => {
|
|
3427
|
+
dateFilterState.startDate = { month, day, year };
|
|
3428
|
+
handleDateRangeUpdated(dateFilterState);
|
|
2989
3429
|
} }),
|
|
2990
3430
|
' ',
|
|
2991
3431
|
"to",
|
|
2992
3432
|
' ',
|
|
2993
|
-
React.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) => {
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
3433
|
+
React.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) => {
|
|
3434
|
+
if (year < dateFilterState.startDate.year
|
|
3435
|
+
|| (year === dateFilterState.startDate.year
|
|
3436
|
+
&& month < dateFilterState.startDate.month)
|
|
3437
|
+
|| (year === dateFilterState.startDate.year
|
|
3438
|
+
&& month === dateFilterState.startDate.month
|
|
3439
|
+
&& day < dateFilterState.startDate.day)) {
|
|
3440
|
+
return alert$1('Invalid Start Date', 'The start date cannot be before the end date.');
|
|
3441
|
+
}
|
|
3442
|
+
dateFilterState.endDate = { month, day, year };
|
|
3443
|
+
handleDateRangeUpdated(dateFilterState);
|
|
2998
3444
|
} })));
|
|
2999
3445
|
}
|
|
3000
3446
|
else if (expandedFilterDrawer === FilterDrawer.Context) {
|
|
3001
3447
|
// Create item picker items
|
|
3002
|
-
const
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3448
|
+
const builtInPickableItem = {
|
|
3449
|
+
id: '_',
|
|
3450
|
+
name: 'Auto-logged',
|
|
3451
|
+
isGroup: true,
|
|
3452
|
+
children: [],
|
|
3453
|
+
};
|
|
3454
|
+
const pickableItems = [];
|
|
3455
|
+
Object.keys(contextMap)
|
|
3456
|
+
.forEach((context) => {
|
|
3457
|
+
const value = (contextMap)[context];
|
|
3006
3458
|
if (typeof value === 'string') {
|
|
3007
3459
|
// No subcategories
|
|
3008
3460
|
const item = {
|
|
@@ -3011,38 +3463,64 @@ const LogReviewer = (props) => {
|
|
|
3011
3463
|
isGroup: false,
|
|
3012
3464
|
checked: !!contextFilterState[context],
|
|
3013
3465
|
};
|
|
3014
|
-
|
|
3466
|
+
// Add built-in items to its own folder
|
|
3467
|
+
const isBuiltIn = context in LogBuiltInMetadata.Context;
|
|
3468
|
+
if (isBuiltIn) {
|
|
3469
|
+
// Add to built-in pickable item
|
|
3470
|
+
builtInPickableItem.children.push(item);
|
|
3471
|
+
}
|
|
3472
|
+
else {
|
|
3473
|
+
// Add to pickable items list
|
|
3474
|
+
pickableItems.push(item);
|
|
3475
|
+
}
|
|
3015
3476
|
}
|
|
3016
3477
|
// Has subcategories
|
|
3017
3478
|
const children = (Object.keys(value)
|
|
3479
|
+
// Remove parent name
|
|
3018
3480
|
.filter((subcontext) => {
|
|
3019
3481
|
return subcontext !== '_';
|
|
3020
3482
|
})
|
|
3483
|
+
// Create child pickable items
|
|
3021
3484
|
.map((subcontext) => {
|
|
3022
3485
|
return {
|
|
3023
|
-
id:
|
|
3486
|
+
id: subcontext,
|
|
3024
3487
|
name: genHumanReadableName(subcontext),
|
|
3025
3488
|
isGroup: false,
|
|
3026
|
-
checked:
|
|
3489
|
+
checked: contextFilterState[context][subcontext],
|
|
3027
3490
|
};
|
|
3028
3491
|
}));
|
|
3029
3492
|
const item = {
|
|
3030
3493
|
id: context,
|
|
3031
|
-
name: context,
|
|
3494
|
+
name: genHumanReadableName(context),
|
|
3032
3495
|
isGroup: true,
|
|
3033
3496
|
children,
|
|
3034
3497
|
};
|
|
3035
|
-
|
|
3036
|
-
})
|
|
3498
|
+
pickableItems.push(item);
|
|
3499
|
+
});
|
|
3500
|
+
// Add built-in contexts to end ofl ist
|
|
3501
|
+
pickableItems.push(builtInPickableItem);
|
|
3037
3502
|
// Create filter UI
|
|
3038
3503
|
filterDrawer = (React.createElement(ItemPicker, { title: "Context", items: pickableItems, onChanged: (updatedItems) => {
|
|
3039
3504
|
// Update our state
|
|
3040
3505
|
updatedItems.forEach((pickableItem) => {
|
|
3041
3506
|
if (pickableItem.isGroup) {
|
|
3042
3507
|
// Has subcontexts
|
|
3043
|
-
pickableItem.
|
|
3044
|
-
|
|
3045
|
-
|
|
3508
|
+
if (pickableItem.id === '_') {
|
|
3509
|
+
// Built-in
|
|
3510
|
+
// Treat as if these were top-level contexts
|
|
3511
|
+
pickableItem.children.forEach((subcontextItem) => {
|
|
3512
|
+
contextFilterState[subcontextItem.id] = ('checked' in subcontextItem
|
|
3513
|
+
&& subcontextItem.checked);
|
|
3514
|
+
});
|
|
3515
|
+
}
|
|
3516
|
+
else {
|
|
3517
|
+
// Not built-in
|
|
3518
|
+
pickableItem.children.forEach((subcontextItem) => {
|
|
3519
|
+
if (!subcontextItem.isGroup) {
|
|
3520
|
+
contextFilterState[pickableItem.id][subcontextItem.id] = (subcontextItem.checked);
|
|
3521
|
+
}
|
|
3522
|
+
});
|
|
3523
|
+
}
|
|
3046
3524
|
}
|
|
3047
3525
|
else {
|
|
3048
3526
|
// No subcontexts
|
|
@@ -3057,13 +3535,19 @@ const LogReviewer = (props) => {
|
|
|
3057
3535
|
}
|
|
3058
3536
|
else if (expandedFilterDrawer === FilterDrawer.Tag) {
|
|
3059
3537
|
// Create filter UI
|
|
3060
|
-
filterDrawer = (React.createElement(TabBox, { title: "Tags" },
|
|
3061
|
-
.
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
} }))
|
|
3066
|
-
|
|
3538
|
+
filterDrawer = (React.createElement(TabBox, { title: "Tags" },
|
|
3539
|
+
React.createElement("div", null, "If any are selected, logs must contain at least one but not necessarily all of the selected tags."),
|
|
3540
|
+
Object.keys((_f = LogMetadata.Tag) !== null && _f !== void 0 ? _f : {})
|
|
3541
|
+
.map((tag, i) => {
|
|
3542
|
+
const description = genHumanReadableName(tag);
|
|
3543
|
+
return (React.createElement(CheckboxButton, { id: `LogReviewer-tag-${tag}-checkbox`, text: description, ariaLabel: `require that logs be tagged with "${description}" or any other selected tag`, noMarginOnRight: i === Object.keys(tagFilterState).length - 1, checked: tagFilterState[tag], onChanged: (checked) => {
|
|
3544
|
+
tagFilterState[tag] = checked;
|
|
3545
|
+
dispatch({
|
|
3546
|
+
type: ActionType.UpdateTagFilterState,
|
|
3547
|
+
tagFilterState,
|
|
3548
|
+
});
|
|
3549
|
+
} }));
|
|
3550
|
+
})));
|
|
3067
3551
|
}
|
|
3068
3552
|
else if (expandedFilterDrawer === FilterDrawer.Action) {
|
|
3069
3553
|
// Create filter UI
|
|
@@ -3092,34 +3576,37 @@ const LogReviewer = (props) => {
|
|
|
3092
3576
|
}, ariaLabel: "only show error logs", selected: actionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true })),
|
|
3093
3577
|
(actionErrorFilterState.type === undefined
|
|
3094
3578
|
|| actionErrorFilterState.type === LogType$1.Action) && (React.createElement(TabBox, { title: "Action Log Details" },
|
|
3095
|
-
React.createElement(ButtonInputGroup, { label: "Action" },
|
|
3096
|
-
.
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
actionErrorFilterState,
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3579
|
+
React.createElement(ButtonInputGroup, { label: "Action", className: "mb-2" },
|
|
3580
|
+
React.createElement("div", { className: "d-flex gap-1 flex-wrap" }, Object.keys(LogAction$1)
|
|
3581
|
+
.map((action) => {
|
|
3582
|
+
const description = genHumanReadableName(action);
|
|
3583
|
+
return (React.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) => {
|
|
3584
|
+
actionErrorFilterState.action[action] = checked;
|
|
3585
|
+
console.log(actionErrorFilterState, action, checked);
|
|
3586
|
+
dispatch({
|
|
3587
|
+
type: ActionType.UpdateActionErrorFilterState,
|
|
3588
|
+
actionErrorFilterState,
|
|
3589
|
+
});
|
|
3590
|
+
} }));
|
|
3591
|
+
}))),
|
|
3592
|
+
React.createElement(ButtonInputGroup, { label: "Target" },
|
|
3593
|
+
React.createElement("div", { className: "d-flex gap-1 flex-wrap" }, Object.keys(targetMap)
|
|
3594
|
+
.map((target) => {
|
|
3595
|
+
const description = genHumanReadableName(target);
|
|
3596
|
+
return (React.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) => {
|
|
3597
|
+
actionErrorFilterState.target[target] = checked;
|
|
3598
|
+
console.log(actionErrorFilterState, target, checked);
|
|
3599
|
+
dispatch({
|
|
3600
|
+
type: ActionType.UpdateActionErrorFilterState,
|
|
3601
|
+
actionErrorFilterState,
|
|
3602
|
+
});
|
|
3603
|
+
} }));
|
|
3604
|
+
}))))),
|
|
3118
3605
|
(actionErrorFilterState.type === undefined
|
|
3119
3606
|
|| actionErrorFilterState.type === LogType$1.Error) && (React.createElement(TabBox, { title: "Error Log Details" },
|
|
3120
3607
|
React.createElement("div", { className: "input-group mb-2" },
|
|
3121
3608
|
React.createElement("span", { className: "input-group-text" }, "Error Message"),
|
|
3122
|
-
React.createElement("input", { type: "text", className: "form-control", "aria-label": "query for error message", value: actionErrorFilterState.errorMessage, onChange: (e) => {
|
|
3609
|
+
React.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) => {
|
|
3123
3610
|
actionErrorFilterState.errorMessage = e.target.value;
|
|
3124
3611
|
dispatch({
|
|
3125
3612
|
type: ActionType.UpdateActionErrorFilterState,
|
|
@@ -3128,7 +3615,7 @@ const LogReviewer = (props) => {
|
|
|
3128
3615
|
} })),
|
|
3129
3616
|
React.createElement("div", { className: "input-group mb-2" },
|
|
3130
3617
|
React.createElement("span", { className: "input-group-text" }, "Error Code"),
|
|
3131
|
-
React.createElement("input", { type: "text", className: "form-control", "aria-label": "query for error code", value: actionErrorFilterState.
|
|
3618
|
+
React.createElement("input", { type: "text", className: "form-control", "aria-label": "query for error code", value: actionErrorFilterState.errorCode, placeholder: "e.g. GC22", onChange: (e) => {
|
|
3132
3619
|
actionErrorFilterState.errorCode = ((e.target.value)
|
|
3133
3620
|
.trim()
|
|
3134
3621
|
.toUpperCase());
|
|
@@ -3144,7 +3631,7 @@ const LogReviewer = (props) => {
|
|
|
3144
3631
|
React.createElement(TabBox, { title: "User Info" },
|
|
3145
3632
|
React.createElement("div", { className: "input-group mb-2" },
|
|
3146
3633
|
React.createElement("span", { className: "input-group-text" }, "User First Name"),
|
|
3147
|
-
React.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user first name", value: advancedFilterState.userFirstName, onChange: (e) => {
|
|
3634
|
+
React.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user first name", value: advancedFilterState.userFirstName, placeholder: "e.g. Divardo", onChange: (e) => {
|
|
3148
3635
|
advancedFilterState.userFirstName = e.target.value;
|
|
3149
3636
|
dispatch({
|
|
3150
3637
|
type: ActionType.UpdateAdvancedFilterState,
|
|
@@ -3153,7 +3640,7 @@ const LogReviewer = (props) => {
|
|
|
3153
3640
|
} })),
|
|
3154
3641
|
React.createElement("div", { className: "input-group mb-2" },
|
|
3155
3642
|
React.createElement("span", { className: "input-group-text" }, "User Last Name"),
|
|
3156
|
-
React.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user last name", value: advancedFilterState.userLastName, onChange: (e) => {
|
|
3643
|
+
React.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user last name", value: advancedFilterState.userLastName, placeholder: "e.g. Calicci", onChange: (e) => {
|
|
3157
3644
|
advancedFilterState.userLastName = e.target.value;
|
|
3158
3645
|
dispatch({
|
|
3159
3646
|
type: ActionType.UpdateAdvancedFilterState,
|
|
@@ -3162,7 +3649,7 @@ const LogReviewer = (props) => {
|
|
|
3162
3649
|
} })),
|
|
3163
3650
|
React.createElement("div", { className: "input-group mb-2" },
|
|
3164
3651
|
React.createElement("span", { className: "input-group-text" }, "User Email"),
|
|
3165
|
-
React.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user email", value: advancedFilterState.userEmail, onChange: (e) => {
|
|
3652
|
+
React.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) => {
|
|
3166
3653
|
advancedFilterState.userEmail = ((e.target.value)
|
|
3167
3654
|
.trim());
|
|
3168
3655
|
dispatch({
|
|
@@ -3172,7 +3659,7 @@ const LogReviewer = (props) => {
|
|
|
3172
3659
|
} })),
|
|
3173
3660
|
React.createElement("div", { className: "input-group mb-2" },
|
|
3174
3661
|
React.createElement("span", { className: "input-group-text" }, "User Canvas Id"),
|
|
3175
|
-
React.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user canvas id", value: advancedFilterState.userId, onChange: (e) => {
|
|
3662
|
+
React.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user canvas id", value: advancedFilterState.userId, placeholder: "e.g. 104985", onChange: (e) => {
|
|
3176
3663
|
const { value } = e.target;
|
|
3177
3664
|
// Only update if value contains only numbers
|
|
3178
3665
|
if (/^\d+$/.test(value)) {
|
|
@@ -3209,7 +3696,7 @@ const LogReviewer = (props) => {
|
|
|
3209
3696
|
React.createElement(TabBox, { title: "Course Info" },
|
|
3210
3697
|
React.createElement("div", { className: "input-group mb-2" },
|
|
3211
3698
|
React.createElement("span", { className: "input-group-text" }, "Course Name"),
|
|
3212
|
-
React.createElement("input", { type: "text", className: "form-control", "aria-label": "query for course name", value: advancedFilterState.courseName, onChange: (e) => {
|
|
3699
|
+
React.createElement("input", { type: "text", className: "form-control", "aria-label": "query for course name", value: advancedFilterState.courseName, placeholder: "e.g. GLC 200", onChange: (e) => {
|
|
3213
3700
|
advancedFilterState.courseName = e.target.value;
|
|
3214
3701
|
dispatch({
|
|
3215
3702
|
type: ActionType.UpdateAdvancedFilterState,
|
|
@@ -3218,7 +3705,7 @@ const LogReviewer = (props) => {
|
|
|
3218
3705
|
} })),
|
|
3219
3706
|
React.createElement("div", { className: "input-group mb-2" },
|
|
3220
3707
|
React.createElement("span", { className: "input-group-text" }, "Course Canvas Id"),
|
|
3221
|
-
React.createElement("input", { type: "text", className: "form-control", "aria-label": "query for course canvas id", value: advancedFilterState.courseId, onChange: (e) => {
|
|
3708
|
+
React.createElement("input", { type: "text", className: "form-control", "aria-label": "query for course canvas id", value: advancedFilterState.courseId, placeholder: "e.g. 15948", onChange: (e) => {
|
|
3222
3709
|
const { value } = e.target;
|
|
3223
3710
|
// Only update if value contains only numbers
|
|
3224
3711
|
if (/^\d+$/.test(value)) {
|
|
@@ -3276,37 +3763,38 @@ const LogReviewer = (props) => {
|
|
|
3276
3763
|
advancedFilterState,
|
|
3277
3764
|
});
|
|
3278
3765
|
}, noMarginOnRight: true })),
|
|
3279
|
-
React.createElement("div", { className: "
|
|
3280
|
-
React.createElement("
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
.
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
React.createElement("
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
.
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3766
|
+
advancedFilterState.source !== LogSource$1.Client && (React.createElement("div", { className: "mt-2" },
|
|
3767
|
+
React.createElement("div", { className: "input-group mb-2" },
|
|
3768
|
+
React.createElement("span", { className: "input-group-text" }, "Server Route Path"),
|
|
3769
|
+
React.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) => {
|
|
3770
|
+
advancedFilterState.courseName = ((e.target.value)
|
|
3771
|
+
.trim());
|
|
3772
|
+
dispatch({
|
|
3773
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
3774
|
+
advancedFilterState,
|
|
3775
|
+
});
|
|
3776
|
+
} })),
|
|
3777
|
+
React.createElement("div", { className: "input-group mb-2" },
|
|
3778
|
+
React.createElement("span", { className: "input-group-text" }, "Server Route Template"),
|
|
3779
|
+
React.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) => {
|
|
3780
|
+
advancedFilterState.courseName = ((e.target.value)
|
|
3781
|
+
.trim());
|
|
3782
|
+
dispatch({
|
|
3783
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
3784
|
+
advancedFilterState,
|
|
3785
|
+
});
|
|
3786
|
+
} })))))));
|
|
3299
3787
|
}
|
|
3300
3788
|
}
|
|
3301
3789
|
// Filters UI
|
|
3302
3790
|
const filters = (React.createElement(React.Fragment, null,
|
|
3303
3791
|
filterToggles,
|
|
3304
|
-
filterDrawer && (React.createElement(Drawer,
|
|
3792
|
+
filterDrawer && (React.createElement(Drawer, { customBackgroundColor: "#eee" }, filterDrawer))));
|
|
3305
3793
|
// Actually filter the logs
|
|
3306
3794
|
// > Perform filters
|
|
3307
3795
|
const logs = [];
|
|
3308
3796
|
Object.keys(logMap).forEach((year) => {
|
|
3309
|
-
Object.keys(logMap).forEach((month) => {
|
|
3797
|
+
Object.keys(logMap[year]).forEach((month) => {
|
|
3310
3798
|
logMap[year][month].forEach((log) => {
|
|
3311
3799
|
/* ----------- Date Filter ---------- */
|
|
3312
3800
|
var _a;
|
|
@@ -3342,16 +3830,23 @@ const LogReviewer = (props) => {
|
|
|
3342
3830
|
// Whole context is deselected
|
|
3343
3831
|
contextFilterState[log.context] === false
|
|
3344
3832
|
// None of the subcontexts are selected
|
|
3345
|
-
|| (
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3833
|
+
|| (
|
|
3834
|
+
// Has subcontexts
|
|
3835
|
+
typeof contextFilterState[log.context] !== 'boolean'
|
|
3836
|
+
// None of the subcontexts are selected
|
|
3837
|
+
&& Object.values((_a = contextFilterState[log.context]) !== null && _a !== void 0 ? _a : {})
|
|
3838
|
+
.every((isSelected) => {
|
|
3839
|
+
return !isSelected;
|
|
3840
|
+
}))) {
|
|
3841
|
+
console.log(log.context, contextFilterState);
|
|
3349
3842
|
return;
|
|
3350
3843
|
}
|
|
3351
3844
|
// Subcontext doesn't match
|
|
3352
3845
|
if (
|
|
3353
|
-
// Log
|
|
3354
|
-
log.
|
|
3846
|
+
// Log context is not "uncategorized" (no point in further filters)
|
|
3847
|
+
log.context !== LogBuiltInMetadata.Context.Uncategorized
|
|
3848
|
+
// Log has a subcontext
|
|
3849
|
+
&& log.subcontext
|
|
3355
3850
|
// Context has subcontexts
|
|
3356
3851
|
&& (contextFilterState[log.context]
|
|
3357
3852
|
&& contextFilterState[log.context] !== false
|
|
@@ -3363,12 +3858,16 @@ const LogReviewer = (props) => {
|
|
|
3363
3858
|
/* -------------- Tags -------------- */
|
|
3364
3859
|
// No tags match
|
|
3365
3860
|
if (
|
|
3366
|
-
//
|
|
3367
|
-
|
|
3861
|
+
// At least one tag is required
|
|
3862
|
+
Object.values(tagFilterState)
|
|
3863
|
+
.filter((isSelected) => {
|
|
3864
|
+
return isSelected;
|
|
3865
|
+
})
|
|
3866
|
+
.length > 0
|
|
3368
3867
|
// No tags match
|
|
3369
|
-
&&
|
|
3868
|
+
&& log.tags.every((tag) => {
|
|
3370
3869
|
return !tagFilterState[tag];
|
|
3371
|
-
}))
|
|
3870
|
+
})) {
|
|
3372
3871
|
return;
|
|
3373
3872
|
}
|
|
3374
3873
|
/* ------- Actions and Errors ------- */
|
|
@@ -3502,7 +4001,7 @@ const LogReviewer = (props) => {
|
|
|
3502
4001
|
// Device info exists
|
|
3503
4002
|
&& log.device
|
|
3504
4003
|
// Mobile filter doesn't match
|
|
3505
|
-
&& (advancedFilterState.isMobile
|
|
4004
|
+
&& (advancedFilterState.isMobile !== log.device.isMobile)) {
|
|
3506
4005
|
return;
|
|
3507
4006
|
}
|
|
3508
4007
|
// Log source doesn't match
|
|
@@ -3544,199 +4043,20 @@ const LogReviewer = (props) => {
|
|
|
3544
4043
|
/*----------------------------------------*/
|
|
3545
4044
|
/* Data */
|
|
3546
4045
|
/*----------------------------------------*/
|
|
3547
|
-
//
|
|
3548
|
-
const
|
|
3549
|
-
{
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
},
|
|
3554
|
-
{
|
|
3555
|
-
title: 'Last Name',
|
|
3556
|
-
param: 'userLastName',
|
|
3557
|
-
type: ParamType$1.String,
|
|
3558
|
-
},
|
|
3559
|
-
{
|
|
3560
|
-
title: 'Email',
|
|
3561
|
-
param: 'userEmail',
|
|
3562
|
-
type: ParamType$1.String,
|
|
3563
|
-
},
|
|
3564
|
-
{
|
|
3565
|
-
title: 'Canvas Id',
|
|
3566
|
-
param: 'userId',
|
|
3567
|
-
type: ParamType$1.Int,
|
|
3568
|
-
},
|
|
3569
|
-
{
|
|
3570
|
-
title: 'Student',
|
|
3571
|
-
param: 'isLearner',
|
|
3572
|
-
type: ParamType$1.Boolean,
|
|
3573
|
-
},
|
|
3574
|
-
{
|
|
3575
|
-
title: 'Teaching Staff',
|
|
3576
|
-
param: 'isTTM',
|
|
3577
|
-
type: ParamType$1.Boolean,
|
|
3578
|
-
startsHidden: true,
|
|
3579
|
-
},
|
|
3580
|
-
{
|
|
3581
|
-
title: 'Admin',
|
|
3582
|
-
param: 'isAdmin',
|
|
3583
|
-
type: ParamType$1.Boolean,
|
|
3584
|
-
startsHidden: true,
|
|
3585
|
-
},
|
|
3586
|
-
{
|
|
3587
|
-
title: 'Course Canvas Id',
|
|
3588
|
-
param: 'courseId',
|
|
3589
|
-
type: ParamType$1.Int,
|
|
3590
|
-
startsHidden: true,
|
|
3591
|
-
},
|
|
3592
|
-
{
|
|
3593
|
-
title: 'Course Name',
|
|
3594
|
-
param: 'courseName',
|
|
3595
|
-
type: ParamType$1.String,
|
|
3596
|
-
},
|
|
3597
|
-
{
|
|
3598
|
-
title: 'Browser Name',
|
|
3599
|
-
param: 'browser.name',
|
|
3600
|
-
type: ParamType$1.String,
|
|
3601
|
-
startsHidden: true,
|
|
3602
|
-
},
|
|
3603
|
-
{
|
|
3604
|
-
title: 'Browser Version',
|
|
3605
|
-
param: 'browser.version',
|
|
3606
|
-
type: ParamType$1.String,
|
|
3607
|
-
startsHidden: true,
|
|
3608
|
-
},
|
|
3609
|
-
{
|
|
3610
|
-
title: 'OS',
|
|
3611
|
-
param: 'device.os',
|
|
3612
|
-
type: ParamType$1.String,
|
|
3613
|
-
startsHidden: true,
|
|
3614
|
-
},
|
|
3615
|
-
{
|
|
3616
|
-
title: 'Mobile',
|
|
3617
|
-
param: 'device.isMobile',
|
|
3618
|
-
type: ParamType$1.Boolean,
|
|
3619
|
-
startsHidden: true,
|
|
3620
|
-
},
|
|
3621
|
-
{
|
|
3622
|
-
title: 'Year',
|
|
3623
|
-
param: 'year',
|
|
3624
|
-
type: ParamType$1.Int,
|
|
3625
|
-
},
|
|
3626
|
-
{
|
|
3627
|
-
title: 'Month',
|
|
3628
|
-
param: 'month',
|
|
3629
|
-
type: ParamType$1.Int,
|
|
3630
|
-
},
|
|
3631
|
-
{
|
|
3632
|
-
title: 'Day',
|
|
3633
|
-
param: 'day',
|
|
3634
|
-
type: ParamType$1.Int,
|
|
3635
|
-
},
|
|
3636
|
-
{
|
|
3637
|
-
title: 'Hour',
|
|
3638
|
-
param: 'hour',
|
|
3639
|
-
type: ParamType$1.Int,
|
|
3640
|
-
},
|
|
3641
|
-
{
|
|
3642
|
-
title: 'Minute',
|
|
3643
|
-
param: 'minute',
|
|
3644
|
-
type: ParamType$1.Int,
|
|
3645
|
-
startsHidden: true,
|
|
3646
|
-
},
|
|
3647
|
-
{
|
|
3648
|
-
title: 'Timestamp',
|
|
3649
|
-
param: 'timestamp',
|
|
3650
|
-
type: ParamType$1.Int,
|
|
3651
|
-
startsHidden: true,
|
|
3652
|
-
},
|
|
3653
|
-
{
|
|
3654
|
-
title: 'Context',
|
|
3655
|
-
param: 'context',
|
|
3656
|
-
type: ParamType$1.String,
|
|
3657
|
-
},
|
|
3658
|
-
{
|
|
3659
|
-
title: 'Subcontext',
|
|
3660
|
-
param: 'subcontext',
|
|
3661
|
-
type: ParamType$1.String,
|
|
3662
|
-
},
|
|
3663
|
-
{
|
|
3664
|
-
title: 'Tags',
|
|
3665
|
-
param: 'tags',
|
|
3666
|
-
type: ParamType$1.JSON,
|
|
3667
|
-
startsHidden: true,
|
|
3668
|
-
},
|
|
3669
|
-
{
|
|
3670
|
-
title: 'Log Level',
|
|
3671
|
-
param: 'level',
|
|
3672
|
-
type: ParamType$1.String,
|
|
3673
|
-
startsHidden: true,
|
|
3674
|
-
},
|
|
3675
|
-
{
|
|
3676
|
-
title: 'Metadata',
|
|
3677
|
-
param: 'metadata',
|
|
3678
|
-
type: ParamType$1.JSON,
|
|
3679
|
-
startsHidden: true,
|
|
3680
|
-
},
|
|
3681
|
-
{
|
|
3682
|
-
title: 'Source',
|
|
3683
|
-
param: 'source',
|
|
3684
|
-
type: ParamType$1.String,
|
|
3685
|
-
},
|
|
3686
|
-
{
|
|
3687
|
-
title: 'Server Route Path',
|
|
3688
|
-
param: 'routePath',
|
|
3689
|
-
type: ParamType$1.String,
|
|
3690
|
-
startsHidden: true,
|
|
3691
|
-
},
|
|
3692
|
-
{
|
|
3693
|
-
title: 'Server Route Template',
|
|
3694
|
-
param: 'routeTemplate',
|
|
3695
|
-
type: ParamType$1.String,
|
|
3696
|
-
startsHidden: true,
|
|
3697
|
-
},
|
|
3698
|
-
{
|
|
3699
|
-
title: 'Type',
|
|
3700
|
-
param: 'type',
|
|
3701
|
-
type: ParamType$1.String,
|
|
3702
|
-
},
|
|
3703
|
-
{
|
|
3704
|
-
title: 'Error Message',
|
|
3705
|
-
param: 'errorMessage',
|
|
3706
|
-
type: ParamType$1.String,
|
|
3707
|
-
startsHidden: true,
|
|
3708
|
-
},
|
|
3709
|
-
{
|
|
3710
|
-
title: 'Error Code',
|
|
3711
|
-
param: 'errorCode',
|
|
3712
|
-
type: ParamType$1.String,
|
|
3713
|
-
startsHidden: true,
|
|
3714
|
-
},
|
|
3715
|
-
{
|
|
3716
|
-
title: 'Error Stack',
|
|
3717
|
-
param: 'errorStack',
|
|
3718
|
-
type: ParamType$1.String,
|
|
3719
|
-
startsHidden: true,
|
|
3720
|
-
},
|
|
3721
|
-
{
|
|
3722
|
-
title: 'Action Target',
|
|
3723
|
-
param: 'target',
|
|
3724
|
-
type: ParamType$1.String,
|
|
3725
|
-
startsHidden: true,
|
|
3726
|
-
},
|
|
3727
|
-
{
|
|
3728
|
-
title: 'Action Type',
|
|
3729
|
-
param: 'action',
|
|
3730
|
-
type: ParamType$1.String,
|
|
3731
|
-
startsHidden: true,
|
|
3732
|
-
},
|
|
3733
|
-
];
|
|
4046
|
+
// Nothing to show notice
|
|
4047
|
+
const noLogsNotice = (logs.length === 0
|
|
4048
|
+
? (React.createElement("div", { className: "alert alert-warning text-center mt-2" },
|
|
4049
|
+
React.createElement("h4", { className: "m-1" }, "No Logs to Show"),
|
|
4050
|
+
React.createElement("div", null, "Either your filters are too strict or no matching logs have been created yet.")))
|
|
4051
|
+
: undefined);
|
|
3734
4052
|
// Create intelliTable
|
|
3735
|
-
const dataTable = (React.createElement(IntelliTable, { title: "Matching Logs", id: "logs", data: logs, columns: columns }));
|
|
4053
|
+
const dataTable = (React.createElement(IntelliTable, { title: "Matching Logs:", csvName: `Logs from ${getHumanReadableDate()}`, id: "logs", data: logs, columns: columns }));
|
|
3736
4054
|
// Main body
|
|
3737
4055
|
body = (React.createElement(React.Fragment, null,
|
|
3738
4056
|
filters,
|
|
3739
|
-
|
|
4057
|
+
React.createElement("div", { className: "mt-2" },
|
|
4058
|
+
dataTable,
|
|
4059
|
+
noLogsNotice)));
|
|
3740
4060
|
}
|
|
3741
4061
|
/* ---------- Wrap in Modal --------- */
|
|
3742
4062
|
return (React.createElement("div", { className: "LogReviewer-outer-container" },
|
|
@@ -3744,14 +4064,10 @@ const LogReviewer = (props) => {
|
|
|
3744
4064
|
React.createElement("div", { className: "LogReviewer-inner-container" },
|
|
3745
4065
|
React.createElement("div", { className: "LogReviewer-header" },
|
|
3746
4066
|
React.createElement("div", { className: "LogReviewer-header-title" },
|
|
3747
|
-
React.createElement("
|
|
3748
|
-
React.createElement("
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
padding: 0,
|
|
3752
|
-
margin: 0,
|
|
3753
|
-
} },
|
|
3754
|
-
React.createElement(FontAwesomeIcon, { icon: faTimes }))),
|
|
4067
|
+
React.createElement("h3", { className: "text-center m-0" }, "Log Review Dashboard")),
|
|
4068
|
+
React.createElement("div", { style: { width: 0 } },
|
|
4069
|
+
React.createElement("button", { type: "button", className: "LogReviewer-header-close-button btn btn-dark btn-lg pe-0", "aria-label": "close log reviewer panel", onClick: onClose },
|
|
4070
|
+
React.createElement(FontAwesomeIcon, { icon: faTimes })))),
|
|
3755
4071
|
React.createElement("div", { className: "LogReviewer-contents" }, body))));
|
|
3756
4072
|
};
|
|
3757
4073
|
|
|
@@ -4082,15 +4398,17 @@ const initServer = (opts) => {
|
|
|
4082
4398
|
},
|
|
4083
4399
|
handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
4084
4400
|
// Get user info
|
|
4085
|
-
const { userId, isAdmin } = params;
|
|
4401
|
+
const { year, month, userId, isAdmin, } = params;
|
|
4086
4402
|
// Validate user
|
|
4087
|
-
// isAdmin is already checked because path starts with '/admin'
|
|
4088
4403
|
const canReview = yield canReviewLogs(userId, isAdmin);
|
|
4089
4404
|
if (!canReview) {
|
|
4090
4405
|
throw new ErrorWithCode('You cannot access this resource because you do not have the appropriate permissions.', ReactKitErrorCode$1.NotAllowedToReviewLogs);
|
|
4091
4406
|
}
|
|
4092
4407
|
// Query for logs
|
|
4093
|
-
const logs = yield _logCollection.find({
|
|
4408
|
+
const logs = yield _logCollection.find({
|
|
4409
|
+
year,
|
|
4410
|
+
month,
|
|
4411
|
+
});
|
|
4094
4412
|
// Return logs
|
|
4095
4413
|
return logs;
|
|
4096
4414
|
}),
|
|
@@ -4739,7 +5057,7 @@ const genRouteHandler = (opts) => {
|
|
|
4739
5057
|
isAdmin: !!launchInfo.isAdmin,
|
|
4740
5058
|
isTTM: !!launchInfo.isTTM,
|
|
4741
5059
|
courseId: launchInfo.courseId,
|
|
4742
|
-
courseName: launchInfo.
|
|
5060
|
+
courseName: launchInfo.contextLabel,
|
|
4743
5061
|
browser,
|
|
4744
5062
|
device,
|
|
4745
5063
|
year,
|
|
@@ -4766,7 +5084,7 @@ const genRouteHandler = (opts) => {
|
|
|
4766
5084
|
}
|
|
4767
5085
|
: {
|
|
4768
5086
|
type: LogType$1.Action,
|
|
4769
|
-
target: ((_m = opts.target) !== null && _m !== void 0 ? _m : LogBuiltInMetadata.Target.
|
|
5087
|
+
target: ((_m = opts.target) !== null && _m !== void 0 ? _m : LogBuiltInMetadata.Target.NoTarget),
|
|
4770
5088
|
action: ((_o = opts.action) !== null && _o !== void 0 ? _o : LogAction$1.Unknown),
|
|
4771
5089
|
});
|
|
4772
5090
|
// Source-specific info
|
|
@@ -4979,29 +5297,6 @@ const startMinWait = (minWaitMs) => {
|
|
|
4979
5297
|
});
|
|
4980
5298
|
};
|
|
4981
5299
|
|
|
4982
|
-
// Import shared helpers
|
|
4983
|
-
/**
|
|
4984
|
-
* Get a human-readable description of a date (all in ET)
|
|
4985
|
-
* @author Gabe Abrams
|
|
4986
|
-
* @param [dateOrTimestamp=today] the date or timestamp for the date to describe
|
|
4987
|
-
* @returns human-readable description of the date
|
|
4988
|
-
*/
|
|
4989
|
-
const getHumanReadableDate = (dateOrTimestamp) => {
|
|
4990
|
-
// Get the date info
|
|
4991
|
-
const { month, day, year, } = getTimeInfoInET(dateOrTimestamp);
|
|
4992
|
-
const currYear = getTimeInfoInET().year;
|
|
4993
|
-
// Get the short month description
|
|
4994
|
-
const monthName = getMonthName(month).short;
|
|
4995
|
-
// Create start of description
|
|
4996
|
-
let description = `${monthName} ${day}${getOrdinal(day)}`;
|
|
4997
|
-
// Add on year if it's different
|
|
4998
|
-
if (year !== currYear) {
|
|
4999
|
-
description += ` ${year}`;
|
|
5000
|
-
}
|
|
5001
|
-
// Return description
|
|
5002
|
-
return description;
|
|
5003
|
-
};
|
|
5004
|
-
|
|
5005
5300
|
/**
|
|
5006
5301
|
* Get the current part of day (morning, evening, etc.)
|
|
5007
5302
|
* @author Gabe Abrams
|