Skip to content

Commit

Permalink
Merge pull request #51 from Jordan-Prescott/48-users-call-activity
Browse files Browse the repository at this point in the history
48 users call activity
  • Loading branch information
Jordan-Prescott authored Jun 21, 2024
2 parents 998026c + 01838cf commit 2a4266c
Show file tree
Hide file tree
Showing 9 changed files with 324 additions and 6 deletions.
Binary file added odins_spear/assets/images/made_with_os.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions odins_spear/methods/get.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,36 @@ def call_pickup_group_user(self, service_provider_id, group_id, user_id):
# CALL PROCESSING POLICIES
# CALL RECORDING
# CALL RECORDS

def users_stats(self, user_id: str, start_date:str, end_date: str = None,
start_time: str = "00:00:00", end_time:str = "23:59:59", time_zone: str = "Z"):
"""Pulls a single users call statistics for a specified period of time.
Args:
user_id (str): Target user ID you would like to pull call statistics for.
start_date (str): Start date of desired time period. Date must follow format 'YYYY-MM-DD'
end_date (str, optional): End date of desired time period. Date must follow format 'YYYY-MM-DD'.\
If this date is the same as Start date you do not need this parameter. Defaults to None.
start_time (_type_, optional): Start time of desired time period. Time must follow formate 'HH:MM:SS'. \
If you do not need to filter by time and want the whole day leave this parameter. Defaults to "00:00:00". MAX Request is 3 months.
end_time (_type_, optional): End time of desired time period. Time must follow formate 'HH:MM:SS'. \
If you do not need to filter by time and want the whole day leave this parameter. Defaults to "23:59:59". MAX Request is 3 months.
time_zone (str, optional): A specified time you would like to see call records in. \
Time zone must follow format 'GMT', 'EST', 'PST'. Defaults to "Z" (UTC Time Zone).
Returns:
Dict: Users call record statistics for specified time period.
"""

# checks if end_date has been left and therefore we assume user wants same date.
if not end_date:
end_date = start_date

endpoint = f"/users/call-records/stats?userIds={user_id}&startTime={start_date}T{start_time}{time_zone} \
&endTime={end_date}T{end_time}{time_zone}"

return self.requester.get(endpoint)

# CALL TRANSFER
# CALL WAITING
# CALLING LINE ID BLOCKING OVERRIDE
Expand Down
23 changes: 23 additions & 0 deletions odins_spear/reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,28 @@ def call_flow(self, service_provider_id: str, group_id: str, number: str, number

return reports.call_flow.main(self.api, service_provider_id, group_id, number, number_type,
broadworks_entity_type)

def group_users_call_statistics(self, service_provider_id: str, group_id: str,
start_date:str, end_date: str = None,
start_time: str = "00:00:00", end_time:str = "23:59:59",
time_zone: str = "Z"):
"""Generates a CSV deatiling each users incoming and outgoing call statistics over
a specified period for a single group. Each row contains user extension, user ID, and call stats.
Args:
service_provider_id (str): Service Provider/ Enterprise where group is hosted.
group_id (str): Target Group you would like to know user statistics for.
start_date (str): Start date of desired time period. Date must follow format 'YYYY-MM-DD'
end_date (str, optional): End date of desired time period. Date must follow format 'YYYY-MM-DD'.\
If this date is the same as Start date you do not need this parameter. Defaults to None.
start_time (_type_, optional): Start time of desired time period. Time must follow formate 'HH:MM:SS'. \
If you do not need to filter by time and want the whole day leave this parameter. Defaults to "00:00:00". MAX Request is 3 months.
end_time (_type_, optional): End time of desired time period. Time must follow formate 'HH:MM:SS'. \
If you do not need to filter by time and want the whole day leave this parameter. Defaults to "23:59:59". MAX Request is 3 months.
time_zone (str, optional): A specified time you would like to see call records in. \
Time zone must follow format 'GMT', 'EST', 'PST'. Defaults to "Z" (UTC Time Zone).
"""
return reports.group_users_call_statistics.main(self.api, service_provider_id, group_id,
start_date, end_date, start_time, end_time, time_zone)


4 changes: 3 additions & 1 deletion odins_spear/reports/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
__all__ = [
"call_flow"
"call_flow",
"group_users_call_statistics"
]

from .call_flow import main
from .group_users_call_statistics import main
78 changes: 78 additions & 0 deletions odins_spear/reports/group_users_call_statistics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import csv
import os

from tqdm import tqdm

from .report_utils.file_manager import copy_single_file_to_target_directory
from .report_utils.report_entities import call_records_statistics

def main(api: object, service_provider_id: str, group_id: str,
start_date:str, end_date: str = None, start_time: str = "00:00:00",
end_time:str = "23:59:59", time_zone: str = "Z"):
"""Generates a CSV deatiling each users incoming and outgoing call statistics over
a specified period for a single group. Each row contains user extension, user ID, and call stats.
Args:
service_provider_id (str): Service Provider/ Enterprise where group is hosted.
group_id (str): Target Group you would like to know user statistics for.
start_date (str): Start date of desired time period. Date must follow format 'YYYY-MM-DD'
end_date (str, optional): End date of desired time period. Date must follow format 'YYYY-MM-DD'.\
If this date is the same as Start date you do not need this parameter. Defaults to None.
start_time (_type_, optional): Start time of desired time period. Time must follow formate 'HH:MM:SS'. \
If you do not need to filter by time and want the whole day leave this parameter. Defaults to "00:00:00". MAX Request is 3 months.
end_time (_type_, optional): End time of desired time period. Time must follow formate 'HH:MM:SS'. \
If you do not need to filter by time and want the whole day leave this parameter. Defaults to "23:59:59". MAX Request is 3 months.
time_zone (str, optional): A specified time you would like to see call records in. \
"""

print("\nStart.")

# List of report_entities.call_records_statistics
group_users_statistics = []

print(f"Fetching list of users in {group_id}.")

# Fetches complete list of users in group
users = api.get.users(service_provider_id, group_id)

# Pulls stats for each user, instantiates call_records_statistics, and append to group_users_statistics
for user in tqdm(users, "Fetching individual stats for each user. This may take several minutes"):
user_statistics = api.get.users_stats(
user["userId"],
start_date,
end_date,
start_time,
end_time,
time_zone
)

# Correction for API removing userId if no calls made by user
if user_statistics["userId"] is None:
user_statistics["userId"] = user["userId"]

user_statistic_record = call_records_statistics.from_dict(user["extension"], user_statistics)
group_users_statistics.append(user_statistic_record)

# replace none with 0 if data returns None. Output is better if 0 allows user to make use of data better
for record in tqdm(group_users_statistics, "Formatting individual stats for each user"):
record.replace_none_with_0()

output_directory = "./os_reports"
file_name = os.path.join(output_directory, f"{group_id} User Call Statistics - {start_date} to {end_date}.csv")

# Ensure the directory exists
os.makedirs(output_directory, exist_ok=True)

# Write statistics to csv
with open(file_name, mode="w", newline="") as file:
fieldnames = [field.name for field in call_records_statistics.__dataclass_fields__.values()]
writer = csv.DictWriter(file, fieldnames=fieldnames)
writer.writeheader()

for user in group_users_statistics:
writer.writerow(user.__dict__)

# Add made_with_os.png for output
copy_single_file_to_target_directory("./odins_spear/assets/images/", "./os_reports/", "made_with_os.png")
print("\nEnd.")

143 changes: 143 additions & 0 deletions odins_spear/reports/report_utils/file_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import os
import json

import shutil

from odins_spear.exceptions import OSFileNotFound


def check_directory_or_file_exists(directory_file_path: str) -> bool:
"""Checks if a directory or file exists.
Args:
directory_file_path (str): Path to the directory or file.
Returns:
bool: If path exists return True else False.
"""

if os.path.exists(directory_file_path):
return True

return False


def join_path(directory: str, file_name: str) -> str:
"""Using os.path this method joins directory with file name and normalises path
for the OS running on.
Args:
directory (str): Directory path.
file_name (str): Name of file.
Returns:
str: Returns normalised string of joined path.
"""

return os.path.normpath(os.path.join(directory, file_name))


def json_fie_to_dict(file_path: str) -> dict:
"""Loads a json file into code as Python dict.
Args:
file_path (str): Path to json file including file name.
Returns:
dict: Python dict of json file. Returns False if file not found.
Raises:
OSFileNotFound: Raised when file cant found.
"""

if check_directory_or_file_exists(file_path):
with open(file_path, 'r') as data:
return json.loads(data.read())

return OSFileNotFound


def make_directory(directory_path: str) -> None:
"""Checks if directory already exists if not it will create it.
Args:
directory_path (str): Path to target directory.
Returns:
None: Function builds directory.
"""

return os.makedirs(directory_path, exist_ok=True)



def copy_all_directorys_files_to_target(source_dir, target_dir) -> bool:
"""
Args:
source_dir (_type_): _description_
target_dir (_type_): _description_
Returns:
bool: Returns True if operation succeeded.
Raises:
OSFileNotFound: Raised when source directoty can't be found.
"""

if check_directory_or_file_exists(source_dir):
# Check if target dir exists if not build it
make_directory(target_dir)

# Copy files
shutil.copytree(source_dir, target_dir)
return True

return OSFileNotFound


def copy_single_file_to_target_directory(source_dir: str, target_dir: str, file_name: str) -> bool:
"""Copies a single targeted file from a source directory to a target directory.
Args:
source_dir (str): Source directory path where target file is located.
target_dir (str): Target directory path where target file is to be copied to.
file_name (str): Name of source file
Returns:
bool: Returns True if operation succeeded.
Raises:
OSFileNotFound: Raised when source target file can't be found.
"""

# Construct the full paths using os.path.join and normalize them
source_file = os.path.normpath(os.path.join(source_dir, file_name))
target_file = os.path.normpath(os.path.join(target_dir, file_name))

if check_directory_or_file_exists(source_file):
# Create the target directory if it does not exist
os.makedirs(target_dir, exist_ok=True)

# Copy the file to the target directory
shutil.copy2(source_file, target_file)
return True

return OSFileNotFound


def remove_directory(directory_path: str) -> bool:
"""Check if file exists and type is directory and if both are true the directory is removed.
Args:
directory_path (str): Path to target directory to be removed.
Returns:
bool: _description_
"""

if check_directory_or_file_exists(directory_path) and os.path.isdir(directory_path):
shutil.rmtree(directory_path)
return True

return False
1 change: 0 additions & 1 deletion odins_spear/reports/report_utils/graphviz_module.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import graphviz

from odins_spear.store import broadwork_entities as bre
from .report_entities import external_number

class GraphvizModule:

Expand Down
49 changes: 46 additions & 3 deletions odins_spear/reports/report_utils/report_entities.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataclasses import dataclass, field
from typing import List, Type
from dataclasses import dataclass, field, fields
from typing import List

@dataclass
class call_flow:
Expand All @@ -9,4 +9,47 @@ class call_flow:

@dataclass
class external_number:
id: str
id: str

@dataclass
class call_records_statistics:
extension: str
userId: str
total: int
totalAnsweredAndMissed: str
answeredTotal: str
missedTotal: str
busyTotal: str
redirectTotal: str
receivedTotal: str
receivedMissed: str
receivedAnswered: str
placedTotal: str
placedMissed: str
placedAnswered: str

def replace_none_with_0(self):
for field in fields(self):
value = getattr(self, field.name)
# Replace None with 0
if value is None or value == 'None':
setattr(self, field.name, 0)

@classmethod
def from_dict(cls, extension, data):
return cls(
extension = extension,
userId= data.get("userId"),
total= data.get("total"),
totalAnsweredAndMissed= str(data.get("totalAnsweredAndMissed")),
answeredTotal= data.get("answeredTotal"),
missedTotal= data.get("missedTotal"),
busyTotal= data.get("busyTotal"),
redirectTotal= data.get("redirectTotal"),
receivedTotal= data.get("receivedTotal"),
receivedMissed= data.get("receivedMissed"),
receivedAnswered= data.get("receivedAnswered"),
placedTotal= data.get("placedTotal"),
placedMissed= data.get("placedMissed"),
placedAnswered= data.get("placedAnswered")
)
2 changes: 1 addition & 1 deletion odins_spear/utils/formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def format_filter(filter, type, value):
elif type.lower() == "contains":
return f"{filter}=*{value}*"
else:
raise OAUnsupportedFilter
raise OSUnsupportedFilter


def format_int_list_of_numbers(counrty_code: int, numbers: list):
Expand Down

0 comments on commit 2a4266c

Please sign in to comment.