Fuckin yaldi, working kimai import 💅

This commit is contained in:
3wc 2023-11-01 19:28:55 +00:00
parent e8ff5f1411
commit ebf2dca695
4 changed files with 118 additions and 34 deletions

View File

@ -17,7 +17,8 @@ from .db import (
KimaiCustomer, KimaiCustomer,
KimaiProject, KimaiProject,
KimaiActivity, KimaiActivity,
HamsterKimaiMapping, HamsterActivityKimaiMapping,
HamsterFactKimaiImport
) )
HAMSTER_DIR = Path.home() / ".local/share/hamster" HAMSTER_DIR = Path.home() / ".local/share/hamster"
@ -630,12 +631,13 @@ def db_():
@db_.command() @db_.command()
def init(): def init():
db.create_tables([KimaiCustomer, KimaiProject, KimaiActivity, HamsterKimaiMapping]) db.create_tables([KimaiCustomer, KimaiProject, KimaiActivity,
HamsterActivityKimaiMapping, HamsterFactKimaiImport])
@db_.command() @db_.command()
def reset(): def reset():
HamsterKimaiMapping.delete().execute() HamsterActivityKimaiMapping.delete().execute()
@db_.command() @db_.command()
@ -653,7 +655,6 @@ def mapping2db(mapping_path=None, global_=False):
mapping_reader = csv.reader(mapping_file) mapping_reader = csv.reader(mapping_file)
for row in mapping_reader: for row in mapping_reader:
hamster_category = HamsterCategory.get(name=row[0]) hamster_category = HamsterCategory.get(name=row[0])
hamster_activity = HamsterActivity.get( hamster_activity = HamsterActivity.get(
name=row[1], category_id=hamster_category.id name=row[1], category_id=hamster_category.id
@ -666,9 +667,10 @@ def mapping2db(mapping_path=None, global_=False):
except KimaiActivity.DoesNotExist: except KimaiActivity.DoesNotExist:
kimai_activity = KimaiActivity.get( kimai_activity = KimaiActivity.get(
name=row[4], name=row[4],
project_id=None
) )
HamsterKimaiMapping.create( HamsterActivityKimaiMapping.create(
hamster_activity=hamster_activity, hamster_activity=hamster_activity,
kimai_customer=kimai_customer, kimai_customer=kimai_customer,
kimai_project=kimai_project, kimai_project=kimai_project,

View File

@ -22,7 +22,7 @@ from .db import (
KimaiProject, KimaiProject,
KimaiCustomer, KimaiCustomer,
KimaiActivity, KimaiActivity,
HamsterKimaiMapping, HamsterActivityKimaiMapping,
) )
from .kimai import ( from .kimai import (
KimaiAPI, KimaiAPI,
@ -303,11 +303,11 @@ class ActivityListScreen(ListScreen):
) )
mappings_count_query = ( mappings_count_query = (
HamsterKimaiMapping.select( HamsterActivityKimaiMapping.select(
HamsterKimaiMapping.hamster_activity_id, HamsterActivityKimaiMapping.hamster_activity_id,
fn.COUNT(HamsterKimaiMapping.id).alias("mappings_count"), fn.COUNT(HamsterActivityKimaiMapping.id).alias("mappings_count"),
) )
.group_by(HamsterKimaiMapping.hamster_activity_id) .group_by(HamsterActivityKimaiMapping.hamster_activity_id)
.alias("mappings_count_query") .alias("mappings_count_query")
) )
@ -460,7 +460,7 @@ class ActivityListScreen(ListScreen):
def handle_mapping(mapping): def handle_mapping(mapping):
if mapping is None: if mapping is None:
return return
m = HamsterKimaiMapping.create( m = HamsterActivityKimaiMapping.create(
hamster_activity=selected_activity, **mapping hamster_activity=selected_activity, **mapping
) )
m.save() m.save()
@ -613,6 +613,7 @@ class KimaiProjectListScreen(ListScreen):
"id": project.id, "id": project.id,
"name": project.name, "name": project.name,
"customer_id": project.customer.id, "customer_id": project.customer.id,
"allow_global_activities": project.allow_global_activities
} }
for project in projects for project in projects
] ]

View File

@ -1,5 +1,6 @@
from datetime import datetime
import logging import logging
from peewee import SqliteDatabase, Model, CharField, ForeignKeyField, DateTimeField from peewee import SqliteDatabase, Model, CharField, ForeignKeyField, DateTimeField, SmallIntegerField, BooleanField
from textual.logging import TextualHandler from textual.logging import TextualHandler
@ -49,6 +50,7 @@ class KimaiCustomer(Model):
class KimaiProject(Model): class KimaiProject(Model):
name = CharField() name = CharField()
customer = ForeignKeyField(KimaiCustomer, backref="projects") customer = ForeignKeyField(KimaiCustomer, backref="projects")
allow_global_activities = BooleanField(default=True)
class Meta: class Meta:
database = db database = db
@ -64,7 +66,7 @@ class KimaiActivity(Model):
table_name = "kimai_activities" table_name = "kimai_activities"
class HamsterKimaiMapping(Model): class HamsterActivityKimaiMapping(Model):
hamster_activity = ForeignKeyField(HamsterActivity, backref="mappings") hamster_activity = ForeignKeyField(HamsterActivity, backref="mappings")
kimai_customer = ForeignKeyField(KimaiCustomer, backref="mappings") kimai_customer = ForeignKeyField(KimaiCustomer, backref="mappings")
kimai_project = ForeignKeyField(KimaiProject, backref="mappings") kimai_project = ForeignKeyField(KimaiProject, backref="mappings")
@ -75,3 +77,13 @@ class HamsterKimaiMapping(Model):
class Meta: class Meta:
database = db database = db
table_name = "hamster_kimai_mappings" table_name = "hamster_kimai_mappings"
class HamsterFactKimaiImport(Model):
hamster_fact = ForeignKeyField(HamsterFact, backref="mappings")
kimai_id = SmallIntegerField()
imported = DateTimeField(default=datetime.now)
class Meta:
database = db
table_name = "hamster_fact_kimai_imports"

View File

@ -1,7 +1,10 @@
from datetime import datetime
import requests import requests
import requests_cache import requests_cache
import os import os
from dataclasses import dataclass, field
class NotFound(Exception): class NotFound(Exception):
pass pass
@ -9,7 +12,8 @@ class NotFound(Exception):
class KimaiAPI(object): class KimaiAPI(object):
# temporary hardcoded config # temporary hardcoded config
KIMAI_API_URL = "https://kimai.autonomic.zone/api" # KIMAI_API_URL = "https://kimai.autonomic.zone/api"
KIMAI_API_URL = "https://kimaitest.autonomic.zone/api"
KIMAI_USERNAME = "3wordchant" KIMAI_USERNAME = "3wordchant"
KIMAI_API_KEY = os.environ["KIMAI_API_KEY"] KIMAI_API_KEY = os.environ["KIMAI_API_KEY"]
@ -17,16 +21,19 @@ class KimaiAPI(object):
auth_headers = {"X-AUTH-USER": KIMAI_USERNAME, "X-AUTH-TOKEN": KIMAI_API_KEY} auth_headers = {"X-AUTH-USER": KIMAI_USERNAME, "X-AUTH-TOKEN": KIMAI_API_KEY}
def __init__(self): def __init__(self):
# requests_cache.install_cache("kimai", backend="sqlite", expire_after=1800) requests_cache.install_cache("kimai", backend="sqlite", expire_after=1800)
self.customers_json = requests.get( self.customers_json = self.get("customers", {"visible": 3})
f"{self.KIMAI_API_URL}/customers?visible=3", headers=self.auth_headers self.projects_json = self.get("projects", {"visible": 3})
).json() self.activities_json = self.get("activities", {"visible": 3})
self.projects_json = requests.get( self.user_json = self.get("users/me")
f"{self.KIMAI_API_URL}/projects?visible=3", headers=self.auth_headers
).json() def get(self, endpoint, params=None):
self.activities_json = requests.get( return requests.get(f"{self.KIMAI_API_URL}/{endpoint}",
f"{self.KIMAI_API_URL}/activities?visible=3", headers=self.auth_headers params=params, headers=self.auth_headers).json()
).json()
def post(self, endpoint, data):
return requests.post(f"{self.KIMAI_API_URL}/{endpoint}",
json=data, headers=self.auth_headers)
class BaseAPI(object): class BaseAPI(object):
@ -54,14 +61,20 @@ class Customer(BaseAPI):
return f"Customer (id={self.id}, name={self.name})" return f"Customer (id={self.id}, name={self.name})"
@dataclass
class Project(BaseAPI): class Project(BaseAPI):
def __init__(self, api, id, name, customer): api: KimaiAPI = field(repr=False)
super().__init__(api, id=id, name=name, customer=customer) id: int
name: str
customer: Customer
allow_global_activities: bool = field(default=True)
@staticmethod @staticmethod
def list(api): def list(api):
return [ return [
Project(api, p["id"], p["name"], Customer.get_by_id(api, p["customer"])) Project(api, p["id"], p["name"], Customer.get_by_id(api,
p["customer"]),
p["globalActivities"])
for p in api.projects_json for p in api.projects_json
] ]
@ -74,17 +87,18 @@ class Project(BaseAPI):
value["id"], value["id"],
value["name"], value["name"],
Customer.get_by_id(api, value["customer"]), Customer.get_by_id(api, value["customer"]),
value["globalActivities"]
) )
if not none: if not none:
raise NotFound() raise NotFound()
def __repr__(self):
return f"Project (id={self.id}, name={self.name}, customer={self.customer})"
@dataclass
class Activity(BaseAPI): class Activity(BaseAPI):
def __init__(self, api, id, name, project): api: KimaiAPI = field(repr=False)
super().__init__(api, id=id, name=name, project=project) id: int
name: str
project: Project
@staticmethod @staticmethod
def list(api): def list(api):
@ -108,5 +122,60 @@ class Activity(BaseAPI):
if not none: if not none:
raise NotFound() raise NotFound()
def __repr__(self):
return f"Activity (id={self.id}, name={self.name}, project={self.project})" @dataclass
class Timesheet(BaseAPI):
api: KimaiAPI = field(repr=False)
activity: Activity
project: Project
begin: datetime
end: datetime
id: int = field(default=None)
description: str = field(default="")
tags: str = field(default="")
@staticmethod
def list(api):
return [
Timesheet(
api,
Activity.get_by_id(api, t["activity"], none=True),
Project.get_by_id(api, t["project"], none=True),
t["begin"],
t["end"],
t["id"],
t["description"],
t["tags"],
)
for t in api.get(
'timesheets',
)
]
@staticmethod
def get_by_id(api, id, none=False):
t = api.get(
f'timesheets/{id}',
)
return Timesheet(
api,
Activity.get_by_id(api, t["activity"], none=True),
Project.get_by_id(api, t["project"], none=True),
t["begin"],
t["end"],
t["id"],
t["description"],
t["tags"],
)
def upload(self):
return self.api.post('timesheets', {
"begin": self.begin.isoformat(),
"end": self.end.isoformat(),
"project": self.project.id,
"activity": self.activity.id,
"description": self.description,
# FIXME: support multiple users
# "user": self.,
"tags": self.tags,
})