Skip to content

Commit 9257915

Browse files
authored
Admin queries (#308)
* Admin queries * Excel download for admin queries * PR comments * small change * revert user language chagne (handeled in different pr) * Submodule update
1 parent 155887b commit 9257915

File tree

6 files changed

+122
-5
lines changed

6 files changed

+122
-5
lines changed

controller/misc/manager.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
from typing import Any, Dict, List
22
from fast_api.types import ServiceVersionResult
3-
from submodules.model.global_objects import customer_button
3+
from submodules.model.global_objects import (
4+
customer_button,
5+
admin_queries as admin_queries_db_go,
6+
)
47
from datetime import datetime
58
import os
69
from controller.auth import kratos
710
from submodules.model.util import sql_alchemy_to_dict
811
from submodules.model import enums
912
import requests
1013
from urllib.parse import urlparse, urlencode, parse_qsl, parse_qs
14+
from util.tmp_export_file_cleanup import add_cleanup_task
15+
from uuid import uuid4
16+
import pandas as pd
17+
from submodules.model.util import ensure_sql_text
18+
from submodules.model.business_objects import general
1119

1220
import base64
1321

@@ -169,3 +177,16 @@ def __patch_url(url: str, **kwargs):
169177
._replace(query=urlencode(dict(parse_qsl(urlparse(url).query), **kwargs)))
170178
.geturl()
171179
)
180+
181+
182+
def create_admin_query_excel(
183+
query: enums.AdminQueries, parameters: Dict[str, Any]
184+
) -> str:
185+
186+
q = admin_queries_db_go.get_result_admin_query(query, parameters, as_query=True)
187+
188+
df = pd.read_sql(ensure_sql_text(q), con=general.get_bind())
189+
tmp_filename = f"tmp/feedback_{uuid4()}.xlsx"
190+
df.to_excel(tmp_filename, index=False)
191+
add_cleanup_task(tmp_filename, 5)
192+
return tmp_filename

fast_api/models.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,5 +513,9 @@ class CheckInviteUsersBody(BaseModel):
513513
emails: List[StrictStr]
514514

515515

516+
class AdminQueryFilterBody(BaseModel):
517+
parameters: Optional[Dict[str, Any]] = None
518+
519+
516520
class RecordDeletion(BaseModel):
517521
record_ids: List[str]

fast_api/routes/misc.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from exceptions.exceptions import AuthManagerError
22
from fastapi import APIRouter, Body, Request, status
3-
from fastapi.responses import PlainTextResponse
3+
from fastapi.responses import PlainTextResponse, FileResponse
44
from fast_api.models import (
55
CancelTaskBody,
66
CheckInviteUsersBody,
@@ -9,6 +9,7 @@
99
ModelProviderDownloadModelBody,
1010
CreateCustomerButton,
1111
UpdateCustomerButton,
12+
AdminQueryFilterBody,
1213
)
1314
from fast_api.routes.client_response import (
1415
pack_json_result,
@@ -22,12 +23,16 @@
2223
from controller.model_provider import manager as model_provider_manager
2324
from controller.task_master import manager as task_master_manager
2425
from submodules.model import enums
25-
from submodules.model.global_objects import customer_button as customer_button_db_go
26+
from submodules.model.global_objects import (
27+
customer_button as customer_button_db_go,
28+
admin_queries as admin_queries_db_go,
29+
)
2630
from submodules.model.util import sql_alchemy_to_dict
2731
from submodules.model.enums import (
2832
try_parse_enum_value,
2933
CustomerButtonType,
3034
CustomerButtonLocation,
35+
AdminQueries,
3136
)
3237
from submodules.model.business_objects import task_queue as task_queue_bo
3338

@@ -296,3 +301,33 @@ def check_valid_emails(request: Request, body: CheckInviteUsersBody = Body(...))
296301
raise AuthManagerError("Full admin access required")
297302
data = auth.check_valid_emails(body.emails)
298303
return pack_json_result(data)
304+
305+
306+
# post to allow for a body to be sent
307+
@router.post("/admin-query/{query}")
308+
def get_admin_queries(
309+
request: Request, query: AdminQueries, body: AdminQueryFilterBody = Body(...)
310+
):
311+
auth.check_admin_access(request.state.info)
312+
if not auth.check_is_full_admin(request):
313+
raise AuthManagerError("Full admin access required")
314+
data = admin_queries_db_go.get_result_admin_query(query, body.parameters)
315+
return pack_json_result(data)
316+
317+
318+
@router.post("/admin-query/{query}/download")
319+
def get_admin_query_excel(
320+
request: Request, query: AdminQueries, body: AdminQueryFilterBody = Body(...)
321+
):
322+
auth.check_admin_access(request.state.info)
323+
if not auth.check_is_full_admin(request):
324+
raise AuthManagerError("Full admin access required")
325+
326+
file_path = manager.create_admin_query_excel(query, body.parameters)
327+
328+
if file_path is None:
329+
return GENERIC_FAILURE_RESPONSE
330+
return FileResponse(
331+
file_path,
332+
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
333+
)

util/clean_up.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from submodules.model.business_objects import upload_task
2-
import os, shutil
2+
import os
3+
import shutil
34

45

56
def clean_up_database() -> None:

util/tmp_export_file_cleanup.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from datetime import datetime, timedelta
2+
from submodules.model.daemon import run_without_db_token
3+
import glob
4+
import os
5+
import traceback
6+
import time
7+
8+
__cleanup_tasks = None
9+
10+
11+
def add_cleanup_task(tmp_filename: str, minutes: int) -> None:
12+
global __cleanup_tasks
13+
if __cleanup_tasks is None:
14+
add_existing_feedback_and_consumption_files_to_cleanup(tmp_filename)
15+
run_without_db_token(start_cleanup)
16+
else:
17+
__cleanup_tasks[tmp_filename] = datetime.now() + timedelta(minutes=minutes)
18+
19+
20+
TMP_PATTERN = [
21+
"tmp/feedback_*.xlsx",
22+
"tmp/consumption_*.zip",
23+
"tmp/consumption_detailed_*.zip",
24+
"tmp/consumption_summary_*.xlsx",
25+
]
26+
27+
28+
def add_existing_feedback_and_consumption_files_to_cleanup(tmp_filename: str) -> None:
29+
# failsafe for shutdown before deletion time ran out
30+
# should only be called for feedback_cleanup_tasks = None
31+
global __cleanup_tasks
32+
if __cleanup_tasks is not None:
33+
raise ValueError("someone didn't read the comment")
34+
__cleanup_tasks = {}
35+
for pattern in TMP_PATTERN:
36+
for filename in glob.glob(pattern):
37+
minutes = 1 # after a restart they most likely are not valid anymore so we delete them sooner
38+
if filename == tmp_filename:
39+
minutes = 60
40+
__cleanup_tasks[filename] = datetime.now() + timedelta(minutes=minutes)
41+
42+
43+
def start_cleanup() -> None:
44+
global __cleanup_tasks
45+
while __cleanup_tasks is not None and len(__cleanup_tasks) > 0:
46+
try:
47+
for filename, delete_time in list(__cleanup_tasks.items()):
48+
if datetime.now() > delete_time:
49+
if os.path.isfile(filename):
50+
os.remove(filename)
51+
del __cleanup_tasks[filename]
52+
except Exception:
53+
print("Error in cleanup task", flush=True)
54+
print(traceback.format_exc(), flush=True)
55+
finally:
56+
time.sleep(60)

0 commit comments

Comments
 (0)