diff --git a/dmapp/dmweb/dm.py b/dmapp/dmweb/dm.py index 9ebd4e3..3303e78 100644 --- a/dmapp/dmweb/dm.py +++ b/dmapp/dmweb/dm.py @@ -1,39 +1,12 @@ -import datetime -from collections import Counter -from pprint import pprint +from datetime import datetime, timedelta -import pytz from flask import Blueprint, render_template -from pymongo import MongoClient -client = MongoClient() -db = client.deskmeter -switches = db.switch +from .get_period_times import get_period_totals, read_and_extract, task_file, timezone dmbp = Blueprint("deskmeter", __name__, url_prefix="/", template_folder="templates") -task_file = "/home/mariano/LETRAS/org/task/main" - - -def extract(line): - if line.rstrip().endswith("*"): - pipe_index = line.find("|") - if pipe_index != -1 and len(line) > pipe_index + 8: - value = line[pipe_index + 1 : pipe_index + 9] - return value - return None - - -def read_and_extract(file_path): - with open(file_path, "r") as file: - for line in file: - value = extract(line) - if value: - return value - return None - - @dmbp.route("/favicon.ico") def favicon(): return "", 204 # No Content @@ -51,37 +24,41 @@ def index(task=None): if task == "all": task = None - start = datetime.datetime.today().replace(hour=0, minute=0, second=0) + start = datetime.today().replace(hour=0, minute=0, second=0, tzinfo=timezone) - end = datetime.datetime.today().replace(hour=23, minute=59, second=59) + end = datetime.today().replace(hour=23, minute=59, second=59, tzinfo=timezone) - rows = get_period_totals(local_date(start), local_date(end), task) + rows = get_period_totals(start, end, task) return render_template("main.html", rows=rows) @dmbp.route("/day//") def oneday(month, day): - start = datetime.datetime(2020, month, day).replace(hour=0, minute=0, second=0) + start = datetime(2020, month, day).replace( + hour=0, minute=0, second=0, tzinfo=timezone + ) - end = datetime.datetime(2020, month, day).replace(hour=23, minute=59, second=59) + end = datetime(2020, month, day).replace( + hour=23, minute=59, second=59, tzinfo=timezone + ) - rows = get_period_totals(local_date(start), local_date(end)) + rows = get_period_totals(start, end) return render_template("pages.html", rows=rows) @dmbp.route("/period//") def period(start, end): - start = datetime.datetime(*map(int, start.split("-"))).replace( - hour=0, minute=0, second=0 + start = datetime(*map(int, start.split("-"))).replace( + hour=0, minute=0, second=0, tzinfo=timezone ) - end = datetime.datetime(*map(int, end.split("-"))).replace( - hour=23, minute=59, second=59 + end = datetime(*map(int, end.split("-"))).replace( + hour=23, minute=59, second=59, tzinfo=timezone ) - rows = get_period_totals(local_date(start), local_date(end)) + rows = get_period_totals(start, end) return render_template("pages.html", rows=rows) @@ -102,141 +79,8 @@ def totals(): rows.append( { "ws": total["_id"], - "total": str(datetime.timedelta(seconds=total["totals"])), + "total": str(timedelta(seconds=total["totals"])), } ) return render_template("pages.html", rows=rows) - - -def get_period_totals(start, end, task=None): - """ - TODOs: refactor to have available the ws array and the active ws function in order to remove - the delta = 0 thing - refactor to pass in the timezone and make the variable names clearer - """ - - task_query = {"task": {"$in": task.split(",")}} if task else {} - - queries = { - "period": {"date": {"$gt": start, "$lt": end}}, - "previous_doc": {"date": {"$lt": start}}, - "task": task_query - # "next_doc" : {"date":{"$lt":start}} - } - - length = switches.count_documents(queries["period"]) - docs = switches.find(queries["period"]).sort([("date", 1)]) - - if not length: - return [{"ws": "No Data", "total": ""}] - # next_doc = switches.find_one(queries["next_doc"]) - last_doc = docs[length - 1] - - first_date = local_date(docs[0]["date"], True) - last_date = local_date(last_doc["date"], True) - - match_query = {**queries["period"], **queries["task"]} - pipe = [ - {"$match": match_query}, - {"$group": {"_id": "$workspace", "totals": {"$sum": "$delta"}}}, - {"$sort": {"_id": 1}}, - ] - - pre_rows = Counter({}) - for total in switches.aggregate(pipeline=pipe): - pre_rows[total["_id"]] = total["totals"] - - time_corrections = [] - if first_date > start: - # TODO if its the first record it fails write test - try: - previous_doc = ( - switches.find(queries["previous_doc"]).sort([("date", -1)]).limit(1)[0] - ) - time_corrections.append( - Counter({previous_doc["workspace"]: split_entry(previous_doc, start)}) - ) - except IndexError: - pass - - now = local_date(datetime.datetime.now()) - - if task != read_and_extract(task_file): - now = local_date(last_doc["date"]) - datetime.timedelta(hours=3) - - if end > now: - time_corrections.append( - Counter({last_doc["workspace"]: split_entry(last_doc, now, after=False)}) - ) - - if end > last_date and now > end: - time_corrections.append( - Counter({last_doc["workspace"]: split_entry(last_doc, end, after=False)}) - ) - - for correction in time_corrections: - pre_rows += correction - - # TODO _1 remove - # day = 0 - rows = [] - active_vs_idle = {"active": 0, "idle": 0} - for ws, total in pre_rows.items(): - # parche por comportamiento weird - if task and ws == "Think": - total -= 1080 - - if ws in ["Think", "Plan", "Work"]: - active_vs_idle["active"] += total - if ws in ["Away", "Other"]: - active_vs_idle["idle"] += total - - rows.append( - {"ws": ws, "total": convert_timedelta(datetime.timedelta(seconds=total))} - ) - - if task == None: - task = "all" - - active_vs_idle["active"] = convert_timedelta( - datetime.timedelta(seconds=active_vs_idle["active"]) - ) - active_vs_idle["idle"] = convert_timedelta( - datetime.timedelta(seconds=active_vs_idle["idle"]) - ) - rows.append({"ws": "Active", "total": active_vs_idle["active"]}) - rows.append({"ws": "Idle", "total": active_vs_idle["idle"]}) - - # TODO _1 remove - # rows.append({"ws":"sum","total": day}) - return rows - - -def split_entry(entry, split_time, after=True): - """ - entry must have a date an number - split_time must be timezone aware - after bolean to return the time after the split time - """ - first_half = round((split_time - local_date(entry["date"], True)).total_seconds()) - last_half = entry["delta"] - first_half - if after: - return last_half - - return last_half * -1 - - -def local_date(date, convert=False): - bsas = pytz.timezone("Etc/GMT+3") - if convert: - return pytz.utc.localize(date, is_dst=None).astimezone(bsas) - return date.replace(tzinfo=bsas) - - -def convert_timedelta(duration): - days, seconds = duration.days, duration.seconds - hours = days * 24 + seconds // 3600 - minutes = (seconds % 3600) // 60 - seconds = seconds % 60 - return "{}:{:02d}:{:02d}".format(hours, minutes, seconds) diff --git a/dmapp/dmweb/dmcal.py b/dmapp/dmweb/dmcal.py index 1397121..811a312 100644 --- a/dmapp/dmweb/dmcal.py +++ b/dmapp/dmweb/dmcal.py @@ -2,10 +2,12 @@ import calendar from datetime import datetime from pprint import pprint -import pytz -from dmweb.dm import dmbp, get_period_totals, local_date +# import pytz +from dmweb.dm import dmbp from flask import Blueprint, render_template +from .get_period_times import get_period_totals, timezone + class DMHTMLCalendar(calendar.HTMLCalendar): # def formatmonth(self, theyear, themonth, withyear=True): @@ -23,11 +25,14 @@ class DMHTMLCalendar(calendar.HTMLCalendar): def oneday(self, month, day): current_year = datetime.today().year - start = datetime(self.dmyear, month, day).replace(hour=0, minute=0, second=0) + start = datetime(self.dmyear, month, day).replace( + hour=0, minute=0, second=0, tzinfo=timezone + ) + end = datetime(self.dmyear, month, day).replace( + hour=23, minute=59, second=59, tzinfo=timezone + ) - end = datetime(self.dmyear, month, day).replace(hour=23, minute=59, second=59) - - rows = get_period_totals(local_date(start), local_date(end), self.task) + rows = get_period_totals(start, end, self.task) returnstr = "" for row in rows: @@ -57,14 +62,14 @@ class DMHTMLCalendar(calendar.HTMLCalendar): break start = datetime(self.dmyear, month, start_day).replace( - hour=0, minute=0, second=0 + hour=0, minute=0, second=0, tzinfo=timezone ) end = datetime(self.dmyear, month, end_day).replace( - hour=23, minute=59, second=59 + hour=23, minute=59, second=59, tzinfo=timezone ) - rows = get_period_totals(local_date(start), local_date(end)) + rows = get_period_totals(start, end) returnstr = "
" for row in rows: diff --git a/dmapp/dmweb/get_period_times.py b/dmapp/dmweb/get_period_times.py new file mode 100644 index 0000000..28e617b --- /dev/null +++ b/dmapp/dmweb/get_period_times.py @@ -0,0 +1,206 @@ +import pprint +from collections import Counter, defaultdict +from datetime import datetime, timedelta + +from pymongo import MongoClient +from zoneinfo import ZoneInfo + +timezone = ZoneInfo("America/Argentina/Buenos_Aires") +utctz = ZoneInfo("UTC") + +client = MongoClient() +db = client.deskmeter +switches = db.switch + + +task_file = "/home/mariano/LETRAS/org/task/main" + + +def now(): + return datetime.now(timezone) + + +def convert_seconds(seconds): + days = seconds // 86400 # 86400 seconds in a day + hours = (seconds % 86400) // 3600 + minutes = (seconds % 3600) // 60 + remaining_seconds = seconds % 60 + if days > 0: + return "{} days, {:02d}:{:02d}:{:02d}".format( + days, hours, minutes, remaining_seconds + ) + else: + return "{:02d}:{:02d}:{:02d}".format(hours, minutes, remaining_seconds) + + +def extract(line): + if line.rstrip().endswith("*"): + pipe_index = line.find("|") + if pipe_index != -1 and len(line) > pipe_index + 8: + value = line[pipe_index + 1 : pipe_index + 9] + return value + return None + + +def read_and_extract(file_path): + with open(file_path, "r") as file: + for line in file: + value = extract(line) + if value: + return value + return None + + +def get_period_totals(start, end, task=None): + task_query = {"$in": task.split(",")} if task else {} + match_query = {"date": {"$gte": start, "$lte": end}} + if task_query: + match_query["task"] = task_query + + pipeline = [ + {"$match": match_query}, + {"$sort": {"date": 1}}, + { + "$group": { + "_id": None, + "documents_in_range": {"$push": "$$ROOT"}, + "first_doc": {"$first": "$$ROOT"}, + "last_doc": {"$last": "$$ROOT"}, + } + }, + # Lookup to get one document before the first document in the range + { + "$lookup": { + "from": "switch", + "let": {"first_date": "$first_doc.date"}, + "pipeline": [ + {"$match": {"$expr": {"$lt": ["$date", "$$first_date"]}}}, + {"$sort": {"date": -1}}, + {"$limit": 1}, + ], + "as": "before_first", + } + }, + { + "$project": { + "documents": { + "$concatArrays": [ + {"$ifNull": ["$before_first", []]}, + "$documents_in_range", + ] + } + } + }, + {"$unwind": "$documents"}, + {"$replaceRoot": {"newRoot": "$documents"}}, + { + "$group": { + "_id": "$workspace", + "total": {"$sum": "$delta"}, + } + }, + ] + + results = list(switches.aggregate(pipeline)) + + if not results: + return [{"ws": "No Data", "total": ""}] + + pipeline_before_after = [ + # Match documents within the date range + {"$match": match_query}, + {"$sort": {"date": 1}}, + { + "$group": { + "_id": None, + "first_doc": {"$first": "$$ROOT"}, + "last_doc": {"$last": "$$ROOT"}, + } + }, + # Lookup to get one document before the first document in the range + { + "$lookup": { + "from": "switch", + "let": {"first_date": "$first_doc.date"}, + "pipeline": [ + {"$match": {"$expr": {"$lt": ["$date", "$$first_date"]}}}, + {"$sort": {"date": -1}}, + {"$limit": 1}, + ], + "as": "before_first", + } + }, + { + "$project": { + "before_first": { + "$ifNull": [{"$arrayElemAt": ["$before_first", 0]}, ""] + }, + "last_doc": "$last_doc", # Include the last_doc from the matched period + } + }, + ] + + aux_results = list(switches.aggregate(pipeline_before_after)) + + bfirst = aux_results[0]["before_first"] + ldoc = aux_results[0]["last_doc"] + + bfdate = bfirst["date"].replace(tzinfo=utctz) + lastdate = ldoc["date"].replace(tzinfo=utctz) + + start_delta = round((start - bfdate.astimezone(timezone)).total_seconds()) + end_delta = round((end - lastdate.astimezone(timezone)).total_seconds()) + + rows = [] + active_vs_idle = {"Active": 0, "Idle": 0} + + print(results) + + for result in results: + if result["_id"] == bfirst["workspace"]: + result["total"] -= start_delta + + if end < now(): + if result["_id"] == ldoc["workspace"]: + result["total"] -= ldoc["delta"] - end_delta + + print(results) + + for result in results: + print(result) + if result["total"] > 0: + rows.append( + {"ws": result["_id"], "total": convert_seconds(result["total"])} + ) + if result["_id"] in ["Think", "Plan", "Work"]: + active_vs_idle["Active"] += result["total"] + if result["_id"] in ["Away", "Other"]: + active_vs_idle["Idle"] += result["total"] + + order = ["Think", "Plan", "Work", "Away", "Other", "Active", "Idle"] + + rows = sorted(rows, key=lambda x: order.index(x["ws"])) + + for k, v in active_vs_idle.items(): + rows.append({"ws": k, "total": convert_seconds(v)}) + + return rows + + +# print( +# get_period_totals( +# datetime.today().replace(hour=0, minute=0, second=0, tzinfo=timezone) +# - timedelta(days=1), +# datetime.today().replace(hour=23, minute=59, second=59, tzinfo=timezone) +# - timedelta(days=1), +# # "ffbe198e", +# ) +# ) + +# print( +# get_period_totals( +# datetime.today().replace(hour=0, minute=0, second=0, tzinfo=timezone), +# datetime.today().replace(hour=23, minute=59, second=59, tzinfo=timezone) +# # "ffbe198e", +# ) +# ) diff --git a/dmapp/dmweb/templates/main.html b/dmapp/dmweb/templates/main.html index 4bd03ae..c71ac3b 100644 --- a/dmapp/dmweb/templates/main.html +++ b/dmapp/dmweb/templates/main.html @@ -1,59 +1,59 @@ - - - {% block head %} - {% endblock %} + + + {% block head %} + {% endblock %} - + table { + font-size: 84pt + } + td { + padding-right: 100px; + } + - - + + - + - {% block content %} -
- {% for row in rows %} - {% if row["ws"] in ['Away', 'Other'] %} - {% set my_class = 'blue' %} - {% elif row["ws"] in ['Active', 'Idle'] %} - {% set my_class = '' %} - {% else %} - {% set my_class = 'grey' %} - {% endif %} - - - - - {% endfor %} -
{{ row["ws"] }}{{ row["total"] }}
- {% endblock %} - + {% block content %} + + {% for row in rows %} + {% if row["ws"] in ['Away', 'Other'] %} + {% set my_class = 'grey' %} + {% elif row["ws"] in ['Active', 'Idle'] %} + {% set my_class = 'blue' %} + {% else %} + {% set my_class = '' %} + {% endif %} + + + + + {% endfor %} +
{{ row["ws"] }}{{ row["total"] }}
+ {% endblock %} + \ No newline at end of file