Skip to content

Commit

Permalink
Merge pull request #113 from ECN/Feature-Load-Item-Headers-Only
Browse files Browse the repository at this point in the history
Feature load item headers only
  • Loading branch information
campb303 authored Mar 12, 2021
2 parents 0460c0c + ebbafcb commit bfbb895
Show file tree
Hide file tree
Showing 22 changed files with 519 additions and 274 deletions.
97 changes: 55 additions & 42 deletions api/ECNQueue.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@
Attributes:
queueDirectory: The directory to load queues from.
queuesToIgnore: Queues that will not be loaded when running getQueues()
Raises:
# TODO: Add description(s) of when a ValueError is raised.
ValueError: [description]
"""

#------------------------------------------------------------------------------#
Expand Down Expand Up @@ -62,7 +58,10 @@
#------------------------------------------------------------------------------#

def isValidItemName(name: str) -> bool:
"""Returns true if file name is a valid item name
"""Returns true if file name is a valid item name.
A file name is true if it contains between 1 and 3 integer numbers allowing for
any integer between 0 and 999.
Example:
isValidItemName("21") -> true
Expand All @@ -83,16 +82,23 @@ def isValidItemName(name: str) -> bool:
# Classes
#------------------------------------------------------------------------------#
class Item:
"""A single issue.
"""A chronological representation of an interaction with a user.
Example:
# Create an Item (ce100)
>>> item = Item("ce", 100)
>>> item = Item("ce", 100, headersOnly=false)
# Create an Item without parsing its contents (ce100)
>>> item = Item("ce", 100, headersOnly=True)
Args:
queue (str): The name of the Item's queue.
number (int): The number of the Item.
headersOnly (bool, optional): Whether or not to parse headers only. Defaults to True.
Attributes:
lastUpdated: An ISO 8601 formatted time string showing the last time the file was updated according to the filesystem.
headers: A list of dictionaries containing header keys and values.
content: A list of section dictionaries.
content: A list of section dictionaries (only included if headersOnly is False).
isLocked: A boolean showing whether or not a lockfile for the item is present.
userEmail: The email address of the person who this item is from.
userName: The real name of the person who this item is from.
Expand All @@ -104,21 +110,22 @@ class Item:
department: The most recent department for this item.
dateReceived: The date this item was created.
jsonData: A JSON serializable representation of the Item.
Raises:
ValueError: When the number passed to the constructor cannot be parsed.
"""

def __init__(self, queue: str, number: int) -> None:
def __init__(self, queue: str, number: int, headersOnly: bool = False) -> None:
self.queue = queue
try:
self.number = int(number)
except ValueError:
raise ValueError(" Could not convert \"" +
number + "\" to an integer")

raise ValueError(f'Could not convert "{number}" to an integer')
self.__path = "/".join([queueDirectory, self.queue, str(self.number)])
self.lastUpdated = self.__getLastUpdated()
self.__rawItem = self.__getRawItem()
self.headers = self.__parseHeaders()
self.content = self.__parseSections()
if not headersOnly: self.content = self.__parseSections()
self.isLocked = self.__isLocked()
self.userEmail = self.__parseFromData(data="userEmail")
self.userName = self.__parseFromData(data="userName")
Expand All @@ -129,28 +136,12 @@ 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.__getFormattedDate(
self.__getMostRecentHeaderByType("Date"))
self.dateReceived = self.__getFormattedDate(self.__getMostRecentHeaderByType("Date"))
self.jsonData = {}

# TODO: Autopopulate jsonData w/ __dir__() command. Exclude `^_` and `jsonData`.
self.jsonData = {
"queue": self.queue,
"number": self.number,
"lastUpdated": self.lastUpdated,
"headers": self.headers,
"content": self.content,
"isLocked": self.isLocked,
"userEmail": self.userEmail,
"userName": self.userName,
"userAlias": self.userAlias,
"assignedTo": self.assignedTo,
"subject": self.subject,
"status": self.status,
"priority": self.priority,
"department": self.department,
"building": self.building,
"dateReceived": self.dateReceived
}
for attribute in self.__dir__():
if "_" not in attribute and attribute != "toJson" and attribute != "jsonData":
self.jsonData[attribute] = self.__getattribute__(attribute)

def __getLastUpdated(self) -> str:
"""Returns last modified time of item reported by the filesystem in mm-dd-yy hh:mm am/pm format.
Expand Down Expand Up @@ -1245,24 +1236,31 @@ def toJson(self) -> dict:
def __repr__(self) -> str:
return self.queue + str(self.number)

# TODO: Make Queue iterable using __iter__. See: https://thispointer.com/python-how-to-make-a-class-iterable-create-iterator-class-for-it/
class Queue:
"""A collection of items.
"""A collection of Items.
Example:
# Create a queue (ce)
>>> queue = Queue("ce")
# Create a queue without parsing item contents (ce)
>>> queue = Queue("ce", headersOnly=False)
Args:
queue (str): The name of the queue.
headersOnly (bool, optional): Whether or not to parse headers only. Defaults to True.
Attributes:
name: The name of the queue.
items: A list of Items in the queue.
jsonData: A JSON serializable representation of the Queue.
"""

def __init__(self, name: str) -> None:
def __init__(self, name: str, headersOnly: bool = True) -> None:
self.name = name
self.headersOnly = headersOnly
self.__directory = queueDirectory + "/" + self.name + "/"
self.items = self.__getItems()
self._index = 0

self.jsonData = {
"name": self.name,
Expand All @@ -1283,7 +1281,7 @@ def __getItems(self) -> list:
isFile = True if os.path.isfile(itemPath) else False

if isFile and isValidItemName(item):
items.append(Item(self.name, item))
items.append(Item(self.name, item, headersOnly=self.headersOnly))

return items

Expand All @@ -1309,6 +1307,13 @@ def __len__(self) -> int:
def __repr__(self) -> str:
return f'{self.name}_queue'

# Implements the interable interface requirements by passing direct references
# to the item list's interable values.
def __iter__(self) -> list:
return iter(self.items)
def __next__(self) -> int:
return iter(self.items).__next__()

def getValidQueues() -> list:
"""Returns a list of queues on the filesystem excluding ignored queues.
Expand Down Expand Up @@ -1359,16 +1364,24 @@ def getQueueCounts() -> list:

return sortedQueueInfo


def loadQueues() -> list:
def loadAllQueues(headersOnly: bool = True) -> list:
"""Return a list of Queues for each queue.
Example:
# Load all Queues without parsing Item content
>>> loadAllQueues();
Load all Queues and parsing Item content
>>> loadAllQueues(headersOnly=False)
Args:
headersOnly (bool, optional): Whether or not to parse headers only. Defaults to True.
Returns:
list: list of Queues for each queue.
list: List of Queues for each queue.
"""
queues = []

for queue in getValidQueues():
queues.append(Queue(queue))
queues.append(Queue(queue, headersOnly=headersOnly))

return queues
19 changes: 12 additions & 7 deletions api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,6 @@ def get(self, queue: str, number: int) -> tuple:
200 (OK): On success.
Example:
/api/ce/100 returns:
{
"lastUpdated": "07-23-20 10:11 PM",
"headers": [...],
Expand All @@ -187,13 +186,21 @@ def get(self, queue: str, number: int) -> tuple:
Returns:
tuple: Item as JSON and HTTP response code.
"""
return (ECNQueue.Item(queue, number).toJson(), 200)

headersOnly = True if request.args.get("headersOnly") == "True" else False
return ECNQueue.Item(queue, number, headersOnly=headersOnly).toJson()

class Queue(Resource):
@jwt_required
def get(self, queues: str) -> tuple:
"""Returns the JSON representation of the queue requested.
Example:
{
"name": ce,
"items": [...]
}
Return Codes:
200 (OK): On success.
Expand All @@ -203,12 +210,12 @@ def get(self, queues: str) -> tuple:
Returns:
tuple: Queues as JSON and HTTP response code.
"""
headersOnly = False if request.args.get("headersOnly") == "False" else True
queues_requested = queues.split("+")

queue_list = []
for queue in queues_requested:
queue_list.append(ECNQueue.Queue(queue).toJson())

queue_list.append(ECNQueue.Queue(queue, headersOnly=headersOnly).toJson())
return (queue_list, 200)

class QueueList(Resource):
Expand All @@ -235,9 +242,7 @@ def get(self) -> tuple:
tuple: Queues and item counts as JSON and HTTP response code.
"""
return (ECNQueue.getQueueCounts(), 200)




api.add_resource(Login, "/login")
api.add_resource(RefreshAccessToken, "/tokens/refresh")
api.add_resource(Item, "/api/<string:queue>/<int:number>")
Expand Down
90 changes: 46 additions & 44 deletions src/components/AppView/AppView.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,46 +11,59 @@ import QueueSelector from "../QueueSelector/";
import { useToken } from "../AuthProvider/";

export default function AppView({ setDarkMode }){
const [activeItem, setActiveItem] = useState({});
// Create stateful variables.
const [sidebarOpen, setSidebarOpen] = useState(false);
const [queues, setQueues] = useState([]);
const [items, setItems] = useState([]);
const [selectedQueues, setSelectedQueues] = useState([]);
const [queueSelectorOpen, setQueueSelectorOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);

const access_token = useToken();

// Get Queues from API.
useEffect( _ => {
async function getQueues(){
(async function getQueues(){
if (access_token === null){
return undefined
return undefined;
}

if (queueSelectorOpen){
return undefined
return undefined;
}

if (selectedQueues.length > 0){
let queuesToLoad = "";

for (let selectedQueue of selectedQueues){
queuesToLoad += `+${selectedQueue.name}`;
}
if (selectedQueues.length === 0){
setQueues([])
return undefined;
}

let myHeaders = new Headers();
myHeaders.append("Authorization", `Bearer ${access_token}`);
let requestOptions = { headers: myHeaders };
setIsLoading(true);
let queuesToLoad = "";

const apiResponse = await fetch(`/api/${queuesToLoad}`, requestOptions);
const queueJson = await apiResponse.json();
setQueues(queueJson);
} else {
setQueues([])
if (selectedQueues.length === 1){
queuesToLoad = selectedQueues[0].name;
}
}
getQueues();
else if (selectedQueues.length > 0){
selectedQueues.forEach( (queue, index) => (
index === 0
? queuesToLoad += queue.name
: queuesToLoad += `+${queue.name}`
));
}

let myHeaders = new Headers();
myHeaders.append("Authorization", `Bearer ${access_token}`);
let requestOptions = { headers: myHeaders };

const apiResponse = await fetch(`/api/${queuesToLoad}`, requestOptions);
const queueJson = await apiResponse.json();

setQueues(queueJson);
setIsLoading(false)
})();
}, [selectedQueues, access_token, queueSelectorOpen]);

// Populate items array.
useEffect( _ => {
let tempItems = [];
for (let queue of queues){
Expand Down Expand Up @@ -94,7 +107,6 @@ export default function AppView({ setDarkMode }){

return(
<Box component={Paper} display="flex" square elevation={0}>

<Box className={classes.leftCol}>
<ItemTableAppBar title="webqueue2" setDarkMode={setDarkMode} />
<QueueSelector
Expand All @@ -103,34 +115,24 @@ export default function AppView({ setDarkMode }){
value={selectedQueues}
setValue={setSelectedQueues}
/>
<ItemTable data={items} rowCanBeSelected={sidebarOpen}/>
<ItemTable data={items} rowCanBeSelected={sidebarOpen} loading={isLoading}/>
</Box>

<Box className={clsx(classes.rightCol, sidebarOpen && classes.rightColShift)}>
{items.length === 0 ? null :
<Route
path="/:queue/:number"
render={({ match }) => {
const item = items.find((item) => {
return item.queue === match.params.queue && item.number === Number(match.params.number);
});

if (item === undefined) {
return (
<ItemViewAppBar title="Item Not Found" setSidebarOpen={setSidebarOpen} />
);
}

setActiveItem(item);

return (
<>
<ItemViewAppBar title={activeItem["queue"] + " " + activeItem["number"]} setSidebarOpen={setSidebarOpen} />
<ItemView activeItem={activeItem} />
</>
);
}
}
render={({ match }) => (
<>
<ItemViewAppBar
title={`${match.params.queue} ${match.params.number}`}
setSidebarOpen={setSidebarOpen}
/>
<ItemView
queue={match.params.queue}
number={match.params.number}
/>
</>
)}
/>
}
</Box>
Expand Down
Loading

0 comments on commit bfbb895

Please sign in to comment.