dce-reactkit 3.2.2-beta.9 → 3.2.3
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 +731 -435
- package/dist/cjs/index.js.map +1 -1
- 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/dynamicConstants/DynamicWord.d.ts +13 -0
- package/dist/cjs/types/index.d.ts +2 -1
- 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 +732 -437
- package/dist/esm/index.js.map +1 -1
- 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/dynamicConstants/DynamicWord.d.ts +13 -0
- package/dist/esm/types/index.d.ts +2 -1
- 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 +63 -30
- package/package.json +1 -1
- package/src/components/ButtonInputGroup.tsx +1 -1
- package/src/components/CSVDownloadButton.tsx +2 -2
- package/src/components/Drawer.tsx +9 -1
- package/src/components/IntelliTable.tsx +178 -23
- package/src/components/ItemPicker/NestableItemList.tsx +32 -14
- package/src/components/LogReviewer.tsx +584 -424
- package/src/components/SimpleDateChooser.tsx +30 -25
- package/src/dynamicConstants/DynamicWord.tsx +55 -0
- package/src/helpers/genCSV.ts +22 -4
- package/src/helpers/genRouteHandler.ts +2 -2
- package/src/helpers/logClientEvent.tsx +1 -1
- package/src/index.ts +5 -0
- 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/cjs/index.js
CHANGED
|
@@ -519,14 +519,14 @@ 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
|
|
|
@@ -682,7 +682,7 @@ const logClientEvent = (opts) => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
682
682
|
? opts.error.stack
|
|
683
683
|
: undefined),
|
|
684
684
|
target: (opts.action
|
|
685
|
-
? ((_g = opts.target) !== null && _g !== void 0 ? _g : LogBuiltInMetadata.Target.
|
|
685
|
+
? ((_g = opts.target) !== null && _g !== void 0 ? _g : LogBuiltInMetadata.Target.NoTarget)
|
|
686
686
|
: undefined),
|
|
687
687
|
action: (opts.action
|
|
688
688
|
? opts.action
|
|
@@ -1202,7 +1202,7 @@ const ButtonInputGroup = (props) => {
|
|
|
1202
1202
|
borderTopRightRadius: 0,
|
|
1203
1203
|
borderBottomRightRadius: 0,
|
|
1204
1204
|
} }, label),
|
|
1205
|
-
React__default["default"].createElement("span", { className: "input-group-text flex-grow-1 rounded-right", style: {
|
|
1205
|
+
React__default["default"].createElement("span", { className: "input-group-text flex-grow-1 rounded-right d-flex flex-wrap", style: {
|
|
1206
1206
|
backgroundColor: 'white',
|
|
1207
1207
|
borderTopLeftRadius: 0,
|
|
1208
1208
|
borderBottomLeftRadius: 0,
|
|
@@ -1307,18 +1307,6 @@ const getTimeInfoInET = (dateOrTimestamp) => {
|
|
|
1307
1307
|
};
|
|
1308
1308
|
};
|
|
1309
1309
|
|
|
1310
|
-
/**
|
|
1311
|
-
* Force a number to stay within specific bounds
|
|
1312
|
-
* @author Gabe Abrams
|
|
1313
|
-
* @param num the number to move into the bounds
|
|
1314
|
-
* @param min the minimum number in the bound
|
|
1315
|
-
* @param max the maximum number in the bound
|
|
1316
|
-
* @returns bounded number
|
|
1317
|
-
*/
|
|
1318
|
-
const forceNumIntoBounds = (num, min, max) => {
|
|
1319
|
-
return Math.max(min, Math.min(max, num));
|
|
1320
|
-
};
|
|
1321
|
-
|
|
1322
1310
|
/**
|
|
1323
1311
|
* A very simple, lightweight date chooser
|
|
1324
1312
|
* @author Gabe Abrams
|
|
@@ -1333,7 +1321,7 @@ const SimpleDateChooser = (props) => {
|
|
|
1333
1321
|
var _a;
|
|
1334
1322
|
/* -------------- Props ------------- */
|
|
1335
1323
|
const { ariaLabel, name, month, day, year, onChange, chooseFromPast, } = props;
|
|
1336
|
-
|
|
1324
|
+
const numMonthsToShow = ((_a = props.numMonthsToShow) !== null && _a !== void 0 ? _a : 6);
|
|
1337
1325
|
/*------------------------------------------------------------------------*/
|
|
1338
1326
|
/* Render */
|
|
1339
1327
|
/*------------------------------------------------------------------------*/
|
|
@@ -1347,7 +1335,7 @@ const SimpleDateChooser = (props) => {
|
|
|
1347
1335
|
let startMonth = today.month;
|
|
1348
1336
|
if (chooseFromPast) {
|
|
1349
1337
|
startMonth -= Math.max(0, numMonthsToShow - 1);
|
|
1350
|
-
|
|
1338
|
+
while (startMonth <= 0) {
|
|
1351
1339
|
startMonth += 12;
|
|
1352
1340
|
startYear -= 1;
|
|
1353
1341
|
}
|
|
@@ -1355,22 +1343,35 @@ const SimpleDateChooser = (props) => {
|
|
|
1355
1343
|
for (let i = 0; i < numMonthsToShow; i++) {
|
|
1356
1344
|
// Get month and year info
|
|
1357
1345
|
const unmoddedMonth = (startMonth + i);
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1346
|
+
let month = unmoddedMonth;
|
|
1347
|
+
while (month > 12) {
|
|
1348
|
+
month -= 12;
|
|
1349
|
+
}
|
|
1361
1350
|
const monthName = getMonthName(month).full;
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
: startYear);
|
|
1351
|
+
// Year is start year +1 for each 12 months
|
|
1352
|
+
const year = (startYear + (Math.floor(unmoddedMonth / 12)));
|
|
1365
1353
|
// Figure out which days are allowed
|
|
1366
1354
|
const days = [];
|
|
1367
1355
|
const numDaysInMonth = (new Date(year, month, 0)).getDate();
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1356
|
+
if (chooseFromPast) {
|
|
1357
|
+
// Past selection
|
|
1358
|
+
const numDaysToAdd = ((month === today.month)
|
|
1359
|
+
? today.day // Current month, only add up to today
|
|
1360
|
+
: numDaysInMonth // Past month, add all days
|
|
1361
|
+
);
|
|
1362
|
+
for (let day = 1; day <= numDaysToAdd; day++) {
|
|
1363
|
+
days.push(day);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
else {
|
|
1367
|
+
// Future selection: add all remaining days of the month
|
|
1368
|
+
const firstDay = (month === today.month
|
|
1369
|
+
? today.day // Current month: start at current date
|
|
1370
|
+
: 1 // Future month: start at beginning of month
|
|
1371
|
+
);
|
|
1372
|
+
for (let day = firstDay; day <= numDaysInMonth; day++) {
|
|
1373
|
+
days.push(day);
|
|
1374
|
+
}
|
|
1374
1375
|
}
|
|
1375
1376
|
choices.push({
|
|
1376
1377
|
choiceName: `${monthName} ${year}`,
|
|
@@ -1384,7 +1385,7 @@ const SimpleDateChooser = (props) => {
|
|
|
1384
1385
|
const dayOptions = [];
|
|
1385
1386
|
choices.forEach((choice) => {
|
|
1386
1387
|
// Create month option
|
|
1387
|
-
monthOptions.push(React__default["default"].createElement("option", { key: choice.month, value: choice.month
|
|
1388
|
+
monthOptions.push(React__default["default"].createElement("option", { key: choice.month, value: `${choice.month}-${choice.year}`, "aria-label": `choose ${choice.choiceName}`, onSelect: () => {
|
|
1388
1389
|
onChange(choice.month, choice.days[0], choice.year);
|
|
1389
1390
|
} }, choice.choiceName));
|
|
1390
1391
|
if (month === choice.month) {
|
|
@@ -1399,7 +1400,7 @@ const SimpleDateChooser = (props) => {
|
|
|
1399
1400
|
}
|
|
1400
1401
|
});
|
|
1401
1402
|
return (React__default["default"].createElement("div", { className: "SimpleDateChooser d-inline-block", "aria-label": `date chooser with selected date: ${month} ${day}, ${year}` },
|
|
1402
|
-
React__default["default"].createElement("select", { "aria-label": `month for ${ariaLabel}`, className: "custom-select d-inline-block mr-1", style: { width: 'auto' }, id: `SimpleDateChooser-${name}-month`, value: month
|
|
1403
|
+
React__default["default"].createElement("select", { "aria-label": `month for ${ariaLabel}`, className: "custom-select d-inline-block mr-1", style: { width: 'auto' }, id: `SimpleDateChooser-${name}-month`, value: `${month}-${year}`, onChange: (e) => {
|
|
1403
1404
|
const choice = choices[e.target.selectedIndex];
|
|
1404
1405
|
// Change day, month, and year
|
|
1405
1406
|
onChange(choice.month, choice.days[0], choice.year);
|
|
@@ -1441,14 +1442,16 @@ const Drawer = (props) => {
|
|
|
1441
1442
|
/*------------------------------------------------------------------------*/
|
|
1442
1443
|
/* -------------- Props ------------- */
|
|
1443
1444
|
// Destructure all props
|
|
1444
|
-
const { children, } = props;
|
|
1445
|
+
const { customBackgroundColor, children, } = props;
|
|
1445
1446
|
/*------------------------------------------------------------------------*/
|
|
1446
1447
|
/* Render */
|
|
1447
1448
|
/*------------------------------------------------------------------------*/
|
|
1448
1449
|
/*----------------------------------------*/
|
|
1449
1450
|
/* Main UI */
|
|
1450
1451
|
/*----------------------------------------*/
|
|
1451
|
-
return (React__default["default"].createElement("div", { className: "Drawer-container"
|
|
1452
|
+
return (React__default["default"].createElement("div", { className: "Drawer-container", style: {
|
|
1453
|
+
backgroundColor: (customBackgroundColor !== null && customBackgroundColor !== void 0 ? customBackgroundColor : undefined),
|
|
1454
|
+
} },
|
|
1452
1455
|
React__default["default"].createElement("style", null, style$4),
|
|
1453
1456
|
children));
|
|
1454
1457
|
};
|
|
@@ -1911,24 +1914,25 @@ const CopiableBox = (props) => {
|
|
|
1911
1914
|
/**
|
|
1912
1915
|
* Reusable nested item picker
|
|
1913
1916
|
* @author Yuen Ler Chow
|
|
1917
|
+
* @author Gabe Abrams
|
|
1914
1918
|
*/
|
|
1915
1919
|
/* ------------- Actions ------------ */
|
|
1916
1920
|
// Types of actions
|
|
1917
1921
|
var ActionType$2;
|
|
1918
1922
|
(function (ActionType) {
|
|
1919
|
-
// Toggle whether
|
|
1920
|
-
ActionType["
|
|
1923
|
+
// Toggle whether a child are being shown
|
|
1924
|
+
ActionType["ToggleChild"] = "toggle-child";
|
|
1921
1925
|
})(ActionType$2 || (ActionType$2 = {}));
|
|
1922
1926
|
/**
|
|
1923
1927
|
* Reducer that executes actions
|
|
1924
|
-
* @author
|
|
1928
|
+
* @author Gabe Abrams
|
|
1925
1929
|
* @param state current state
|
|
1926
1930
|
* @param action action to execute
|
|
1927
1931
|
*/
|
|
1928
1932
|
const reducer$2 = (state, action) => {
|
|
1929
1933
|
switch (action.type) {
|
|
1930
|
-
case ActionType$2.
|
|
1931
|
-
return {
|
|
1934
|
+
case ActionType$2.ToggleChild: {
|
|
1935
|
+
return Object.assign(Object.assign({}, state), { childExpanded: Object.assign(Object.assign({}, state.childExpanded), { [String(action.id)]: !state.childExpanded[String(action.id)] }) });
|
|
1932
1936
|
}
|
|
1933
1937
|
default: {
|
|
1934
1938
|
return state;
|
|
@@ -1946,14 +1950,19 @@ const NestableItemList = (props) => {
|
|
|
1946
1950
|
// Destructure all props
|
|
1947
1951
|
const { items, onChanged, } = props;
|
|
1948
1952
|
/* -------------- State ------------- */
|
|
1953
|
+
// Create initial map of child expanded booleans
|
|
1954
|
+
const initChildExpanded = {};
|
|
1955
|
+
items.forEach((item) => {
|
|
1956
|
+
initChildExpanded[String(item.id)] = false;
|
|
1957
|
+
});
|
|
1949
1958
|
// Initial state
|
|
1950
1959
|
const initialState = {
|
|
1951
|
-
|
|
1960
|
+
childExpanded: initChildExpanded,
|
|
1952
1961
|
};
|
|
1953
1962
|
// Initialize state
|
|
1954
1963
|
const [state, dispatch] = React.useReducer(reducer$2, initialState);
|
|
1955
1964
|
// Destructure common state
|
|
1956
|
-
const {
|
|
1965
|
+
const { childExpanded, } = state;
|
|
1957
1966
|
/*------------------------------------------------------------------------*/
|
|
1958
1967
|
/* Component Functions */
|
|
1959
1968
|
/*------------------------------------------------------------------------*/
|
|
@@ -2041,14 +2050,15 @@ const NestableItemList = (props) => {
|
|
|
2041
2050
|
backgroundColor: 'transparent',
|
|
2042
2051
|
}, type: "button", onClick: () => {
|
|
2043
2052
|
dispatch({
|
|
2044
|
-
type: ActionType$2.
|
|
2053
|
+
type: ActionType$2.ToggleChild,
|
|
2054
|
+
id: item.id,
|
|
2045
2055
|
});
|
|
2046
|
-
}, "aria-label": `${
|
|
2047
|
-
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon:
|
|
2056
|
+
}, "aria-label": `${childExpanded[item.id] ? 'Hide' : 'Show'} items in ${item.name}` },
|
|
2057
|
+
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: childExpanded[item.id] ? freeSolidSvgIcons.faChevronDown : freeSolidSvgIcons.faChevronRight })))),
|
|
2048
2058
|
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) => {
|
|
2049
2059
|
onChanged(changeChecked(item.id, checked, items));
|
|
2050
2060
|
}, ariaLabel: `Select ${item.name}`, checkedVariant: Variant$1.Light }),
|
|
2051
|
-
item.isGroup &&
|
|
2061
|
+
(item.isGroup && childExpanded[item.id]) && (React__default["default"].createElement("div", { className: "NestableItemList-children-container", style: {
|
|
2052
2062
|
paddingLeft: '2.2rem',
|
|
2053
2063
|
} },
|
|
2054
2064
|
React__default["default"].createElement(NestableItemList, { items: item.children, onChanged: (updatedItems) => {
|
|
@@ -2085,6 +2095,29 @@ const ItemPicker = (props) => {
|
|
|
2085
2095
|
React__default["default"].createElement(NestableItemList, { items: items, onChanged: onChanged }))));
|
|
2086
2096
|
};
|
|
2087
2097
|
|
|
2098
|
+
// Import shared helpers
|
|
2099
|
+
/**
|
|
2100
|
+
* Get a human-readable description of a date (all in ET)
|
|
2101
|
+
* @author Gabe Abrams
|
|
2102
|
+
* @param [dateOrTimestamp=today] the date or timestamp for the date to describe
|
|
2103
|
+
* @returns human-readable description of the date
|
|
2104
|
+
*/
|
|
2105
|
+
const getHumanReadableDate = (dateOrTimestamp) => {
|
|
2106
|
+
// Get the date info
|
|
2107
|
+
const { month, day, year, } = getTimeInfoInET(dateOrTimestamp);
|
|
2108
|
+
const currYear = getTimeInfoInET().year;
|
|
2109
|
+
// Get the short month description
|
|
2110
|
+
const monthName = getMonthName(month).short;
|
|
2111
|
+
// Create start of description
|
|
2112
|
+
let description = `${monthName} ${day}${getOrdinal(day)}`;
|
|
2113
|
+
// Add on year if it's different
|
|
2114
|
+
if (year !== currYear) {
|
|
2115
|
+
description += ` ${year}`;
|
|
2116
|
+
}
|
|
2117
|
+
// Return description
|
|
2118
|
+
return description;
|
|
2119
|
+
};
|
|
2120
|
+
|
|
2088
2121
|
/**
|
|
2089
2122
|
* Path of the route for storing client-side logs
|
|
2090
2123
|
* @author Gabe Abrams
|
|
@@ -2124,37 +2157,37 @@ var LogType$1 = LogType;
|
|
|
2124
2157
|
var LogAction;
|
|
2125
2158
|
(function (LogAction) {
|
|
2126
2159
|
// Target was opened by the user (it was not on screen, but now it is)
|
|
2127
|
-
LogAction["Open"] = "
|
|
2160
|
+
LogAction["Open"] = "Open";
|
|
2128
2161
|
// Target was closed by the user (it was on screen, but now it is not)
|
|
2129
|
-
LogAction["Close"] = "
|
|
2162
|
+
LogAction["Close"] = "Close";
|
|
2130
2163
|
// Target was cancelled by the user (it was on closed without saving)
|
|
2131
|
-
LogAction["Cancel"] = "
|
|
2164
|
+
LogAction["Cancel"] = "Cancel";
|
|
2132
2165
|
// Target was expanded by the user (it always remains on screen, but size was changed)
|
|
2133
|
-
LogAction["Expand"] = "
|
|
2166
|
+
LogAction["Expand"] = "Expand";
|
|
2134
2167
|
// Target was collapsed by the user (it always remains on screen, but size was changed)
|
|
2135
|
-
LogAction["Collapse"] = "
|
|
2168
|
+
LogAction["Collapse"] = "Collapse";
|
|
2136
2169
|
// Target was viewed by the user (only for items that are not opened or closed, those must use Open/Close actions)
|
|
2137
|
-
LogAction["View"] = "
|
|
2170
|
+
LogAction["View"] = "View";
|
|
2138
2171
|
// Target interrupted the user (popup, dialog, validation message, etc. appeared without user prompting)
|
|
2139
|
-
LogAction["Interrupt"] = "
|
|
2172
|
+
LogAction["Interrupt"] = "Interrupt";
|
|
2140
2173
|
// Target was created by the user (it did not exist before)
|
|
2141
|
-
LogAction["Create"] = "
|
|
2174
|
+
LogAction["Create"] = "Create";
|
|
2142
2175
|
// Target was edited by the user (it existed and was changed)
|
|
2143
|
-
LogAction["Edit"] = "
|
|
2176
|
+
LogAction["Edit"] = "Edit";
|
|
2144
2177
|
// Target was deleted by the user (it existed and now it doesn't)
|
|
2145
|
-
LogAction["Delete"] = "
|
|
2178
|
+
LogAction["Delete"] = "Delete";
|
|
2146
2179
|
// Target was added by the user (it already existed and was added to another place)
|
|
2147
|
-
LogAction["Add"] = "
|
|
2180
|
+
LogAction["Add"] = "Add";
|
|
2148
2181
|
// Target was removed by the user (it was removed from something but still exists)
|
|
2149
|
-
LogAction["Remove"] = "
|
|
2182
|
+
LogAction["Remove"] = "Remove";
|
|
2150
2183
|
// Target was activated by the user (click, check, tap, keypress, etc.)
|
|
2151
|
-
LogAction["Activate"] = "
|
|
2184
|
+
LogAction["Activate"] = "Activate";
|
|
2152
2185
|
// Target was deactivated by the user (click away, uncheck, tap outside of, tab away, etc.)
|
|
2153
|
-
LogAction["Deactivate"] = "
|
|
2186
|
+
LogAction["Deactivate"] = "Deactivate";
|
|
2154
2187
|
// User showed interest in a target (hover, peek, etc.)
|
|
2155
|
-
LogAction["Peek"] = "
|
|
2188
|
+
LogAction["Peek"] = "Peek";
|
|
2156
2189
|
// Unknown action
|
|
2157
|
-
LogAction["Unknown"] = "
|
|
2190
|
+
LogAction["Unknown"] = "Unknown";
|
|
2158
2191
|
})(LogAction || (LogAction = {}));
|
|
2159
2192
|
var LogAction$1 = LogAction;
|
|
2160
2193
|
|
|
@@ -2196,12 +2229,12 @@ const roundToNumDecimals = (num, numDecimals) => {
|
|
|
2196
2229
|
* @returns escaped cell text
|
|
2197
2230
|
*/
|
|
2198
2231
|
const escapeCellText = (text) => {
|
|
2199
|
-
if (!text.includes(',')) {
|
|
2232
|
+
if (!String(text).includes(',')) {
|
|
2200
2233
|
// No need to escape
|
|
2201
|
-
return text;
|
|
2234
|
+
return String(text);
|
|
2202
2235
|
}
|
|
2203
2236
|
// Perform escape
|
|
2204
|
-
return `"${text.replace(/"/g, '""')}`;
|
|
2237
|
+
return `"${String(text).replace(/"/g, '""')}`;
|
|
2205
2238
|
};
|
|
2206
2239
|
/**
|
|
2207
2240
|
* Generate a CSV file
|
|
@@ -2220,9 +2253,26 @@ const genCSV = (data, columns) => {
|
|
|
2220
2253
|
.join(','));
|
|
2221
2254
|
// Add each row
|
|
2222
2255
|
data.forEach((datum) => {
|
|
2256
|
+
csv += '\n';
|
|
2223
2257
|
csv += (columns
|
|
2224
2258
|
.map((column) => {
|
|
2225
|
-
|
|
2259
|
+
let contents;
|
|
2260
|
+
const cell = datum[column.param];
|
|
2261
|
+
if (typeof cell === 'string'
|
|
2262
|
+
|| typeof cell === 'number') {
|
|
2263
|
+
contents = String(cell);
|
|
2264
|
+
}
|
|
2265
|
+
else if (typeof cell === 'undefined'
|
|
2266
|
+
|| cell === null) {
|
|
2267
|
+
contents = '';
|
|
2268
|
+
}
|
|
2269
|
+
else if (typeof cell === 'object') {
|
|
2270
|
+
contents = JSON.stringify(cell);
|
|
2271
|
+
}
|
|
2272
|
+
else {
|
|
2273
|
+
contents = '';
|
|
2274
|
+
}
|
|
2275
|
+
return escapeCellText(contents);
|
|
2226
2276
|
})
|
|
2227
2277
|
.join(','));
|
|
2228
2278
|
});
|
|
@@ -2251,11 +2301,12 @@ const CSVDownloadButton = (props) => {
|
|
|
2251
2301
|
/* Main UI */
|
|
2252
2302
|
/*----------------------------------------*/
|
|
2253
2303
|
// Render the button
|
|
2254
|
-
return (React__default["default"].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
|
|
2304
|
+
return (React__default["default"].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
|
|
2255
2305
|
? `Click to download ${filename}`
|
|
2256
2306
|
: ariaLabel), style: style, onClick: onClick },
|
|
2257
2307
|
!children && (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
2258
|
-
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCloudDownloadAlt
|
|
2308
|
+
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCloudDownloadAlt }),
|
|
2309
|
+
' ',
|
|
2259
2310
|
"Download CSV")),
|
|
2260
2311
|
children));
|
|
2261
2312
|
};
|
|
@@ -2279,9 +2330,13 @@ var ActionType$1;
|
|
|
2279
2330
|
// Toggle sort column param
|
|
2280
2331
|
ActionType["ToggleSortColumn"] = "toggle-sort-column";
|
|
2281
2332
|
// Toggle the visibility of a column
|
|
2282
|
-
ActionType["
|
|
2333
|
+
ActionType["UpdateColumnVisibility"] = "update-column-visibility";
|
|
2283
2334
|
// Toggle the column visibility customization modal
|
|
2284
2335
|
ActionType["ToggleColVisCusModalVisibility"] = "toggle-col-vis-cus-modal-visibility";
|
|
2336
|
+
// Show all columns
|
|
2337
|
+
ActionType["ShowAllColumns"] = "show-all-columns";
|
|
2338
|
+
// Hide all columns
|
|
2339
|
+
ActionType["HideAllColumns"] = "hide-all-columns";
|
|
2285
2340
|
})(ActionType$1 || (ActionType$1 = {}));
|
|
2286
2341
|
/**
|
|
2287
2342
|
* Reducer that executes actions
|
|
@@ -2303,14 +2358,28 @@ const reducer$1 = (state, action) => {
|
|
|
2303
2358
|
// Stop sorting by column
|
|
2304
2359
|
return Object.assign(Object.assign({}, state), { sortColumnParam: undefined, sortType: SortType.Ascending });
|
|
2305
2360
|
}
|
|
2306
|
-
case ActionType$1.
|
|
2361
|
+
case ActionType$1.UpdateColumnVisibility: {
|
|
2307
2362
|
const { columnVisibilityMap } = state;
|
|
2308
|
-
columnVisibilityMap[action.param] =
|
|
2363
|
+
columnVisibilityMap[action.param] = action.visible;
|
|
2309
2364
|
return Object.assign(Object.assign({}, state), { columnVisibilityMap });
|
|
2310
2365
|
}
|
|
2311
2366
|
case ActionType$1.ToggleColVisCusModalVisibility: {
|
|
2312
2367
|
return Object.assign(Object.assign({}, state), { columnVisibilityCustomizationModalVisible: !state.columnVisibilityCustomizationModalVisible });
|
|
2313
2368
|
}
|
|
2369
|
+
case ActionType$1.ShowAllColumns: {
|
|
2370
|
+
const { columnVisibilityMap } = state;
|
|
2371
|
+
Object.keys(columnVisibilityMap).forEach((param) => {
|
|
2372
|
+
columnVisibilityMap[param] = true;
|
|
2373
|
+
});
|
|
2374
|
+
return Object.assign(Object.assign({}, state), { columnVisibilityMap });
|
|
2375
|
+
}
|
|
2376
|
+
case ActionType$1.HideAllColumns: {
|
|
2377
|
+
const { columnVisibilityMap } = state;
|
|
2378
|
+
Object.keys(columnVisibilityMap).forEach((param) => {
|
|
2379
|
+
columnVisibilityMap[param] = false;
|
|
2380
|
+
});
|
|
2381
|
+
return Object.assign(Object.assign({}, state), { columnVisibilityMap });
|
|
2382
|
+
}
|
|
2314
2383
|
default: {
|
|
2315
2384
|
return state;
|
|
2316
2385
|
}
|
|
@@ -2326,7 +2395,18 @@ const IntelliTable = (props) => {
|
|
|
2326
2395
|
var _a;
|
|
2327
2396
|
/* -------------- Props ------------- */
|
|
2328
2397
|
// Destructure all props
|
|
2329
|
-
const { title, id,
|
|
2398
|
+
const { title, id, columns, } = props;
|
|
2399
|
+
// Get data, show empty row if none
|
|
2400
|
+
const data = ((props.data && props.data.length > 0)
|
|
2401
|
+
? props.data
|
|
2402
|
+
: [{ id: 'empty-row' }]);
|
|
2403
|
+
// Get CSV filename
|
|
2404
|
+
let filename = `${title}.csv`;
|
|
2405
|
+
if (props.csvName) {
|
|
2406
|
+
filename = (props.csvName.endsWith('.csv')
|
|
2407
|
+
? props.csvName
|
|
2408
|
+
: `${props.csvName}.csv`);
|
|
2409
|
+
}
|
|
2330
2410
|
/* -------------- State ------------- */
|
|
2331
2411
|
// Initial state
|
|
2332
2412
|
const initColumnVisibilityMap = {};
|
|
@@ -2343,21 +2423,49 @@ const IntelliTable = (props) => {
|
|
|
2343
2423
|
const [state, dispatch] = React.useReducer(reducer$1, initialState);
|
|
2344
2424
|
// Destructure common state
|
|
2345
2425
|
const { sortColumnParam, sortType, columnVisibilityMap, columnVisibilityCustomizationModalVisible, } = state;
|
|
2426
|
+
/*------------------------------------------------------------------------*/
|
|
2427
|
+
/* Render */
|
|
2428
|
+
/*------------------------------------------------------------------------*/
|
|
2429
|
+
/*----------------------------------------*/
|
|
2430
|
+
/* Modal */
|
|
2431
|
+
/*----------------------------------------*/
|
|
2432
|
+
// Modal that may be defined
|
|
2433
|
+
let modal;
|
|
2346
2434
|
/* ------- Col Vis Customization Modal ------ */
|
|
2347
2435
|
if (columnVisibilityCustomizationModalVisible) {
|
|
2348
2436
|
// Create modal
|
|
2349
|
-
(React__default["default"].createElement(Modal, { type: ModalType$1.Okay, title: "Choose columns to show:", onClose: () => {
|
|
2437
|
+
modal = (React__default["default"].createElement(Modal, { type: ModalType$1.Okay, title: "Choose columns to show:", onClose: () => {
|
|
2438
|
+
const noColumnsSelected = (Object.values(columnVisibilityMap)
|
|
2439
|
+
.every((isSelected) => {
|
|
2440
|
+
return !isSelected;
|
|
2441
|
+
}));
|
|
2442
|
+
if (noColumnsSelected) {
|
|
2443
|
+
return alert$1('Choose at least one column', 'To continue, you have to choose at least one column to show');
|
|
2444
|
+
}
|
|
2350
2445
|
dispatch({
|
|
2351
2446
|
type: ActionType$1.ToggleColVisCusModalVisibility,
|
|
2352
2447
|
});
|
|
2353
|
-
}
|
|
2354
|
-
|
|
2448
|
+
}, okayVariant: Variant$1.Light },
|
|
2449
|
+
columns.map((column) => {
|
|
2450
|
+
return (React__default["default"].createElement(CheckboxButton, { key: column.param, id: `IntelliTable-${id}-toggle-visibility-${column.param}`, className: "mb-2", text: column.title, onChanged: (checked) => {
|
|
2451
|
+
dispatch({
|
|
2452
|
+
type: ActionType$1.UpdateColumnVisibility,
|
|
2453
|
+
param: column.param,
|
|
2454
|
+
visible: checked,
|
|
2455
|
+
});
|
|
2456
|
+
}, checked: columnVisibilityMap[column.param], ariaLabel: `show "${column.title}" column in the ${title} table`, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Secondary }));
|
|
2457
|
+
}),
|
|
2458
|
+
React__default["default"].createElement("div", { className: "mt-3" }, "Or you can:"),
|
|
2459
|
+
React__default["default"].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: () => {
|
|
2460
|
+
dispatch({
|
|
2461
|
+
type: ActionType$1.ShowAllColumns,
|
|
2462
|
+
});
|
|
2463
|
+
} }, "Select All"),
|
|
2464
|
+
React__default["default"].createElement("button", { type: "button", id: `IntelliTable-${id}-select-none-columns`, className: "btn btn-secondary", "aria-label": `hide all columns in the ${title} table`, onClick: () => {
|
|
2355
2465
|
dispatch({
|
|
2356
|
-
type: ActionType$1.
|
|
2357
|
-
param: column.param,
|
|
2466
|
+
type: ActionType$1.HideAllColumns,
|
|
2358
2467
|
});
|
|
2359
|
-
}
|
|
2360
|
-
})));
|
|
2468
|
+
} }, "Deselect All")));
|
|
2361
2469
|
}
|
|
2362
2470
|
/*----------------------------------------*/
|
|
2363
2471
|
/* Main UI */
|
|
@@ -2371,6 +2479,7 @@ const IntelliTable = (props) => {
|
|
|
2371
2479
|
// Custom info based on current sort type
|
|
2372
2480
|
let sortButtonAriaLabel;
|
|
2373
2481
|
let sortIcon = freeSolidSvgIcons.faSort;
|
|
2482
|
+
let sortingByThisColumn = false;
|
|
2374
2483
|
if (!sortColumnParam) {
|
|
2375
2484
|
// Not being sorted yet
|
|
2376
2485
|
sortButtonAriaLabel = `sort ascending by ${column.title}`;
|
|
@@ -2378,6 +2487,7 @@ const IntelliTable = (props) => {
|
|
|
2378
2487
|
}
|
|
2379
2488
|
else if (column.param === sortColumnParam) {
|
|
2380
2489
|
// Already sorted by this column
|
|
2490
|
+
sortingByThisColumn = true;
|
|
2381
2491
|
if (sortType === SortType.Ascending) {
|
|
2382
2492
|
// Sorted ascending
|
|
2383
2493
|
sortButtonAriaLabel = `sort descending by ${column.title}`;
|
|
@@ -2395,11 +2505,14 @@ const IntelliTable = (props) => {
|
|
|
2395
2505
|
sortIcon = freeSolidSvgIcons.faSort;
|
|
2396
2506
|
}
|
|
2397
2507
|
// Create the cell UI
|
|
2398
|
-
return (React__default["default"].createElement("th", { key: column.param, scope: "col", id: `IntelliTable-${id}-header-${column.param}
|
|
2399
|
-
|
|
2508
|
+
return (React__default["default"].createElement("th", { key: column.param, scope: "col", id: `IntelliTable-${id}-header-${column.param}`, className: "text-start", style: {
|
|
2509
|
+
borderRight: '0.05rem solid #555',
|
|
2510
|
+
borderLeft: '0.05rem solid #555',
|
|
2511
|
+
} },
|
|
2512
|
+
React__default["default"].createElement("div", { className: "d-flex align-items-center justify-content-start flex-row h-100" },
|
|
2400
2513
|
React__default["default"].createElement("span", { className: "text-nowrap" }, column.title),
|
|
2401
2514
|
React__default["default"].createElement("div", null,
|
|
2402
|
-
React__default["default"].createElement("button", { type: "button", className:
|
|
2515
|
+
React__default["default"].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: () => {
|
|
2403
2516
|
dispatch({
|
|
2404
2517
|
type: ActionType$1.ToggleSortColumn,
|
|
2405
2518
|
param: column.param,
|
|
@@ -2417,8 +2530,28 @@ const IntelliTable = (props) => {
|
|
|
2417
2530
|
const descending = (sortType === SortType.Descending);
|
|
2418
2531
|
if (sortColumnParam) {
|
|
2419
2532
|
sortedData.sort((a, b) => {
|
|
2533
|
+
var _a, _b;
|
|
2420
2534
|
const aVal = a[sortColumnParam];
|
|
2421
2535
|
const bVal = b[sortColumnParam];
|
|
2536
|
+
// Tiebreaker sort by timestamp, most recent first (used if tied)
|
|
2537
|
+
const tiebreaker = (((_a = b.timestamp) !== null && _a !== void 0 ? _a : 0)
|
|
2538
|
+
- ((_b = a.timestamp) !== null && _b !== void 0 ? _b : 0));
|
|
2539
|
+
// Auto-sort undefined and null to end of list
|
|
2540
|
+
if ((aVal === undefined || aVal === null)
|
|
2541
|
+
|| (bVal === undefined || bVal === null)) {
|
|
2542
|
+
// At least one was undefined
|
|
2543
|
+
if ((aVal === undefined || aVal === null)
|
|
2544
|
+
&& (bVal === undefined || bVal === null)) {
|
|
2545
|
+
// Both undefined
|
|
2546
|
+
return tiebreaker;
|
|
2547
|
+
}
|
|
2548
|
+
if (aVal === undefined || aVal === null) {
|
|
2549
|
+
// First was undefined
|
|
2550
|
+
return 1;
|
|
2551
|
+
}
|
|
2552
|
+
// Second was undefined
|
|
2553
|
+
return -1;
|
|
2554
|
+
}
|
|
2422
2555
|
// Sort differently based on the data type
|
|
2423
2556
|
// > Boolean
|
|
2424
2557
|
if (paramType === ParamType$1.Boolean) {
|
|
@@ -2428,7 +2561,7 @@ const IntelliTable = (props) => {
|
|
|
2428
2561
|
if (!aVal && bVal) {
|
|
2429
2562
|
return (descending ? 1 : -1);
|
|
2430
2563
|
}
|
|
2431
|
-
return
|
|
2564
|
+
return tiebreaker;
|
|
2432
2565
|
}
|
|
2433
2566
|
// > Number
|
|
2434
2567
|
if (paramType === ParamType$1.Int
|
|
@@ -2440,12 +2573,12 @@ const IntelliTable = (props) => {
|
|
|
2440
2573
|
// > String
|
|
2441
2574
|
if (paramType === ParamType$1.String) {
|
|
2442
2575
|
if (aVal < bVal) {
|
|
2443
|
-
return (descending ?
|
|
2576
|
+
return (descending ? 1 : -1);
|
|
2444
2577
|
}
|
|
2445
2578
|
if (aVal > bVal) {
|
|
2446
|
-
return (descending ? 1 :
|
|
2579
|
+
return (descending ? -1 : 1);
|
|
2447
2580
|
}
|
|
2448
|
-
return
|
|
2581
|
+
return tiebreaker;
|
|
2449
2582
|
}
|
|
2450
2583
|
// > JSON
|
|
2451
2584
|
if (paramType === ParamType$1.JSON) {
|
|
@@ -2460,7 +2593,7 @@ const IntelliTable = (props) => {
|
|
|
2460
2593
|
: (aSize - bSize));
|
|
2461
2594
|
}
|
|
2462
2595
|
// No sort
|
|
2463
|
-
return
|
|
2596
|
+
return tiebreaker;
|
|
2464
2597
|
});
|
|
2465
2598
|
}
|
|
2466
2599
|
// Table body
|
|
@@ -2485,9 +2618,12 @@ const IntelliTable = (props) => {
|
|
|
2485
2618
|
const noValue = (value === undefined
|
|
2486
2619
|
|| value === null);
|
|
2487
2620
|
visibleValue = (noValue
|
|
2488
|
-
? (React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.
|
|
2489
|
-
: (React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faXmarkCircle })));
|
|
2621
|
+
? (React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faMinus }))
|
|
2622
|
+
: (React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: fullValue ? freeSolidSvgIcons.faCheckCircle : freeSolidSvgIcons.faXmarkCircle })));
|
|
2490
2623
|
title = (fullValue ? 'True' : 'False');
|
|
2624
|
+
if (noValue) {
|
|
2625
|
+
title = 'Empty Cell';
|
|
2626
|
+
}
|
|
2491
2627
|
}
|
|
2492
2628
|
else if (column.type === ParamType$1.Int) {
|
|
2493
2629
|
fullValue = Number.parseInt(value, 10);
|
|
@@ -2496,6 +2632,9 @@ const IntelliTable = (props) => {
|
|
|
2496
2632
|
? (React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faMinus }))
|
|
2497
2633
|
: fullValue);
|
|
2498
2634
|
title = String(fullValue);
|
|
2635
|
+
if (noValue) {
|
|
2636
|
+
title = 'Empty Cell';
|
|
2637
|
+
}
|
|
2499
2638
|
}
|
|
2500
2639
|
else if (column.type === ParamType$1.Float) {
|
|
2501
2640
|
fullValue = Number.parseFloat(value);
|
|
@@ -2504,14 +2643,22 @@ const IntelliTable = (props) => {
|
|
|
2504
2643
|
? (React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faMinus }))
|
|
2505
2644
|
: roundToNumDecimals(fullValue, 2));
|
|
2506
2645
|
title = String(fullValue);
|
|
2646
|
+
if (noValue) {
|
|
2647
|
+
title = 'Empty Cell';
|
|
2648
|
+
}
|
|
2507
2649
|
}
|
|
2508
2650
|
else if (column.type === ParamType$1.String) {
|
|
2509
2651
|
fullValue = String(value).trim();
|
|
2510
|
-
const noValue = (value
|
|
2652
|
+
const noValue = (value === undefined
|
|
2653
|
+
|| value === null
|
|
2654
|
+
|| String(fullValue).trim().length === 0);
|
|
2511
2655
|
visibleValue = (noValue
|
|
2512
2656
|
? (React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faMinus }))
|
|
2513
2657
|
: fullValue);
|
|
2514
2658
|
title = `"${value}"`;
|
|
2659
|
+
if (noValue) {
|
|
2660
|
+
title = 'Empty Cell';
|
|
2661
|
+
}
|
|
2515
2662
|
}
|
|
2516
2663
|
else if (column.type === ParamType$1.JSON) {
|
|
2517
2664
|
fullValue = JSON.stringify(value);
|
|
@@ -2521,9 +2668,13 @@ const IntelliTable = (props) => {
|
|
|
2521
2668
|
visibleValue = (noValue
|
|
2522
2669
|
? (React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faMinus }))
|
|
2523
2670
|
: fullValue);
|
|
2671
|
+
title = "JSON Object";
|
|
2524
2672
|
}
|
|
2525
2673
|
// Create UI
|
|
2526
|
-
return (React__default["default"].createElement("td", { key: `${datum.id}-${column.param}`, title: title
|
|
2674
|
+
return (React__default["default"].createElement("td", { key: `${datum.id}-${column.param}`, title: title, style: {
|
|
2675
|
+
borderRight: '0.05rem solid #555',
|
|
2676
|
+
borderLeft: '0.05rem solid #555',
|
|
2677
|
+
} }, visibleValue));
|
|
2527
2678
|
}));
|
|
2528
2679
|
// Add cells to a row
|
|
2529
2680
|
return (React__default["default"].createElement("tr", { key: datum.id }, cells));
|
|
@@ -2540,13 +2691,16 @@ const IntelliTable = (props) => {
|
|
|
2540
2691
|
})
|
|
2541
2692
|
.length);
|
|
2542
2693
|
// Build CSV
|
|
2543
|
-
const csv = genCSV(data, columns)
|
|
2694
|
+
const csv = genCSV(data, columns.filter((column) => {
|
|
2695
|
+
return columnVisibilityMap[column.param];
|
|
2696
|
+
}));
|
|
2544
2697
|
// Build main UI
|
|
2545
2698
|
return (React__default["default"].createElement("div", { className: `IntelliTable-container-${id}` },
|
|
2546
|
-
|
|
2699
|
+
modal,
|
|
2700
|
+
React__default["default"].createElement("div", { className: "d-flex align-items-center justify-content-start" },
|
|
2547
2701
|
React__default["default"].createElement("h3", { className: "m-0" }, title),
|
|
2548
2702
|
React__default["default"].createElement("div", { className: "flex-grow-1 text-end" },
|
|
2549
|
-
React__default["default"].createElement(CSVDownloadButton, { "aria-label": `download data as csv for ${title}`, id: `IntelliTable-${id}-download-as-csv`, filename:
|
|
2703
|
+
React__default["default"].createElement(CSVDownloadButton, { "aria-label": `download data as csv for ${title}`, id: `IntelliTable-${id}-download-as-csv`, filename: filename, csv: csv }),
|
|
2550
2704
|
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: () => {
|
|
2551
2705
|
dispatch({
|
|
2552
2706
|
type: ActionType$1.ToggleColVisCusModalVisibility,
|
|
@@ -2559,7 +2713,7 @@ const IntelliTable = (props) => {
|
|
|
2559
2713
|
numHiddenCols,
|
|
2560
2714
|
' ',
|
|
2561
2715
|
"hidden)"))))),
|
|
2562
|
-
React__default["default"].createElement("div", { className: `IntelliTable-table-${id}`, style: {
|
|
2716
|
+
React__default["default"].createElement("div", { className: `IntelliTable-table-${id} mt-2`, style: {
|
|
2563
2717
|
overflowX: 'auto',
|
|
2564
2718
|
} }, table)));
|
|
2565
2719
|
};
|
|
@@ -2648,8 +2802,12 @@ const style = `
|
|
|
2648
2802
|
background-color: transparent !important;
|
|
2649
2803
|
padding-top: 0 !important;
|
|
2650
2804
|
padding-bottom: 0 !important;
|
|
2805
|
+
padding-right: 1em !important;
|
|
2651
2806
|
margin: 0 !important;
|
|
2652
|
-
color: #
|
|
2807
|
+
color: #444 !important;
|
|
2808
|
+
|
|
2809
|
+
right: 0 !important;
|
|
2810
|
+
position: absolute !important;
|
|
2653
2811
|
}
|
|
2654
2812
|
.LogReviewer-header-close-button:hover {
|
|
2655
2813
|
border: 0 !important;
|
|
@@ -2661,6 +2819,195 @@ const style = `
|
|
|
2661
2819
|
}
|
|
2662
2820
|
`;
|
|
2663
2821
|
/*------------------------------------------------------------------------*/
|
|
2822
|
+
/* Constants */
|
|
2823
|
+
/*------------------------------------------------------------------------*/
|
|
2824
|
+
const columns = [
|
|
2825
|
+
{
|
|
2826
|
+
title: 'First Name',
|
|
2827
|
+
param: 'userFirstName',
|
|
2828
|
+
type: ParamType$1.String,
|
|
2829
|
+
},
|
|
2830
|
+
{
|
|
2831
|
+
title: 'Last Name',
|
|
2832
|
+
param: 'userLastName',
|
|
2833
|
+
type: ParamType$1.String,
|
|
2834
|
+
},
|
|
2835
|
+
{
|
|
2836
|
+
title: 'Email',
|
|
2837
|
+
param: 'userEmail',
|
|
2838
|
+
type: ParamType$1.String,
|
|
2839
|
+
},
|
|
2840
|
+
{
|
|
2841
|
+
title: 'Canvas Id',
|
|
2842
|
+
param: 'userId',
|
|
2843
|
+
type: ParamType$1.Int,
|
|
2844
|
+
},
|
|
2845
|
+
{
|
|
2846
|
+
title: 'Student?',
|
|
2847
|
+
param: 'isLearner',
|
|
2848
|
+
type: ParamType$1.Boolean,
|
|
2849
|
+
},
|
|
2850
|
+
{
|
|
2851
|
+
title: 'Teaching Staff?',
|
|
2852
|
+
param: 'isTTM',
|
|
2853
|
+
type: ParamType$1.Boolean,
|
|
2854
|
+
startsHidden: true,
|
|
2855
|
+
},
|
|
2856
|
+
{
|
|
2857
|
+
title: 'Admin?',
|
|
2858
|
+
param: 'isAdmin',
|
|
2859
|
+
type: ParamType$1.Boolean,
|
|
2860
|
+
startsHidden: true,
|
|
2861
|
+
},
|
|
2862
|
+
{
|
|
2863
|
+
title: 'Course Canvas Id',
|
|
2864
|
+
param: 'courseId',
|
|
2865
|
+
type: ParamType$1.Int,
|
|
2866
|
+
startsHidden: true,
|
|
2867
|
+
},
|
|
2868
|
+
{
|
|
2869
|
+
title: 'Course Name',
|
|
2870
|
+
param: 'courseName',
|
|
2871
|
+
type: ParamType$1.String,
|
|
2872
|
+
},
|
|
2873
|
+
{
|
|
2874
|
+
title: 'Browser Name',
|
|
2875
|
+
param: 'browser.name',
|
|
2876
|
+
type: ParamType$1.String,
|
|
2877
|
+
startsHidden: true,
|
|
2878
|
+
},
|
|
2879
|
+
{
|
|
2880
|
+
title: 'Browser Version',
|
|
2881
|
+
param: 'browser.version',
|
|
2882
|
+
type: ParamType$1.String,
|
|
2883
|
+
startsHidden: true,
|
|
2884
|
+
},
|
|
2885
|
+
{
|
|
2886
|
+
title: 'OS',
|
|
2887
|
+
param: 'device.os',
|
|
2888
|
+
type: ParamType$1.String,
|
|
2889
|
+
startsHidden: true,
|
|
2890
|
+
},
|
|
2891
|
+
{
|
|
2892
|
+
title: 'Mobile?',
|
|
2893
|
+
param: 'device.isMobile',
|
|
2894
|
+
type: ParamType$1.Boolean,
|
|
2895
|
+
startsHidden: true,
|
|
2896
|
+
},
|
|
2897
|
+
{
|
|
2898
|
+
title: 'Year',
|
|
2899
|
+
param: 'year',
|
|
2900
|
+
type: ParamType$1.Int,
|
|
2901
|
+
},
|
|
2902
|
+
{
|
|
2903
|
+
title: 'Month',
|
|
2904
|
+
param: 'month',
|
|
2905
|
+
type: ParamType$1.Int,
|
|
2906
|
+
},
|
|
2907
|
+
{
|
|
2908
|
+
title: 'Day',
|
|
2909
|
+
param: 'day',
|
|
2910
|
+
type: ParamType$1.Int,
|
|
2911
|
+
},
|
|
2912
|
+
{
|
|
2913
|
+
title: 'Hour',
|
|
2914
|
+
param: 'hour',
|
|
2915
|
+
type: ParamType$1.Int,
|
|
2916
|
+
},
|
|
2917
|
+
{
|
|
2918
|
+
title: 'Minute',
|
|
2919
|
+
param: 'minute',
|
|
2920
|
+
type: ParamType$1.Int,
|
|
2921
|
+
startsHidden: true,
|
|
2922
|
+
},
|
|
2923
|
+
{
|
|
2924
|
+
title: 'Timestamp',
|
|
2925
|
+
param: 'timestamp',
|
|
2926
|
+
type: ParamType$1.Int,
|
|
2927
|
+
startsHidden: true,
|
|
2928
|
+
},
|
|
2929
|
+
{
|
|
2930
|
+
title: 'Context',
|
|
2931
|
+
param: 'context',
|
|
2932
|
+
type: ParamType$1.String,
|
|
2933
|
+
},
|
|
2934
|
+
{
|
|
2935
|
+
title: 'Subcontext',
|
|
2936
|
+
param: 'subcontext',
|
|
2937
|
+
type: ParamType$1.String,
|
|
2938
|
+
},
|
|
2939
|
+
{
|
|
2940
|
+
title: 'Tags',
|
|
2941
|
+
param: 'tags',
|
|
2942
|
+
type: ParamType$1.JSON,
|
|
2943
|
+
startsHidden: true,
|
|
2944
|
+
},
|
|
2945
|
+
{
|
|
2946
|
+
title: 'Log Level',
|
|
2947
|
+
param: 'level',
|
|
2948
|
+
type: ParamType$1.String,
|
|
2949
|
+
startsHidden: true,
|
|
2950
|
+
},
|
|
2951
|
+
{
|
|
2952
|
+
title: 'Metadata',
|
|
2953
|
+
param: 'metadata',
|
|
2954
|
+
type: ParamType$1.JSON,
|
|
2955
|
+
startsHidden: true,
|
|
2956
|
+
},
|
|
2957
|
+
{
|
|
2958
|
+
title: 'Source',
|
|
2959
|
+
param: 'source',
|
|
2960
|
+
type: ParamType$1.String,
|
|
2961
|
+
},
|
|
2962
|
+
{
|
|
2963
|
+
title: 'Server Route Path',
|
|
2964
|
+
param: 'routePath',
|
|
2965
|
+
type: ParamType$1.String,
|
|
2966
|
+
startsHidden: true,
|
|
2967
|
+
},
|
|
2968
|
+
{
|
|
2969
|
+
title: 'Server Route Template',
|
|
2970
|
+
param: 'routeTemplate',
|
|
2971
|
+
type: ParamType$1.String,
|
|
2972
|
+
startsHidden: true,
|
|
2973
|
+
},
|
|
2974
|
+
{
|
|
2975
|
+
title: 'Type',
|
|
2976
|
+
param: 'type',
|
|
2977
|
+
type: ParamType$1.String,
|
|
2978
|
+
},
|
|
2979
|
+
{
|
|
2980
|
+
title: 'Error Message',
|
|
2981
|
+
param: 'errorMessage',
|
|
2982
|
+
type: ParamType$1.String,
|
|
2983
|
+
startsHidden: true,
|
|
2984
|
+
},
|
|
2985
|
+
{
|
|
2986
|
+
title: 'Error Code',
|
|
2987
|
+
param: 'errorCode',
|
|
2988
|
+
type: ParamType$1.String,
|
|
2989
|
+
startsHidden: true,
|
|
2990
|
+
},
|
|
2991
|
+
{
|
|
2992
|
+
title: 'Error Stack',
|
|
2993
|
+
param: 'errorStack',
|
|
2994
|
+
type: ParamType$1.String,
|
|
2995
|
+
startsHidden: true,
|
|
2996
|
+
},
|
|
2997
|
+
{
|
|
2998
|
+
title: 'Action Target',
|
|
2999
|
+
param: 'target',
|
|
3000
|
+
type: ParamType$1.String,
|
|
3001
|
+
startsHidden: true,
|
|
3002
|
+
},
|
|
3003
|
+
{
|
|
3004
|
+
title: 'Action Type',
|
|
3005
|
+
param: 'action',
|
|
3006
|
+
type: ParamType$1.String,
|
|
3007
|
+
startsHidden: true,
|
|
3008
|
+
},
|
|
3009
|
+
];
|
|
3010
|
+
/*------------------------------------------------------------------------*/
|
|
2664
3011
|
/* Static Functions */
|
|
2665
3012
|
/*------------------------------------------------------------------------*/
|
|
2666
3013
|
/**
|
|
@@ -2760,10 +3107,35 @@ const LogReviewer = (props) => {
|
|
|
2760
3107
|
/*------------------------------------------------------------------------*/
|
|
2761
3108
|
/* Setup */
|
|
2762
3109
|
/*------------------------------------------------------------------------*/
|
|
2763
|
-
var _a, _b, _c, _d
|
|
3110
|
+
var _a, _b, _c, _d;
|
|
2764
3111
|
/* -------------- Props ------------- */
|
|
2765
3112
|
// Destructure props
|
|
2766
3113
|
const { LogMetadata, onClose, } = props;
|
|
3114
|
+
// Create complete map of contexts
|
|
3115
|
+
const contextMap = {};
|
|
3116
|
+
[
|
|
3117
|
+
((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {}),
|
|
3118
|
+
LogBuiltInMetadata.Context,
|
|
3119
|
+
].forEach((partialContextMap) => {
|
|
3120
|
+
Object.keys(partialContextMap).forEach((context) => {
|
|
3121
|
+
// Add context
|
|
3122
|
+
contextMap[context] = partialContextMap[context];
|
|
3123
|
+
// If context has children, add an "uncategorized" subcontext
|
|
3124
|
+
if (typeof contextMap[context] !== 'string') {
|
|
3125
|
+
contextMap[context][LogBuiltInMetadata.Context.Uncategorized] = (LogBuiltInMetadata.Context.Uncategorized);
|
|
3126
|
+
}
|
|
3127
|
+
});
|
|
3128
|
+
});
|
|
3129
|
+
// Create complete map of targets
|
|
3130
|
+
const targetMap = {};
|
|
3131
|
+
[
|
|
3132
|
+
((_b = LogMetadata.Target) !== null && _b !== void 0 ? _b : {}),
|
|
3133
|
+
LogBuiltInMetadata.Target,
|
|
3134
|
+
].forEach((partialTargetMap) => {
|
|
3135
|
+
Object.keys(partialTargetMap).forEach((target) => {
|
|
3136
|
+
targetMap[target] = partialTargetMap[target];
|
|
3137
|
+
});
|
|
3138
|
+
});
|
|
2767
3139
|
/* -------------- State ------------- */
|
|
2768
3140
|
// Create initial date filter state
|
|
2769
3141
|
const today = getTimeInfoInET();
|
|
@@ -2783,26 +3155,29 @@ const LogReviewer = (props) => {
|
|
|
2783
3155
|
};
|
|
2784
3156
|
// Create initial context filter state
|
|
2785
3157
|
const initContextFilterState = {};
|
|
2786
|
-
Object.keys(
|
|
2787
|
-
|
|
2788
|
-
const contextValue = ((_a = LogMetadata.Context) !== null && _a !== void 0 ? _a : {})[context];
|
|
3158
|
+
Object.keys(contextMap).forEach((context) => {
|
|
3159
|
+
const contextValue = contextMap[context];
|
|
2789
3160
|
if (typeof contextValue === 'string') {
|
|
2790
|
-
// Case: no subcontexts
|
|
3161
|
+
// Case: no subcontexts, init as checked
|
|
2791
3162
|
initContextFilterState[contextValue] = true;
|
|
2792
3163
|
}
|
|
2793
3164
|
else {
|
|
2794
3165
|
// Case: subcontexts exist
|
|
2795
|
-
initContextFilterState[
|
|
2796
|
-
Object.values(
|
|
2797
|
-
|
|
2798
|
-
|
|
3166
|
+
initContextFilterState[context] = {};
|
|
3167
|
+
Object.values(contextMap[context]).forEach((subcontext) => {
|
|
3168
|
+
// Skip self ("_")
|
|
3169
|
+
if (subcontext === '_') {
|
|
3170
|
+
return;
|
|
3171
|
+
}
|
|
3172
|
+
// Initialize as checked
|
|
3173
|
+
initContextFilterState[context][subcontext] = true;
|
|
2799
3174
|
});
|
|
2800
3175
|
}
|
|
2801
3176
|
});
|
|
2802
3177
|
// Create initial tag filter state
|
|
2803
3178
|
const initTagFilterState = {};
|
|
2804
|
-
Object.
|
|
2805
|
-
initTagFilterState[
|
|
3179
|
+
Object.keys((_c = LogMetadata.Tag) !== null && _c !== void 0 ? _c : {}).forEach((tag) => {
|
|
3180
|
+
initTagFilterState[tag] = false;
|
|
2806
3181
|
});
|
|
2807
3182
|
// Create advanced filter state
|
|
2808
3183
|
const initAdvancedFilterState = {
|
|
@@ -2828,7 +3203,7 @@ const LogReviewer = (props) => {
|
|
|
2828
3203
|
target: {},
|
|
2829
3204
|
action: {},
|
|
2830
3205
|
};
|
|
2831
|
-
Object.values(
|
|
3206
|
+
Object.values(targetMap).forEach((target) => {
|
|
2832
3207
|
initActionErrorFilterState.target[target] = true;
|
|
2833
3208
|
});
|
|
2834
3209
|
Object.values(LogAction$1).forEach((action) => {
|
|
@@ -2867,15 +3242,18 @@ const LogReviewer = (props) => {
|
|
|
2867
3242
|
let month = newDateFilterState.startDate.month;
|
|
2868
3243
|
while (
|
|
2869
3244
|
// Earlier year
|
|
2870
|
-
(year
|
|
3245
|
+
(year < newDateFilterState.endDate.year)
|
|
2871
3246
|
// Current year but included month
|
|
2872
3247
|
|| (year === newDateFilterState.endDate.year
|
|
2873
3248
|
&& month <= newDateFilterState.endDate.month)) {
|
|
2874
|
-
// Add to list
|
|
2875
|
-
|
|
2876
|
-
year
|
|
2877
|
-
|
|
2878
|
-
|
|
3249
|
+
// Add to list if not already loaded
|
|
3250
|
+
if (!logMap[year]
|
|
3251
|
+
|| !logMap[year][month]) {
|
|
3252
|
+
toLoad.push({
|
|
3253
|
+
year,
|
|
3254
|
+
month,
|
|
3255
|
+
});
|
|
3256
|
+
}
|
|
2879
3257
|
// Increment
|
|
2880
3258
|
month += 1;
|
|
2881
3259
|
if (month > 12) {
|
|
@@ -2963,7 +3341,7 @@ const LogReviewer = (props) => {
|
|
|
2963
3341
|
/* Filters */
|
|
2964
3342
|
/*----------------------------------------*/
|
|
2965
3343
|
// Filter toggle
|
|
2966
|
-
const filterToggles = (React__default["default"].createElement("div", { className: "LogReviewer-filter-toggles
|
|
3344
|
+
const filterToggles = (React__default["default"].createElement("div", { className: "LogReviewer-filter-toggles" },
|
|
2967
3345
|
React__default["default"].createElement("h3", { className: "m-0" }, "Filters:"),
|
|
2968
3346
|
React__default["default"].createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
|
|
2969
3347
|
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: () => {
|
|
@@ -2998,41 +3376,64 @@ const LogReviewer = (props) => {
|
|
|
2998
3376
|
} },
|
|
2999
3377
|
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faHammer, className: "me-2" }),
|
|
3000
3378
|
"Action"),
|
|
3001
|
-
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: () => {
|
|
3379
|
+
React__default["default"].createElement("button", { type: "button", id: "LogReviewer-toggle-advanced-filter-drawer", className: `btn btn-${FilterDrawer.Advanced === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle advanced filter drawer", onClick: () => {
|
|
3002
3380
|
dispatch({
|
|
3003
3381
|
type: ActionType.ToggleFilterDrawer,
|
|
3004
3382
|
filterDrawer: FilterDrawer.Advanced,
|
|
3005
3383
|
});
|
|
3006
3384
|
} },
|
|
3007
3385
|
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faList, className: "me-2" }),
|
|
3008
|
-
"Advanced")
|
|
3386
|
+
"Advanced"),
|
|
3387
|
+
React__default["default"].createElement("button", { type: "button", id: "LogReviewer-reset-filters-button", className: "btn btn-light", "aria-label": "reset filters", onClick: () => {
|
|
3388
|
+
dispatch({
|
|
3389
|
+
type: ActionType.ResetFilters,
|
|
3390
|
+
initActionErrorFilterState,
|
|
3391
|
+
initAdvancedFilterState,
|
|
3392
|
+
initContextFilterState,
|
|
3393
|
+
initDateFilterState,
|
|
3394
|
+
initTagFilterState,
|
|
3395
|
+
});
|
|
3396
|
+
} },
|
|
3397
|
+
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes }),
|
|
3398
|
+
' ',
|
|
3399
|
+
"Reset"))));
|
|
3009
3400
|
// Filter drawer
|
|
3010
3401
|
let filterDrawer;
|
|
3011
3402
|
if (expandedFilterDrawer) {
|
|
3012
3403
|
if (expandedFilterDrawer === FilterDrawer.Date) {
|
|
3013
3404
|
filterDrawer = (React__default["default"].createElement(TabBox, { title: "Date" },
|
|
3014
|
-
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) => {
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
dateFilterState: Object.assign(Object.assign({}, dateFilterState), { startDate: { month, day, year } }),
|
|
3018
|
-
});
|
|
3405
|
+
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: 36, onChange: (month, day, year) => {
|
|
3406
|
+
dateFilterState.startDate = { month, day, year };
|
|
3407
|
+
handleDateRangeUpdated(dateFilterState);
|
|
3019
3408
|
} }),
|
|
3020
3409
|
' ',
|
|
3021
3410
|
"to",
|
|
3022
3411
|
' ',
|
|
3023
|
-
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) => {
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3412
|
+
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) => {
|
|
3413
|
+
if (year < dateFilterState.startDate.year
|
|
3414
|
+
|| (year === dateFilterState.startDate.year
|
|
3415
|
+
&& month < dateFilterState.startDate.month)
|
|
3416
|
+
|| (year === dateFilterState.startDate.year
|
|
3417
|
+
&& month === dateFilterState.startDate.month
|
|
3418
|
+
&& day < dateFilterState.startDate.day)) {
|
|
3419
|
+
return alert$1('Invalid Start Date', 'The start date cannot be before the end date.');
|
|
3420
|
+
}
|
|
3421
|
+
dateFilterState.endDate = { month, day, year };
|
|
3422
|
+
handleDateRangeUpdated(dateFilterState);
|
|
3028
3423
|
} })));
|
|
3029
3424
|
}
|
|
3030
3425
|
else if (expandedFilterDrawer === FilterDrawer.Context) {
|
|
3031
3426
|
// Create item picker items
|
|
3032
|
-
const
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3427
|
+
const builtInPickableItem = {
|
|
3428
|
+
id: 'built-in-contexts',
|
|
3429
|
+
name: 'Auto-logged',
|
|
3430
|
+
isGroup: true,
|
|
3431
|
+
children: [],
|
|
3432
|
+
};
|
|
3433
|
+
const pickableItems = [];
|
|
3434
|
+
Object.keys(contextMap)
|
|
3435
|
+
.forEach((context) => {
|
|
3436
|
+
const value = contextMap[context];
|
|
3036
3437
|
if (typeof value === 'string') {
|
|
3037
3438
|
// No subcategories
|
|
3038
3439
|
const item = {
|
|
@@ -3041,38 +3442,65 @@ const LogReviewer = (props) => {
|
|
|
3041
3442
|
isGroup: false,
|
|
3042
3443
|
checked: !!contextFilterState[context],
|
|
3043
3444
|
};
|
|
3044
|
-
|
|
3445
|
+
// Add built-in items to its own folder
|
|
3446
|
+
const isBuiltIn = context in LogBuiltInMetadata.Context;
|
|
3447
|
+
if (isBuiltIn) {
|
|
3448
|
+
// Add to built-in pickable item
|
|
3449
|
+
builtInPickableItem.children.push(item);
|
|
3450
|
+
}
|
|
3451
|
+
else {
|
|
3452
|
+
// Add to pickable items list
|
|
3453
|
+
pickableItems.push(item);
|
|
3454
|
+
}
|
|
3455
|
+
return;
|
|
3045
3456
|
}
|
|
3046
3457
|
// Has subcategories
|
|
3047
3458
|
const children = (Object.keys(value)
|
|
3459
|
+
// Remove parent name
|
|
3048
3460
|
.filter((subcontext) => {
|
|
3049
3461
|
return subcontext !== '_';
|
|
3050
3462
|
})
|
|
3463
|
+
// Create child pickable items
|
|
3051
3464
|
.map((subcontext) => {
|
|
3052
3465
|
return {
|
|
3053
|
-
id:
|
|
3466
|
+
id: subcontext,
|
|
3054
3467
|
name: genHumanReadableName(subcontext),
|
|
3055
3468
|
isGroup: false,
|
|
3056
|
-
checked:
|
|
3469
|
+
checked: contextFilterState[context][subcontext],
|
|
3057
3470
|
};
|
|
3058
3471
|
}));
|
|
3059
3472
|
const item = {
|
|
3060
3473
|
id: context,
|
|
3061
|
-
name: context,
|
|
3474
|
+
name: genHumanReadableName(context),
|
|
3062
3475
|
isGroup: true,
|
|
3063
3476
|
children,
|
|
3064
3477
|
};
|
|
3065
|
-
|
|
3066
|
-
})
|
|
3478
|
+
pickableItems.push(item);
|
|
3479
|
+
});
|
|
3480
|
+
// Add built-in contexts to end ofl ist
|
|
3481
|
+
pickableItems.push(builtInPickableItem);
|
|
3067
3482
|
// Create filter UI
|
|
3068
3483
|
filterDrawer = (React__default["default"].createElement(ItemPicker, { title: "Context", items: pickableItems, onChanged: (updatedItems) => {
|
|
3069
3484
|
// Update our state
|
|
3070
3485
|
updatedItems.forEach((pickableItem) => {
|
|
3071
3486
|
if (pickableItem.isGroup) {
|
|
3072
3487
|
// Has subcontexts
|
|
3073
|
-
pickableItem.
|
|
3074
|
-
|
|
3075
|
-
|
|
3488
|
+
if (pickableItem.id === 'built-in-contexts') {
|
|
3489
|
+
// Built-in
|
|
3490
|
+
// Treat as if these were top-level contexts
|
|
3491
|
+
pickableItem.children.forEach((subcontextItem) => {
|
|
3492
|
+
contextFilterState[subcontextItem.id] = ('checked' in subcontextItem
|
|
3493
|
+
&& subcontextItem.checked);
|
|
3494
|
+
});
|
|
3495
|
+
}
|
|
3496
|
+
else {
|
|
3497
|
+
// Not built-in
|
|
3498
|
+
pickableItem.children.forEach((subcontextItem) => {
|
|
3499
|
+
if (!subcontextItem.isGroup) {
|
|
3500
|
+
contextFilterState[pickableItem.id][subcontextItem.id] = (subcontextItem.checked);
|
|
3501
|
+
}
|
|
3502
|
+
});
|
|
3503
|
+
}
|
|
3076
3504
|
}
|
|
3077
3505
|
else {
|
|
3078
3506
|
// No subcontexts
|
|
@@ -3087,13 +3515,19 @@ const LogReviewer = (props) => {
|
|
|
3087
3515
|
}
|
|
3088
3516
|
else if (expandedFilterDrawer === FilterDrawer.Tag) {
|
|
3089
3517
|
// Create filter UI
|
|
3090
|
-
filterDrawer = (React__default["default"].createElement(TabBox, { title: "Tags" },
|
|
3091
|
-
.
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
} })
|
|
3096
|
-
|
|
3518
|
+
filterDrawer = (React__default["default"].createElement(TabBox, { title: "Tags" },
|
|
3519
|
+
React__default["default"].createElement("div", null, "If any tags are selected, logs must contain at least one (but not necessarily all) of the selected tags."),
|
|
3520
|
+
React__default["default"].createElement("div", { className: "d-flex gap-1 flex-wrap" }, Object.keys((_d = LogMetadata.Tag) !== null && _d !== void 0 ? _d : {})
|
|
3521
|
+
.map((tag) => {
|
|
3522
|
+
const description = genHumanReadableName(tag);
|
|
3523
|
+
return (React__default["default"].createElement(CheckboxButton, { key: tag, id: `LogReviewer-tag-${tag}-checkbox`, text: description, ariaLabel: `require that logs be tagged with "${description}" or any other selected tag`, checked: tagFilterState[tag], onChanged: (checked) => {
|
|
3524
|
+
tagFilterState[tag] = checked;
|
|
3525
|
+
dispatch({
|
|
3526
|
+
type: ActionType.UpdateTagFilterState,
|
|
3527
|
+
tagFilterState,
|
|
3528
|
+
});
|
|
3529
|
+
} }));
|
|
3530
|
+
}))));
|
|
3097
3531
|
}
|
|
3098
3532
|
else if (expandedFilterDrawer === FilterDrawer.Action) {
|
|
3099
3533
|
// Create filter UI
|
|
@@ -3122,36 +3556,35 @@ const LogReviewer = (props) => {
|
|
|
3122
3556
|
}, ariaLabel: "only show error logs", selected: actionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true })),
|
|
3123
3557
|
(actionErrorFilterState.type === undefined
|
|
3124
3558
|
|| actionErrorFilterState.type === LogType$1.Action) && (React__default["default"].createElement(TabBox, { title: "Action Log Details" },
|
|
3125
|
-
React__default["default"].createElement(ButtonInputGroup, { label: "Action", className: "mb-2" },
|
|
3126
|
-
.
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3559
|
+
React__default["default"].createElement(ButtonInputGroup, { label: "Action", className: "mb-2" },
|
|
3560
|
+
React__default["default"].createElement("div", { className: "d-flex gap-1 flex-wrap" }, Object.keys(LogAction$1)
|
|
3561
|
+
.map((action) => {
|
|
3562
|
+
const description = genHumanReadableName(action);
|
|
3563
|
+
return (React__default["default"].createElement(CheckboxButton, { key: action, id: `LogReviewer-action-${action}-checkbox`, text: description, ariaLabel: `include logs with action type "${description}" in results`, noMarginOnRight: true, checked: actionErrorFilterState.action[action], onChanged: (checked) => {
|
|
3564
|
+
actionErrorFilterState.action[action] = checked;
|
|
3565
|
+
dispatch({
|
|
3566
|
+
type: ActionType.UpdateActionErrorFilterState,
|
|
3567
|
+
actionErrorFilterState,
|
|
3568
|
+
});
|
|
3569
|
+
} }));
|
|
3570
|
+
}))),
|
|
3136
3571
|
React__default["default"].createElement(ButtonInputGroup, { label: "Target" },
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
.map((target, i) => {
|
|
3140
|
-
var _a;
|
|
3572
|
+
React__default["default"].createElement("div", { className: "d-flex gap-1 flex-wrap" }, Object.keys(targetMap)
|
|
3573
|
+
.map((target) => {
|
|
3141
3574
|
const description = genHumanReadableName(target);
|
|
3142
|
-
return (React__default["default"].createElement(CheckboxButton, { id: `LogReviewer-target-${target}-checkbox`, text: description, ariaLabel: `include logs with target "${description}" in results`, onChanged: (checked) => {
|
|
3575
|
+
return (React__default["default"].createElement(CheckboxButton, { key: target, id: `LogReviewer-target-${target}-checkbox`, text: description, ariaLabel: `include logs with target "${description}" in results`, checked: actionErrorFilterState.target[target], noMarginOnRight: true, onChanged: (checked) => {
|
|
3143
3576
|
actionErrorFilterState.target[target] = checked;
|
|
3144
3577
|
dispatch({
|
|
3145
3578
|
type: ActionType.UpdateActionErrorFilterState,
|
|
3146
3579
|
actionErrorFilterState,
|
|
3147
3580
|
});
|
|
3148
|
-
}
|
|
3149
|
-
})))),
|
|
3581
|
+
} }));
|
|
3582
|
+
}))))),
|
|
3150
3583
|
(actionErrorFilterState.type === undefined
|
|
3151
3584
|
|| actionErrorFilterState.type === LogType$1.Error) && (React__default["default"].createElement(TabBox, { title: "Error Log Details" },
|
|
3152
3585
|
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3153
3586
|
React__default["default"].createElement("span", { className: "input-group-text" }, "Error Message"),
|
|
3154
|
-
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for error message", value: actionErrorFilterState.errorMessage, onChange: (e) => {
|
|
3587
|
+
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for error message", value: actionErrorFilterState.errorMessage, placeholder: "e.g. undefined is not a function", onChange: (e) => {
|
|
3155
3588
|
actionErrorFilterState.errorMessage = e.target.value;
|
|
3156
3589
|
dispatch({
|
|
3157
3590
|
type: ActionType.UpdateActionErrorFilterState,
|
|
@@ -3160,7 +3593,7 @@ const LogReviewer = (props) => {
|
|
|
3160
3593
|
} })),
|
|
3161
3594
|
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3162
3595
|
React__default["default"].createElement("span", { className: "input-group-text" }, "Error Code"),
|
|
3163
|
-
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for error code", value: actionErrorFilterState.
|
|
3596
|
+
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for error code", value: actionErrorFilterState.errorCode, placeholder: "e.g. GC22", onChange: (e) => {
|
|
3164
3597
|
actionErrorFilterState.errorCode = ((e.target.value)
|
|
3165
3598
|
.trim()
|
|
3166
3599
|
.toUpperCase());
|
|
@@ -3173,10 +3606,10 @@ const LogReviewer = (props) => {
|
|
|
3173
3606
|
else if (expandedFilterDrawer === FilterDrawer.Advanced) {
|
|
3174
3607
|
// Create advanced filter ui
|
|
3175
3608
|
filterDrawer = (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
3176
|
-
React__default["default"].createElement(TabBox, { title: "User
|
|
3609
|
+
React__default["default"].createElement(TabBox, { title: "User" },
|
|
3177
3610
|
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3178
3611
|
React__default["default"].createElement("span", { className: "input-group-text" }, "User First Name"),
|
|
3179
|
-
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user first name", value: advancedFilterState.userFirstName, onChange: (e) => {
|
|
3612
|
+
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user first name", value: advancedFilterState.userFirstName, placeholder: "e.g. Divardo", onChange: (e) => {
|
|
3180
3613
|
advancedFilterState.userFirstName = e.target.value;
|
|
3181
3614
|
dispatch({
|
|
3182
3615
|
type: ActionType.UpdateAdvancedFilterState,
|
|
@@ -3185,7 +3618,7 @@ const LogReviewer = (props) => {
|
|
|
3185
3618
|
} })),
|
|
3186
3619
|
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3187
3620
|
React__default["default"].createElement("span", { className: "input-group-text" }, "User Last Name"),
|
|
3188
|
-
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user last name", value: advancedFilterState.userLastName, onChange: (e) => {
|
|
3621
|
+
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user last name", value: advancedFilterState.userLastName, placeholder: "e.g. Calicci", onChange: (e) => {
|
|
3189
3622
|
advancedFilterState.userLastName = e.target.value;
|
|
3190
3623
|
dispatch({
|
|
3191
3624
|
type: ActionType.UpdateAdvancedFilterState,
|
|
@@ -3194,7 +3627,7 @@ const LogReviewer = (props) => {
|
|
|
3194
3627
|
} })),
|
|
3195
3628
|
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3196
3629
|
React__default["default"].createElement("span", { className: "input-group-text" }, "User Email"),
|
|
3197
|
-
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user email", value: advancedFilterState.userEmail, onChange: (e) => {
|
|
3630
|
+
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user email", value: advancedFilterState.userEmail, placeholder: "e.g. calicci@fas.harvard.edu", onChange: (e) => {
|
|
3198
3631
|
advancedFilterState.userEmail = ((e.target.value)
|
|
3199
3632
|
.trim());
|
|
3200
3633
|
dispatch({
|
|
@@ -3204,7 +3637,7 @@ const LogReviewer = (props) => {
|
|
|
3204
3637
|
} })),
|
|
3205
3638
|
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3206
3639
|
React__default["default"].createElement("span", { className: "input-group-text" }, "User Canvas Id"),
|
|
3207
|
-
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user canvas id", value: advancedFilterState.userId, onChange: (e) => {
|
|
3640
|
+
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user canvas id", value: advancedFilterState.userId, placeholder: "e.g. 104985", onChange: (e) => {
|
|
3208
3641
|
const { value } = e.target;
|
|
3209
3642
|
// Only update if value contains only numbers
|
|
3210
3643
|
if (/^\d+$/.test(value)) {
|
|
@@ -3238,10 +3671,10 @@ const LogReviewer = (props) => {
|
|
|
3238
3671
|
advancedFilterState,
|
|
3239
3672
|
});
|
|
3240
3673
|
}, checked: advancedFilterState.includeAdmins, ariaLabel: "show logs from admins" }))),
|
|
3241
|
-
React__default["default"].createElement(TabBox, { title: "Course
|
|
3674
|
+
React__default["default"].createElement(TabBox, { title: "Course" },
|
|
3242
3675
|
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3243
3676
|
React__default["default"].createElement("span", { className: "input-group-text" }, "Course Name"),
|
|
3244
|
-
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for course name", value: advancedFilterState.courseName, onChange: (e) => {
|
|
3677
|
+
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for course name", value: advancedFilterState.courseName, placeholder: "e.g. GLC 200", onChange: (e) => {
|
|
3245
3678
|
advancedFilterState.courseName = e.target.value;
|
|
3246
3679
|
dispatch({
|
|
3247
3680
|
type: ActionType.UpdateAdvancedFilterState,
|
|
@@ -3250,7 +3683,7 @@ const LogReviewer = (props) => {
|
|
|
3250
3683
|
} })),
|
|
3251
3684
|
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3252
3685
|
React__default["default"].createElement("span", { className: "input-group-text" }, "Course Canvas Id"),
|
|
3253
|
-
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for course canvas id", value: advancedFilterState.courseId, onChange: (e) => {
|
|
3686
|
+
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for course canvas id", value: advancedFilterState.courseId, placeholder: "e.g. 15948", onChange: (e) => {
|
|
3254
3687
|
const { value } = e.target;
|
|
3255
3688
|
// Only update if value contains only numbers
|
|
3256
3689
|
if (/^\d+$/.test(value)) {
|
|
@@ -3262,7 +3695,7 @@ const LogReviewer = (props) => {
|
|
|
3262
3695
|
advancedFilterState,
|
|
3263
3696
|
});
|
|
3264
3697
|
} }))),
|
|
3265
|
-
React__default["default"].createElement(TabBox, { title: "Device
|
|
3698
|
+
React__default["default"].createElement(TabBox, { title: "Device" },
|
|
3266
3699
|
React__default["default"].createElement(ButtonInputGroup, { label: "Device Type" },
|
|
3267
3700
|
React__default["default"].createElement(RadioButton, { text: "All Devices", ariaLabel: "show logs from all devices", selected: advancedFilterState.isMobile === undefined, onSelected: () => {
|
|
3268
3701
|
advancedFilterState.isMobile = undefined;
|
|
@@ -3308,32 +3741,33 @@ const LogReviewer = (props) => {
|
|
|
3308
3741
|
advancedFilterState,
|
|
3309
3742
|
});
|
|
3310
3743
|
}, noMarginOnRight: true })),
|
|
3311
|
-
React__default["default"].createElement("div", { className: "
|
|
3312
|
-
React__default["default"].createElement("
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
.
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
React__default["default"].createElement("
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
.
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3744
|
+
advancedFilterState.source !== LogSource$1.Client && (React__default["default"].createElement("div", { className: "mt-2" },
|
|
3745
|
+
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3746
|
+
React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Path"),
|
|
3747
|
+
React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for server route path", value: advancedFilterState.routePath, placeholder: "e.g. /api/ttm/courses/12345", onChange: (e) => {
|
|
3748
|
+
advancedFilterState.courseName = ((e.target.value)
|
|
3749
|
+
.trim());
|
|
3750
|
+
dispatch({
|
|
3751
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
3752
|
+
advancedFilterState,
|
|
3753
|
+
});
|
|
3754
|
+
} })),
|
|
3755
|
+
React__default["default"].createElement("div", { className: "input-group mb-2" },
|
|
3756
|
+
React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Template"),
|
|
3757
|
+
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) => {
|
|
3758
|
+
advancedFilterState.courseName = ((e.target.value)
|
|
3759
|
+
.trim());
|
|
3760
|
+
dispatch({
|
|
3761
|
+
type: ActionType.UpdateAdvancedFilterState,
|
|
3762
|
+
advancedFilterState,
|
|
3763
|
+
});
|
|
3764
|
+
} })))))));
|
|
3331
3765
|
}
|
|
3332
3766
|
}
|
|
3333
3767
|
// Filters UI
|
|
3334
3768
|
const filters = (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
3335
3769
|
filterToggles,
|
|
3336
|
-
filterDrawer && (React__default["default"].createElement(Drawer,
|
|
3770
|
+
filterDrawer && (React__default["default"].createElement(Drawer, { customBackgroundColor: "#eee" }, filterDrawer))));
|
|
3337
3771
|
// Actually filter the logs
|
|
3338
3772
|
// > Perform filters
|
|
3339
3773
|
const logs = [];
|
|
@@ -3374,16 +3808,22 @@ const LogReviewer = (props) => {
|
|
|
3374
3808
|
// Whole context is deselected
|
|
3375
3809
|
contextFilterState[log.context] === false
|
|
3376
3810
|
// None of the subcontexts are selected
|
|
3377
|
-
|| (
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3811
|
+
|| (
|
|
3812
|
+
// Has subcontexts
|
|
3813
|
+
typeof contextFilterState[log.context] !== 'boolean'
|
|
3814
|
+
// None of the subcontexts are selected
|
|
3815
|
+
&& Object.values((_a = contextFilterState[log.context]) !== null && _a !== void 0 ? _a : {})
|
|
3816
|
+
.every((isSelected) => {
|
|
3817
|
+
return !isSelected;
|
|
3818
|
+
}))) {
|
|
3381
3819
|
return;
|
|
3382
3820
|
}
|
|
3383
3821
|
// Subcontext doesn't match
|
|
3384
3822
|
if (
|
|
3385
|
-
// Log
|
|
3386
|
-
log.
|
|
3823
|
+
// Log context is not "uncategorized" (no point in further filters)
|
|
3824
|
+
log.context !== LogBuiltInMetadata.Context.Uncategorized
|
|
3825
|
+
// Log has a subcontext
|
|
3826
|
+
&& log.subcontext
|
|
3387
3827
|
// Context has subcontexts
|
|
3388
3828
|
&& (contextFilterState[log.context]
|
|
3389
3829
|
&& contextFilterState[log.context] !== false
|
|
@@ -3395,12 +3835,16 @@ const LogReviewer = (props) => {
|
|
|
3395
3835
|
/* -------------- Tags -------------- */
|
|
3396
3836
|
// No tags match
|
|
3397
3837
|
if (
|
|
3398
|
-
//
|
|
3399
|
-
|
|
3838
|
+
// At least one tag is required
|
|
3839
|
+
Object.values(tagFilterState)
|
|
3840
|
+
.filter((isSelected) => {
|
|
3841
|
+
return isSelected;
|
|
3842
|
+
})
|
|
3843
|
+
.length > 0
|
|
3400
3844
|
// No tags match
|
|
3401
|
-
&&
|
|
3845
|
+
&& log.tags.every((tag) => {
|
|
3402
3846
|
return !tagFilterState[tag];
|
|
3403
|
-
}))
|
|
3847
|
+
})) {
|
|
3404
3848
|
return;
|
|
3405
3849
|
}
|
|
3406
3850
|
/* ------- Actions and Errors ------- */
|
|
@@ -3534,7 +3978,7 @@ const LogReviewer = (props) => {
|
|
|
3534
3978
|
// Device info exists
|
|
3535
3979
|
&& log.device
|
|
3536
3980
|
// Mobile filter doesn't match
|
|
3537
|
-
&& (advancedFilterState.isMobile
|
|
3981
|
+
&& (advancedFilterState.isMobile !== log.device.isMobile)) {
|
|
3538
3982
|
return;
|
|
3539
3983
|
}
|
|
3540
3984
|
// Log source doesn't match
|
|
@@ -3576,205 +4020,20 @@ const LogReviewer = (props) => {
|
|
|
3576
4020
|
/*----------------------------------------*/
|
|
3577
4021
|
/* Data */
|
|
3578
4022
|
/*----------------------------------------*/
|
|
3579
|
-
//
|
|
3580
|
-
const
|
|
3581
|
-
{
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
},
|
|
3586
|
-
{
|
|
3587
|
-
title: 'Last Name',
|
|
3588
|
-
param: 'userLastName',
|
|
3589
|
-
type: ParamType$1.String,
|
|
3590
|
-
},
|
|
3591
|
-
{
|
|
3592
|
-
title: 'Email',
|
|
3593
|
-
param: 'userEmail',
|
|
3594
|
-
type: ParamType$1.String,
|
|
3595
|
-
},
|
|
3596
|
-
{
|
|
3597
|
-
title: 'Canvas Id',
|
|
3598
|
-
param: 'userId',
|
|
3599
|
-
type: ParamType$1.Int,
|
|
3600
|
-
},
|
|
3601
|
-
{
|
|
3602
|
-
title: 'Student',
|
|
3603
|
-
param: 'isLearner',
|
|
3604
|
-
type: ParamType$1.Boolean,
|
|
3605
|
-
},
|
|
3606
|
-
{
|
|
3607
|
-
title: 'Teaching Staff',
|
|
3608
|
-
param: 'isTTM',
|
|
3609
|
-
type: ParamType$1.Boolean,
|
|
3610
|
-
startsHidden: true,
|
|
3611
|
-
},
|
|
3612
|
-
{
|
|
3613
|
-
title: 'Admin',
|
|
3614
|
-
param: 'isAdmin',
|
|
3615
|
-
type: ParamType$1.Boolean,
|
|
3616
|
-
startsHidden: true,
|
|
3617
|
-
},
|
|
3618
|
-
{
|
|
3619
|
-
title: 'Course Canvas Id',
|
|
3620
|
-
param: 'courseId',
|
|
3621
|
-
type: ParamType$1.Int,
|
|
3622
|
-
startsHidden: true,
|
|
3623
|
-
},
|
|
3624
|
-
{
|
|
3625
|
-
title: 'Course Name',
|
|
3626
|
-
param: 'courseName',
|
|
3627
|
-
type: ParamType$1.String,
|
|
3628
|
-
},
|
|
3629
|
-
{
|
|
3630
|
-
title: 'Browser Name',
|
|
3631
|
-
param: 'browser.name',
|
|
3632
|
-
type: ParamType$1.String,
|
|
3633
|
-
startsHidden: true,
|
|
3634
|
-
},
|
|
3635
|
-
{
|
|
3636
|
-
title: 'Browser Version',
|
|
3637
|
-
param: 'browser.version',
|
|
3638
|
-
type: ParamType$1.String,
|
|
3639
|
-
startsHidden: true,
|
|
3640
|
-
},
|
|
3641
|
-
{
|
|
3642
|
-
title: 'OS',
|
|
3643
|
-
param: 'device.os',
|
|
3644
|
-
type: ParamType$1.String,
|
|
3645
|
-
startsHidden: true,
|
|
3646
|
-
},
|
|
3647
|
-
{
|
|
3648
|
-
title: 'Mobile',
|
|
3649
|
-
param: 'device.isMobile',
|
|
3650
|
-
type: ParamType$1.Boolean,
|
|
3651
|
-
startsHidden: true,
|
|
3652
|
-
},
|
|
3653
|
-
{
|
|
3654
|
-
title: 'Year',
|
|
3655
|
-
param: 'year',
|
|
3656
|
-
type: ParamType$1.Int,
|
|
3657
|
-
},
|
|
3658
|
-
{
|
|
3659
|
-
title: 'Month',
|
|
3660
|
-
param: 'month',
|
|
3661
|
-
type: ParamType$1.Int,
|
|
3662
|
-
},
|
|
3663
|
-
{
|
|
3664
|
-
title: 'Day',
|
|
3665
|
-
param: 'day',
|
|
3666
|
-
type: ParamType$1.Int,
|
|
3667
|
-
},
|
|
3668
|
-
{
|
|
3669
|
-
title: 'Hour',
|
|
3670
|
-
param: 'hour',
|
|
3671
|
-
type: ParamType$1.Int,
|
|
3672
|
-
},
|
|
3673
|
-
{
|
|
3674
|
-
title: 'Minute',
|
|
3675
|
-
param: 'minute',
|
|
3676
|
-
type: ParamType$1.Int,
|
|
3677
|
-
startsHidden: true,
|
|
3678
|
-
},
|
|
3679
|
-
{
|
|
3680
|
-
title: 'Timestamp',
|
|
3681
|
-
param: 'timestamp',
|
|
3682
|
-
type: ParamType$1.Int,
|
|
3683
|
-
startsHidden: true,
|
|
3684
|
-
},
|
|
3685
|
-
{
|
|
3686
|
-
title: 'Context',
|
|
3687
|
-
param: 'context',
|
|
3688
|
-
type: ParamType$1.String,
|
|
3689
|
-
},
|
|
3690
|
-
{
|
|
3691
|
-
title: 'Subcontext',
|
|
3692
|
-
param: 'subcontext',
|
|
3693
|
-
type: ParamType$1.String,
|
|
3694
|
-
},
|
|
3695
|
-
{
|
|
3696
|
-
title: 'Tags',
|
|
3697
|
-
param: 'tags',
|
|
3698
|
-
type: ParamType$1.JSON,
|
|
3699
|
-
startsHidden: true,
|
|
3700
|
-
},
|
|
3701
|
-
{
|
|
3702
|
-
title: 'Log Level',
|
|
3703
|
-
param: 'level',
|
|
3704
|
-
type: ParamType$1.String,
|
|
3705
|
-
startsHidden: true,
|
|
3706
|
-
},
|
|
3707
|
-
{
|
|
3708
|
-
title: 'Metadata',
|
|
3709
|
-
param: 'metadata',
|
|
3710
|
-
type: ParamType$1.JSON,
|
|
3711
|
-
startsHidden: true,
|
|
3712
|
-
},
|
|
3713
|
-
{
|
|
3714
|
-
title: 'Source',
|
|
3715
|
-
param: 'source',
|
|
3716
|
-
type: ParamType$1.String,
|
|
3717
|
-
},
|
|
3718
|
-
{
|
|
3719
|
-
title: 'Server Route Path',
|
|
3720
|
-
param: 'routePath',
|
|
3721
|
-
type: ParamType$1.String,
|
|
3722
|
-
startsHidden: true,
|
|
3723
|
-
},
|
|
3724
|
-
{
|
|
3725
|
-
title: 'Server Route Template',
|
|
3726
|
-
param: 'routeTemplate',
|
|
3727
|
-
type: ParamType$1.String,
|
|
3728
|
-
startsHidden: true,
|
|
3729
|
-
},
|
|
3730
|
-
{
|
|
3731
|
-
title: 'Type',
|
|
3732
|
-
param: 'type',
|
|
3733
|
-
type: ParamType$1.String,
|
|
3734
|
-
},
|
|
3735
|
-
{
|
|
3736
|
-
title: 'Error Message',
|
|
3737
|
-
param: 'errorMessage',
|
|
3738
|
-
type: ParamType$1.String,
|
|
3739
|
-
startsHidden: true,
|
|
3740
|
-
},
|
|
3741
|
-
{
|
|
3742
|
-
title: 'Error Code',
|
|
3743
|
-
param: 'errorCode',
|
|
3744
|
-
type: ParamType$1.String,
|
|
3745
|
-
startsHidden: true,
|
|
3746
|
-
},
|
|
3747
|
-
{
|
|
3748
|
-
title: 'Error Stack',
|
|
3749
|
-
param: 'errorStack',
|
|
3750
|
-
type: ParamType$1.String,
|
|
3751
|
-
startsHidden: true,
|
|
3752
|
-
},
|
|
3753
|
-
{
|
|
3754
|
-
title: 'Action Target',
|
|
3755
|
-
param: 'target',
|
|
3756
|
-
type: ParamType$1.String,
|
|
3757
|
-
startsHidden: true,
|
|
3758
|
-
},
|
|
3759
|
-
{
|
|
3760
|
-
title: 'Action Type',
|
|
3761
|
-
param: 'action',
|
|
3762
|
-
type: ParamType$1.String,
|
|
3763
|
-
startsHidden: true,
|
|
3764
|
-
},
|
|
3765
|
-
];
|
|
4023
|
+
// Nothing to show notice
|
|
4024
|
+
const noLogsNotice = (logs.length === 0
|
|
4025
|
+
? (React__default["default"].createElement("div", { className: "alert alert-warning text-center mt-2" },
|
|
4026
|
+
React__default["default"].createElement("h4", { className: "m-1" }, "No Logs to Show"),
|
|
4027
|
+
React__default["default"].createElement("div", null, "Either your filters are too strict or no matching logs have been created yet.")))
|
|
4028
|
+
: undefined);
|
|
3766
4029
|
// Create intelliTable
|
|
3767
|
-
const dataTable = (
|
|
3768
|
-
? (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
3769
|
-
React__default["default"].createElement("h3", { className: "m-0" }, "Matching Logs:"),
|
|
3770
|
-
React__default["default"].createElement("div", { className: "alert alert-warning text-center" },
|
|
3771
|
-
React__default["default"].createElement("h4", { className: "m-1" }, "No Logs to Show"),
|
|
3772
|
-
React__default["default"].createElement("div", null, "Either your filters are too strict or no matching logs have been created yet."))))
|
|
3773
|
-
: (React__default["default"].createElement(IntelliTable, { title: "Matching Logs:", id: "logs", data: logs, columns: columns })));
|
|
4030
|
+
const dataTable = (React__default["default"].createElement(IntelliTable, { title: "Matching Logs:", csvName: `Logs from ${getHumanReadableDate()}`, id: "logs", data: logs, columns: columns }));
|
|
3774
4031
|
// Main body
|
|
3775
4032
|
body = (React__default["default"].createElement(React__default["default"].Fragment, null,
|
|
3776
4033
|
filters,
|
|
3777
|
-
|
|
4034
|
+
React__default["default"].createElement("div", { className: "mt-2" },
|
|
4035
|
+
dataTable,
|
|
4036
|
+
noLogsNotice)));
|
|
3778
4037
|
}
|
|
3779
4038
|
/* ---------- Wrap in Modal --------- */
|
|
3780
4039
|
return (React__default["default"].createElement("div", { className: "LogReviewer-outer-container" },
|
|
@@ -3783,8 +4042,9 @@ const LogReviewer = (props) => {
|
|
|
3783
4042
|
React__default["default"].createElement("div", { className: "LogReviewer-header" },
|
|
3784
4043
|
React__default["default"].createElement("div", { className: "LogReviewer-header-title" },
|
|
3785
4044
|
React__default["default"].createElement("h3", { className: "text-center m-0" }, "Log Review Dashboard")),
|
|
3786
|
-
React__default["default"].createElement("
|
|
3787
|
-
React__default["default"].createElement(
|
|
4045
|
+
React__default["default"].createElement("div", { style: { width: 0 } },
|
|
4046
|
+
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 },
|
|
4047
|
+
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes })))),
|
|
3788
4048
|
React__default["default"].createElement("div", { className: "LogReviewer-contents" }, body))));
|
|
3789
4049
|
};
|
|
3790
4050
|
|
|
@@ -3806,6 +4066,50 @@ const HOUR_IN_MS = 3600000;
|
|
|
3806
4066
|
*/
|
|
3807
4067
|
const DAY_IN_MS = 86400000;
|
|
3808
4068
|
|
|
4069
|
+
/**
|
|
4070
|
+
* Check if the user is on mobile
|
|
4071
|
+
* from https://stackoverflow.com/questions/11381673/detecting-a-mobile-browser
|
|
4072
|
+
* @returns true if user is on a mobile device
|
|
4073
|
+
*/
|
|
4074
|
+
const checkIfMobileOrTablet = () => {
|
|
4075
|
+
var _a;
|
|
4076
|
+
try {
|
|
4077
|
+
let check = false;
|
|
4078
|
+
((a) => { if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4)))
|
|
4079
|
+
check = true; })((navigator !== null && navigator !== void 0 ? navigator : {}).userAgent || (navigator !== null && navigator !== void 0 ? navigator : {}).vendor || ((_a = window) !== null && _a !== void 0 ? _a : {}).opera);
|
|
4080
|
+
return check;
|
|
4081
|
+
}
|
|
4082
|
+
catch (err) {
|
|
4083
|
+
// Probably this code is being executed on a server
|
|
4084
|
+
return false;
|
|
4085
|
+
}
|
|
4086
|
+
};
|
|
4087
|
+
const isMobileOrTablet = checkIfMobileOrTablet();
|
|
4088
|
+
/**
|
|
4089
|
+
* Dynamic words determined by the user's platform
|
|
4090
|
+
* @author Gabe Abrams
|
|
4091
|
+
*/
|
|
4092
|
+
const DynamicWord = {
|
|
4093
|
+
Click: (isMobileOrTablet
|
|
4094
|
+
? 'tap'
|
|
4095
|
+
: 'click'),
|
|
4096
|
+
ClickCapitalized: (isMobileOrTablet
|
|
4097
|
+
? 'Tap'
|
|
4098
|
+
: 'Click'),
|
|
4099
|
+
App: (isMobileOrTablet
|
|
4100
|
+
? 'app'
|
|
4101
|
+
: 'application'),
|
|
4102
|
+
AppCapitalized: (isMobileOrTablet
|
|
4103
|
+
? 'App'
|
|
4104
|
+
: 'Application'),
|
|
4105
|
+
Device: (isMobileOrTablet
|
|
4106
|
+
? 'device'
|
|
4107
|
+
: 'computer'),
|
|
4108
|
+
DeviceCapitalized: (isMobileOrTablet
|
|
4109
|
+
? 'Device'
|
|
4110
|
+
: 'Computer'),
|
|
4111
|
+
};
|
|
4112
|
+
|
|
3809
4113
|
/**
|
|
3810
4114
|
* Shorten text so it fits into a certain number of chars
|
|
3811
4115
|
* @author Gabe Abrams
|
|
@@ -3880,6 +4184,18 @@ const floorToNumDecimals = (num, numDecimals) => {
|
|
|
3880
4184
|
return (Math.floor(num * rounder) / rounder);
|
|
3881
4185
|
};
|
|
3882
4186
|
|
|
4187
|
+
/**
|
|
4188
|
+
* Force a number to stay within specific bounds
|
|
4189
|
+
* @author Gabe Abrams
|
|
4190
|
+
* @param num the number to move into the bounds
|
|
4191
|
+
* @param min the minimum number in the bound
|
|
4192
|
+
* @param max the maximum number in the bound
|
|
4193
|
+
* @returns bounded number
|
|
4194
|
+
*/
|
|
4195
|
+
const forceNumIntoBounds = (num, min, max) => {
|
|
4196
|
+
return Math.max(min, Math.min(max, num));
|
|
4197
|
+
};
|
|
4198
|
+
|
|
3883
4199
|
/**
|
|
3884
4200
|
* Pad a number's decimal with zeros on the right
|
|
3885
4201
|
* (e.g. 5.2 becomes 5.20 with 2 digit padding)
|
|
@@ -4115,15 +4431,17 @@ const initServer = (opts) => {
|
|
|
4115
4431
|
},
|
|
4116
4432
|
handler: ({ params }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
4117
4433
|
// Get user info
|
|
4118
|
-
const { userId, isAdmin } = params;
|
|
4434
|
+
const { year, month, userId, isAdmin, } = params;
|
|
4119
4435
|
// Validate user
|
|
4120
|
-
// isAdmin is already checked because path starts with '/admin'
|
|
4121
4436
|
const canReview = yield canReviewLogs(userId, isAdmin);
|
|
4122
4437
|
if (!canReview) {
|
|
4123
4438
|
throw new ErrorWithCode('You cannot access this resource because you do not have the appropriate permissions.', ReactKitErrorCode$1.NotAllowedToReviewLogs);
|
|
4124
4439
|
}
|
|
4125
4440
|
// Query for logs
|
|
4126
|
-
const logs = yield _logCollection.find({
|
|
4441
|
+
const logs = yield _logCollection.find({
|
|
4442
|
+
year,
|
|
4443
|
+
month,
|
|
4444
|
+
});
|
|
4127
4445
|
// Return logs
|
|
4128
4446
|
return logs;
|
|
4129
4447
|
}),
|
|
@@ -4772,7 +5090,7 @@ const genRouteHandler = (opts) => {
|
|
|
4772
5090
|
isAdmin: !!launchInfo.isAdmin,
|
|
4773
5091
|
isTTM: !!launchInfo.isTTM,
|
|
4774
5092
|
courseId: launchInfo.courseId,
|
|
4775
|
-
courseName: launchInfo.
|
|
5093
|
+
courseName: launchInfo.contextLabel,
|
|
4776
5094
|
browser,
|
|
4777
5095
|
device,
|
|
4778
5096
|
year,
|
|
@@ -4799,7 +5117,7 @@ const genRouteHandler = (opts) => {
|
|
|
4799
5117
|
}
|
|
4800
5118
|
: {
|
|
4801
5119
|
type: LogType$1.Action,
|
|
4802
|
-
target: ((_m = opts.target) !== null && _m !== void 0 ? _m : LogBuiltInMetadata.Target.
|
|
5120
|
+
target: ((_m = opts.target) !== null && _m !== void 0 ? _m : LogBuiltInMetadata.Target.NoTarget),
|
|
4803
5121
|
action: ((_o = opts.action) !== null && _o !== void 0 ? _o : LogAction$1.Unknown),
|
|
4804
5122
|
});
|
|
4805
5123
|
// Source-specific info
|
|
@@ -5012,29 +5330,6 @@ const startMinWait = (minWaitMs) => {
|
|
|
5012
5330
|
});
|
|
5013
5331
|
};
|
|
5014
5332
|
|
|
5015
|
-
// Import shared helpers
|
|
5016
|
-
/**
|
|
5017
|
-
* Get a human-readable description of a date (all in ET)
|
|
5018
|
-
* @author Gabe Abrams
|
|
5019
|
-
* @param [dateOrTimestamp=today] the date or timestamp for the date to describe
|
|
5020
|
-
* @returns human-readable description of the date
|
|
5021
|
-
*/
|
|
5022
|
-
const getHumanReadableDate = (dateOrTimestamp) => {
|
|
5023
|
-
// Get the date info
|
|
5024
|
-
const { month, day, year, } = getTimeInfoInET(dateOrTimestamp);
|
|
5025
|
-
const currYear = getTimeInfoInET().year;
|
|
5026
|
-
// Get the short month description
|
|
5027
|
-
const monthName = getMonthName(month).short;
|
|
5028
|
-
// Create start of description
|
|
5029
|
-
let description = `${monthName} ${day}${getOrdinal(day)}`;
|
|
5030
|
-
// Add on year if it's different
|
|
5031
|
-
if (year !== currYear) {
|
|
5032
|
-
description += ` ${year}`;
|
|
5033
|
-
}
|
|
5034
|
-
// Return description
|
|
5035
|
-
return description;
|
|
5036
|
-
};
|
|
5037
|
-
|
|
5038
5333
|
/**
|
|
5039
5334
|
* Get the current part of day (morning, evening, etc.)
|
|
5040
5335
|
* @author Gabe Abrams
|
|
@@ -5215,6 +5510,7 @@ exports.CopiableBox = CopiableBox;
|
|
|
5215
5510
|
exports.DAY_IN_MS = DAY_IN_MS;
|
|
5216
5511
|
exports.DayOfWeek = DayOfWeek$1;
|
|
5217
5512
|
exports.Drawer = Drawer;
|
|
5513
|
+
exports.DynamicWord = DynamicWord;
|
|
5218
5514
|
exports.ErrorBox = ErrorBox;
|
|
5219
5515
|
exports.ErrorWithCode = ErrorWithCode;
|
|
5220
5516
|
exports.HOUR_IN_MS = HOUR_IN_MS;
|