-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Start working with ttrack integration #2
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
*.env* | ||
__pycache__ | ||
.vscode/ | ||
data_test/ | ||
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,5 @@ | ||||||
.ONESHELL: | ||||||
|
||||||
.PHONY:ttrack-db | ||||||
ttrack-db: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is it used just for development right? if yes, better to have a name that forces this idea:
Suggested change
|
||||||
cp ~/.timetrackdb data/.timetrackdb |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,7 +24,18 @@ Example: | |
```bash | ||
python invoicex/main.py \ | ||
--year-month 2022-04 \ | ||
--gh-user xmnlab \ | ||
--gh-user $USER \ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is just an example, it is not a problem to have a fixed user here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please check this comment |
||
--gh-org osl-incubator/invoicex \ | ||
--timezone "-0400" | ||
``` | ||
## Integrating TTrack | ||
|
||
1) Run: | ||
```bash | ||
make ttrack-db | ||
``` | ||
|
||
2) Add tasks to the report: | ||
``` | ||
--ttrack-task foo --ttrack-task bar | ||
``` |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -5,8 +5,9 @@ | |||||
import os | ||||||
import time | ||||||
|
||||||
import reader | ||||||
import invoicex.reader.github as github | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
the idea is to hide all the complexity and use ComposeReader as the main interface for the reading. ComposeReader will receive the parameters about which datasets/data-sources will be used (for now, we just have github and ttrack) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please check this comment |
||||||
import report | ||||||
import invoicex.reader.ttrack as ttrack | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
in main.py we don't need to access ttrack nor github .. just ComposeReader There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please check this comment |
||||||
|
||||||
|
||||||
def cli_parser(): | ||||||
|
@@ -59,7 +60,7 @@ def cli_parser(): | |||||
action="store", | ||||||
type=str, | ||||||
default=time.strftime("%z"), | ||||||
help="The GitHub access token.", | ||||||
help="The invoice timezone", | ||||||
) | ||||||
# TODO: add option for custom output dir | ||||||
""" | ||||||
|
@@ -72,14 +73,23 @@ def cli_parser(): | |||||
help="The output directory for the reports (default: /tmp)", | ||||||
) | ||||||
""" | ||||||
parser.add_argument( | ||||||
"--ttrack-task", | ||||||
dest="ttrack_task", | ||||||
action="append", | ||||||
required=False, | ||||||
default=[], | ||||||
help="Task name from TTrack", | ||||||
) | ||||||
|
||||||
return parser | ||||||
|
||||||
|
||||||
async def main(): | ||||||
args = cli_parser().parse_args() | ||||||
results = await reader.get_data(args) | ||||||
await report.generate(results, args) | ||||||
results = await ttrack.get_data(args) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. here you should instantiate the ComposeReader, not the ttrack class |
||||||
print(results) | ||||||
# await report.generate(results, args) | ||||||
|
||||||
|
||||||
if __name__ == "__main__": | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
class ComposeReader: | ||
"""github + ttrack -> DF""" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
from asyncio import tasks | ||
import sqlite3 | ||
from typing import Any | ||
import pandas as pd | ||
import datetime as dt | ||
|
||
TTRACK_DB = "data/.timetrackdb" | ||
|
||
class TTrack: | ||
def __init__(self, timetrackdb_file, parameters): | ||
self.timetrackdb = timetrackdb_file | ||
self.year_month = parameters.year_month | ||
self.tasks = parameters.ttrack_task | ||
|
||
async def _conn_point(self): | ||
"""Connect and point to .timetrackdb SQLite DB""" | ||
conn = sqlite3.connect(TTRACK_DB) | ||
cur = conn.cursor() | ||
return cur | ||
|
||
async def _get_query(self, task): | ||
"""Do the query defined by --ttrack_task""" | ||
if len(task) > 1: | ||
tasks_text = ", ".join([f'"{v}"' for v in task]) | ||
else: | ||
tasks_text = f'"{task[0]}"' | ||
return ( | ||
"SELECT name, start, end FROM tasks AS T" | ||
" INNER JOIN tasklog AS L ON T.id=L.task" | ||
f' WHERE name IN ({tasks_text})' | ||
" ORDER BY start" | ||
) | ||
|
||
async def _execute_query(self): | ||
"""Execute the query and returns a list cointaining""" | ||
"""tasks with time in timestamp format""" | ||
cur = await self._conn_point() | ||
entries_in_timestamp = [] | ||
for row in cur.execute( | ||
await self._get_query(self.tasks) # TODO Except type error or use regex | ||
): | ||
entries_in_timestamp.append(row) | ||
return entries_in_timestamp | ||
|
||
async def _format_date(self): | ||
"""Format timestamp date to datetime objects""" | ||
entries = await self._execute_query() | ||
list_of_entries_with_formated_date = [] | ||
|
||
for task, start, end in entries: | ||
start_f = dt.datetime.fromtimestamp(start) | ||
end_f = dt.datetime.fromtimestamp(end) | ||
list_of_entries_with_formated_date.append([task, start_f, end_f]) | ||
return list_of_entries_with_formated_date | ||
|
||
async def _prepare_dataframe(self): | ||
"""Get the result and transform in a Pandas DataFrame""" | ||
raw_data = await self._format_date() | ||
data = [] | ||
for task, start, end in raw_data: | ||
time_worked = end - start | ||
task_dict = { | ||
"task": task, | ||
"date": start.strftime("%Y-%m-%d"), | ||
"time_worked": time_worked, | ||
} | ||
data.append(task_dict) | ||
df = pd.DataFrame(data=data).sort_values(["date"]) | ||
return df | ||
|
||
async def _filter_by_month(self, year_month=None): | ||
"""Month is defined along with the Invoicex generation""" | ||
df = await self._prepare_dataframe() | ||
if year_month is None: | ||
return df | ||
else: | ||
return df[df["date"].str.startswith(str(year_month))] | ||
|
||
def _group_tasks_remove_duplicates(self, v): | ||
tasks = v.to_string(index=False).split() | ||
unique_tasks = set(tasks) | ||
for t in unique_tasks: | ||
return ", ".join(str(t) for t in unique_tasks) | ||
|
||
def _group_time_and_sum(self, v): | ||
return v.sum() | ||
|
||
async def _generate_dataframe(self): | ||
"""Create the final DataFrame""" | ||
df = await self._filter_by_month(self.year_month) | ||
df_grouped = df.groupby("date").aggregate( | ||
lambda v: self._group_tasks_remove_duplicates(v) | ||
if v.name == "task" | ||
else self._group_time_and_sum(v) | ||
) | ||
return df_grouped | ||
|
||
|
||
async def get_data(args) -> pd.DataFrame: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you probably will not need this function because all the construction should be done by the ComposeReader |
||
""" """ | ||
database = TTRACK_DB | ||
ttrack_df = TTrack(database, args) | ||
return await ttrack_df._generate_dataframe() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you want a test data folder change it to:
it is the most common way to do that