From 099ebdf4dec2d1b5ea15eca915b9bd51d38149c7 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Sun, 13 Sep 2020 22:28:31 -0400 Subject: [PATCH 01/36] Create JSON spec for section parsing --- docs/parseSectionsFormat.jsonc | 207 +++++++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 docs/parseSectionsFormat.jsonc diff --git a/docs/parseSectionsFormat.jsonc b/docs/parseSectionsFormat.jsonc new file mode 100644 index 0000000..b78b033 --- /dev/null +++ b/docs/parseSectionsFormat.jsonc @@ -0,0 +1,207 @@ +[ + { + "type": "directoryInformation", + + // An array of lines with non-printable characters. + // Example from aae 1 + "content": [ + "\n", + "\tName: Jerry L Guerrero\n", + " Login: jerry\n", + " Computer: x-ee27å0bpc1 (128.46.164.29)\n", + " Location: EE 270B\n", + " Email: jerry@purdue.edu\n", + " Phone: \n", + " Office: \n", + " UNIX Dir: /home/pier/c/jerry\n", + " Zero Dir: U=\\\\pier.ecn.purdue.edu\\jerry\n", + " User ECNDB: http://eng.purdue.edu/jump/bcafa8\n", + " Host ECNDB: http://eng.purdue.edu/jump/2dbd461 \n", + " Subject: Win7 to Win10 Migration List - kevin\n" + ] + }, + + { + "type": "initialMessage", + + // RFC 8061 formatted date string + // Maps to Item.dateReceived + "date": "2020-09-11", + + // RFC 8061 formatted time string + // Maps to Item.dateReceived + "time": "01:26:45+00:00", + + // String of user's real name. Could be blank + // Maps to Item.userName + "userName": "Justin Campbell", + + // String of user's email + // Maps to Item.userEmail + "userEmail": "campb303@purdue.edu", + + // Array of dictionaries of names and emails for cc'd recipients. Could be blank, could have multiple addresses, names could be blank + // Comes from headers + // See: https://docs.python.org/3/library/email.utils.html#email.utils.parseaddr for parsing this field + "ccRecipients": [ + { + "name": "John Doe", + "email": "johndoe@example.com" + }, + { + "name": "", + "email": "janesmith@example.com" + } + ], + + // An array of lines with non-printable characters. + "content": [ + "I need some help with something.\n" + ] + }, + + { + "type": "edit", + + // Career account alias for ECN user who added edit + // Appears after the first colon followed by a space `: ` in the delimiter string + // Ends just before the next space + // Ex: *** Edited by: knewell at: 04/22/20 16:39:51 *** + // (Start) ^ ^ (End) + "by": "knewell", + + // RFC 8061 formatted date string + // Appears after the second colon followed by a space `: ` in the delimiter string + // Ends just before the next space + // Ex: *** Edited by: knewell at: 04/22/20 16:39:51 *** + // (Start) ^ ^ (End) + "date": "2020-04-22T16:39:51", + + // RFC 8061 formatted time string + // Appears after the first space following the second colon followed by a space `: ` in the delimiter string + // Ends just before the next space + // Ex: *** Edited by: knewell at: 04/22/20 16:39:51 *** + // (Start) ^ ^ (End) + "time": "16:39:51", + + // An array of lines with non-printable characters. + "content": [ + "This is related to another item. I need to do X next.\n" + ] + }, + + { + "type": "status", + + // Career account alias for ECN user who added edit + // Appears after the first colon followed by a space `: ` in the delimiter string + // Ends just before the next space + // Ex: *** Status updated by: knewell at: 4/23/2020 10:35:47 *** + // (Start) ^ ^ (End) + "by": "knewell", + + // RFC 8061 formatted date string + // Appears after the second colon followed by a space `: ` in the delimiter string + // Ends just before the next space + // Ex: *** Status updated by: knewell at: 4/23/2020 10:35:47 *** + // (Start) ^ ^ (End) + "date": "2020-04-23T10:35:47", + + // RFC 8061 formatted time string + // Appears after the first space following the second colon followed by a space `: ` in the delimiter string + // Ends just before the next space + // Ex: *** Status updated by: knewell at: 4/23/2020 10:35:47 *** + // (Start) ^ ^ (End) + "time": "10:35:47", + + // An array of lines with non-printable characters. + "content": [ + "Doing X thing." + ] + }, + + { + "type": "assign", + + // Career account alias for ECN user changed the assignment + // Appears in the item headers and the most recent entry can be accessed by: + // >>> self.__getMostRecentHeaderByType("Assigned-To-Updated-By") + "by": "campb303", + + // RFC 8061 formatted date string + // Appears in the item headers and the most recent entry can be accessed by: + // >>> self.__getMostRecentHeaderByType("Assigned-To-Updated-Time") + "date": "2020-06-23T13:27:00", + + // RFC 8061 formatted time string + // Appears in the item headers and the most recent entry can be accessed by: + // >>> self.__getMostRecentHeaderByType("Assigned-To-Updated-Time") + "time": "13:27:00", + + // Career account alias for ECN user the item was assigned to + // Appears in the item headers and the most recent entry can be accessed by: + // >>> self.__getMostRecentHeaderByType("Assigned-To") + "to": "campb303", + }, + + { + "type": "replyFromECN", + + // Career account alias for ECN user who added edit + // Appears after the first colon followed by a space `: ` in the delimiter string + // Ends just before the next space + // Ex: *** Replied by: ewhile at: 05/08/20 09:21:43 *** + // (Start) ^ ^ (End) + "by": "ewhile", + + // RFC 8061 formatted date string + // Appears after the second colon followed by a space `: ` in the delimiter string + // Ends just before the next space + // Ex: *** Replied by: ewhile at: 05/08/20 09:21:43 *** + // (Start) ^ ^ (End) + "date": "2020-05-08", + + // RFC 8061 formatted time string + // Appears after the first space following the second colon followed by a space `: ` in the delimiter string + // Ends just before the next space + // Ex: *** Replied by: ewhile at: 05/08/20 09:21:43 *** + // (Start) ^ ^ (End) + "time": "09:21:43", + }, + + { + "type": "replyFromUser", + + // RFC 8061 formatted date string + // Appears in the headers of a merged message + // Might be possible to create submessage and parse the headers using the email library + "date": "2020-05-08", + + // RFC 8061 formatted date string + // Appears in the headers of a merged message + // Might be possible to create submessage and parse the headers using the email library + "time": "13:57:18+00:00", + + // String of user's real name. Could be blank + // Maps to Item.userName + "userName": "Reckowsky, Michael J.", + + // String of user's email + // Maps to Item.userEmail + "userEmail": "mreckowsky@purdue.edu", + + // Array of dictionaries of names and emails for cc'd recipients. Could be blank, could have multiple addresses, names could be blank + // Comes from headers + // See: https://docs.python.org/3/library/email.utils.html#email.utils.parseaddr for parsing this field + "ccRecipients": [], + + // An array of lines with non-printable characters. + "content": [ + "Ethan,\n", + "\n", + "Biltong beef ribs doner chuck, pork chop jowl salami cow filet mignon pork.\n", + "\n", + "Mike\n", + ] + }, +] \ No newline at end of file From f86d8da50bfbfde19b7245fcd4f424e79d435d52 Mon Sep 17 00:00:00 2001 From: Jacob Daniel Bennett Date: Mon, 14 Sep 2020 13:05:11 -0400 Subject: [PATCH 02/36] Partial Section Parsing Capability for Directory Info, edits, reply to user, and status changes. --- api/ECNQueue.py | 184 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 181 insertions(+), 3 deletions(-) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index e38cb70..976f770 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -3,7 +3,8 @@ #------------------------------------------------------------------------------# # Imports #------------------------------------------------------------------------------# -import os, time, email, re +import os, time, email, re, datetime +from dateutil.parser import parse from typing import Union import json @@ -42,7 +43,7 @@ def __init__(self, queue: str, number: int) -> None: self.lastUpdated = self.__getLastUpdated() self.__rawItem = self.__getRawItem() self.headers = self.__parseHeaders() - self.content = self.__getContent() + self.content = self.__parseSections() self.isLocked = self.__isLocked() self.userEmail = self.__parseFromData(data="userEmail") self.userName = self.__parseFromData(data="userName") @@ -174,6 +175,179 @@ def __getContent(self) -> list: # TODO: Implement section parsing. + def __parseSections(self) -> list: + sections = [] + headerEnd = self.__getHeaderBoundary() + + contentStart = self.__getHeaderBoundary() + 1 + contentEnd = len(self.__rawItem) - 1 + + # Find line numbers where sections start + sectionBoundaries = [ {"start": contentStart} ] + + directoryInfoPattern = ["\tName: ", + " Login: ", + " Computer: ", + " Location: ", + " Email: ", + " Phone: ", + " Office: ", + " UNIX Dir: ", + " Zero Dir: ", + " User ECNDB: ", + " Host ECNDB: ", + " Subject: "] + directoryInfo = ["\n"] + + for lineNumber in range(contentStart, contentEnd + 1): + line = self.__rawItem[lineNumber] + if line.startswith("***") or line.startswith("===") and not line.startswith("===="): + sectionBoundaries.append({"start": lineNumber}) + else: + for item in directoryInfoPattern: + if(line.startswith(item)): + directoryInfo.append(line) + + if len(directoryInfo) > 1: + sections.append( + {"type": "directoryInformation", + "content": directoryInfo + } + ) + sectionBoundaries.append({"start": contentEnd + 1}) + + # Set line number where section end + for boundaryIndex in range(0, len(sectionBoundaries) - 1): + sectionBoundaries[boundaryIndex]["end"] = sectionBoundaries[boundaryIndex + 1]["start"] + + # Remove End of File boundary + del sectionBoundaries[-1] + + + # Make list of sections and parse content + delimiters = [ + {"name": "edit", "pattern": "*** Edited"}, + {"name": "status", "pattern": "*** Status"}, + {"name": "replyToUser", "pattern": "*** Replied"}, + {"name": "replyFromUser", "pattern": "=== "}, + ] + + for boundary in sectionBoundaries: + line = self.__rawItem[boundary["start"]] + sectionType = None + + for delimiter in delimiters: + if line.startswith(delimiter["pattern"]): + sectionType = delimiter["name"] + break + + sectionContent = self.__rawItem[boundary["start"] : boundary["end"]] + + if sectionType is None: + sectionType = "initialMessage" + + elif sectionType == "edit": + formattedDate = "" + formattedTime = "" + editedBy = "" + + #parses for the author of the edit, which is located between the "*** Edited by: " and " at:" substrings + editedBy = (re.search("(?<=\*{3} Edited by: )(.*)(?= at:)", line)).group() + + #parses for the date and time of the edit, which is located between the " at: " and "***\n" substrings + dateTimeString = (re.search("(?<= at: )(.*)(?= \*\*\*\n)", line)).group() + + try: + dateObject = parse(dateTimeString) + formattedDate = dateObject.strftime("%Y-%m-%dT%H:%M:%S%z") + formattedTime = dateObject.strftime("%H:%M:%S%z") + except: + formattedDate = "invalid" + formattedTime = "Invalid" + + sections.append( + {"type": sectionType, + "by": editedBy, + "date": formattedDate, + "time": formattedTime, + "content": sectionContent,} + ) + + elif sectionType == "replyToUser": + formattedDate = "" + formattedTime = "" + repliedBy = "" + + #parses for the author of the reply, which is located between the "*** Replied by: " and " at:" substrings + repliedBy = (re.search("(?<=\*{3} Replied by: )(.*)(?= at:)", line)).group() + + #parses for the date and time of the reply, which is located between the " at: " and "***\n" substrings + dateTimeString = (re.search("(?<= at: )(.*)(?= \*\*\*\n)", line)).group() + + try: + dateObject = parse(dateTimeString) + formattedDate = dateObject.strftime("%Y-%m-%dT%H:%M:%S%z") + formattedTime = dateObject.strftime("%H:%M:%S%z") + except: + formattedDate = "invalid" + formattedTime = "Invalid" + + sections.append( + {"type": sectionType, + "by": repliedBy, + "date": formattedDate, + "time": formattedTime, + "content": sectionContent} + ) + + elif sectionType == "status": + formattedDate = "" + formattedTime = "" + updatedBy = "" + + #parses for the author of the status change, which is located between the "*** Status updated by: " and " at:" substrings + updatedBy = (re.search("(?<=\*{3} Status updated by: )(.*)(?= at:)", line)).group() + + #parses for the date and time of the status change, which is located between the " at: " and "***\n" substrings + dateTimeString = (re.search("(?<= at: )(.*)(?= \*\*\*\n)", line)).group() + + try: + dateObject = parse(dateTimeString) + formattedDate = dateObject.strftime("%Y-%m-%dT%H:%M:%S%z") + formattedTime = dateObject.strftime("%H:%M:%S%z") + except: + formattedDate = "invalid" + formattedTime = "Invalid" + + sections.append( + {"type": sectionType, + "by": updatedBy, + "date": formattedDate, + "time": formattedTime, + "content": sectionContent} + ) + + elif sectionType == "": + #elif sectionType == "replyFromUser": + formattedDate = "" + formattedTime = "" + repliedBy = "" + + #parses for the author of the status change, which is located between the "*** Status updated by: " and " at:" substrings + updatedBy = (re.search("(?<=\*{3} Status updated by: )(.*)(?= at:)", line)).group() + + #parses for the date and time of the status change, which is located between the " at: " and "***\n" substrings + dateTimeString = (re.search("(?<= at: )(.*)(?= \*\*\*\n)", line)).group() + + + else: + sections.append( + {"type": sectionType, + "content": sectionContent} + ) + + return sections + def __isLocked(self) -> Union[str, bool]: """Returns a string info about the lock if true and a bool False if false @@ -345,4 +519,8 @@ def getQueues() -> list: if isDirectory and isValid: queues.append(Queue(file)) - return queues \ No newline at end of file + return queues + +if __name__ == "__main__": + item = Item("ce", 11) + print() From 58381c5c73ed6298625664a8876cb7c601b779cc Mon Sep 17 00:00:00 2001 From: Jacob Daniel Bennett Date: Tue, 15 Sep 2020 10:03:12 -0400 Subject: [PATCH 03/36] Partial functionality added for parsing reply from user section --- api/ECNQueue.py | 75 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 67 insertions(+), 8 deletions(-) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index 976f770..983b179 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -272,6 +272,8 @@ def __parseSections(self) -> list: "time": formattedTime, "content": sectionContent,} ) + + sectionContent.remove(line) elif sectionType == "replyToUser": formattedDate = "" @@ -326,19 +328,76 @@ def __parseSections(self) -> list: "time": formattedTime, "content": sectionContent} ) + #sectionContent.remove(line) + + #elif sectionType == "": + elif sectionType == "replyFromUser": + + #replyMetaDataDelimiters = ["Subject: ", "From: ", "Date: "] - elif sectionType == "": - #elif sectionType == "replyFromUser": formattedDate = "" formattedTime = "" - repliedBy = "" - + repliedByName = "" + repliedByEmail = "" + subject = "" + ccRecipientsList = [] + ccRecipientDict = {} + + """ + ccRecipientDict Format: + ccRecipientDict = { + "Name": Name + "Email": Email + } + """ + + lineNum = 0 + linesToBeDeleted = [0] + + for items in sectionContent: + if items.startswith("Subject: "): + subject = (re.search("(?<=Subject: )(.*)", items)).group() + linesToBeDeleted.append(lineNum) + + elif items.startswith("From: "): + repliedByEmail = (re.search("(?<=From: )(.*)(?= <)", items)).group() + repliedByName = (re.search("(?<= <)(.*)(?=>)", items)).group() + linesToBeDeleted.append(lineNum) + + elif items.startswith("Date: "): + dateStr = (re.search("(?<=Date: )(.*)", items)).group() + + try: + dateObject = parse(dateStr) + formattedDate = dateObject.strftime("%Y-%m-%dT%H:%M:%S%z") + formattedTime = dateObject.strftime("%H:%M:%S%z") + linesToBeDeleted.append(lineNum) + except: + formattedDate = "invalid" + formattedTime = "Invalid" + + elif items.startswith("Cc: "): + ccString = (re.search("(?<=Cc: )(.*)", items)).group() + ####################################################################################### + + lineNum = lineNum + 1 + + for lineNumbers in sorted(linesToBeDeleted, reverse=True): + sectionContent.pop(lineNumbers) #parses for the author of the status change, which is located between the "*** Status updated by: " and " at:" substrings - updatedBy = (re.search("(?<=\*{3} Status updated by: )(.*)(?= at:)", line)).group() + #updatedBy = (re.search("(?<=\*{3} Status updated by: )(.*)(?= at:)", line)).group() #parses for the date and time of the status change, which is located between the " at: " and "***\n" substrings - dateTimeString = (re.search("(?<= at: )(.*)(?= \*\*\*\n)", line)).group() - + #dateTimeString = (re.search("(?<= at: )(.*)(?= \*\*\*\n)", line)).group() + sections.append( + {"type": sectionType, + "date": formattedDate, + "time": formattedTime, + "subject": subject, + "userName": repliedByName, + "userEmail": repliedByEmail, + "content": sectionContent} + ) else: sections.append( @@ -522,5 +581,5 @@ def getQueues() -> list: return queues if __name__ == "__main__": - item = Item("ce", 11) + item = Item("ce", 100) print() From 307cc8becdf00a2a9e77bf8916c17b962b15ada0 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Wed, 16 Sep 2020 11:36:04 -0400 Subject: [PATCH 04/36] Update section parsing JSON spec --- docs/parseSectionsFormat.jsonc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/parseSectionsFormat.jsonc b/docs/parseSectionsFormat.jsonc index b78b033..3752cd5 100644 --- a/docs/parseSectionsFormat.jsonc +++ b/docs/parseSectionsFormat.jsonc @@ -167,6 +167,16 @@ // Ex: *** Replied by: ewhile at: 05/08/20 09:21:43 *** // (Start) ^ ^ (End) "time": "09:21:43", + + // An array of lines with non-printable characters. + "content": [ + "Sascha,\n", + "\n", + "Chicken kevin biltong, flank jowl prosciutto shoulder meatball meatloaf sirloin.\n", + "\n", + "Ethan White\n", + "ECN\n" + ] }, { From d0aebf6ca9cb5eb84d0dbeb5a838d5e9a2575e98 Mon Sep 17 00:00:00 2001 From: Jacob Daniel Bennett Date: Wed, 16 Sep 2020 14:10:40 -0400 Subject: [PATCH 05/36] Full functionality for reply from user section parsing and partial parsing functionaltiy for initial message parsing --- api/ECNQueue.py | 73 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 13 deletions(-) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index 983b179..05e3d4f 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -177,7 +177,8 @@ def __getContent(self) -> list: def __parseSections(self) -> list: sections = [] - headerEnd = self.__getHeaderBoundary() + initialMessageContent = [] + #headerEnd = self.__getHeaderBoundary() contentStart = self.__getHeaderBoundary() + 1 contentEnd = len(self.__rawItem) - 1 @@ -199,14 +200,37 @@ def __parseSections(self) -> list: " Subject: "] directoryInfo = ["\n"] + endInitialMessage = False for lineNumber in range(contentStart, contentEnd + 1): line = self.__rawItem[lineNumber] if line.startswith("***") or line.startswith("===") and not line.startswith("===="): + endInitialMessage = True sectionBoundaries.append({"start": lineNumber}) - else: - for item in directoryInfoPattern: - if(line.startswith(item)): - directoryInfo.append(line) + + if endInitialMessage == False: + initialMessageContent.append(line) + + lineCounter = 0 + linesToRemove = [] + for lineContents in initialMessageContent: + + for itemsindirectory in directoryInfoPattern: + if lineContents.startswith(itemsindirectory): + directoryInfo.append(lineContents) + linesToRemove.append(lineCounter) + lineCounter = lineCounter + 1 + + for stuff in sorted(linesToRemove, reverse=True): + initialMessageContent.pop(stuff) + + sections.append( + {"type": "initialMessage", + "date": "", + "time": "", + "userName": "", + "ccRecipients": "", + "content": initialMessageContent} + ) if len(directoryInfo) > 1: sections.append( @@ -223,7 +247,8 @@ def __parseSections(self) -> list: # Remove End of File boundary del sectionBoundaries[-1] - + #for linestuff in range(contentStart, contentEnd): + #initialMessageContent.append(linestuff) # Make list of sections and parse content delimiters = [ {"name": "edit", "pattern": "*** Edited"}, @@ -243,6 +268,8 @@ def __parseSections(self) -> list: sectionContent = self.__rawItem[boundary["start"] : boundary["end"]] + + if sectionType is None: sectionType = "initialMessage" @@ -341,7 +368,7 @@ def __parseSections(self) -> list: repliedByEmail = "" subject = "" ccRecipientsList = [] - ccRecipientDict = {} + #ccRecipientDict = {} """ ccRecipientDict Format: @@ -351,17 +378,32 @@ def __parseSections(self) -> list: } """ + #Counter for line number and list of lines that can be deleted from the content lineNum = 0 linesToBeDeleted = [0] + newLineCounter = 0 + #Parses the section content looking for any line that starts with a metadata for items in sectionContent: + + #Checks for a newline and breaks for loop on second occurance of a newline + if items == "\n": + newLineCounter = newLineCounter + 1 + if newLineCounter == 2: + break + else: + continue + #Checks for lines starting with these delimiters if items.startswith("Subject: "): subject = (re.search("(?<=Subject: )(.*)", items)).group() linesToBeDeleted.append(lineNum) elif items.startswith("From: "): - repliedByEmail = (re.search("(?<=From: )(.*)(?= <)", items)).group() - repliedByName = (re.search("(?<= <)(.*)(?=>)", items)).group() + emailList = email.utils.getaddresses([items]) + #repliedByName = (re.search("(?<=From: )(.*)(?= <)", items)).group() + repliedByName = emailList[0][0] + #repliedByEmail = (re.search("(?<= <)(.*)(?=>)", items)).group() + repliedByEmail = emailList[0][1] linesToBeDeleted.append(lineNum) elif items.startswith("Date: "): @@ -377,8 +419,12 @@ def __parseSections(self) -> list: formattedTime = "Invalid" elif items.startswith("Cc: "): - ccString = (re.search("(?<=Cc: )(.*)", items)).group() - ####################################################################################### + recipientsList = email.utils.getaddresses([items]) + for cc in recipientsList: + ccRecipientsList.append( + {"name":cc[0], + "email":cc[1]} + ) lineNum = lineNum + 1 @@ -396,7 +442,8 @@ def __parseSections(self) -> list: "subject": subject, "userName": repliedByName, "userEmail": repliedByEmail, - "content": sectionContent} + "content": sectionContent, + "ccRecipients": ccRecipientsList} ) else: @@ -581,5 +628,5 @@ def getQueues() -> list: return queues if __name__ == "__main__": - item = Item("ce", 100) + item = Item("ce", 11) print() From 82c5fceb3b9f30bfc53d7699cfbe2f4b3f09b3c9 Mon Sep 17 00:00:00 2001 From: Jacob Daniel Bennett Date: Wed, 16 Sep 2020 17:27:31 -0400 Subject: [PATCH 06/36] Full section parsing for initial message --- api/ECNQueue.py | 57 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index 05e3d4f..88c7e0b 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -198,7 +198,7 @@ def __parseSections(self) -> list: " User ECNDB: ", " Host ECNDB: ", " Subject: "] - directoryInfo = ["\n"] + directoryInfo = [] endInitialMessage = False for lineNumber in range(contentStart, contentEnd + 1): @@ -212,27 +212,49 @@ def __parseSections(self) -> list: lineCounter = 0 linesToRemove = [] + initialMessageParsed = False for lineContents in initialMessageContent: for itemsindirectory in directoryInfoPattern: - if lineContents.startswith(itemsindirectory): - directoryInfo.append(lineContents) - linesToRemove.append(lineCounter) - lineCounter = lineCounter + 1 + if lineContents.startswith(itemsindirectory): + directoryInfo.append(lineContents) + linesToRemove.append(lineCounter) + lineCounter = lineCounter + 1 + for stuff in sorted(linesToRemove, reverse=True): initialMessageContent.pop(stuff) + initialMessageDateStr = self.__getMostRecentHeaderByType("Date") + initialMessageFormattedDate = "" + initialMessageFormattedTime = "" + try: + dateObject = parse(initialMessageDateStr) + initialMessageFormattedDate = dateObject.strftime("%Y-%m-%dT%H:%M:%S%z") + initialMessageFormattedTime = dateObject.strftime("%H:%M:%S%z") + except: + initialMessageFormattedDate = "invalid" + initialMessageFormattedTime = "Invalid" + + initialMessageCCMasterList =[] + initialMessageCCList = email.utils.getaddresses([self.__getMostRecentHeaderByType("CC")]) + for ccRecipients in initialMessageCCList: + initialMessageCCMasterList.append( + {"name": ccRecipients[0], + "email": ccRecipients[1]} + ) + sections.append( {"type": "initialMessage", - "date": "", - "time": "", - "userName": "", - "ccRecipients": "", + "date": initialMessageFormattedDate, + "time": initialMessageFormattedTime, + "userName": self.__parseFromData(data="userName"), + "userEmail": self.__parseFromData(data="userEmail"), + "ccRecipients": initialMessageCCMasterList, "content": initialMessageContent} ) - if len(directoryInfo) > 1: + if len(directoryInfo) > 0: sections.append( {"type": "directoryInformation", "content": directoryInfo @@ -257,6 +279,7 @@ def __parseSections(self) -> list: {"name": "replyFromUser", "pattern": "=== "}, ] + for boundary in sectionBoundaries: line = self.__rawItem[boundary["start"]] sectionType = None @@ -270,10 +293,10 @@ def __parseSections(self) -> list: - if sectionType is None: - sectionType = "initialMessage" + #if sectionType is None: + #sectionType = "initialMessage" - elif sectionType == "edit": + if sectionType == "edit": formattedDate = "" formattedTime = "" editedBy = "" @@ -446,12 +469,6 @@ def __parseSections(self) -> list: "ccRecipients": ccRecipientsList} ) - else: - sections.append( - {"type": sectionType, - "content": sectionContent} - ) - return sections def __isLocked(self) -> Union[str, bool]: @@ -628,5 +645,5 @@ def getQueues() -> list: return queues if __name__ == "__main__": - item = Item("ce", 11) + item = Item("aae", 7) print() From 09841c15aceb7ea620606acdaa73685c7438dff7 Mon Sep 17 00:00:00 2001 From: Jacob Daniel Bennett Date: Thu, 17 Sep 2020 13:31:54 -0400 Subject: [PATCH 07/36] Full section parsing for asignments and additional commenting for section parsing --- api/ECNQueue.py | 129 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 88 insertions(+), 41 deletions(-) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index 88c7e0b..d0c8ea9 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -173,20 +173,32 @@ def __getContent(self) -> list: contentEnd = len(self.__rawItem) - 1 return self.__rawItem[ contentStart : contentEnd ] - # TODO: Implement section parsing. def __parseSections(self) -> list: sections = [] - initialMessageContent = [] - #headerEnd = self.__getHeaderBoundary() contentStart = self.__getHeaderBoundary() + 1 contentEnd = len(self.__rawItem) - 1 # Find line numbers where sections start sectionBoundaries = [ {"start": contentStart} ] + + # Parses the entire contents of the message, stores everything before any delimiter as the initial message + initialMessageContent = [] + endInitialMessage = False + for lineNumber in range(contentStart, contentEnd + 1): + line = self.__rawItem[lineNumber] + if line.startswith("***") or line.startswith("===") and not line.startswith("===="): + endInitialMessage = True + #Soters what line every delimeter starts/ends + sectionBoundaries.append({"start": lineNumber}) - directoryInfoPattern = ["\tName: ", + if endInitialMessage == False: + initialMessageContent.append(line) + + # All possible Directory Items + directoryInfoPattern = [ + "\tName: ", " Login: ", " Computer: ", " Location: ", @@ -197,37 +209,42 @@ def __parseSections(self) -> list: " Zero Dir: ", " User ECNDB: ", " Host ECNDB: ", - " Subject: "] + " Subject: " + ] directoryInfo = [] - - endInitialMessage = False - for lineNumber in range(contentStart, contentEnd + 1): - line = self.__rawItem[lineNumber] - if line.startswith("***") or line.startswith("===") and not line.startswith("===="): - endInitialMessage = True - sectionBoundaries.append({"start": lineNumber}) - - if endInitialMessage == False: - initialMessageContent.append(line) - - lineCounter = 0 + + # Stores line numbers for directory info that can be removed from initial content linesToRemove = [] - initialMessageParsed = False - for lineContents in initialMessageContent: + lineCounter = 0 + # Directory Info is stored + for lineContents in initialMessageContent: for itemsindirectory in directoryInfoPattern: if lineContents.startswith(itemsindirectory): directoryInfo.append(lineContents) linesToRemove.append(lineCounter) + # Breaks loop early if a non new line is ecnountered and if a directory Item wasn't found + elif lineContents != "\n": + break lineCounter = lineCounter + 1 - for stuff in sorted(linesToRemove, reverse=True): - initialMessageContent.pop(stuff) + # Appends Directory Information into the sections array + sections.append( + {"type": "directoryInformation", + "content": directoryInfo} + ) + + # Sorts the linesToRemove array in reverse and deletes the specified lines from the initial message + for lineNums in sorted(linesToRemove, reverse=True): + initialMessageContent.pop(lineNums) + # Gets the initial message date from the header initialMessageDateStr = self.__getMostRecentHeaderByType("Date") initialMessageFormattedDate = "" initialMessageFormattedTime = "" + + # Attempts to format the date and time into utc format try: dateObject = parse(initialMessageDateStr) initialMessageFormattedDate = dateObject.strftime("%Y-%m-%dT%H:%M:%S%z") @@ -236,30 +253,66 @@ def __parseSections(self) -> list: initialMessageFormattedDate = "invalid" initialMessageFormattedTime = "Invalid" - initialMessageCCMasterList =[] + # Parses the header looking for CC recipients of the initial message + initialMessageCCSection =[] initialMessageCCList = email.utils.getaddresses([self.__getMostRecentHeaderByType("CC")]) + # Parses the CC list and stores the cc recipient information in a list of dictionaries for ccRecipients in initialMessageCCList: - initialMessageCCMasterList.append( + initialMessageCCSection.append( {"name": ccRecipients[0], "email": ccRecipients[1]} ) - + # Appends the initial message information to the sections array sections.append( {"type": "initialMessage", "date": initialMessageFormattedDate, "time": initialMessageFormattedTime, "userName": self.__parseFromData(data="userName"), "userEmail": self.__parseFromData(data="userEmail"), - "ccRecipients": initialMessageCCMasterList, + "ccRecipients": initialMessageCCSection, "content": initialMessageContent} ) - if len(directoryInfo) > 0: - sections.append( - {"type": "directoryInformation", - "content": directoryInfo - } + # Stores all assignment history in a list of dictionaries + assignmentHistoryList = [] + assignedBy = "" + assignedDate = "" + assignedTime = "" + assignedTo = "" + + # Parses the header looking for assignment delimeters and stores info into their respective variables + for headerContent in range(0, contentStart): + line = self.__rawItem[headerContent] + if line.startswith("Assigned-To: "): + assignedTo = (re.search("(?<=Assigned-To: )(.*)", line)).group() + elif line.startswith("Assigned-To-Updated-Time: "): + + # Attempts to format the date and time into utc format + try: + dateFromLine = (re.search("(?<=Assigned-To-Updated-Time: )(.*)", line)).group() + dateObject = parse(dateFromLine) + assignedDate = dateObject.strftime("%Y-%m-%dT%H:%M:%S%z") + assignedTime = dateObject.strftime("%H:%M:%S%z") + except: + initialMessageFormattedDate = "invalid" + initialMessageFormattedTime = "Invalid" + + # Checks for the ending delimiter for an assignmnet event and appends it to the assignment history list + elif line.startswith("Assigned-To-Updated-By: "): + assignedBy = (re.search("(?<=Assigned-To-Updated-By: )(.*)", line)).group() + assignmentHistoryList.append( + {"by": assignedBy, + "date": assignedDate, + "time": assignedTime, + "to": assignedTo} + ) + + # Appends the whole list of assignment history to the sections list + sections.append( + {"type": "assign", + "content": assignmentHistoryList} ) + sectionBoundaries.append({"start": contentEnd + 1}) # Set line number where section end @@ -269,9 +322,7 @@ def __parseSections(self) -> list: # Remove End of File boundary del sectionBoundaries[-1] - #for linestuff in range(contentStart, contentEnd): - #initialMessageContent.append(linestuff) - # Make list of sections and parse content + # Different delimiters for different sections delimiters = [ {"name": "edit", "pattern": "*** Edited"}, {"name": "status", "pattern": "*** Status"}, @@ -291,22 +342,18 @@ def __parseSections(self) -> list: sectionContent = self.__rawItem[boundary["start"] : boundary["end"]] - - - #if sectionType is None: - #sectionType = "initialMessage" - if sectionType == "edit": formattedDate = "" formattedTime = "" editedBy = "" - #parses for the author of the edit, which is located between the "*** Edited by: " and " at:" substrings + # Parses for the author of the edit, which is located between the "*** Edited by: " and " at:" substrings editedBy = (re.search("(?<=\*{3} Edited by: )(.*)(?= at:)", line)).group() - #parses for the date and time of the edit, which is located between the " at: " and "***\n" substrings + # Parses for the date and time of the edit, which is located between the " at: " and "***\n" substrings dateTimeString = (re.search("(?<= at: )(.*)(?= \*\*\*\n)", line)).group() + # Attempts to format the date and time into utc format try: dateObject = parse(dateTimeString) formattedDate = dateObject.strftime("%Y-%m-%dT%H:%M:%S%z") @@ -645,5 +692,5 @@ def getQueues() -> list: return queues if __name__ == "__main__": - item = Item("aae", 7) + item = Item("ce", 11) print() From 4ab1efa12584cc1c87c6e8839d77e5658449d453 Mon Sep 17 00:00:00 2001 From: Jacob Daniel Bennett Date: Thu, 17 Sep 2020 15:48:50 -0400 Subject: [PATCH 08/36] DateTime syntax for storing date and time information. Minor functionality changes --- api/ECNQueue.py | 133 ++++++++++-------------------------------------- 1 file changed, 28 insertions(+), 105 deletions(-) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index 82b0e7e..6c29c1f 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -54,7 +54,7 @@ def __init__(self, queue: str, number: int) -> None: self.priority = self.__getMostRecentHeaderByType("Priority") self.department = self.__getMostRecentHeaderByType("Department") self.building = self.__getMostRecentHeaderByType("Building") - self.dateReceived = self.__getParsedDate(self.__getMostRecentHeaderByType("Date")) + self.dateReceived = self.__getFormattedDate(self.__getMostRecentHeaderByType("Date")) self.jsonData = { "queue": self.queue, @@ -214,45 +214,23 @@ def __parseSections(self) -> list: directoryInfo = [] # Stores line numbers for directory info that can be removed from initial content - linesToRemove = [] - - lineCounter = 0 + # Directory Info is stored for lineContents in initialMessageContent: for itemsindirectory in directoryInfoPattern: if lineContents.startswith(itemsindirectory): directoryInfo.append(lineContents) - linesToRemove.append(lineCounter) - # Breaks loop early if a non new line is ecnountered and if a directory Item wasn't found - elif lineContents != "\n": - break - - lineCounter = lineCounter + 1 # Appends Directory Information into the sections array sections.append( {"type": "directoryInformation", "content": directoryInfo} ) - - # Sorts the linesToRemove array in reverse and deletes the specified lines from the initial message - for lineNums in sorted(linesToRemove, reverse=True): - initialMessageContent.pop(lineNums) # Gets the initial message date from the header initialMessageDateStr = self.__getMostRecentHeaderByType("Date") - initialMessageFormattedDate = "" - initialMessageFormattedTime = "" + initialMessageFormattedDate = self.__getFormattedDate(initialMessageDateStr) - # Attempts to format the date and time into utc format - try: - dateObject = parse(initialMessageDateStr) - initialMessageFormattedDate = dateObject.strftime("%Y-%m-%dT%H:%M:%S%z") - initialMessageFormattedTime = dateObject.strftime("%H:%M:%S%z") - except: - initialMessageFormattedDate = "invalid" - initialMessageFormattedTime = "Invalid" - # Parses the header looking for CC recipients of the initial message initialMessageCCSection =[] initialMessageCCList = email.utils.getaddresses([self.__getMostRecentHeaderByType("CC")]) @@ -265,8 +243,7 @@ def __parseSections(self) -> list: # Appends the initial message information to the sections array sections.append( {"type": "initialMessage", - "date": initialMessageFormattedDate, - "time": initialMessageFormattedTime, + "datetime": initialMessageFormattedDate, "userName": self.__parseFromData(data="userName"), "userEmail": self.__parseFromData(data="userEmail"), "ccRecipients": initialMessageCCSection, @@ -276,34 +253,31 @@ def __parseSections(self) -> list: # Stores all assignment history in a list of dictionaries assignmentHistoryList = [] assignedBy = "" - assignedDate = "" - assignedTime = "" + assignedDateTime = "" assignedTo = "" # Parses the header looking for assignment delimeters and stores info into their respective variables for headerContent in range(0, contentStart): line = self.__rawItem[headerContent] + + # Gets who the Item was assigned to if line.startswith("Assigned-To: "): assignedTo = (re.search("(?<=Assigned-To: )(.*)", line)).group() + + # Gets the date the Item was assigned elif line.startswith("Assigned-To-Updated-Time: "): + dateFromLine = (re.search("(?<=Assigned-To-Updated-Time: )(.*)", line)).group() + assignedDateTime = self.__getFormattedDate(dateFromLine) - # Attempts to format the date and time into utc format - try: - dateFromLine = (re.search("(?<=Assigned-To-Updated-Time: )(.*)", line)).group() - dateObject = parse(dateFromLine) - assignedDate = dateObject.strftime("%Y-%m-%dT%H:%M:%S%z") - assignedTime = dateObject.strftime("%H:%M:%S%z") - except: - initialMessageFormattedDate = "invalid" - initialMessageFormattedTime = "Invalid" - - # Checks for the ending delimiter for an assignmnet event and appends it to the assignment history list + # Gets who assigned the Item elif line.startswith("Assigned-To-Updated-By: "): assignedBy = (re.search("(?<=Assigned-To-Updated-By: )(.*)", line)).group() + + # Aissignment_Updated_By signifies the end of the assignment event + # and all information is appended in a dictionary to assignment history assignmentHistoryList.append( {"by": assignedBy, - "date": assignedDate, - "time": assignedTime, + "datetime": assignedDateTime, "to": assignedTo} ) @@ -343,8 +317,7 @@ def __parseSections(self) -> list: sectionContent = self.__rawItem[boundary["start"] : boundary["end"]] if sectionType == "edit": - formattedDate = "" - formattedTime = "" + formattedDateTime = "" editedBy = "" # Parses for the author of the edit, which is located between the "*** Edited by: " and " at:" substrings @@ -354,27 +327,17 @@ def __parseSections(self) -> list: dateTimeString = (re.search("(?<= at: )(.*)(?= \*\*\*\n)", line)).group() # Attempts to format the date and time into utc format - try: - dateObject = parse(dateTimeString) - formattedDate = dateObject.strftime("%Y-%m-%dT%H:%M:%S%z") - formattedTime = dateObject.strftime("%H:%M:%S%z") - except: - formattedDate = "invalid" - formattedTime = "Invalid" + formattedDateTime = self.__getFormattedDate(dateTimeString) sections.append( {"type": sectionType, "by": editedBy, - "date": formattedDate, - "time": formattedTime, + "datetime": formattedDateTime, "content": sectionContent,} ) - - sectionContent.remove(line) elif sectionType == "replyToUser": - formattedDate = "" - formattedTime = "" + formattedDateTime = "" repliedBy = "" #parses for the author of the reply, which is located between the "*** Replied by: " and " at:" substrings @@ -383,25 +346,17 @@ def __parseSections(self) -> list: #parses for the date and time of the reply, which is located between the " at: " and "***\n" substrings dateTimeString = (re.search("(?<= at: )(.*)(?= \*\*\*\n)", line)).group() - try: - dateObject = parse(dateTimeString) - formattedDate = dateObject.strftime("%Y-%m-%dT%H:%M:%S%z") - formattedTime = dateObject.strftime("%H:%M:%S%z") - except: - formattedDate = "invalid" - formattedTime = "Invalid" + formattedDateTime = self.__getFormattedDate(dateTimeString) sections.append( {"type": sectionType, "by": repliedBy, - "date": formattedDate, - "time": formattedTime, + "datetime": formattedDateTime, "content": sectionContent} ) elif sectionType == "status": - formattedDate = "" - formattedTime = "" + formattedDateTime = "" updatedBy = "" #parses for the author of the status change, which is located between the "*** Status updated by: " and " at:" substrings @@ -410,19 +365,12 @@ def __parseSections(self) -> list: #parses for the date and time of the status change, which is located between the " at: " and "***\n" substrings dateTimeString = (re.search("(?<= at: )(.*)(?= \*\*\*\n)", line)).group() - try: - dateObject = parse(dateTimeString) - formattedDate = dateObject.strftime("%Y-%m-%dT%H:%M:%S%z") - formattedTime = dateObject.strftime("%H:%M:%S%z") - except: - formattedDate = "invalid" - formattedTime = "Invalid" + formattedDateTime = self.__getFormattedDate(dateTimeString) sections.append( {"type": sectionType, "by": updatedBy, - "date": formattedDate, - "time": formattedTime, + "datetime": formattedDateTime, "content": sectionContent} ) #sectionContent.remove(line) @@ -430,15 +378,11 @@ def __parseSections(self) -> list: #elif sectionType == "": elif sectionType == "replyFromUser": - #replyMetaDataDelimiters = ["Subject: ", "From: ", "Date: "] - - formattedDate = "" - formattedTime = "" + formattedDateTime = "" repliedByName = "" repliedByEmail = "" subject = "" ccRecipientsList = [] - #ccRecipientDict = {} """ ccRecipientDict Format: @@ -448,9 +392,6 @@ def __parseSections(self) -> list: } """ - #Counter for line number and list of lines that can be deleted from the content - lineNum = 0 - linesToBeDeleted = [0] newLineCounter = 0 #Parses the section content looking for any line that starts with a metadata @@ -466,7 +407,6 @@ def __parseSections(self) -> list: #Checks for lines starting with these delimiters if items.startswith("Subject: "): subject = (re.search("(?<=Subject: )(.*)", items)).group() - linesToBeDeleted.append(lineNum) elif items.startswith("From: "): emailList = email.utils.getaddresses([items]) @@ -474,19 +414,11 @@ def __parseSections(self) -> list: repliedByName = emailList[0][0] #repliedByEmail = (re.search("(?<= <)(.*)(?=>)", items)).group() repliedByEmail = emailList[0][1] - linesToBeDeleted.append(lineNum) elif items.startswith("Date: "): dateStr = (re.search("(?<=Date: )(.*)", items)).group() - try: - dateObject = parse(dateStr) - formattedDate = dateObject.strftime("%Y-%m-%dT%H:%M:%S%z") - formattedTime = dateObject.strftime("%H:%M:%S%z") - linesToBeDeleted.append(lineNum) - except: - formattedDate = "invalid" - formattedTime = "Invalid" + formattedDateTime = self.__getFormattedDate(dateStr) elif items.startswith("Cc: "): recipientsList = email.utils.getaddresses([items]) @@ -496,10 +428,6 @@ def __parseSections(self) -> list: "email":cc[1]} ) - lineNum = lineNum + 1 - - for lineNumbers in sorted(linesToBeDeleted, reverse=True): - sectionContent.pop(lineNumbers) #parses for the author of the status change, which is located between the "*** Status updated by: " and " at:" substrings #updatedBy = (re.search("(?<=\*{3} Status updated by: )(.*)(?= at:)", line)).group() @@ -507,8 +435,7 @@ def __parseSections(self) -> list: #dateTimeString = (re.search("(?<= at: )(.*)(?= \*\*\*\n)", line)).group() sections.append( {"type": sectionType, - "date": formattedDate, - "time": formattedTime, + "datetime": formattedDateTime, "subject": subject, "userName": repliedByName, "userEmail": repliedByEmail, @@ -707,7 +634,3 @@ def getQueues() -> list: queues.append(Queue(file)) return queues - -if __name__ == "__main__": - item = Item("ce", 11) - print() From 8d9a72877f7816d1bf583b74e58549ee1ebc94d5 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Thu, 17 Sep 2020 17:17:48 -0400 Subject: [PATCH 09/36] Remove unused component --- src/components/TeamMemberCard/TeamMemberCard.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/TeamMemberCard/TeamMemberCard.js b/src/components/TeamMemberCard/TeamMemberCard.js index 832ac6b..febfa5e 100644 --- a/src/components/TeamMemberCard/TeamMemberCard.js +++ b/src/components/TeamMemberCard/TeamMemberCard.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types' import { Card, CardHeader, Avatar, IconButton, CardContent, - CardActions, Typography, Link, makeStyles } from '@material-ui/core'; + CardActions, Link, makeStyles } from '@material-ui/core'; import WebsiteIcon from '@material-ui/icons/Language'; import webqueue2Theme from "../../theme"; From 1b04a6a0bee8262d92e472fc670cea8e11e7cd7e Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Thu, 17 Sep 2020 17:31:07 -0400 Subject: [PATCH 10/36] Fix component path generation is documentation --- styleguidist/styleguide.config.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/styleguidist/styleguide.config.js b/styleguidist/styleguide.config.js index f08f9c1..de44a92 100644 --- a/styleguidist/styleguide.config.js +++ b/styleguidist/styleguide.config.js @@ -119,8 +119,7 @@ module.exports = { */ getComponentPathLine(componentPath) { const name = path.basename(componentPath, '.js') - const dir = path.dirname(componentPath) - return `import ${name} from '${dir}/';` + return `import ${name} from './components/${name}/';` }, /** From 7309063dac649a577e84d967188d670e8254cb08 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Thu, 17 Sep 2020 17:35:34 -0400 Subject: [PATCH 11/36] Rename CustomAppBar to ItemTableAppBar --- src/App.js | 4 ++-- src/{CustomAppBar.js => ItemTableAppBar.js} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/{CustomAppBar.js => ItemTableAppBar.js} (96%) diff --git a/src/App.js b/src/App.js index 66b6dc8..1dbcdea 100644 --- a/src/App.js +++ b/src/App.js @@ -2,7 +2,7 @@ import React, { useState } from "react"; import { ThemeProvider } from "@material-ui/core/styles"; import webqueueTheme from "./theme"; import { Box, makeStyles } from "@material-ui/core"; -import CustomAppBar from "./CustomAppBar"; +import ItemTableAppBar from "./ItemTableAppBar"; import ItemTable from "./components/ItemTable/"; import ItemViewAppBar from "./ItemViewAppBar"; import ItemView from "./ItemView"; @@ -55,7 +55,7 @@ function App(){ - + diff --git a/src/CustomAppBar.js b/src/ItemTableAppBar.js similarity index 96% rename from src/CustomAppBar.js rename to src/ItemTableAppBar.js index c5c8f63..3c467c3 100644 --- a/src/CustomAppBar.js +++ b/src/ItemTableAppBar.js @@ -5,7 +5,7 @@ import DarkModeIcon from '@material-ui/icons/Brightness4'; import LightModeIcon from '@material-ui/icons/Brightness7'; -export default function CustomAppBar(props){ +export default function ItemTableAppBar(props){ const useStyles = makeStyles((theme) => ({ menuButton: { marginLeft: theme.spacing(2), From 5498151b3c08a33263d665a5f6d684a3084f0ec1 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Thu, 17 Sep 2020 18:09:21 -0400 Subject: [PATCH 12/36] Remove unused menu icon --- src/ItemTableAppBar.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/ItemTableAppBar.js b/src/ItemTableAppBar.js index 3c467c3..2517ac9 100644 --- a/src/ItemTableAppBar.js +++ b/src/ItemTableAppBar.js @@ -1,6 +1,5 @@ import React from "react"; import {makeStyles, Tooltip, Typography, AppBar, Toolbar, IconButton, Zoom} from "@material-ui/core" -import MenuIcon from "@material-ui/icons/Menu"; import DarkModeIcon from '@material-ui/icons/Brightness4'; import LightModeIcon from '@material-ui/icons/Brightness7'; @@ -36,10 +35,6 @@ export default function ItemTableAppBar(props){ {props.darkMode ? : } - - - - From a8f1017d009f95b0abe231472d6b904ad01cf9d9 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Thu, 17 Sep 2020 18:23:15 -0400 Subject: [PATCH 13/36] Move ItemTableAppBar to react-styleguidist folder structure w/ docs --- src/App.js | 2 +- .../ItemTableAppBar}/ItemTableAppBar.js | 0 .../ItemTableAppBar/ItemTableAppBar.md | 16 ++++++++++++++++ src/components/ItemTableAppBar/index.js | 3 +++ 4 files changed, 20 insertions(+), 1 deletion(-) rename src/{ => components/ItemTableAppBar}/ItemTableAppBar.js (100%) create mode 100644 src/components/ItemTableAppBar/ItemTableAppBar.md create mode 100644 src/components/ItemTableAppBar/index.js diff --git a/src/App.js b/src/App.js index 1dbcdea..6ccb0b4 100644 --- a/src/App.js +++ b/src/App.js @@ -2,7 +2,7 @@ import React, { useState } from "react"; import { ThemeProvider } from "@material-ui/core/styles"; import webqueueTheme from "./theme"; import { Box, makeStyles } from "@material-ui/core"; -import ItemTableAppBar from "./ItemTableAppBar"; +import ItemTableAppBar from "./components/ItemTableAppBar/"; import ItemTable from "./components/ItemTable/"; import ItemViewAppBar from "./ItemViewAppBar"; import ItemView from "./ItemView"; diff --git a/src/ItemTableAppBar.js b/src/components/ItemTableAppBar/ItemTableAppBar.js similarity index 100% rename from src/ItemTableAppBar.js rename to src/components/ItemTableAppBar/ItemTableAppBar.js diff --git a/src/components/ItemTableAppBar/ItemTableAppBar.md b/src/components/ItemTableAppBar/ItemTableAppBar.md new file mode 100644 index 0000000..e870d28 --- /dev/null +++ b/src/components/ItemTableAppBar/ItemTableAppBar.md @@ -0,0 +1,16 @@ +The ItemTableAppBar is the primary toolbar for the [ItemTable](/#/Components/ItemTable). It displays the application title and application wide actions. + +```jsx +import React, { useState } from "react"; +import webqueue2Theme from "../../theme.js"; + +const [darkMode, setDarkMode] = useState(false); + +const theme = webqueue2Theme(darkMode); + + +``` + +```jsx static + +``` \ No newline at end of file diff --git a/src/components/ItemTableAppBar/index.js b/src/components/ItemTableAppBar/index.js new file mode 100644 index 0000000..474e0bc --- /dev/null +++ b/src/components/ItemTableAppBar/index.js @@ -0,0 +1,3 @@ +import ItemTableAppBar from "./ItemTableAppBar"; + +export default ItemTableAppBar; \ No newline at end of file From 585f9e7a7a6889d495299129c24d4cb8dd88ca6c Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Thu, 17 Sep 2020 18:49:48 -0400 Subject: [PATCH 14/36] Update ItemTable docs to reference ItemTableAppBar --- src/components/ItemTable/ItemTable.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ItemTable/ItemTable.md b/src/components/ItemTable/ItemTable.md index 0631361..335eee0 100644 --- a/src/components/ItemTable/ItemTable.md +++ b/src/components/ItemTable/ItemTable.md @@ -1,4 +1,4 @@ -The ItemTable is the primary view for webqueue2. It displays item metadata for items of selected queues and allows for filtering by field and opening an item by clicking. By default, it is pre-configured to fetch data from the webqueue2 API. +The ItemTable is the primary view for webqueue2. It displays item metadata for items of selected queues and allows for filtering by field and opening an item by clicking. By default, it is pre-configured to fetch data from the webqueue2 API. It is to be used with the [ItemTableAppBar](/#/Components/ItemTableAppBar). It is based on [material-table](https://material-table.com/). ```jsx From b32e80250116a95fbcf5717a6c77e9f54753b7f9 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Thu, 17 Sep 2020 19:18:46 -0400 Subject: [PATCH 15/36] Add propTypes and defaultProps to ItemTableAppBar --- .../ItemTableAppBar/ItemTableAppBar.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/components/ItemTableAppBar/ItemTableAppBar.js b/src/components/ItemTableAppBar/ItemTableAppBar.js index 2517ac9..44dffd6 100644 --- a/src/components/ItemTableAppBar/ItemTableAppBar.js +++ b/src/components/ItemTableAppBar/ItemTableAppBar.js @@ -1,4 +1,5 @@ import React from "react"; +import PropTypes from 'prop-types' import {makeStyles, Tooltip, Typography, AppBar, Toolbar, IconButton, Zoom} from "@material-ui/core" import DarkModeIcon from '@material-ui/icons/Brightness4'; import LightModeIcon from '@material-ui/icons/Brightness7'; @@ -39,4 +40,17 @@ export default function ItemTableAppBar(props){ ); -} \ No newline at end of file +}; + +ItemTableAppBar.propTypes = { + /** The title of the app bar. */ + "title": PropTypes.string, + /** Function to toggle darkMode. */ + "setDarkMode": PropTypes.func.isRequired, + /** State variable for darkMode */ + "darkMode": PropTypes.bool.isRequired +}; + +ItemTableAppBar.defaultProps = { + "title": "" +}; \ No newline at end of file From 9d07a5db0a8cc4f564257abab9b9d2ad7aab9482 Mon Sep 17 00:00:00 2001 From: Jacob Daniel Bennett Date: Fri, 18 Sep 2020 10:54:34 -0400 Subject: [PATCH 16/36] Further documentation and separted edits, reply-to, reply-from-user, and status update parsing from the section parsing function to their own individual parsing functions --- api/ECNQueue.py | 358 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 237 insertions(+), 121 deletions(-) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index 6c29c1f..458536d 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -175,6 +175,7 @@ def __getContent(self) -> list: def __parseSections(self) -> list: + # List of all item events sections = [] contentStart = self.__getHeaderBoundary() + 1 @@ -182,18 +183,27 @@ def __parseSections(self) -> list: # Find line numbers where sections start sectionBoundaries = [ {"start": contentStart} ] - - # Parses the entire contents of the message, stores everything before any delimiter as the initial message + + directoryInfo = [] initialMessageContent = [] endInitialMessage = False + + # Parses the entire contents of the message, stores everything before any delimiter as the initial message for lineNumber in range(contentStart, contentEnd + 1): + line = self.__rawItem[lineNumber] + if line.startswith("***") or line.startswith("===") and not line.startswith("===="): + + # Signifies that the inital message has been copletely parsed endInitialMessage = True - #Soters what line every delimeter starts/ends + + # Stores what line every delimeter starts/ends sectionBoundaries.append({"start": lineNumber}) - if endInitialMessage == False: + elif endInitialMessage == False: + + # Delimiter not encountered yet, so append line to initial message list initialMessageContent.append(line) # All possible Directory Items @@ -210,15 +220,16 @@ def __parseSections(self) -> list: " User ECNDB: ", " Host ECNDB: ", " Subject: " - ] - directoryInfo = [] - - # Stores line numbers for directory info that can be removed from initial content + ] - # Directory Info is stored + # Parses the initial message for directory information for lineContents in initialMessageContent: + for itemsindirectory in directoryInfoPattern: + + # Checks if the line starts with any of the directory parameters if lineContents.startswith(itemsindirectory): + directoryInfo.append(lineContents) # Appends Directory Information into the sections array @@ -229,18 +240,25 @@ def __parseSections(self) -> list: # Gets the initial message date from the header initialMessageDateStr = self.__getMostRecentHeaderByType("Date") + + # Formats the initial message date to UTC initialMessageFormattedDate = self.__getFormattedDate(initialMessageDateStr) - # Parses the header looking for CC recipients of the initial message + # Stores list of dictionaries for CC information initialMessageCCSection =[] + + # Parses the header looking for CC recipients of the initial message and stores it in a list of tuples initialMessageCCList = email.utils.getaddresses([self.__getMostRecentHeaderByType("CC")]) + # Parses the CC list and stores the cc recipient information in a list of dictionaries for ccRecipients in initialMessageCCList: + initialMessageCCSection.append( {"name": ccRecipients[0], "email": ccRecipients[1]} ) - # Appends the initial message information to the sections array + + # Appends all initial message information to the sections array sections.append( {"type": "initialMessage", "datetime": initialMessageFormattedDate, @@ -258,19 +276,24 @@ def __parseSections(self) -> list: # Parses the header looking for assignment delimeters and stores info into their respective variables for headerContent in range(0, contentStart): + line = self.__rawItem[headerContent] # Gets who the Item was assigned to if line.startswith("Assigned-To: "): + assignedTo = (re.search("(?<=Assigned-To: )(.*)", line)).group() # Gets the date the Item was assigned elif line.startswith("Assigned-To-Updated-Time: "): + dateFromLine = (re.search("(?<=Assigned-To-Updated-Time: )(.*)", line)).group() + assignedDateTime = self.__getFormattedDate(dateFromLine) # Gets who assigned the Item elif line.startswith("Assigned-To-Updated-By: "): + assignedBy = (re.search("(?<=Assigned-To-Updated-By: )(.*)", line)).group() # Aissignment_Updated_By signifies the end of the assignment event @@ -291,12 +314,13 @@ def __parseSections(self) -> list: # Set line number where section end for boundaryIndex in range(0, len(sectionBoundaries) - 1): + sectionBoundaries[boundaryIndex]["end"] = sectionBoundaries[boundaryIndex + 1]["start"] # Remove End of File boundary del sectionBoundaries[-1] - # Different delimiters for different sections + # Different delimiters for different message events delimiters = [ {"name": "edit", "pattern": "*** Edited"}, {"name": "status", "pattern": "*** Status"}, @@ -304,146 +328,238 @@ def __parseSections(self) -> list: {"name": "replyFromUser", "pattern": "=== "}, ] - + # Parses through all the boundaries in section boundaries for boundary in sectionBoundaries: + + # Sets line to the first line of the boundary (which is always the delimiter) line = self.__rawItem[boundary["start"]] + sectionType = None + # Looks at the begining of line and determines if it starts with any of the delimiters, + # if so, name it accordingly for delimiter in delimiters: + if line.startswith(delimiter["pattern"]): + sectionType = delimiter["name"] + break + # Returns all of the lines within the current section sectionContent = self.__rawItem[boundary["start"] : boundary["end"]] + # Checks for each section type if sectionType == "edit": - formattedDateTime = "" - editedBy = "" - - # Parses for the author of the edit, which is located between the "*** Edited by: " and " at:" substrings - editedBy = (re.search("(?<=\*{3} Edited by: )(.*)(?= at:)", line)).group() + + # Returns a dictionary with edit information + editInfo = self.__editParsing(line) + + # Appends content of the edit message to the dictionary + editInfo["content"] = sectionContent - # Parses for the date and time of the edit, which is located between the " at: " and "***\n" substrings - dateTimeString = (re.search("(?<= at: )(.*)(?= \*\*\*\n)", line)).group() + # Appends the edit dictionary to sections + sections.append(editInfo) - # Attempts to format the date and time into utc format - formattedDateTime = self.__getFormattedDate(dateTimeString) + continue - sections.append( - {"type": sectionType, - "by": editedBy, - "datetime": formattedDateTime, - "content": sectionContent,} - ) - elif sectionType == "replyToUser": - formattedDateTime = "" - repliedBy = "" + + # Returns a dictionary with reply-to information + replyToInfo = self.__replyToParsing(line) - #parses for the author of the reply, which is located between the "*** Replied by: " and " at:" substrings - repliedBy = (re.search("(?<=\*{3} Replied by: )(.*)(?= at:)", line)).group() + # Appends content of the reply-to message to the dicionary + replyToInfo['content'] = sectionContent - #parses for the date and time of the reply, which is located between the " at: " and "***\n" substrings - dateTimeString = (re.search("(?<= at: )(.*)(?= \*\*\*\n)", line)).group() + # Appends the reply-to to sections + sections.append(replyToInfo) - formattedDateTime = self.__getFormattedDate(dateTimeString) - - sections.append( - {"type": sectionType, - "by": repliedBy, - "datetime": formattedDateTime, - "content": sectionContent} - ) + continue elif sectionType == "status": - formattedDateTime = "" - updatedBy = "" + + # Returns a dictionary with status information + statusInfo = self.__statusParsing(line) - #parses for the author of the status change, which is located between the "*** Status updated by: " and " at:" substrings - updatedBy = (re.search("(?<=\*{3} Status updated by: )(.*)(?= at:)", line)).group() + # Appends content to empty content key to avoid passing large amounts of info that isnt used within the function + statusInfo['content'] = sectionContent + + # Appends the status to sections + sections.append(statusInfo) - #parses for the date and time of the status change, which is located between the " at: " and "***\n" substrings - dateTimeString = (re.search("(?<= at: )(.*)(?= \*\*\*\n)", line)).group() + continue - formattedDateTime = self.__getFormattedDate(dateTimeString) - - sections.append( - {"type": sectionType, - "by": updatedBy, - "datetime": formattedDateTime, - "content": sectionContent} - ) - #sectionContent.remove(line) - - #elif sectionType == "": elif sectionType == "replyFromUser": + + # Returns a dictionary with userReply information + replyFromInfo = self.__userReplyParsing(sectionContent) + + # Appends content to empty content key to avoid passing large amounts of info that isnt used within the function + replyFromInfo['content'] = sectionContent - formattedDateTime = "" - repliedByName = "" - repliedByEmail = "" - subject = "" - ccRecipientsList = [] + # Appends the replyFrom to sections + sections.append(replyFromInfo) + - """ - ccRecipientDict Format: - ccRecipientDict = { - "Name": Name - "Email": Email - } - """ + return sections - newLineCounter = 0 + def __editParsing(self, line: str) -> dict: + """Returns a dictionary with edit information - #Parses the section content looking for any line that starts with a metadata - for items in sectionContent: + Returns: + dictionary: "type": "edit", by, datetime and content + """ + + formattedDateTime = "" + editedBy = "" + + # Parses for the author of the edit, which is located between the "*** Edited by: " and " at:" substrings + editedBy = (re.search("(?<=\*{3} Edited by: )(.*)(?= at:)", line)).group() + + # Parses for the date and time of the edit, which is located between the " at: " and "***\n" substrings + dateTimeString = (re.search("(?<= at: )(.*)(?= \*\*\*\n)", line)).group() + + # Attempts to format the date and time into utc format + formattedDateTime = self.__getFormattedDate(dateTimeString) + + editInfo = { + "type": "edit", + "by": editedBy, + "datetime": formattedDateTime, + "content": "" + } + + return editInfo + + def __replyToParsing(self, line: str) -> dict: + """Returns a dictionary with reply to user information + + Returns: + dictionary: "type": "replyToUser", by, datetime and content + """ + + formattedDateTime = "" + repliedBy = "" + + # Parses for the author of the reply, which is located between the "*** Replied by: " and " at:" substrings + repliedBy = (re.search("(?<=\*{3} Replied by: )(.*)(?= at:)", line)).group() + + # Parses for the date and time of the reply, which is located between the " at: " and "***\n" substrings + dateTimeString = (re.search("(?<= at: )(.*)(?= \*\*\*\n)", line)).group() + + # Formats date to UTC + formattedDateTime = self.__getFormattedDate(dateTimeString) + + replyInfo = { + "type": "replyToUser", + "by": repliedBy, + "datetime": formattedDateTime, + "content": "" + } + + return replyInfo + + def __statusParsing(self, line: str) -> dict: + """Returns a dictionary with status information + + Returns: + dictionary: "type": "status", by, datetime and content + """ + + formattedDateTime = "" + updatedBy = "" + + # Parses for the author of the status change, which is located between the "*** Status updated by: " and " at:" substrings + updatedBy = (re.search("(?<=\*{3} Status updated by: )(.*)(?= at:)", line)).group() + + # Parses for the date and time of the status change, which is located between the " at: " and "***\n" substrings + dateTimeString = re.search("(?<= at: )(.*)(?= \*\*\*\n)", line).group() + + # Formats the date to UTC + formattedDateTime = self.__getFormattedDate(dateTimeString) + + statusInfo = { + "type": "status", + "by": updatedBy, + "datetime": formattedDateTime, + "content": "" + } + + return statusInfo + + def __userReplyParsing(self, replyContent: list) -> dict: + """Returns a dictionary with user Reply information information + + Returns: + dictionary: "type": "replyFromUser", datetime, subject, userName, userEmail, content, ccRecipients + """ + formattedDateTime = "" + repliedByName = "" + repliedByEmail = "" + subject = "" + ccRecipientsList = [] + newLineCounter = 0 + + #Parses the section content looking for any line that starts with a metadata + for line in replyContent: - #Checks for a newline and breaks for loop on second occurance of a newline - if items == "\n": - newLineCounter = newLineCounter + 1 - if newLineCounter == 2: - break - else: - continue - #Checks for lines starting with these delimiters - if items.startswith("Subject: "): - subject = (re.search("(?<=Subject: )(.*)", items)).group() - - elif items.startswith("From: "): - emailList = email.utils.getaddresses([items]) - #repliedByName = (re.search("(?<=From: )(.*)(?= <)", items)).group() - repliedByName = emailList[0][0] - #repliedByEmail = (re.search("(?<= <)(.*)(?=>)", items)).group() - repliedByEmail = emailList[0][1] - - elif items.startswith("Date: "): - dateStr = (re.search("(?<=Date: )(.*)", items)).group() - - formattedDateTime = self.__getFormattedDate(dateStr) - - elif items.startswith("Cc: "): - recipientsList = email.utils.getaddresses([items]) - for cc in recipientsList: - ccRecipientsList.append( - {"name":cc[0], - "email":cc[1]} - ) - - #parses for the author of the status change, which is located between the "*** Status updated by: " and " at:" substrings - #updatedBy = (re.search("(?<=\*{3} Status updated by: )(.*)(?= at:)", line)).group() - - #parses for the date and time of the status change, which is located between the " at: " and "***\n" substrings - #dateTimeString = (re.search("(?<= at: )(.*)(?= \*\*\*\n)", line)).group() - sections.append( - {"type": sectionType, - "datetime": formattedDateTime, - "subject": subject, - "userName": repliedByName, - "userEmail": repliedByEmail, - "content": sectionContent, - "ccRecipients": ccRecipientsList} - ) + #Checks for a newline and breaks for loop on second occurance of a newline + if line == "\n": + newLineCounter = newLineCounter + 1 - return sections + if newLineCounter == 2: + break + + # Checks for lines starting with Subject, From, Date and CC + if line.startswith("Subject: "): + + # Matches everything after "Subject: " in the line + subject = (re.search("(?<=Subject: )(.*)", line)).group() + + elif line.startswith("From: "): + + # Returns a list of tuples with name and email information + emailList = email.utils.getaddresses([line]) + + # The name in stored in the first index of the tuple + repliedByName = emailList[0][0] + + # The email is stored in the second index of the tuple + repliedByEmail = emailList[0][1] + + elif line.startswith("Date: "): + + # Matches everything after "Date: " + dateStr = (re.search("(?<=Date: )(.*)", line)).group() + + # Formatts the date to UTC + formattedDateTime = self.__getFormattedDate(dateStr) + + elif line.startswith("Cc: "): + + # Returns a list of tuples with email information + recipientsList = email.utils.getaddresses([line]) + + # Parses through the cc tuple list + for cc in recipientsList: + + # Stores the cc information in a dictionary and appends it to the ccRecipientsList + ccRecipientsList.append( + {"name":cc[0], + "email":cc[1]} + ) + + replyFromInfo = { + "type": "replyFromUser", + "datetime": formattedDateTime, + "subject": subject, + "userName": repliedByName, + "userEmail": repliedByEmail, + "content": "", + "ccRecipients": ccRecipientsList + } + + return replyFromInfo def __isLocked(self) -> Union[str, bool]: """Returns a string info about the lock if true and a bool False if false From ebe9518d14b14d8e59f33f0e68075d8e7783b63c Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Fri, 18 Sep 2020 15:42:01 -0400 Subject: [PATCH 17/36] Add propTypes to ItemMetadataView --- src/ItemMetadataView.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ItemMetadataView.js b/src/ItemMetadataView.js index 9a08889..55f7a56 100644 --- a/src/ItemMetadataView.js +++ b/src/ItemMetadataView.js @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types' import { makeStyles, Grid, Paper } from '@material-ui/core'; import { Alert } from '@material-ui/lab' @@ -40,4 +41,9 @@ export default function ItemMetadataView({item}){ ); -} \ No newline at end of file +} + +ItemV.ItemMetadataView = { + /** The item to be displayed. */ + "item": PropTypes.object.isRequired +}; \ No newline at end of file From c08073df6408e1eb8dbb120b310974b22dceba85 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Sat, 19 Sep 2020 21:14:42 -0400 Subject: [PATCH 18/36] Move ItemMetadataView to react-styleguidist folder structure w/ docs --- src/ItemView.js | 2 +- .../ItemMetadataView}/ItemMetadataView.js | 2 +- .../ItemMetadataView/ItemMetadataView.md | 16 ++++++++++++++++ src/components/ItemMetadataView/index.js | 3 +++ 4 files changed, 21 insertions(+), 2 deletions(-) rename src/{ => components/ItemMetadataView}/ItemMetadataView.js (97%) create mode 100644 src/components/ItemMetadataView/ItemMetadataView.md create mode 100644 src/components/ItemMetadataView/index.js diff --git a/src/ItemView.js b/src/ItemView.js index c340f51..9ab3bef 100644 --- a/src/ItemView.js +++ b/src/ItemView.js @@ -1,6 +1,6 @@ import React from 'react'; import { Paper, Typography, makeStyles } from '@material-ui/core'; -import ItemMetadataView from "./ItemMetadataView" +import ItemMetadataView from "./components/ItemMetadataView/" function ItemView(props){ diff --git a/src/ItemMetadataView.js b/src/components/ItemMetadataView/ItemMetadataView.js similarity index 97% rename from src/ItemMetadataView.js rename to src/components/ItemMetadataView/ItemMetadataView.js index 55f7a56..f6fda1e 100644 --- a/src/ItemMetadataView.js +++ b/src/components/ItemMetadataView/ItemMetadataView.js @@ -43,7 +43,7 @@ export default function ItemMetadataView({item}){ ); } -ItemV.ItemMetadataView = { +ItemMetadataView.propTypes = { /** The item to be displayed. */ "item": PropTypes.object.isRequired }; \ No newline at end of file diff --git a/src/components/ItemMetadataView/ItemMetadataView.md b/src/components/ItemMetadataView/ItemMetadataView.md new file mode 100644 index 0000000..5aa20e8 --- /dev/null +++ b/src/components/ItemMetadataView/ItemMetadataView.md @@ -0,0 +1,16 @@ +The ItemMetadataView displays the metadata for an item as part of the [ItemView](/#/Components/ItemView). + +```jsx +import Paper from "@material-ui/core"; +import ItemMetadataView from "./ItemMetadataView"; + +const demoItem = {"queue": "ce", "number": 100, "lastUpdated": "07-23-20 10:11 PM", "headers": [{"type": "Merged-Time", "content": "Tue, 23 Jun 2020 13:31:53 -0400"}, {"type": "Merged-By", "content": "campb303"}, {"type": "QTime", "content": "1"}, {"type": "QTime-Updated-Time", "content": "Tue, 23 Jun 2020 13:28:50 EDT"}, {"type": "QTime-Updated-By", "content": "campb303"}, {"type": "Time", "content": "1"}, {"type": "Time-Updated-Time", "content": "Tue, 23 Jun 2020 13:28:50 EDT"}, {"type": "Time-Updated-By", "content": "campb303"}, {"type": "Replied-Time", "content": "Tue, 23 Jun 2020 13:28:48 -0400"}, {"type": "Replied-By", "content": "campb303"}, {"type": "Edited-Time", "content": "Tue, 23 Jun 2020 13:27:56 -0400"}, {"type": "Edited-By", "content": "campb303"}, {"type": "QAssigned-To", "content": "campb303"}, {"type": "QAssigned-To-Updated-Time", "content": "Tue, 23 Jun 2020 13:27:00 EDT"}, {"type": "QAssigned-To-Updated-By", "content": "campb303"}, {"type": "Assigned-To", "content": "campb303"}, {"type": "Assigned-To-Updated-Time", "content": "Tue, 23 Jun 2020 13:27:00 EDT"}, {"type": "Assigned-To-Updated-By", "content": "campb303"}, {"type": "QStatus", "content": "Dont Delete"}, {"type": "QStatus-Updated-Time", "content": "Tue, 23 Jun 2020 13:26:55 EDT"}, {"type": "QStatus-Updated-By", "content": "campb303"}, {"type": "Status", "content": "Dont Delete"}, {"type": "Status-Updated-Time", "content": "Tue, 23 Jun 2020 13:26:55 EDT"}, {"type": "Status-Updated-By", "content": "campb303"}, {"type": "Date", "content": "Tue, 23 Jun 2020 13:25:51 -0400"}, {"type": "From", "content": "Justin Campbell "}, {"type": "Message-ID", "content": "<911CE050-3240-4980-91DD-9C3EBB8DBCF8@purdue.edu>"}, {"type": "Subject", "content": "Beepboop"}, {"type": "To", "content": "cesite@ecn.purdue.edu"}, {"type": "Content-Type", "content": "text/plain; charset=\"utf-8\""}, {"type": "X-ECN-Queue-Original-Path", "content": "/home/pier/e/queue/Attachments/inbox/2020-06-23/208-original.txt"}], "content": ["Testtest\n", "\n", "*** Status updated by: campb303 at: 6/23/2020 13:26:55 ***\n", "Dont Delete\n", "*** Edited by: campb303 at: 06/23/20 13:27:56 ***\n", "\n", "This be an edit my boy\n", "\n", "\n", "\n", "*** Replied by: campb303 at: 06/23/20 13:28:18 ***\n", "\n", "This be a reply my son\n", "\n", "Justin\n", "ECN\n", "\n", "=== Additional information supplied by user ===\n", "\n", "Subject: Re: Beepboop\n", "From: Justin Campbell \n", "Date: Tue, 23 Jun 2020 13:30:45 -0400\n", "X-ECN-Queue-Original-Path: /home/pier/e/queue/Attachments/inbox/2020-06-23/212-original.txt\n", "X-ECN-Queue-Original-URL: https://engineering.purdue.edu/webqueue/Attachments/inbox/2020-06-23/212-original.txt\n", "\n", "Huzzah!\n", "\n", "===============================================\n"], "isLocked": "ce 100 is locked by knewell using qvi", "userEmail": "campb303@purdue.edu", "userName": "Justin Campbell", "userAlias": "campb303", "assignedTo": "campb303", "subject": "Beepboop", "status": "Dont Delete", "priority": "", "department": "", "building": "", "dateReceived": "Tue, 23 Jun 2020 13:25:51 -0400"}; + +
+ +
+``` + +```jsx static + +``` \ No newline at end of file diff --git a/src/components/ItemMetadataView/index.js b/src/components/ItemMetadataView/index.js new file mode 100644 index 0000000..7de396d --- /dev/null +++ b/src/components/ItemMetadataView/index.js @@ -0,0 +1,3 @@ +import ItemMetadataView from "./ItemMetadataView"; + +export default ItemMetadataView; \ No newline at end of file From ba98c459cac39aef698380ed6bea259ef8aa7daf Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Sat, 19 Sep 2020 21:37:34 -0400 Subject: [PATCH 19/36] Move ItemViewAppBar to react-styleguidist folder structure w/ docs --- src/App.js | 2 +- .../ItemViewAppBar}/ItemViewAppBar.js | 16 +++++++++++++++- src/components/ItemViewAppBar/ItemViewAppBar.md | 17 +++++++++++++++++ src/components/ItemViewAppBar/index.js | 3 +++ 4 files changed, 36 insertions(+), 2 deletions(-) rename src/{ => components/ItemViewAppBar}/ItemViewAppBar.js (73%) create mode 100644 src/components/ItemViewAppBar/ItemViewAppBar.md create mode 100644 src/components/ItemViewAppBar/index.js diff --git a/src/App.js b/src/App.js index 6ccb0b4..e1fabb3 100644 --- a/src/App.js +++ b/src/App.js @@ -4,7 +4,7 @@ import webqueueTheme from "./theme"; import { Box, makeStyles } from "@material-ui/core"; import ItemTableAppBar from "./components/ItemTableAppBar/"; import ItemTable from "./components/ItemTable/"; -import ItemViewAppBar from "./ItemViewAppBar"; +import ItemViewAppBar from "./components/ItemViewAppBar/"; import ItemView from "./ItemView"; import clsx from "clsx"; diff --git a/src/ItemViewAppBar.js b/src/components/ItemViewAppBar/ItemViewAppBar.js similarity index 73% rename from src/ItemViewAppBar.js rename to src/components/ItemViewAppBar/ItemViewAppBar.js index 45dec54..a150cd6 100644 --- a/src/ItemViewAppBar.js +++ b/src/components/ItemViewAppBar/ItemViewAppBar.js @@ -1,4 +1,5 @@ import React from "react"; +import PropTypes from "prop-types"; import {makeStyles, Tooltip, Typography, AppBar, Toolbar, IconButton, Zoom} from "@material-ui/core" import CloseItemViewIcon from '@material-ui/icons/ChevronRight'; @@ -36,4 +37,17 @@ export default function ItemViewAppBar(props){ ); -} \ No newline at end of file +} + +ItemViewAppBar.propTypes = { + /** The webqueue2 MUI theme. */ + "theme": PropTypes.object.isRequired, + /** Function to toggle sidebar open. */ + "setSidebarOpen": PropTypes.func.isRequired, + /** The title of the app bar. */ + "title": PropTypes.string +}; + +ItemViewAppBar.defaultProps = { + "title": "" +}; \ No newline at end of file diff --git a/src/components/ItemViewAppBar/ItemViewAppBar.md b/src/components/ItemViewAppBar/ItemViewAppBar.md new file mode 100644 index 0000000..acf98df --- /dev/null +++ b/src/components/ItemViewAppBar/ItemViewAppBar.md @@ -0,0 +1,17 @@ +The ItemViewAppBar is the primary toolbar for the [ItemView](/#/Components/ItemView). It displays the item title and action for closing the sidebar. + +```jsx +import React, { useState } from "react"; +import ItemViewAppBar from "./ItemViewAppBar"; +import webqueue2Theme from "../../theme.js"; + +const theme = webqueue2Theme(false); + +const [sidebarOpen, setSidebarOpen] = useState(false); + + +``` + +```jsx static + +``` \ No newline at end of file diff --git a/src/components/ItemViewAppBar/index.js b/src/components/ItemViewAppBar/index.js new file mode 100644 index 0000000..4c009ea --- /dev/null +++ b/src/components/ItemViewAppBar/index.js @@ -0,0 +1,3 @@ +import ItemViewAppBar from "./ItemViewAppBar"; + +export default ItemViewAppBar; \ No newline at end of file From 2456f83a5ee67aa178b0302c75f3a415dda771a3 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Sat, 19 Sep 2020 21:37:48 -0400 Subject: [PATCH 20/36] Add theme propType to ItemTableAppBar --- src/components/ItemTableAppBar/ItemTableAppBar.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/ItemTableAppBar/ItemTableAppBar.js b/src/components/ItemTableAppBar/ItemTableAppBar.js index 44dffd6..e6f032a 100644 --- a/src/components/ItemTableAppBar/ItemTableAppBar.js +++ b/src/components/ItemTableAppBar/ItemTableAppBar.js @@ -1,5 +1,5 @@ import React from "react"; -import PropTypes from 'prop-types' +import PropTypes from 'prop-types'; import {makeStyles, Tooltip, Typography, AppBar, Toolbar, IconButton, Zoom} from "@material-ui/core" import DarkModeIcon from '@material-ui/icons/Brightness4'; import LightModeIcon from '@material-ui/icons/Brightness7'; @@ -48,7 +48,9 @@ ItemTableAppBar.propTypes = { /** Function to toggle darkMode. */ "setDarkMode": PropTypes.func.isRequired, /** State variable for darkMode */ - "darkMode": PropTypes.bool.isRequired + "darkMode": PropTypes.bool.isRequired, + /** The webqueue2 MUI theme. */ + "theme": PropTypes.object.isRequired }; ItemTableAppBar.defaultProps = { From c3237acbaa51709ea5b98c6079bdff6382fafd22 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Sat, 19 Sep 2020 21:50:42 -0400 Subject: [PATCH 21/36] Move ItemView to react-styleguidist folder structure w/ docs --- src/App.js | 2 +- .../ItemMetadataView/ItemMetadataView.md | 2 -- src/{ => components/ItemView}/ItemView.js | 12 ++++++++---- src/components/ItemView/ItemView.md | 18 ++++++++++++++++++ src/components/ItemView/index.js | 3 +++ 5 files changed, 30 insertions(+), 7 deletions(-) rename src/{ => components/ItemView}/ItemView.js (69%) create mode 100644 src/components/ItemView/ItemView.md create mode 100644 src/components/ItemView/index.js diff --git a/src/App.js b/src/App.js index e1fabb3..ce76978 100644 --- a/src/App.js +++ b/src/App.js @@ -5,7 +5,7 @@ import { Box, makeStyles } from "@material-ui/core"; import ItemTableAppBar from "./components/ItemTableAppBar/"; import ItemTable from "./components/ItemTable/"; import ItemViewAppBar from "./components/ItemViewAppBar/"; -import ItemView from "./ItemView"; +import ItemView from "./components/ItemView/"; import clsx from "clsx"; const testItem = {"queue": "ce", "number": 100, "lastUpdated": "07-23-20 10:11 PM", "headers": [{"type": "Merged-Time", "content": "Tue, 23 Jun 2020 13:31:53 -0400"}, {"type": "Merged-By", "content": "campb303"}, {"type": "QTime", "content": "1"}, {"type": "QTime-Updated-Time", "content": "Tue, 23 Jun 2020 13:28:50 EDT"}, {"type": "QTime-Updated-By", "content": "campb303"}, {"type": "Time", "content": "1"}, {"type": "Time-Updated-Time", "content": "Tue, 23 Jun 2020 13:28:50 EDT"}, {"type": "Time-Updated-By", "content": "campb303"}, {"type": "Replied-Time", "content": "Tue, 23 Jun 2020 13:28:48 -0400"}, {"type": "Replied-By", "content": "campb303"}, {"type": "Edited-Time", "content": "Tue, 23 Jun 2020 13:27:56 -0400"}, {"type": "Edited-By", "content": "campb303"}, {"type": "QAssigned-To", "content": "campb303"}, {"type": "QAssigned-To-Updated-Time", "content": "Tue, 23 Jun 2020 13:27:00 EDT"}, {"type": "QAssigned-To-Updated-By", "content": "campb303"}, {"type": "Assigned-To", "content": "campb303"}, {"type": "Assigned-To-Updated-Time", "content": "Tue, 23 Jun 2020 13:27:00 EDT"}, {"type": "Assigned-To-Updated-By", "content": "campb303"}, {"type": "QStatus", "content": "Dont Delete"}, {"type": "QStatus-Updated-Time", "content": "Tue, 23 Jun 2020 13:26:55 EDT"}, {"type": "QStatus-Updated-By", "content": "campb303"}, {"type": "Status", "content": "Dont Delete"}, {"type": "Status-Updated-Time", "content": "Tue, 23 Jun 2020 13:26:55 EDT"}, {"type": "Status-Updated-By", "content": "campb303"}, {"type": "Date", "content": "Tue, 23 Jun 2020 13:25:51 -0400"}, {"type": "From", "content": "Justin Campbell "}, {"type": "Message-ID", "content": "<911CE050-3240-4980-91DD-9C3EBB8DBCF8@purdue.edu>"}, {"type": "Subject", "content": "Beepboop"}, {"type": "To", "content": "cesite@ecn.purdue.edu"}, {"type": "Content-Type", "content": "text/plain; charset=\"utf-8\""}, {"type": "X-ECN-Queue-Original-Path", "content": "/home/pier/e/queue/Attachments/inbox/2020-06-23/208-original.txt"}], "content": ["Testtest\n", "\n", "*** Status updated by: campb303 at: 6/23/2020 13:26:55 ***\n", "Dont Delete\n", "*** Edited by: campb303 at: 06/23/20 13:27:56 ***\n", "\n", "This be an edit my boy\n", "\n", "\n", "\n", "*** Replied by: campb303 at: 06/23/20 13:28:18 ***\n", "\n", "This be a reply my son\n", "\n", "Justin\n", "ECN\n", "\n", "=== Additional information supplied by user ===\n", "\n", "Subject: Re: Beepboop\n", "From: Justin Campbell \n", "Date: Tue, 23 Jun 2020 13:30:45 -0400\n", "X-ECN-Queue-Original-Path: /home/pier/e/queue/Attachments/inbox/2020-06-23/212-original.txt\n", "X-ECN-Queue-Original-URL: https://engineering.purdue.edu/webqueue/Attachments/inbox/2020-06-23/212-original.txt\n", "\n", "Huzzah!\n", "\n", "===============================================\n"], "isLocked": "ce 100 is locked by knewell using qvi", "userEmail": "campb303@purdue.edu", "userName": "Justin Campbell", "userAlias": "campb303", "assignedTo": "campb303", "subject": "Beepboop", "status": "Dont Delete", "priority": "", "department": "", "building": "", "dateReceived": "Tue, 23 Jun 2020 13:25:51 -0400"} diff --git a/src/components/ItemMetadataView/ItemMetadataView.md b/src/components/ItemMetadataView/ItemMetadataView.md index 5aa20e8..5d4f5f3 100644 --- a/src/components/ItemMetadataView/ItemMetadataView.md +++ b/src/components/ItemMetadataView/ItemMetadataView.md @@ -4,8 +4,6 @@ The ItemMetadataView displays the metadata for an item as part of the [ItemView] import Paper from "@material-ui/core"; import ItemMetadataView from "./ItemMetadataView"; -const demoItem = {"queue": "ce", "number": 100, "lastUpdated": "07-23-20 10:11 PM", "headers": [{"type": "Merged-Time", "content": "Tue, 23 Jun 2020 13:31:53 -0400"}, {"type": "Merged-By", "content": "campb303"}, {"type": "QTime", "content": "1"}, {"type": "QTime-Updated-Time", "content": "Tue, 23 Jun 2020 13:28:50 EDT"}, {"type": "QTime-Updated-By", "content": "campb303"}, {"type": "Time", "content": "1"}, {"type": "Time-Updated-Time", "content": "Tue, 23 Jun 2020 13:28:50 EDT"}, {"type": "Time-Updated-By", "content": "campb303"}, {"type": "Replied-Time", "content": "Tue, 23 Jun 2020 13:28:48 -0400"}, {"type": "Replied-By", "content": "campb303"}, {"type": "Edited-Time", "content": "Tue, 23 Jun 2020 13:27:56 -0400"}, {"type": "Edited-By", "content": "campb303"}, {"type": "QAssigned-To", "content": "campb303"}, {"type": "QAssigned-To-Updated-Time", "content": "Tue, 23 Jun 2020 13:27:00 EDT"}, {"type": "QAssigned-To-Updated-By", "content": "campb303"}, {"type": "Assigned-To", "content": "campb303"}, {"type": "Assigned-To-Updated-Time", "content": "Tue, 23 Jun 2020 13:27:00 EDT"}, {"type": "Assigned-To-Updated-By", "content": "campb303"}, {"type": "QStatus", "content": "Dont Delete"}, {"type": "QStatus-Updated-Time", "content": "Tue, 23 Jun 2020 13:26:55 EDT"}, {"type": "QStatus-Updated-By", "content": "campb303"}, {"type": "Status", "content": "Dont Delete"}, {"type": "Status-Updated-Time", "content": "Tue, 23 Jun 2020 13:26:55 EDT"}, {"type": "Status-Updated-By", "content": "campb303"}, {"type": "Date", "content": "Tue, 23 Jun 2020 13:25:51 -0400"}, {"type": "From", "content": "Justin Campbell "}, {"type": "Message-ID", "content": "<911CE050-3240-4980-91DD-9C3EBB8DBCF8@purdue.edu>"}, {"type": "Subject", "content": "Beepboop"}, {"type": "To", "content": "cesite@ecn.purdue.edu"}, {"type": "Content-Type", "content": "text/plain; charset=\"utf-8\""}, {"type": "X-ECN-Queue-Original-Path", "content": "/home/pier/e/queue/Attachments/inbox/2020-06-23/208-original.txt"}], "content": ["Testtest\n", "\n", "*** Status updated by: campb303 at: 6/23/2020 13:26:55 ***\n", "Dont Delete\n", "*** Edited by: campb303 at: 06/23/20 13:27:56 ***\n", "\n", "This be an edit my boy\n", "\n", "\n", "\n", "*** Replied by: campb303 at: 06/23/20 13:28:18 ***\n", "\n", "This be a reply my son\n", "\n", "Justin\n", "ECN\n", "\n", "=== Additional information supplied by user ===\n", "\n", "Subject: Re: Beepboop\n", "From: Justin Campbell \n", "Date: Tue, 23 Jun 2020 13:30:45 -0400\n", "X-ECN-Queue-Original-Path: /home/pier/e/queue/Attachments/inbox/2020-06-23/212-original.txt\n", "X-ECN-Queue-Original-URL: https://engineering.purdue.edu/webqueue/Attachments/inbox/2020-06-23/212-original.txt\n", "\n", "Huzzah!\n", "\n", "===============================================\n"], "isLocked": "ce 100 is locked by knewell using qvi", "userEmail": "campb303@purdue.edu", "userName": "Justin Campbell", "userAlias": "campb303", "assignedTo": "campb303", "subject": "Beepboop", "status": "Dont Delete", "priority": "", "department": "", "building": "", "dateReceived": "Tue, 23 Jun 2020 13:25:51 -0400"}; -
diff --git a/src/ItemView.js b/src/components/ItemView/ItemView.js similarity index 69% rename from src/ItemView.js rename to src/components/ItemView/ItemView.js index 9ab3bef..6f8926e 100644 --- a/src/ItemView.js +++ b/src/components/ItemView/ItemView.js @@ -1,8 +1,9 @@ import React from 'react'; +import PropTypes from "prop-types"; import { Paper, Typography, makeStyles } from '@material-ui/core'; -import ItemMetadataView from "./components/ItemMetadataView/" +import ItemMetadataView from "../ItemMetadataView/" -function ItemView(props){ +export default function ItemView(props){ const useStyles = makeStyles((theme) => ({ "paperPadding": { @@ -24,6 +25,9 @@ return( )): "" } ); -} +}; -export default ItemView; \ No newline at end of file +ItemView.propTypes = { + /** The item to be viewed. */ + "activeItem": PropTypes.object.isRequired +}; \ No newline at end of file diff --git a/src/components/ItemView/ItemView.md b/src/components/ItemView/ItemView.md new file mode 100644 index 0000000..0546b58 --- /dev/null +++ b/src/components/ItemView/ItemView.md @@ -0,0 +1,18 @@ +The ItemView is the primary view for an iten. It displays the messages and actions in a timeline view. + +```jsx +import React, { useState } from "react"; + +const demoItem = {"queue": "ce", "number": 100, "lastUpdated": "07-23-20 10:11 PM", "headers": [{"type": "Merged-Time", "content": "Tue, 23 Jun 2020 13:31:53 -0400"}, {"type": "Merged-By", "content": "campb303"}, {"type": "QTime", "content": "1"}, {"type": "QTime-Updated-Time", "content": "Tue, 23 Jun 2020 13:28:50 EDT"}, {"type": "QTime-Updated-By", "content": "campb303"}, {"type": "Time", "content": "1"}, {"type": "Time-Updated-Time", "content": "Tue, 23 Jun 2020 13:28:50 EDT"}, {"type": "Time-Updated-By", "content": "campb303"}, {"type": "Replied-Time", "content": "Tue, 23 Jun 2020 13:28:48 -0400"}, {"type": "Replied-By", "content": "campb303"}, {"type": "Edited-Time", "content": "Tue, 23 Jun 2020 13:27:56 -0400"}, {"type": "Edited-By", "content": "campb303"}, {"type": "QAssigned-To", "content": "campb303"}, {"type": "QAssigned-To-Updated-Time", "content": "Tue, 23 Jun 2020 13:27:00 EDT"}, {"type": "QAssigned-To-Updated-By", "content": "campb303"}, {"type": "Assigned-To", "content": "campb303"}, {"type": "Assigned-To-Updated-Time", "content": "Tue, 23 Jun 2020 13:27:00 EDT"}, {"type": "Assigned-To-Updated-By", "content": "campb303"}, {"type": "QStatus", "content": "Dont Delete"}, {"type": "QStatus-Updated-Time", "content": "Tue, 23 Jun 2020 13:26:55 EDT"}, {"type": "QStatus-Updated-By", "content": "campb303"}, {"type": "Status", "content": "Dont Delete"}, {"type": "Status-Updated-Time", "content": "Tue, 23 Jun 2020 13:26:55 EDT"}, {"type": "Status-Updated-By", "content": "campb303"}, {"type": "Date", "content": "Tue, 23 Jun 2020 13:25:51 -0400"}, {"type": "From", "content": "Justin Campbell "}, {"type": "Message-ID", "content": "<911CE050-3240-4980-91DD-9C3EBB8DBCF8@purdue.edu>"}, {"type": "Subject", "content": "Beepboop"}, {"type": "To", "content": "cesite@ecn.purdue.edu"}, {"type": "Content-Type", "content": "text/plain; charset=\"utf-8\""}, {"type": "X-ECN-Queue-Original-Path", "content": "/home/pier/e/queue/Attachments/inbox/2020-06-23/208-original.txt"}], "content": ["Testtest\n", "\n", "*** Status updated by: campb303 at: 6/23/2020 13:26:55 ***\n", "Dont Delete\n", "*** Edited by: campb303 at: 06/23/20 13:27:56 ***\n", "\n", "This be an edit my boy\n", "\n", "\n", "\n", "*** Replied by: campb303 at: 06/23/20 13:28:18 ***\n", "\n", "This be a reply my son\n", "\n", "Justin\n", "ECN\n", "\n", "=== Additional information supplied by user ===\n", "\n", "Subject: Re: Beepboop\n", "From: Justin Campbell \n", "Date: Tue, 23 Jun 2020 13:30:45 -0400\n", "X-ECN-Queue-Original-Path: /home/pier/e/queue/Attachments/inbox/2020-06-23/212-original.txt\n", "X-ECN-Queue-Original-URL: https://engineering.purdue.edu/webqueue/Attachments/inbox/2020-06-23/212-original.txt\n", "\n", "Huzzah!\n", "\n", "===============================================\n"], "isLocked": "ce 100 is locked by knewell using qvi", "userEmail": "campb303@purdue.edu", "userName": "Justin Campbell", "userAlias": "campb303", "assignedTo": "campb303", "subject": "Beepboop", "status": "Dont Delete", "priority": "", "department": "", "building": "", "dateReceived": "Tue, 23 Jun 2020 13:25:51 -0400"}; + + +const [activeItem, setActiveItem] = useState({}); +const [sidebarOpen, setSidebarOpen] = useState(false); + + + +``` + +```jsx static + +``` \ No newline at end of file diff --git a/src/components/ItemView/index.js b/src/components/ItemView/index.js new file mode 100644 index 0000000..44014ba --- /dev/null +++ b/src/components/ItemView/index.js @@ -0,0 +1,3 @@ +import ItemView from "./ItemView"; + +export default ItemView; \ No newline at end of file From 22b2e65e9579d2166c016eb8c7fc85ef3dc02fb5 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Sun, 20 Sep 2020 15:40:42 -0400 Subject: [PATCH 22/36] Include python-dateutil in Python requirements --- api/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/api/requirements.txt b/api/requirements.txt index 4554304..8a009e1 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -11,6 +11,7 @@ lazy-object-proxy==1.4.3 MarkupSafe==1.1.1 mccabe==0.6.1 pylint==2.5.3 +python-dateutil==2.8.1 pytz==2020.1 six==1.15.0 toml==0.10.1 From 04484ad56d264b6a595db6854ccbc6d7e2f3cefb Mon Sep 17 00:00:00 2001 From: Jacob Daniel Bennett Date: Sun, 20 Sep 2020 16:51:53 -0400 Subject: [PATCH 23/36] remove Directory Information from initial message --- api/ECNQueue.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index 458536d..6535ad8 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -222,6 +222,12 @@ def __parseSections(self) -> list: " Subject: " ] + # Reference to Remove Directory Items from initial message + directoryLinesToRemove = [] + + # Line Counter + lineCounter = 0 + # Parses the initial message for directory information for lineContents in initialMessageContent: @@ -229,8 +235,20 @@ def __parseSections(self) -> list: # Checks if the line starts with any of the directory parameters if lineContents.startswith(itemsindirectory): + + # Appends line number to be removed from initial message + directoryLinesToRemove.append(lineCounter) + # Adds the contents of the line to the directory info directoryInfo.append(lineContents) + + break + + # Increment the line counter by after each line + lineCounter = lineCounter + 1 + # Parses the initial message to remove directory information + for lineNumber in sorted(directoryLinesToRemove, reverse=True): + initialMessageContent.pop(lineNumber) # Appends Directory Information into the sections array sections.append( @@ -750,3 +768,4 @@ def getQueues() -> list: queues.append(Queue(file)) return queues + From e45ce20cf4864e2a8a78bc12c0713af86ed23e2f Mon Sep 17 00:00:00 2001 From: Jacob Daniel Bennett Date: Sun, 20 Sep 2020 17:27:20 -0400 Subject: [PATCH 24/36] Remove unecessary newlines from the begining and end of initial message section --- api/ECNQueue.py | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index 6535ad8..da2cf33 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -242,14 +242,49 @@ def __parseSections(self) -> list: # Adds the contents of the line to the directory info directoryInfo.append(lineContents) + # allows to move to the next iteration of the parent for loop, no need to continue parsing directory delimiters break # Increment the line counter by after each line lineCounter = lineCounter + 1 + # Parses the initial message to remove directory information for lineNumber in sorted(directoryLinesToRemove, reverse=True): + + # Remove the directory line from the intital message initialMessageContent.pop(lineNumber) + + # Removes unecessary newlines from the begining and the end of the initial message + newLinebegining = True + newLineEnd = True + + while newLinebegining or newLineEnd: + # Initializes the Length of Message content each iteration of the loop + initialmessagecontentLength = len(initialMessageContent) + + # Checks if the last line is a newline + if initialMessageContent[initialmessagecontentLength -1] == "\n": + + # Deletes the last line if it is a newline + initialMessageContent.pop(initialmessagecontentLength - 1) + + # If the previous condition failed, then set the new line end to False if it isn't false already + elif newLineEnd == True: + + newLineEnd = False + + # Checks if the first line in message content is a newline + if initialMessageContent[0] == "\n": + + # Removes the first line in message content if it is a newline + initialMessageContent.pop(0) + + # If the previous condition failed, then set the new line begining to False if it isn't false already + elif newLinebegining == True: + + newLinebegining = False + # Appends Directory Information into the sections array sections.append( {"type": "directoryInformation", @@ -767,5 +802,4 @@ def getQueues() -> list: if isDirectory and isValid: queues.append(Queue(file)) - return queues - + return queues \ No newline at end of file From 347890360791d750b98c3e60d8497396bb0a529e Mon Sep 17 00:00:00 2001 From: Jacob Daniel Bennett Date: Sun, 20 Sep 2020 17:40:01 -0400 Subject: [PATCH 25/36] Assignments are now directly added to sections and not into one list within the type: assign dictionary --- api/ECNQueue.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index da2cf33..114c856 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -321,8 +321,7 @@ def __parseSections(self) -> list: "content": initialMessageContent} ) - # Stores all assignment history in a list of dictionaries - assignmentHistoryList = [] + # Assignment Information assignedBy = "" assignedDateTime = "" assignedTo = "" @@ -349,19 +348,13 @@ def __parseSections(self) -> list: assignedBy = (re.search("(?<=Assigned-To-Updated-By: )(.*)", line)).group() - # Aissignment_Updated_By signifies the end of the assignment event - # and all information is appended in a dictionary to assignment history - assignmentHistoryList.append( - {"by": assignedBy, + # Appends the assignment to the sections list + sections.append( + {"type": "assign", + "by": assignedBy, "datetime": assignedDateTime, "to": assignedTo} - ) - - # Appends the whole list of assignment history to the sections list - sections.append( - {"type": "assign", - "content": assignmentHistoryList} - ) + ) sectionBoundaries.append({"start": contentEnd + 1}) From 6d9126b0a0a729f724fad56a0e73c497a9483c1d Mon Sep 17 00:00:00 2001 From: Jacob Daniel Bennett Date: Sun, 20 Sep 2020 19:30:08 -0400 Subject: [PATCH 26/36] Bug fix for blank initial message content --- api/ECNQueue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/ECNQueue.py b/api/ECNQueue.py index 114c856..1042cb6 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -259,7 +259,7 @@ def __parseSections(self) -> list: newLinebegining = True newLineEnd = True - while newLinebegining or newLineEnd: + while (newLinebegining or newLineEnd) and len(initialMessageContent) > 1: # Initializes the Length of Message content each iteration of the loop initialmessagecontentLength = len(initialMessageContent) From e8bd3db8688cac58d43aa037a334ab7e5db7754e Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Sun, 20 Sep 2020 23:07:41 -0400 Subject: [PATCH 27/36] Create UserAvatar component w/ docs --- src/components/UserAvatar/UserAvatar.js | 21 +++++++++++++++++++++ src/components/UserAvatar/UserAvatar.md | 23 +++++++++++++++++++++++ src/components/UserAvatar/index.js | 3 +++ 3 files changed, 47 insertions(+) create mode 100644 src/components/UserAvatar/UserAvatar.js create mode 100644 src/components/UserAvatar/UserAvatar.md create mode 100644 src/components/UserAvatar/index.js diff --git a/src/components/UserAvatar/UserAvatar.js b/src/components/UserAvatar/UserAvatar.js new file mode 100644 index 0000000..7040737 --- /dev/null +++ b/src/components/UserAvatar/UserAvatar.js @@ -0,0 +1,21 @@ +import React, { useState, useEffect } from "react"; +import PropTypes from "prop-types"; +import { Avatar } from "@material-ui/core"; + +export default function UserAvatar({userName, userAlias}){ + + return( + + {userName === "" ? null : userName.charAt(0)} + + ); +}; + +UserAvatar.propTypes = { + /** The name of the user. */ + "userName": PropTypes.string +}; + +UserAvatar.defaultProps = { + "userName": "" +}; \ No newline at end of file diff --git a/src/components/UserAvatar/UserAvatar.md b/src/components/UserAvatar/UserAvatar.md new file mode 100644 index 0000000..40c9a48 --- /dev/null +++ b/src/components/UserAvatar/UserAvatar.md @@ -0,0 +1,23 @@ +The UserAvatar displays a graphical representation of a person and is meant to be used in conjunction with other identifiers like names or emails. + +--- + +The UserAvatar will the first letter of the `userName` prop. +```jsx +import UserAvatar from "./UserAvatar"; + +``` + +```jsx static + +``` + +If no value, a null value, or an empty string is passed to the UserAvatar, it will fall back to a generic icon. +```jsx +import UserAvatar from "./UserAvatar"; + +``` + +```jsx static + +``` \ No newline at end of file diff --git a/src/components/UserAvatar/index.js b/src/components/UserAvatar/index.js new file mode 100644 index 0000000..3f9a167 --- /dev/null +++ b/src/components/UserAvatar/index.js @@ -0,0 +1,3 @@ +import UserAvatar from "./UserAvatar"; + +export default UserAvatar; \ No newline at end of file From c37ac25d591e645faa01b054755684344c5db4ca Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Mon, 21 Sep 2020 00:26:40 -0400 Subject: [PATCH 28/36] Update ItemMetadataView to show subject, avatar, name, alias, email and date --- .../ItemMetadataView/ItemMetadataView.js | 84 ++++++++++++++----- 1 file changed, 61 insertions(+), 23 deletions(-) diff --git a/src/components/ItemMetadataView/ItemMetadataView.js b/src/components/ItemMetadataView/ItemMetadataView.js index f6fda1e..7c7877c 100644 --- a/src/components/ItemMetadataView/ItemMetadataView.js +++ b/src/components/ItemMetadataView/ItemMetadataView.js @@ -1,24 +1,58 @@ import React from 'react'; -import PropTypes from 'prop-types' -import { makeStyles, Grid, Paper } from '@material-ui/core'; -import { Alert } from '@material-ui/lab' +import PropTypes from 'prop-types'; +import { makeStyles, Card, CardHeader, Avatar, Typography } from '@material-ui/core'; +import { Alert } from '@material-ui/lab'; +import webqueue2Theme from "../../theme"; +import UserAvatar from "../UserAvatar/"; export default function ItemMetadataView({item}){ + + const theme = webqueue2Theme(false); + const useStyles = makeStyles({ + "verticalPadding": { + paddingTop: theme.spacing(1), + paddingBottom: theme.spacing(1), + }, + "removeCardHeaderPadding": { + padding: "0" + } + }); + const classes = useStyles(); + const LockedAlert = () => { return ( {item.isLocked} ); - } - const useStyles = makeStyles({ - "gridContainer": { - paddingTop: ".75em", - } - }); + }; - const classes = useStyles(); + + /** + * Returns the title for the user. + * @example + * // Has alias and name + * return "campb303 (Justin Campbell)" + * // Has alias but no name + * return "campb303" + * // Has no alias and no name + * return "" + * @returns {string} + */ + function buildTitle(){ + let title = ""; + const isNotEmpty = (value) => { + return value === "" || value === null ? false : true; + }; + if (isNotEmpty(item.userName)){ + title += `${item.userName}`; + }; + if (isNotEmpty(item.userEmail)){ + title += ` <${item.userEmail}>` + } + return title; + }; const metadataFields = ["userEmail", "userAlias", "subject", "dateReceived", "assignedTo", "status", "priority", "department", "building"]; @@ -26,19 +60,23 @@ export default function ItemMetadataView({item}){ return( <> {item.isLocked ? LockedAlert() : ""} - - {metadataFields.map((field) => { - const title = field.toUpperCase(); - const subtitle = item[field] === undefined ? "Unknown" : item[field]; - return ( - - - {title}: {subtitle} - - - ); - })} - + + + {item.subject} + + + + } + title={buildTitle()} + subheader={Date(item.dateReceived)} + classes={{root: classes.removeCardHeaderPadding}} + /> + + + + Status, assignments, priority, refile, archive and delete controls coming soon to a theater near you. + ); } From 223fcc6ea2652ec8dc4ff240fb6092cef0dc8271 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Mon, 21 Sep 2020 14:22:44 -0400 Subject: [PATCH 29/36] Create EmailHeader component w/ docs --- src/components/EmailHeader/EmailHeader.js | 73 +++++++++++++++++++++++ src/components/EmailHeader/EmailHeader.md | 9 +++ src/components/EmailHeader/index.js | 1 + 3 files changed, 83 insertions(+) create mode 100644 src/components/EmailHeader/EmailHeader.js create mode 100644 src/components/EmailHeader/EmailHeader.md create mode 100644 src/components/EmailHeader/index.js diff --git a/src/components/EmailHeader/EmailHeader.js b/src/components/EmailHeader/EmailHeader.js new file mode 100644 index 0000000..3af9c0c --- /dev/null +++ b/src/components/EmailHeader/EmailHeader.js @@ -0,0 +1,73 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { Card, CardHeader, makeStyles } from "@material-ui/core"; +import UserAvatar from "../UserAvatar/"; +import webqueue2Theme from "../../theme"; + +export default function EmailHeader({name, date, email}){ + + const theme = webqueue2Theme(false); + const useStyles = makeStyles({ + "verticalPadding": { + paddingTop: theme.spacing(1), + paddingBottom: theme.spacing(1), + }, + "removeCardHeaderPadding": { + padding: "0" + } + }); + const classes = useStyles(); + + /** + * Returns the title for the user. + * @param {string} name Name of the user. + * @param {string} email Email address of the user. + * @example + * // Has alias and name + * return "campb303 (Justin Campbell)" + * // Has alias but no name + * return "campb303" + * // Has no alias and no name + * return "" + * @returns {string} + */ + function buildTitle(name, email){ + let title = ""; + const isNotEmpty = (value) => { + return value === "" || value === null ? false : true; + }; + if (isNotEmpty(name)){ + title += `${name}`; + }; + if (isNotEmpty(email)){ + title += ` <${email}>` + } + return title; + }; + + return( + + } + title={buildTitle(name, email)} + subheader={Date(date)} + classes={{root: classes.removeCardHeaderPadding}} + /> + + ); +}; + +EmailHeader.propTypes = { + /** Name of the user. */ + "name": PropTypes.string, + /** Date of the message. */ + "date": PropTypes.string, + /** Email address of the user. */ + "email": PropTypes.string +}; + +EmailHeader.defaultProps = { + "name": "", + "date": "", + "email": "" +}; \ No newline at end of file diff --git a/src/components/EmailHeader/EmailHeader.md b/src/components/EmailHeader/EmailHeader.md new file mode 100644 index 0000000..e163f38 --- /dev/null +++ b/src/components/EmailHeader/EmailHeader.md @@ -0,0 +1,9 @@ +Description of EmailHeader. + +```jsx +import EmailHeader from "./EmailHeader"; + +``` +```jsx static + +``` \ No newline at end of file diff --git a/src/components/EmailHeader/index.js b/src/components/EmailHeader/index.js new file mode 100644 index 0000000..d4df79e --- /dev/null +++ b/src/components/EmailHeader/index.js @@ -0,0 +1 @@ +export { default as EmailHeader } from "./EmailHeader"; \ No newline at end of file From 35d9c7459933763adf086f64c0659e7e138a47a4 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Mon, 21 Sep 2020 14:23:49 -0400 Subject: [PATCH 30/36] Update ItemMetadataView to use new EmalHeader component --- .../ItemMetadataView/ItemMetadataView.js | 52 ++++--------------- 1 file changed, 10 insertions(+), 42 deletions(-) diff --git a/src/components/ItemMetadataView/ItemMetadataView.js b/src/components/ItemMetadataView/ItemMetadataView.js index 7c7877c..9adb41a 100644 --- a/src/components/ItemMetadataView/ItemMetadataView.js +++ b/src/components/ItemMetadataView/ItemMetadataView.js @@ -1,18 +1,18 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { makeStyles, Card, CardHeader, Avatar, Typography } from '@material-ui/core'; +import { makeStyles, Typography } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; import webqueue2Theme from "../../theme"; -import UserAvatar from "../UserAvatar/"; +import EmailHeader from '../EmailHeader/EmailHeader'; export default function ItemMetadataView({item}){ const theme = webqueue2Theme(false); const useStyles = makeStyles({ - "verticalPadding": { - paddingTop: theme.spacing(1), - paddingBottom: theme.spacing(1), + "verticalSpacing": { + marginTop: theme.spacing(1), + marginBottom: theme.spacing(1), }, "removeCardHeaderPadding": { padding: "0" @@ -28,32 +28,6 @@ export default function ItemMetadataView({item}){ ); }; - - /** - * Returns the title for the user. - * @example - * // Has alias and name - * return "campb303 (Justin Campbell)" - * // Has alias but no name - * return "campb303" - * // Has no alias and no name - * return "" - * @returns {string} - */ - function buildTitle(){ - let title = ""; - const isNotEmpty = (value) => { - return value === "" || value === null ? false : true; - }; - if (isNotEmpty(item.userName)){ - title += `${item.userName}`; - }; - if (isNotEmpty(item.userEmail)){ - title += ` <${item.userEmail}>` - } - return title; - }; - const metadataFields = ["userEmail", "userAlias", "subject", "dateReceived", "assignedTo", "status", "priority", "department", "building"]; @@ -61,22 +35,16 @@ export default function ItemMetadataView({item}){ <> {item.isLocked ? LockedAlert() : ""} - + {item.subject} - - } - title={buildTitle()} - subheader={Date(item.dateReceived)} - classes={{root: classes.removeCardHeaderPadding}} - /> - + - + Status, assignments, priority, refile, archive and delete controls coming soon to a theater near you. - + + ); } From e97b164067c533fcaf82e820383930ee4284e8f8 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Mon, 21 Sep 2020 14:24:14 -0400 Subject: [PATCH 31/36] Update ItemMetadataView docs w/ demoItem --- src/components/ItemMetadataView/ItemMetadataView.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/ItemMetadataView/ItemMetadataView.md b/src/components/ItemMetadataView/ItemMetadataView.md index 5d4f5f3..641eff7 100644 --- a/src/components/ItemMetadataView/ItemMetadataView.md +++ b/src/components/ItemMetadataView/ItemMetadataView.md @@ -4,7 +4,9 @@ The ItemMetadataView displays the metadata for an item as part of the [ItemView] import Paper from "@material-ui/core"; import ItemMetadataView from "./ItemMetadataView"; -
+const demoItem = {"queue": "ce", "number": 100, "lastUpdated": "07-23-20 10:11 PM", "headers": [{"type": "Merged-Time", "content": "Tue, 23 Jun 2020 13:31:53 -0400"}, {"type": "Merged-By", "content": "campb303"}, {"type": "QTime", "content": "1"}, {"type": "QTime-Updated-Time", "content": "Tue, 23 Jun 2020 13:28:50 EDT"}, {"type": "QTime-Updated-By", "content": "campb303"}, {"type": "Time", "content": "1"}, {"type": "Time-Updated-Time", "content": "Tue, 23 Jun 2020 13:28:50 EDT"}, {"type": "Time-Updated-By", "content": "campb303"}, {"type": "Replied-Time", "content": "Tue, 23 Jun 2020 13:28:48 -0400"}, {"type": "Replied-By", "content": "campb303"}, {"type": "Edited-Time", "content": "Tue, 23 Jun 2020 13:27:56 -0400"}, {"type": "Edited-By", "content": "campb303"}, {"type": "QAssigned-To", "content": "campb303"}, {"type": "QAssigned-To-Updated-Time", "content": "Tue, 23 Jun 2020 13:27:00 EDT"}, {"type": "QAssigned-To-Updated-By", "content": "campb303"}, {"type": "Assigned-To", "content": "campb303"}, {"type": "Assigned-To-Updated-Time", "content": "Tue, 23 Jun 2020 13:27:00 EDT"}, {"type": "Assigned-To-Updated-By", "content": "campb303"}, {"type": "QStatus", "content": "Dont Delete"}, {"type": "QStatus-Updated-Time", "content": "Tue, 23 Jun 2020 13:26:55 EDT"}, {"type": "QStatus-Updated-By", "content": "campb303"}, {"type": "Status", "content": "Dont Delete"}, {"type": "Status-Updated-Time", "content": "Tue, 23 Jun 2020 13:26:55 EDT"}, {"type": "Status-Updated-By", "content": "campb303"}, {"type": "Date", "content": "Tue, 23 Jun 2020 13:25:51 -0400"}, {"type": "From", "content": "Justin Campbell "}, {"type": "Message-ID", "content": "<911CE050-3240-4980-91DD-9C3EBB8DBCF8@purdue.edu>"}, {"type": "Subject", "content": "Beepboop"}, {"type": "To", "content": "cesite@ecn.purdue.edu"}, {"type": "Content-Type", "content": "text/plain; charset=\"utf-8\""}, {"type": "X-ECN-Queue-Original-Path", "content": "/home/pier/e/queue/Attachments/inbox/2020-06-23/208-original.txt"}], "content": ["Testtest\n", "\n", "*** Status updated by: campb303 at: 6/23/2020 13:26:55 ***\n", "Dont Delete\n", "*** Edited by: campb303 at: 06/23/20 13:27:56 ***\n", "\n", "This be an edit my boy\n", "\n", "\n", "\n", "*** Replied by: campb303 at: 06/23/20 13:28:18 ***\n", "\n", "This be a reply my son\n", "\n", "Justin\n", "ECN\n", "\n", "=== Additional information supplied by user ===\n", "\n", "Subject: Re: Beepboop\n", "From: Justin Campbell \n", "Date: Tue, 23 Jun 2020 13:30:45 -0400\n", "X-ECN-Queue-Original-Path: /home/pier/e/queue/Attachments/inbox/2020-06-23/212-original.txt\n", "X-ECN-Queue-Original-URL: https://engineering.purdue.edu/webqueue/Attachments/inbox/2020-06-23/212-original.txt\n", "\n", "Huzzah!\n", "\n", "===============================================\n"], "isLocked": "ce 100 is locked by knewell using qvi", "userEmail": "campb303@purdue.edu", "userName": "Justin Campbell", "userAlias": "campb303", "assignedTo": "campb303", "subject": "Beepboop", "status": "Dont Delete", "priority": "", "department": "", "building": "", "dateReceived": "Tue, 23 Jun 2020 13:25:51 -0400"}; + +
``` From e4bfc3ea60375cae456594fa4b2be51b05c96aff Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Mon, 21 Sep 2020 15:40:20 -0400 Subject: [PATCH 32/36] Fix EmailHeader export bug --- src/components/EmailHeader/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/EmailHeader/index.js b/src/components/EmailHeader/index.js index d4df79e..47c222d 100644 --- a/src/components/EmailHeader/index.js +++ b/src/components/EmailHeader/index.js @@ -1 +1 @@ -export { default as EmailHeader } from "./EmailHeader"; \ No newline at end of file +export { default } from "./EmailHeader"; \ No newline at end of file From 2b43eadf940f396f13014499a027fc0a2c806a18 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Mon, 21 Sep 2020 15:49:13 -0400 Subject: [PATCH 33/36] Create ItemBodyView component w/ docs --- src/components/ItemBodyView/ItemBodyView.js | 105 ++++++++++++++++++++ src/components/ItemBodyView/ItemBodyView.md | 23 +++++ src/components/ItemBodyView/index.js | 3 + 3 files changed, 131 insertions(+) create mode 100644 src/components/ItemBodyView/ItemBodyView.js create mode 100644 src/components/ItemBodyView/ItemBodyView.md create mode 100644 src/components/ItemBodyView/index.js diff --git a/src/components/ItemBodyView/ItemBodyView.js b/src/components/ItemBodyView/ItemBodyView.js new file mode 100644 index 0000000..3d6266e --- /dev/null +++ b/src/components/ItemBodyView/ItemBodyView.js @@ -0,0 +1,105 @@ +import React, { useState } from "react"; +import PropTypes from "prop-types"; +import { Timeline, TimelineItem, TimelineSeparator, TimelineConnector, TimelineContent, TimelineDot } from '@material-ui/lab'; +import { Card, CardHeader, CardContent, Typography, makeStyles } from "@material-ui/core"; +import webqueue2Theme from "../../theme"; +import EmailHeader from "../EmailHeader/"; +import { objectIsEmpty } from "../../utilities"; + +export default function ItemBodyView({ item }) { + + const theme = webqueue2Theme(false); + const useStyles = makeStyles((theme) => ({ + missingOppositeContent: { + '&:before': { + content: '""', + flex: 0, + padding: '0', + }, + }, + })); + const classes = useStyles(theme); + + const generateTimelineItem = (section) => { + switch(section.type) { + case "directoryInformation": + if (section.content.length === 0){ + return "No Directory Information"; + } else { + return "Directory Information Present" + } + case "initialMessage": + return( + <> + + {section.content.map((line, index) => {line})} + + ); + case "edit": + return( + <> + + {`${section.by} assigned thisat ${Date(section.datetime)}`} + + {section.content.map((line) => {line})} + + ); + case "status": + return( + <> + + {`${section.by} update the status to at ${Date(section.datetime)}`} + + {section.content.map((line) => {line})} + + ); + case "assign": + return ( + + {`${section.by} assigned this to ${section.to} at ${Date(section.datetime)}`} + + ); + case "replyToUser": + return( + <> + + {`${section.by} replied ${Date(section.datetime)}`} + + {section.content.map((line) => {line})} + + ); + case "replyFromUser": + return( + <> + + {section.content.map((line, index) => {line})} + + ); + default: + return "No Match Found"; + }; + }; + + return ( + + {objectIsEmpty(item) ? "" : item.content.map((section, index) => { + return ( + + + + + + + {generateTimelineItem(section)} + + + ); + })} + + ); +}; + +ItemBodyView.propTypes = { + /** The item to diplay. */ + "item": PropTypes.object.isRequired +}; \ No newline at end of file diff --git a/src/components/ItemBodyView/ItemBodyView.md b/src/components/ItemBodyView/ItemBodyView.md new file mode 100644 index 0000000..2c224d1 --- /dev/null +++ b/src/components/ItemBodyView/ItemBodyView.md @@ -0,0 +1,23 @@ +The ItemBodyView displays the seven actions possible in an item: + +- **Directory information**: present when a user submits a ticket from the Contact Us page +- **Initial Message**: the first message a user sends. +- **Edit:** an internal note to and from ECN staff. +- **Status:** an internal summary of the current/next task for the item. +- **Assignment:** the career account alias for the ECN employee the item is assigned to. +- **Reply To User:** a message sent from an ECN employee to a user. +- **Reply To ECN:** a message sent from the user to ECN that has been merged into an existing item. + +```jsx +import ItemBodyView from "./ItemBodyView"; + +const demoItem = {"queue": "ce", "number": 100, "lastUpdated": "08-27-20 09:49 PM", "headers": [{"type": "Merged-Time", "content": "Tue, 23 Jun 2020 13:31:53 -0400"}, {"type": "Merged-By", "content": "campb303"}, {"type": "QTime", "content": "1"}, {"type": "QTime-Updated-Time", "content": "Tue, 23 Jun 2020 13:28:50 EDT"}, {"type": "QTime-Updated-By", "content": "campb303"}, {"type": "Time", "content": "1"}, {"type": "Time-Updated-Time", "content": "Tue, 23 Jun 2020 13:28:50 EDT"}, {"type": "Time-Updated-By", "content": "campb303"}, {"type": "Replied-Time", "content": "Tue, 23 Jun 2020 13:28:48 -0400"}, {"type": "Replied-By", "content": "campb303"}, {"type": "Edited-Time", "content": "Tue, 23 Jun 2020 13:27:56 -0400"}, {"type": "Edited-By", "content": "campb303"}, {"type": "QAssigned-To", "content": "campb303"}, {"type": "QAssigned-To-Updated-Time", "content": "Tue, 23 Jun 2020 13:27:00 EDT"}, {"type": "QAssigned-To-Updated-By", "content": "campb303"}, {"type": "Assigned-To", "content": "campb303"}, {"type": "Assigned-To-Updated-Time", "content": "Tue, 23 Jun 2020 13:27:00 EDT"}, {"type": "Assigned-To-Updated-By", "content": "campb303"}, {"type": "QStatus", "content": "Dont Delete"}, {"type": "QStatus-Updated-Time", "content": "Tue, 23 Jun 2020 13:26:55 EDT"}, {"type": "QStatus-Updated-By", "content": "campb303"}, {"type": "Status", "content": "Dont Delete"}, {"type": "Status-Updated-Time", "content": "Tue, 23 Jun 2020 13:26:55 EDT"}, {"type": "Status-Updated-By", "content": "campb303"}, {"type": "Date", "content": "Tue, 23 Jun 2020 13:25:51 -0400"}, {"type": "From", "content": "Justin Campbell "}, {"type": "Message-ID", "content": "<911CE050-3240-4980-91DD-9C3EBB8DBCF8@purdue.edu>"}, {"type": "Subject", "content": "Beepboop"}, {"type": "To", "content": "cesite@ecn.purdue.edu"}, {"type": "Content-Type", "content": "text/plain; charset=\"utf-8\""}, {"type": "X-ECN-Queue-Original-Path", "content": "/home/pier/e/queue/Attachments/inbox/2020-06-23/208-original.txt"}, {"type": "X-ECN-Queue-Original-URL", "content": "https://engineering.purdue.edu/webqueue/Attachments/inbox/2020-06-23/208-original.txt"}], "content": [{"type": "directoryInformation", "content": []}, {"type": "initialMessage", "datetime": "2020-06-23T13:25:51-0400", "userName": "Justin Campbell", "userEmail": "campb303@purdue.edu", "ccRecipients": [], "content": ["Testtest\n"]}, {"type": "assign", "by": "campb303", "datetime": "2020-06-23T13:27:00-0400", "to": "campb303"}, {"type": "status", "by": "campb303", "datetime": "2020-06-23T13:26:55", "content": ["*** Status updated by: campb303 at: 6/23/2020 13:26:55 ***\n", "Dont Delete\n"]}, {"type": "edit", "by": "campb303", "datetime": "2020-06-23T13:27:56", "content": ["*** Edited by: campb303 at: 06/23/20 13:27:56 ***\n", "\n", "This be an edit my boy\n", "\n", "\n", "\n"]}, {"type": "replyToUser", "by": "campb303", "datetime": "2020-06-23T13:28:18", "content": ["*** Replied by: campb303 at: 06/23/20 13:28:18 ***\n", "\n", "This be a reply my son\n", "\n", "Justin\n", "ECN\n", "\n"]}, {"type": "replyFromUser", "datetime": "2020-06-23T13:30:45-0400", "subject": "Re: Beepboop", "userName": "Justin Campbell", "userEmail": "campb303@purdue.edu", "content": ["=== Additional information supplied by user ===\n", "\n", "Subject: Re: Beepboop\n", "From: Justin Campbell \n", "Date: Tue, 23 Jun 2020 13:30:45 -0400\n", "X-ECN-Queue-Original-Path: /home/pier/e/queue/Attachments/inbox/2020-06-23/212-original.txt\n", "X-ECN-Queue-Original-URL: https://engineering.purdue.edu/webqueue/Attachments/inbox/2020-06-23/212-original.txt\n", "\n", "Huzzah!\n", "\n", "===============================================\n", "\n"], "ccRecipients": []}], "isLocked": "ce 100 is locked by knewell using qvi", "userEmail": "campb303@purdue.edu", "userName": "Justin Campbell", "userAlias": "campb303", "assignedTo": "campb303", "subject": "Beepboop", "status": "Dont Delete", "priority": "", "department": "", "building": "", "dateReceived": "2020-06-23T13:25:51-0400"}; + +
+ +
+``` + +```jsx static + +``` \ No newline at end of file diff --git a/src/components/ItemBodyView/index.js b/src/components/ItemBodyView/index.js new file mode 100644 index 0000000..076a727 --- /dev/null +++ b/src/components/ItemBodyView/index.js @@ -0,0 +1,3 @@ +import ItemBodyView from "./ItemBodyView"; + +export default ItemBodyView; \ No newline at end of file From 731f8e3618dc872d3f549e26c3ba778b751b4a6c Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Mon, 21 Sep 2020 15:49:35 -0400 Subject: [PATCH 34/36] Update ItemView to user new ItemBodyView component --- src/components/ItemView/ItemView.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/components/ItemView/ItemView.js b/src/components/ItemView/ItemView.js index 6f8926e..e584207 100644 --- a/src/components/ItemView/ItemView.js +++ b/src/components/ItemView/ItemView.js @@ -1,7 +1,8 @@ import React from 'react'; import PropTypes from "prop-types"; -import { Paper, Typography, makeStyles } from '@material-ui/core'; +import { Paper, makeStyles } from '@material-ui/core'; import ItemMetadataView from "../ItemMetadataView/" +import ItemBodyView from "../ItemBodyView"; export default function ItemView(props){ @@ -15,14 +16,8 @@ export default function ItemView(props){ return( - - - {props.activeItem["content"] ? props.activeItem["content"].map(line => ( - - {line} - - )): "" } + ); }; From 0cd273416007461d92041520fe2abb64491d9204 Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Mon, 21 Sep 2020 15:50:58 -0400 Subject: [PATCH 35/36] Remove testing data for presentation --- src/App.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/App.js b/src/App.js index ce76978..d573c3f 100644 --- a/src/App.js +++ b/src/App.js @@ -1,19 +1,17 @@ import React, { useState } from "react"; import { ThemeProvider } from "@material-ui/core/styles"; import webqueueTheme from "./theme"; -import { Box, makeStyles } from "@material-ui/core"; +import { Box, makeStyles, Paper } from "@material-ui/core"; import ItemTableAppBar from "./components/ItemTableAppBar/"; import ItemTable from "./components/ItemTable/"; import ItemViewAppBar from "./components/ItemViewAppBar/"; import ItemView from "./components/ItemView/"; import clsx from "clsx"; -const testItem = {"queue": "ce", "number": 100, "lastUpdated": "07-23-20 10:11 PM", "headers": [{"type": "Merged-Time", "content": "Tue, 23 Jun 2020 13:31:53 -0400"}, {"type": "Merged-By", "content": "campb303"}, {"type": "QTime", "content": "1"}, {"type": "QTime-Updated-Time", "content": "Tue, 23 Jun 2020 13:28:50 EDT"}, {"type": "QTime-Updated-By", "content": "campb303"}, {"type": "Time", "content": "1"}, {"type": "Time-Updated-Time", "content": "Tue, 23 Jun 2020 13:28:50 EDT"}, {"type": "Time-Updated-By", "content": "campb303"}, {"type": "Replied-Time", "content": "Tue, 23 Jun 2020 13:28:48 -0400"}, {"type": "Replied-By", "content": "campb303"}, {"type": "Edited-Time", "content": "Tue, 23 Jun 2020 13:27:56 -0400"}, {"type": "Edited-By", "content": "campb303"}, {"type": "QAssigned-To", "content": "campb303"}, {"type": "QAssigned-To-Updated-Time", "content": "Tue, 23 Jun 2020 13:27:00 EDT"}, {"type": "QAssigned-To-Updated-By", "content": "campb303"}, {"type": "Assigned-To", "content": "campb303"}, {"type": "Assigned-To-Updated-Time", "content": "Tue, 23 Jun 2020 13:27:00 EDT"}, {"type": "Assigned-To-Updated-By", "content": "campb303"}, {"type": "QStatus", "content": "Dont Delete"}, {"type": "QStatus-Updated-Time", "content": "Tue, 23 Jun 2020 13:26:55 EDT"}, {"type": "QStatus-Updated-By", "content": "campb303"}, {"type": "Status", "content": "Dont Delete"}, {"type": "Status-Updated-Time", "content": "Tue, 23 Jun 2020 13:26:55 EDT"}, {"type": "Status-Updated-By", "content": "campb303"}, {"type": "Date", "content": "Tue, 23 Jun 2020 13:25:51 -0400"}, {"type": "From", "content": "Justin Campbell "}, {"type": "Message-ID", "content": "<911CE050-3240-4980-91DD-9C3EBB8DBCF8@purdue.edu>"}, {"type": "Subject", "content": "Beepboop"}, {"type": "To", "content": "cesite@ecn.purdue.edu"}, {"type": "Content-Type", "content": "text/plain; charset=\"utf-8\""}, {"type": "X-ECN-Queue-Original-Path", "content": "/home/pier/e/queue/Attachments/inbox/2020-06-23/208-original.txt"}], "content": ["Testtest\n", "\n", "*** Status updated by: campb303 at: 6/23/2020 13:26:55 ***\n", "Dont Delete\n", "*** Edited by: campb303 at: 06/23/20 13:27:56 ***\n", "\n", "This be an edit my boy\n", "\n", "\n", "\n", "*** Replied by: campb303 at: 06/23/20 13:28:18 ***\n", "\n", "This be a reply my son\n", "\n", "Justin\n", "ECN\n", "\n", "=== Additional information supplied by user ===\n", "\n", "Subject: Re: Beepboop\n", "From: Justin Campbell \n", "Date: Tue, 23 Jun 2020 13:30:45 -0400\n", "X-ECN-Queue-Original-Path: /home/pier/e/queue/Attachments/inbox/2020-06-23/212-original.txt\n", "X-ECN-Queue-Original-URL: https://engineering.purdue.edu/webqueue/Attachments/inbox/2020-06-23/212-original.txt\n", "\n", "Huzzah!\n", "\n", "===============================================\n"], "isLocked": "ce 100 is locked by knewell using qvi", "userEmail": "campb303@purdue.edu", "userName": "Justin Campbell", "userAlias": "campb303", "assignedTo": "campb303", "subject": "Beepboop", "status": "Dont Delete", "priority": "", "department": "", "building": "", "dateReceived": "Tue, 23 Jun 2020 13:25:51 -0400"} - function App(){ const [darkMode, setDarkMode] = useState(false); - const [activeItem, setActiveItem] = useState(testItem); - const [sidebarOpen, setSidebarOpen] = useState(true); + const [activeItem, setActiveItem] = useState({}); + const [sidebarOpen, setSidebarOpen] = useState(false); const theme = webqueueTheme(darkMode); @@ -53,16 +51,18 @@ function App(){ return ( - - - - - - - - + + + + + + + + + + - + ); } From c538754b2f22738dd25e2836301acba9d6ad57ac Mon Sep 17 00:00:00 2001 From: Justin Campbell Date: Tue, 22 Sep 2020 13:51:44 -0400 Subject: [PATCH 36/36] Remove unused components --- src/components/ItemBodyView/ItemBodyView.js | 4 ++-- src/components/ItemMetadataView/ItemMetadataView.js | 3 --- src/components/UserAvatar/UserAvatar.js | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/components/ItemBodyView/ItemBodyView.js b/src/components/ItemBodyView/ItemBodyView.js index 3d6266e..bed15c1 100644 --- a/src/components/ItemBodyView/ItemBodyView.js +++ b/src/components/ItemBodyView/ItemBodyView.js @@ -1,7 +1,7 @@ -import React, { useState } from "react"; +import React from "react"; import PropTypes from "prop-types"; import { Timeline, TimelineItem, TimelineSeparator, TimelineConnector, TimelineContent, TimelineDot } from '@material-ui/lab'; -import { Card, CardHeader, CardContent, Typography, makeStyles } from "@material-ui/core"; +import { Typography, makeStyles } from "@material-ui/core"; import webqueue2Theme from "../../theme"; import EmailHeader from "../EmailHeader/"; import { objectIsEmpty } from "../../utilities"; diff --git a/src/components/ItemMetadataView/ItemMetadataView.js b/src/components/ItemMetadataView/ItemMetadataView.js index 9adb41a..e0f27bc 100644 --- a/src/components/ItemMetadataView/ItemMetadataView.js +++ b/src/components/ItemMetadataView/ItemMetadataView.js @@ -28,9 +28,6 @@ export default function ItemMetadataView({item}){ ); }; - const metadataFields = ["userEmail", "userAlias", "subject", "dateReceived", - "assignedTo", "status", "priority", "department", "building"]; - return( <> {item.isLocked ? LockedAlert() : ""} diff --git a/src/components/UserAvatar/UserAvatar.js b/src/components/UserAvatar/UserAvatar.js index 7040737..9a56370 100644 --- a/src/components/UserAvatar/UserAvatar.js +++ b/src/components/UserAvatar/UserAvatar.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React from "react"; import PropTypes from "prop-types"; import { Avatar } from "@material-ui/core";