From abaaabdaf335139d868faf4cfd075ee94fa399db Mon Sep 17 00:00:00 2001 From: Jacob Daniel Bennett Date: Thu, 22 Oct 2020 16:32:27 -0400 Subject: [PATCH 01/29] Parsing for the whole delimiters and incorporation for errors regarding reply-from headers --- api/ECNQueue.py | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index a80dce7..4f0ebee 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -19,6 +19,7 @@ currentFileDirectory = os.path.dirname(currentFilePath) currentFileDirectoryParent = os.path.dirname(currentFileDirectory) queueDirectory = os.path.join(currentFileDirectoryParent, "q-snapshot") +#queueDirectory = "/usr/site/uds/qcopy/11" # Queues to not load in getQueues() queuesToIgnore = ["archives", "drafts", "inbox"] @@ -228,7 +229,12 @@ def __parseSections(self) -> list: line = self.__rawItem[lineNumber] # Looks for a starting delimiter and explicity excludes the reply-from-user ending delimiter - if line.startswith("***") or line.startswith("===") and not line.startswith("===="): + if (line.startswith("*** Edited by: ") or + line.startswith("*** Replied by: ") or + line.startswith("*** Status updated by: ") or + line == "=== Additional information supplied by user ===\n" and not + line == "===============================================\n" + ): # Sets the delimiter type based on the pattern within the delimiters list for delimiter in delimiters: @@ -585,7 +591,7 @@ def __editParsing(self, content: list, lineNum: int) -> dict: editInfo = {} for count, line in enumerate(content): - if line.startswith("===="): + if line == "===============================================\n" : errorMessage = "Reply-from-user ending delimter encountered without Reply-from-user starting delimter" return self.__errorParsing(line, lineNum + count + 1, errorMessage) @@ -645,7 +651,7 @@ def __replyToParsing(self, content: list, lineNum: int) -> dict: delimiterLine = content[0] for count, line in enumerate(content): - if line.startswith("===="): + if line == "===============================================\n": errorMessage = "Reply-from-user ending delimter encountered without Reply-from-user starting delimter" return self.__errorParsing(line, lineNum + count + 1, errorMessage) @@ -695,7 +701,7 @@ def __statusParsing(self, content: list, lineNum: int) -> dict: delimiterLine = content[0] for count, line in enumerate(content): - if line.startswith("===="): + if line == "===============================================\n": errorMessage = "Reply-from-user ending delimter encountered without Reply-from-user starting delimter" return self.__errorParsing(line, lineNum + count + 1, errorMessage) @@ -793,15 +799,24 @@ def __userReplyParsing(self, replyContent: list, lineNumber: int) -> dict: ) except: lenReplyFromHeaders = len(replyFromHeaders) - - replyFromHeaders[lenReplyFromHeaders - 1]["content"] = replyFromHeaders[lenReplyFromHeaders - 1]["content"] + " " + line + if lenReplyFromHeaders == 0: + errorMessage = ("Expected reply-from-user header information:\n" + + "=== Additional information supplied by user ===\n" + + "\n" + + "[Header Type]: [Header Value]\n" + + "\n" + ) + return self.__errorParsing(line, lineNumber + lineNum + 1, errorMessage) + + else: + replyFromHeaders[lenReplyFromHeaders - 1]["content"] = replyFromHeaders[lenReplyFromHeaders - 1]["content"] + " " + line linesToRemove.append(lineNum) #Checks for a newline and breaks for loop on second occurance of a newline if line == "\n": newLineCounter = newLineCounter + 1 - elif line.startswith("===="): + elif line == "===============================================\n": endingDelimiterCount = endingDelimiterCount + 1 elif line.startswith("From: ") and newLineCounter == 1: @@ -880,7 +895,13 @@ def __getFormattedSectionContent(self, sectionContent: list) -> list: """ # Continually removes the first line of sectionContent if it is a newline or delimiter in each iteration while len(sectionContent) > 1: - if sectionContent[0] == "\n" or sectionContent[0].startswith("***") or sectionContent[0].startswith("===") : + if (sectionContent[0] == "\n" or + sectionContent[0].startswith("*** Edited by: ") or + sectionContent[0].startswith("*** Replied by: ") or + sectionContent[0].startswith("*** Status updated by: ") or + sectionContent[0] == "=== Additional information supplied by user ===\n" or + sectionContent[0] == "===============================================\n" + ): sectionContent.pop(0) else: # Breaks the loop if the first line isn't a newline or delimiter @@ -891,7 +912,9 @@ def __getFormattedSectionContent(self, sectionContent: list) -> list: # Initializes the Length of sectionContent each iteration of the loop sectionContentLength = len(sectionContent) - if sectionContent[sectionContentLength -1] == "\n" or sectionContent[sectionContentLength -1].startswith("===="): + if (sectionContent[sectionContentLength -1] == "\n" or + sectionContent[sectionContentLength -1] == "===============================================\n" + ): sectionContent.pop(sectionContentLength - 1) else: # Breaks the loop if the last line isn't a newline or delimiter From 4670f69e22aedd82a603678101d2edc8eb2b4f31 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Fri, 23 Oct 2020 00:12:34 -0400 Subject: [PATCH 02/29] Add status option for TimelineActionCard --- src/components/TimelineActionCard/TimelineActionCard.js | 8 ++++++-- src/theme.js | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/TimelineActionCard/TimelineActionCard.js b/src/components/TimelineActionCard/TimelineActionCard.js index 431cd3e..1137b19 100644 --- a/src/components/TimelineActionCard/TimelineActionCard.js +++ b/src/components/TimelineActionCard/TimelineActionCard.js @@ -16,6 +16,10 @@ export default function TimelineActionCard({ type, datetime, by, content }){ "reply_to_user": { "verbage": "replied", "coloring": theme.palette.reply_to_user.main + }, + "status": { + "verbage": "updated the status", + "coloring": theme.palette.status.main } } @@ -54,9 +58,9 @@ TimelineActionCard.propTypes = { ]), /** ISO 8601 formatted time string. */ "datetime": PropTypes.string.isRequired, - /** The name of the person who added the edit. */ + /** The name of the person who added the action. */ "by": PropTypes.string.isRequired, - /** An array of strings containing the content of the edit. */ + /** An array of strings containing the content of the action. */ "content": PropTypes.array.isRequired }; diff --git a/src/theme.js b/src/theme.js index 685626a..5d2edb2 100644 --- a/src/theme.js +++ b/src/theme.js @@ -26,7 +26,7 @@ export default function theme(darkMode = false) { "reply_to_user": { main: "rgba(99, 125, 255, 0.2)", }, - "reply_from_user": { + "status": { main: "rgba(99, 255, 151, 0.2)", } }, From 014ad96efaf678fd6d2017b66e6b07225fe4b4a7 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Fri, 23 Oct 2020 00:13:46 -0400 Subject: [PATCH 03/29] Use TimelineActionCard for status --- src/components/ItemBodyView/ItemBodyView.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/components/ItemBodyView/ItemBodyView.js b/src/components/ItemBodyView/ItemBodyView.js index 19e48fb..d823133 100644 --- a/src/components/ItemBodyView/ItemBodyView.js +++ b/src/components/ItemBodyView/ItemBodyView.js @@ -1,7 +1,7 @@ import React from "react"; import PropTypes from "prop-types"; import { Timeline, TimelineItem, TimelineSeparator, TimelineConnector, TimelineContent, TimelineDot } from '@material-ui/lab'; -import { Typography, makeStyles } from "@material-ui/core"; +import { makeStyles } from "@material-ui/core"; import DirectoryInformation from "../DirectoryInformation/"; import Assignment from "../Assignment/"; import TimelineActionCard from "../TimelineActionCard/"; @@ -36,9 +36,7 @@ export default function ItemBodyView({ item }) { ); case "initial_message": return ( - <> - - + ); case "edit": return ( @@ -46,12 +44,7 @@ export default function ItemBodyView({ item }) { ); case "status": return ( - <> - - {`${section.by} update the status to at ${Date(section.datetime)}`} - - {section.content.map((line) => {line})} - + ); case "assignment": return ( From b0d230a1843622a6b1a0c53c2321303d60143063 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Fri, 23 Oct 2020 00:14:07 -0400 Subject: [PATCH 04/29] Add indicator for uncaught sections --- src/components/ItemBodyView/ItemBodyView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ItemBodyView/ItemBodyView.js b/src/components/ItemBodyView/ItemBodyView.js index d823133..ba1fbf2 100644 --- a/src/components/ItemBodyView/ItemBodyView.js +++ b/src/components/ItemBodyView/ItemBodyView.js @@ -59,7 +59,7 @@ export default function ItemBodyView({ item }) { ); default: - return "No Match Found"; + return `No match found for type: ${section.type}`; }; }; From d9bf26b69d0ffa0ca0facc217824adfdea86fe54 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Fri, 23 Oct 2020 00:16:48 -0400 Subject: [PATCH 05/29] Remove unneccesary return clauses --- src/components/ItemBodyView/ItemBodyView.js | 28 ++++++--------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/src/components/ItemBodyView/ItemBodyView.js b/src/components/ItemBodyView/ItemBodyView.js index ba1fbf2..1d7c4f8 100644 --- a/src/components/ItemBodyView/ItemBodyView.js +++ b/src/components/ItemBodyView/ItemBodyView.js @@ -31,33 +31,19 @@ export default function ItemBodyView({ item }) { const generateTimelineItem = (section) => { switch (section.type) { case "directory_information": - return ( - - ); + return case "initial_message": - return ( - - ); + return case "edit": - return ( - - ); + return case "status": - return ( - - ); + return case "assignment": - return ( - - ); + return case "reply_to_user": - return ( - - ); + return case "reply_from_user": - return ( - - ); + return default: return `No match found for type: ${section.type}`; }; From ac5349b88ebff58438cc51f08273475c07120ceb Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Fri, 23 Oct 2020 00:48:22 -0400 Subject: [PATCH 06/29] Create ParseError component w/ docs --- src/components/ParseError/ParseError.js | 61 +++++++++++++++++++++++++ src/components/ParseError/ParseError.md | 36 +++++++++++++++ src/components/ParseError/index.js | 1 + 3 files changed, 98 insertions(+) create mode 100644 src/components/ParseError/ParseError.js create mode 100644 src/components/ParseError/ParseError.md create mode 100644 src/components/ParseError/index.js diff --git a/src/components/ParseError/ParseError.js b/src/components/ParseError/ParseError.js new file mode 100644 index 0000000..28dd626 --- /dev/null +++ b/src/components/ParseError/ParseError.js @@ -0,0 +1,61 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { Typography, Paper, makeStyles, useTheme } from '@material-ui/core'; +import clsx from "clsx"; + +export default function ParseError({ file_path, expected, got, line_num }){ + + const theme = useTheme(); + + const useStyles = makeStyles({ + "Paper-root": { + overflow: "hidden" + }, + "headerColor": { + backgroundColor: theme.palette.parse_error.main + }, + "padding": { + padding: theme.spacing(1) + } + + }); + const classes = useStyles(); + + return( + +
+ + Parsing Error + +
+
+ + File Path: {file_path} + + + Line: {line_num} + + + Expected: {expected} + + + Got: {got} + +
+
+ ); +} + +ParseError.propTypes = { + "file_path": PropTypes.string, + "expected": PropTypes.string, + "got": PropTypes.string, + "line_num": PropTypes.number +}; + +ParseError.defaultProps = { + "file_path": "", + "expected": "", + "got": "", + "line_num": "" +}; \ No newline at end of file diff --git a/src/components/ParseError/ParseError.md b/src/components/ParseError/ParseError.md new file mode 100644 index 0000000..98b58a2 --- /dev/null +++ b/src/components/ParseError/ParseError.md @@ -0,0 +1,36 @@ +Displays a parsing error. + +--- + +```jsx +import { ThemeProvider } from "@material-ui/core/styles"; +import webqueue2Theme from "../../theme"; +import ParseError from "./ParseError"; + +const theme = webqueue2Theme(false); + +const demo_data = { + "type": "parse_error", + "datetime": "2020-10-23T00:45:32", + "file_path": "/home/pier/e/campb303/webqueue2/q-snapshot/ce/32", + "expected": "Did not encounter a reply-from-user ending delimiter", + "got": "765-869-4032 to assist please?\tThank you\n", + "line_num": 120 +}; + + + +``` +```jsx static + +``` \ No newline at end of file diff --git a/src/components/ParseError/index.js b/src/components/ParseError/index.js new file mode 100644 index 0000000..8a5ec40 --- /dev/null +++ b/src/components/ParseError/index.js @@ -0,0 +1 @@ +export { default } from "./ParseError"; \ No newline at end of file From c1a15813e50af10c13d0e139a94dd3590f7449e7 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Fri, 23 Oct 2020 00:48:48 -0400 Subject: [PATCH 07/29] Add parse_error color to theme --- src/theme.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/theme.js b/src/theme.js index 5d2edb2..52c59bd 100644 --- a/src/theme.js +++ b/src/theme.js @@ -28,6 +28,9 @@ export default function theme(darkMode = false) { }, "status": { main: "rgba(99, 255, 151, 0.2)", + }, + "parse_error": { + main: "rgba(255, 99, 204, 0.2)", } }, }) From bfc0bd0ab3b485db7c9ff930a32c22ca960dfa28 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Fri, 23 Oct 2020 00:49:02 -0400 Subject: [PATCH 08/29] Started using ParseError --- src/components/ItemBodyView/ItemBodyView.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/ItemBodyView/ItemBodyView.js b/src/components/ItemBodyView/ItemBodyView.js index 1d7c4f8..4e5edce 100644 --- a/src/components/ItemBodyView/ItemBodyView.js +++ b/src/components/ItemBodyView/ItemBodyView.js @@ -6,6 +6,7 @@ import DirectoryInformation from "../DirectoryInformation/"; import Assignment from "../Assignment/"; import TimelineActionCard from "../TimelineActionCard/"; import MessageView from "../MessageView/"; +import ParseError from "../ParseError/"; import { objectIsEmpty } from "../../utilities"; export default function ItemBodyView({ item }) { @@ -44,6 +45,8 @@ export default function ItemBodyView({ item }) { return case "reply_from_user": return + case "parse_error": + return default: return `No match found for type: ${section.type}`; }; From 787ec0ad760680a73e0d28753a5ea3730f207736 Mon Sep 17 00:00:00 2001 From: Jacob Daniel Bennett Date: Fri, 23 Oct 2020 02:26:45 -0400 Subject: [PATCH 09/29] Creation and implementation of section-sorter-by-date helper function --- api/ECNQueue.py | 59 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index 4f0ebee..89c4d87 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -5,6 +5,7 @@ #------------------------------------------------------------------------------# import os, time, email, re, datetime from dateutil.parser import parse +from dateutil import tz from typing import Union import json @@ -284,7 +285,7 @@ def __parseSections(self) -> list: # Checks for a parse error and appends it, returning the sections list which stops the parsing if editInfo["type"] == "parse_error": sections.append(editInfo) - return sections + return self.__getSortedSections(sections) # Appends the edit dictionary to sections sections.append(editInfo) @@ -296,7 +297,7 @@ def __parseSections(self) -> list: # Checks for a parse error and appends it, returning the sections list which stops the parsing if replyToInfo["type"] == "parse_error": sections.append(replyToInfo) - return sections + return self.__getSortedSections(sections) # Appends the reply-to to sections sections.append(replyToInfo) @@ -307,7 +308,7 @@ def __parseSections(self) -> list: if statusInfo["type"] == "parse_error": sections.append(statusInfo) - return sections + return self.__getSortedSections(sections) # Appends the status to sections sections.append(statusInfo) @@ -318,12 +319,15 @@ def __parseSections(self) -> list: if replyFromInfo["type"] == "parse_error": sections.append(replyFromInfo) - return sections + return self.__getSortedSections(sections) # Appends the replyFrom to sections sections.append(replyFromInfo) - - return sections + + sortedSections = self.__getSortedSections(sections) + + return sortedSections + #return sections def __directoryParsing(self, directoryStartLine: int) -> dict: """Returns a dictionary with directory information @@ -965,6 +969,46 @@ def __errorParsing(self, line: str, lineNum: int, expectedSyntax: str) -> dict: # returns the error dictionary return errorDictionary + def __getSortedSections(self, sectionsList: list) -> list: + """Sorts the sections chronologically by datetime + + Example: + [example] need to do + + Args: + sections (list): the list of sections to be sorted + + Returns: + list: a list of sections sorted by datetime + """ + sectionsLength = len(sectionsList) + sortedSections = [] + oldestSection = {} + + while len(sortedSections) < sectionsLength: + + for iteration, currentSection in enumerate(sectionsList): + + if currentSection["type"] == "directory_information": + sortedSections.append(currentSection) + sectionsList.remove(currentSection) + break + + if iteration == 0: + oldestSection = currentSection + + #datetime.datetime.strptime(date_time_str, '%Y-%m-%d %H:%M:%S.%f') + + elif parse(currentSection["datetime"]) < parse(oldestSection["datetime"]): + oldestSection = currentSection + + if iteration == len(sectionsList) - 1: + sortedSections.append(oldestSection) + sectionsList.remove(oldestSection) + + return sortedSections + + def __isLocked(self) -> Union[str, bool]: """Returns a string info about the lock if true and a bool False if false @@ -1065,7 +1109,8 @@ def __getFormattedDate(self, date: str) -> str: str: Properly formatted date/time recieved or empty string. """ try: - parsedDate = parse(date) + parsedDate = parse(date, default= datetime.datetime(2017, 10, 13, tzinfo=tz.gettz('EDT'))) + #parsedDate = parse(date, default= datetime.datetime(2017, 10, 13, tzinfo=datetime.timezone.) except: return "" From a388e719bc56de2c2f70be6717bddd2b80afcc8c Mon Sep 17 00:00:00 2001 From: "Campbell, Justin" Date: Fri, 23 Oct 2020 10:24:03 -0400 Subject: [PATCH 10/29] Allow multiple queues to be selected from URL --- api/api.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/api/api.py b/api/api.py index 29a4492..1e91580 100644 --- a/api/api.py +++ b/api/api.py @@ -52,7 +52,13 @@ def get(self, queue: str) -> str: Returns: str: JSON representation of the queue requested. """ - return ECNQueue.Queue(queue).toJson() + queues_requested = queue.split("+") + + queues = [] + for queue in queues_requested: + queues.append(ECNQueue.Queue(queue).toJson()) + + return queues @@ -62,4 +68,4 @@ def get(self, queue: str) -> str: if __name__ == "__main__": - app.run() \ No newline at end of file + app.run() From 85cd6c2d4a874b14ac4a7197c7b1e94adea6c729 Mon Sep 17 00:00:00 2001 From: Tyler Jordan Wright Date: Fri, 23 Oct 2020 12:23:37 -0400 Subject: [PATCH 11/29] Removed class from the Toolbar component that was causing ItemBodyView to not be scrollable. --- src/components/ItemViewAppBar/ItemViewAppBar.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/ItemViewAppBar/ItemViewAppBar.js b/src/components/ItemViewAppBar/ItemViewAppBar.js index a517cb3..0dc081f 100644 --- a/src/components/ItemViewAppBar/ItemViewAppBar.js +++ b/src/components/ItemViewAppBar/ItemViewAppBar.js @@ -22,11 +22,8 @@ export default function ItemViewAppBar({ title, setSidebarOpen }){ }, appBarRoot: { width: "inherit", - position: "inherit" + }, - paddingToolbar: { - position: "absolute" - } })); const classes = useStyles(theme); @@ -55,7 +52,7 @@ export default function ItemViewAppBar({ title, setSidebarOpen }){ - + ); } From 7f299210af960f0901cc4f5ed9dae13b10a9b3e3 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Tue, 27 Oct 2020 17:56:22 -0400 Subject: [PATCH 12/29] Fix multi queue loading in UI --- src/App.js | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/App.js b/src/App.js index 9999793..8320c96 100644 --- a/src/App.js +++ b/src/App.js @@ -13,15 +13,25 @@ function App() { const [darkMode, setDarkMode] = useState(false); const [activeItem, setActiveItem] = useState({}); const [sidebarOpen, setSidebarOpen] = useState(false); + const [queues, setQueues] = useState([]); const [items, setItems] = useState([]); - useEffect(() => { - fetch("/api/ce") - .then(res => res.json()) - .then(queue => { - setItems(queue.items) - }) - }, []) + useEffect( _ => { + async function getQueues(){ + const apiResponse = await fetch("/api/ce"); + const queueJson = await apiResponse.json(); + setQueues(queueJson); + } + getQueues(); + }, []); + + useEffect( _ => { + let tempItems = []; + for (let queue of queues){ + tempItems = tempItems.concat(queue.items); + } + setItems(tempItems); + }, [queues]); const theme = webqueueTheme(darkMode); const transitionWidth = theme.transitions.create(["width"], { From 7779ad44596fb62f971f463070727e33316160e8 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Wed, 28 Oct 2020 16:59:51 -0400 Subject: [PATCH 13/29] Use RelativeTime component for time values in ItemTable --- src/components/ItemTable/ItemTable.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/ItemTable/ItemTable.js b/src/components/ItemTable/ItemTable.js index 695a96f..ef379d0 100644 --- a/src/components/ItemTable/ItemTable.js +++ b/src/components/ItemTable/ItemTable.js @@ -1,9 +1,10 @@ import React from "react"; import PropTypes from "prop-types"; import { useTable, useFilters, useFlexLayout, useSortBy } from "react-table"; -import { makeStyles, Table, TableBody, TableCell, TableHead, TableRow, TableContainer, TableSortLabel - , Paper, Grid, useTheme, } from "@material-ui/core"; +import { makeStyles, Table, TableBody, TableCell, TableHead, TableRow, TableContainer, TableSortLabel, + Paper, Grid, useTheme } from "@material-ui/core"; import { useHistory } from "react-router-dom"; +import RelativeTime from "react-relative-time"; import ItemTableFilter from "../ItemTableFilter/" export default function ItemTable({ data, onRowClick }) { @@ -36,10 +37,10 @@ export default function ItemTable({ data, onRowClick }) { { Header: 'Subject', accessor: 'subject' }, { Header: 'Status', accessor: 'status', }, { Header: 'Priority', accessor: 'priority' }, - { Header: 'Last Updated', accessor: 'lastUpdated' }, + { Header: 'Last Updated', accessor: 'lastUpdated', Cell: ({ value }) => }, { Header: 'Department', accessor: 'department' }, { Header: 'Building', accessor: 'building' }, - { Header: 'Date Received', accessor: 'dateReceived' }, + { Header: 'Date Received', accessor: 'dateReceived', Cell: ({ value }) => }, ], []); const tableInstance = useTable( @@ -75,7 +76,6 @@ export default function ItemTable({ data, onRowClick }) { {column.render("Filter")} - { prepareRow(row); return ( - history.push(`/${row.original.queue}/${row.original.number}`) } + history.push(`/${row.original.queue}/${row.original.number}`)} className={classes.bandedRows} {...row.getRowProps()}> {row.cells.map(cell => ( From 3bdb99fa8569152a6d065111d202db0ae56bfddf Mon Sep 17 00:00:00 2001 From: "Campbell, Justin" Date: Wed, 28 Oct 2020 17:26:20 -0400 Subject: [PATCH 14/29] Format item lastupdated time as iso8601 --- api/ECNQueue.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index 89c4d87..7c2ae1d 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -93,7 +93,7 @@ def __getLastUpdated(self) -> str: """ unixTime = os.path.getmtime(self.__path) formattedTime = time.strftime('%m-%d-%y %I:%M %p', time.localtime(unixTime)) - return formattedTime + return self.__getFormattedDate(formattedTime) def __getRawItem(self) -> list: """Returns a list of all lines in the item file @@ -1198,4 +1198,4 @@ def getQueues() -> list: if isDirectory and isValid: queues.append(Queue(file)) - return queues \ No newline at end of file + return queues From 78d002e1432eaaa9c492b98b39620b75e652de25 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Wed, 28 Oct 2020 22:44:16 -0400 Subject: [PATCH 15/29] Add column borders to ItemTable --- src/components/ItemTable/ItemTable.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/ItemTable/ItemTable.js b/src/components/ItemTable/ItemTable.js index ef379d0..8d3cb91 100644 --- a/src/components/ItemTable/ItemTable.js +++ b/src/components/ItemTable/ItemTable.js @@ -24,6 +24,9 @@ export default function ItemTable({ data, onRowClick }) { backgroundColor: theme.palette.type === 'light' ? theme.palette.grey[50] : theme.palette.grey[700], }, }, + columnBorders: { + borderLeft: `1px solid ${theme.palette.grey[300]}` + } }); const classes = useStyles(); @@ -100,7 +103,7 @@ export default function ItemTable({ data, onRowClick }) { onClick={() => history.push(`/${row.original.queue}/${row.original.number}`)} className={classes.bandedRows} {...row.getRowProps()}> {row.cells.map(cell => ( - + {cell.render("Cell")} ))} From 9a6f7819ad77a55e31e07e9dba95b113a7b289b7 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Wed, 28 Oct 2020 23:55:58 -0400 Subject: [PATCH 16/29] Change TableCell border color according to theme type --- src/components/ItemTable/ItemTable.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/ItemTable/ItemTable.js b/src/components/ItemTable/ItemTable.js index 8d3cb91..80ebc0a 100644 --- a/src/components/ItemTable/ItemTable.js +++ b/src/components/ItemTable/ItemTable.js @@ -25,7 +25,9 @@ export default function ItemTable({ data, onRowClick }) { }, }, columnBorders: { - borderLeft: `1px solid ${theme.palette.grey[300]}` + borderLeftWidth: "1px", + borderLeftStyle: "solid", + borderColor: theme.palette.type === "light" ? theme.palette.grey[300] : theme.palette.grey[500] } }); const classes = useStyles(); From 962a672cb8e085ea490fb7011572ab1a79784e9a Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Thu, 29 Oct 2020 14:27:10 -0400 Subject: [PATCH 17/29] Add module level docs --- api/ECNQueue.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index 7c2ae1d..901bc92 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -1,4 +1,12 @@ -# TODO: Add ECNQueue module documentation +"""A library for interacting with Purdue ECN's Queue. + +This library allows for the creation of and interaction with individual issues called Items. +It also allows for the same with collections of Items called Queues. + +Raises: + # TODO: Add description(s) of when a ValueError is raised. + ValueError: [description] +""" #------------------------------------------------------------------------------# # Imports From 6c446628fde7abf546345a645a1c01ad0f77f358 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Thu, 29 Oct 2020 14:27:57 -0400 Subject: [PATCH 18/29] Remove debug comment --- api/ECNQueue.py | 1 - 1 file changed, 1 deletion(-) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index 901bc92..3e362a5 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -28,7 +28,6 @@ currentFileDirectory = os.path.dirname(currentFilePath) currentFileDirectoryParent = os.path.dirname(currentFileDirectory) queueDirectory = os.path.join(currentFileDirectoryParent, "q-snapshot") -#queueDirectory = "/usr/site/uds/qcopy/11" # Queues to not load in getQueues() queuesToIgnore = ["archives", "drafts", "inbox"] From d0f9bc60d880fbe3803b8f2cfb96349d480d9054 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Thu, 29 Oct 2020 14:39:34 -0400 Subject: [PATCH 19/29] Add TODO for coral queue issues --- api/ECNQueue.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index 3e362a5..8ae9a79 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -32,6 +32,7 @@ # Queues to not load in getQueues() queuesToIgnore = ["archives", "drafts", "inbox"] +# TODO: Investigate coral items not having a From header # B/c some items don't have a From field # See coral259 queuesToIgnore.append("coral") From 0c28cfecf600457a4be02dc323801b25435e909a Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Thu, 29 Oct 2020 14:46:26 -0400 Subject: [PATCH 20/29] Update module docs --- api/ECNQueue.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index 8ae9a79..2c89889 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -1,7 +1,23 @@ -"""A library for interacting with Purdue ECN's Queue. - -This library allows for the creation of and interaction with individual issues called Items. -It also allows for the same with collections of Items called Queues. +"""A library for interacting with Purdue ECN's ticketing system. + +This library allows interacting with queue Items (called Items) and collections +of items (called Queues). + +Example: + # Create a single Item (ce100) + >>> item = Item("ce", 100) + # Get the sender's email address from an Item + >>> item = Item("ce", 100) + >>> item.userEmail + + # Create an entire Queue (ce) + >>> queue = Queue("ce") + # Get the number of items in a Queue + >>> queue = Queue("ce") + >>> numItems = len(queue) + + # Get all queues (and their items) + >>> queues = getQueues() Raises: # TODO: Add description(s) of when a ValueError is raised. From a507ac7436455ccd19bea99ecf234e74b3c75c50 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Thu, 29 Oct 2020 14:55:15 -0400 Subject: [PATCH 21/29] Add Item class doc block --- api/ECNQueue.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index 2c89889..43ad0f0 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -59,7 +59,27 @@ # Classes #------------------------------------------------------------------------------# class Item: - # TODO: Add Item class documentation + """A single issue. + + Example: + # Create an Item (ce100) + >>> item = Item("ce", 100) + + Attributes: + lastUpdated: An ISO 8601 formatted time string showing the last time the file was updated according to the filesystem. + headers: A list of dictionaries containing header keys and values. + content: A list of section dictionaries. + isLocked: A boolean showing whether or not a lockfile for the item is present. + userEmail: The email address of the person who this item is from. + userName: The real name of the person who this item is from. + userAlias: The Purdue career account alias of the person this item is from. + assignedTo: The Purdue career account alias of the person this item is assigned to + subject: The subject of the original message for this item. + status: The most recent status update for the item. + priority: The most recent priority for this item. + department: The most recent department for this item. + dateReceived: The date this item was created. + """ def __init__(self, queue: str, number: int) -> None: From 4feaaada3d56709da58af5ac37df75777510c1f6 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Thu, 29 Oct 2020 14:57:30 -0400 Subject: [PATCH 22/29] Add Queue doc block --- api/ECNQueue.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index 43ad0f0..beab098 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -1174,7 +1174,17 @@ def __repr__(self) -> str: return self.queue + str(self.number) class Queue: - # TODO: Add Queue class documentation + """A collection of items. + + Example: + # Create a queue (ce) + >>> queue = Queue("ce") + + Attributes: + name: The name of the queue. + items: A list of Items in the queue. + jsonData: A JSON serializable representation of the Queue. + """ def __init__(self, name: str) -> None: self.name = name From ab3cb8a859e8a182e2df52b0444aae35a14578e4 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Thu, 29 Oct 2020 14:58:09 -0400 Subject: [PATCH 23/29] Update Item doc block --- api/ECNQueue.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index beab098..1d28a59 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -79,6 +79,7 @@ class Item: priority: The most recent priority for this item. department: The most recent department for this item. dateReceived: The date this item was created. + jsonData: A JSON serializable representation of the Item. """ def __init__(self, queue: str, number: int) -> None: From b1d258b7c281434c9241701a60883a3ecfd819ec Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Thu, 29 Oct 2020 15:14:25 -0400 Subject: [PATCH 24/29] Permenantly add coral queue to queuesToIgnore --- api/ECNQueue.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index 1d28a59..ffc5ac0 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -46,12 +46,7 @@ queueDirectory = os.path.join(currentFileDirectoryParent, "q-snapshot") # Queues to not load in getQueues() -queuesToIgnore = ["archives", "drafts", "inbox"] - -# TODO: Investigate coral items not having a From header -# B/c some items don't have a From field -# See coral259 -queuesToIgnore.append("coral") +queuesToIgnore = ["archives", "drafts", "inbox", "coral"] From fea27fd5d83e1664d3433088cd25cd4ab8ed6dea Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Thu, 29 Oct 2020 15:17:19 -0400 Subject: [PATCH 25/29] Update module doc block --- api/ECNQueue.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index ffc5ac0..bc9c8cd 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -19,6 +19,10 @@ # Get all queues (and their items) >>> queues = getQueues() +Attributes: + queueDirectory: The directory to load queues from. + queuesToIgnore: Queues that will not be loaded when running getQueues() + Raises: # TODO: Add description(s) of when a ValueError is raised. ValueError: [description] From 202e04cc62e1f53bce92f762084628b10a445b74 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Thu, 29 Oct 2020 15:53:56 -0400 Subject: [PATCH 26/29] Remove getContent function /c no longer neede --- api/ECNQueue.py | 35 +++++++++++------------------------ 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index bc9c8cd..2181861 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -82,13 +82,11 @@ class Item: """ def __init__(self, queue: str, number: int) -> None: - self.queue = queue try: self.number = int(number) except ValueError: - raise ValueError("Could not convert \"" + number + "\" to an integer") - #self.number = number + raise ValueError(" Could not convert \"" + number + "\" to an integer") self.__path = "/".join([queueDirectory, self.queue, str(self.number)]) self.lastUpdated = self.__getLastUpdated() @@ -107,6 +105,7 @@ def __init__(self, queue: str, number: int) -> None: self.building = self.__getMostRecentHeaderByType("Building") self.dateReceived = self.__getFormattedDate(self.__getMostRecentHeaderByType("Date")) + # TODO: Autopopulate jsonData w/ __dir__() command. Exclude `^_` and `jsonData`. self.jsonData = { "queue": self.queue, "number": self.number, @@ -135,6 +134,7 @@ def __getLastUpdated(self) -> str: Returns: str: last modified time of item reported by the filesystem in mm-dd-yy hh:mm am/pm format. """ + # TODO: Simplify this code block by allowing __getFormattedDate to accept milliseconds since the epoch. unixTime = os.path.getmtime(self.__path) formattedTime = time.strftime('%m-%d-%y %I:%M %p', time.localtime(unixTime)) return self.__getFormattedDate(formattedTime) @@ -195,7 +195,8 @@ def __parseHeaders(self) -> list: headerString += line - message = email.message_from_string(headerString + "".join(self.__getContent())) + # message = email.message_from_string(headerString + "".join(self.__getContent())) + message = email.message_from_string(headerString) headers = [] for key in message.keys(): @@ -205,26 +206,6 @@ def __parseHeaders(self) -> list: # TODO: Implement attachment parsing - def __getContent(self) -> list: - """Returns a dictionary of lines of the item body. - - Example: - "Hello. I need help.\\n\\n*** Status updated by: campb303 at: 6/23/2020 13:26:55 ***\\nDont Delete" becomes - [ - "Hello. I need help.\\n", - "\\n", - "*** Status updated by: campb303 at: 6/23/2020 13:26:55 ***\\n", - "Don't Delete", - ] - - Returns: - list: Lines of the body item. - """ - contentStart = self.__getHeaderBoundary() + 1 - contentEnd = len(self.__rawItem) - 1 - return self.__rawItem[ contentStart : contentEnd ] - - def __parseSections(self) -> list: # List of all item events sections = [] @@ -1253,3 +1234,9 @@ def getQueues() -> list: queues.append(Queue(file)) return queues + +if __name__ == "__main__": + start = time.time() + queues = getQueues() + end = time.time() + print(f'Fetching all queues took {end - start}s') \ No newline at end of file From 0b8776b4a062b0df93c2a9a3f98ab5a902474a2e Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Thu, 29 Oct 2020 16:03:35 -0400 Subject: [PATCH 27/29] Remove __getAssignedTo from Item b/c it is no longer needed --- api/ECNQueue.py | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index 2181861..cea3cc4 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -97,7 +97,7 @@ def __init__(self, queue: str, number: int) -> None: self.userEmail = self.__parseFromData(data="userEmail") self.userName = self.__parseFromData(data="userName") self.userAlias = self.__getUserAlias() - self.assignedTo = self.__getAssignedTo() + self.assignedTo = self.__getMostRecentHeaderByType("Assigned-To") self.subject = self.__getMostRecentHeaderByType("Subject") self.status = self.__getMostRecentHeaderByType("Status") self.priority = self.__getMostRecentHeaderByType("Priority") @@ -1033,7 +1033,6 @@ def __getSortedSections(self, sectionsList: list) -> list: return sortedSections - def __isLocked(self) -> Union[str, bool]: """Returns a string info about the lock if true and a bool False if false @@ -1114,16 +1113,6 @@ def __getUserAlias(self) -> str: """ emailUser, emailDomain = self.userEmail.split("@") return emailUser if emailDomain.endswith("purdue.edu") else "" - - def __getAssignedTo(self) -> str: - """Returns the alias of the person this item was most recently assigned to. - Returns empty string if this item isn't assigned. - - Returns: - str: Alias of the person item is assigned to or empty string. - """ - assignedTo = self.__getMostRecentHeaderByType("Assigned-To") - return assignedTo def __getFormattedDate(self, date: str) -> str: """Returns the date/time formatted as RFC 8601 YYYY-MM-DDTHH:MM:SS+00:00. @@ -1233,10 +1222,4 @@ def getQueues() -> list: if isDirectory and isValid: queues.append(Queue(file)) - return queues - -if __name__ == "__main__": - start = time.time() - queues = getQueues() - end = time.time() - print(f'Fetching all queues took {end - start}s') \ No newline at end of file + return queues \ No newline at end of file From 29f7dab069675a971c59b0a236546cf79537df6c Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Thu, 29 Oct 2020 16:03:52 -0400 Subject: [PATCH 28/29] Add todo for queue iteration --- api/ECNQueue.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index cea3cc4..b1fba76 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -1143,6 +1143,7 @@ def toJson(self) -> dict: def __repr__(self) -> str: return self.queue + str(self.number) +# TODO: Make Queue iterable using __iter__. See: https://thispointer.com/python-how-to-make-a-class-iterable-create-iterator-class-for-it/ class Queue: """A collection of items. From b078ed9b51ab2bb3d4bc23110bf57854899e24d6 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Thu, 29 Oct 2020 16:08:59 -0400 Subject: [PATCH 29/29] Add comments for default date in getFormattedDate() --- api/ECNQueue.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index b1fba76..f5e7c47 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -1123,8 +1123,8 @@ def __getFormattedDate(self, date: str) -> str: str: Properly formatted date/time recieved or empty string. """ try: - parsedDate = parse(date, default= datetime.datetime(2017, 10, 13, tzinfo=tz.gettz('EDT'))) - #parsedDate = parse(date, default= datetime.datetime(2017, 10, 13, tzinfo=datetime.timezone.) + # This date is never meant to be used. The default attribute is just to set timezone. + parsedDate = parse(date, default=datetime.datetime(1970, 0, 1, tzinfo=tz.gettz('EDT'))) except: return ""