Skip to content
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

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
*.env*
__pycache__
.vscode/
data_test/
Copy link
Contributor

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:

Suggested change
data_test/
tests/data

it is the most common way to do that

5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.ONESHELL:

.PHONY:ttrack-db
ttrack-db:
Copy link
Contributor

Choose a reason for hiding this comment

The 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
ttrack-db:
dev-dump-ttrack-db:

cp ~/.timetrackdb data/.timetrackdb
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,18 @@ Example:
```bash
python invoicex/main.py \
--year-month 2022-04 \
--gh-user xmnlab \
--gh-user $USER \
Copy link
Contributor

Choose a reason for hiding this comment

The 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.
$USER is a system user, not a gh user, so it will be more confusing

Copy link
Contributor

Choose a reason for hiding this comment

The 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
```
18 changes: 14 additions & 4 deletions invoicex/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
import os
import time

import reader
import invoicex.reader.github as github
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import invoicex.reader.github as github
from invoicex.reader import ComposeReader

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)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please check this comment

import report
import invoicex.reader.ttrack as ttrack
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import invoicex.reader.ttrack as ttrack

in main.py we don't need to access ttrack nor github .. just ComposeReader

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please check this comment



def cli_parser():
Expand Down Expand Up @@ -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
"""
Expand All @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The 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__":
Expand Down
Empty file added invoicex/reader/__init__.py
Empty file.
2 changes: 2 additions & 0 deletions invoicex/reader/compose.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class ComposeReader:
"""github + ttrack -> DF"""
File renamed without changes.
103 changes: 103 additions & 0 deletions invoicex/reader/ttrack.py
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:
Copy link
Contributor

Choose a reason for hiding this comment

The 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()