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

View File

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

View File

@ -1,5 +1,6 @@
from datetime import datetime
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
@ -49,6 +50,7 @@ class KimaiCustomer(Model):
class KimaiProject(Model):
name = CharField()
customer = ForeignKeyField(KimaiCustomer, backref="projects")
allow_global_activities = BooleanField(default=True)
class Meta:
database = db
@ -64,7 +66,7 @@ class KimaiActivity(Model):
table_name = "kimai_activities"
class HamsterKimaiMapping(Model):
class HamsterActivityKimaiMapping(Model):
hamster_activity = ForeignKeyField(HamsterActivity, backref="mappings")
kimai_customer = ForeignKeyField(KimaiCustomer, backref="mappings")
kimai_project = ForeignKeyField(KimaiProject, backref="mappings")
@ -75,3 +77,13 @@ class HamsterKimaiMapping(Model):
class Meta:
database = db
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_cache
import os
from dataclasses import dataclass, field
class NotFound(Exception):
pass
@ -9,7 +12,8 @@ class NotFound(Exception):
class KimaiAPI(object):
# 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_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}
def __init__(self):
# requests_cache.install_cache("kimai", backend="sqlite", expire_after=1800)
self.customers_json = requests.get(
f"{self.KIMAI_API_URL}/customers?visible=3", headers=self.auth_headers
).json()
self.projects_json = requests.get(
f"{self.KIMAI_API_URL}/projects?visible=3", headers=self.auth_headers
).json()
self.activities_json = requests.get(
f"{self.KIMAI_API_URL}/activities?visible=3", headers=self.auth_headers
).json()
requests_cache.install_cache("kimai", backend="sqlite", expire_after=1800)
self.customers_json = self.get("customers", {"visible": 3})
self.projects_json = self.get("projects", {"visible": 3})
self.activities_json = self.get("activities", {"visible": 3})
self.user_json = self.get("users/me")
def get(self, endpoint, params=None):
return requests.get(f"{self.KIMAI_API_URL}/{endpoint}",
params=params, headers=self.auth_headers).json()
def post(self, endpoint, data):
return requests.post(f"{self.KIMAI_API_URL}/{endpoint}",
json=data, headers=self.auth_headers)
class BaseAPI(object):
@ -54,14 +61,20 @@ class Customer(BaseAPI):
return f"Customer (id={self.id}, name={self.name})"
@dataclass
class Project(BaseAPI):
def __init__(self, api, id, name, customer):
super().__init__(api, id=id, name=name, customer=customer)
api: KimaiAPI = field(repr=False)
id: int
name: str
customer: Customer
allow_global_activities: bool = field(default=True)
@staticmethod
def list(api):
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
]
@ -74,17 +87,18 @@ class Project(BaseAPI):
value["id"],
value["name"],
Customer.get_by_id(api, value["customer"]),
value["globalActivities"]
)
if not none:
raise NotFound()
def __repr__(self):
return f"Project (id={self.id}, name={self.name}, customer={self.customer})"
@dataclass
class Activity(BaseAPI):
def __init__(self, api, id, name, project):
super().__init__(api, id=id, name=name, project=project)
api: KimaiAPI = field(repr=False)
id: int
name: str
project: Project
@staticmethod
def list(api):
@ -108,5 +122,60 @@ class Activity(BaseAPI):
if not none:
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,
})