Skip to content

Commit

Permalink
adapter for export files
Browse files Browse the repository at this point in the history
  • Loading branch information
githubering182 committed Jul 15, 2024
1 parent bb7eaea commit 9acec66
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 14 deletions.
98 changes: 98 additions & 0 deletions backend-app/file/export.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from abc import ABC, abstractmethod, abstractproperty
from typing import Any, List, Dict
from io import BytesIO
from json import dumps

ROW = Dict[str, Any]
DATA = List[ROW]
ENCODING = "utf-8"
IMPLEMENTED = {"json", "csv"}


class Export(ABC):
@abstractmethod
def __init__(self, data: DATA, *args): ...

@abstractmethod
def into_response(self) -> BytesIO: ...

@property
def _data(self) -> DATA: return self.__data

@_data.setter
def _data(self, data: DATA): self.__data = data


class JSON(Export):
def __init__(self, data: DATA, *args): self._data = data

def into_response(self) -> BytesIO:
file = BytesIO()

prepared_data = bytes(dumps(self._data), encoding=ENCODING)

file.write(prepared_data)
file.seek(0)

return file


class CSV(Export):
ATTRIBUTE_HEADERS = "Attribute,Level,On Validation,Accepted,Declined,Total\n"
USER_HEADERS = "User,On Validation,Accepted,Declined,Total\n"

t_val = lambda _, x: (x.get('image', 0), x.get('video', 0))
t_str = lambda _, x: f"images: {x[0]} videos: {x[1]}"
sm = lambda _, x, y, z: sum(x + y + z)

def __init__(self, data: DATA, *args):
self._data = data
self._type = args[0]

def _write_attribute(self, dest: BytesIO, data: ROW):
name = data.get("name")
level_name = data.get("levelName")

val = self.t_val(data.get("v", {}))
acc = self.t_val(data.get("a", {}))
dec = self.t_val(data.get("d", {}))
total = self.sm(val, acc, dec)

dest.write(bytes(
f"{name},{level_name},{self.t_str(val)},{self.t_str(acc)},{self.t_str(dec)},{total}\n",
encoding=ENCODING
))

for child in (children := data.get("children", [])): self._write_attribute(dest, child)

def _write_user(self, dest: BytesIO, data: ROW):
name = data.get("name")

val = self.t_val(data.get("v", {}))
acc = self.t_val(data.get("a", {}))
dec = self.t_val(data.get("d", {}))
total = self.sm(val, acc, dec)

dest.write(bytes(
f"{name},{self.t_str(val)},{self.t_str(acc)},{self.t_str(dec)},{total}\n",
encoding=ENCODING
))

def into_response(self) -> BytesIO:
file = BytesIO()

match self._type:
case "attribute":
headers = self.ATTRIBUTE_HEADERS
write = self._write_attribute
case "user":
headers = self.USER_HEADERS
write = self._write_user
case _: raise AttributeError

file.write(bytes(headers, encoding=ENCODING))
for row in self._data: write(file, row)

file.seek(0)

return file
15 changes: 6 additions & 9 deletions backend-app/file/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from datetime import datetime as dt
from io import BytesIO
from json import dumps
from .export import IMPLEMENTED, JSON, CSV
from .serializers import File, FileSerializer


Expand Down Expand Up @@ -420,20 +421,16 @@ def form_export_file(query: dict[str, Any]) -> BytesIO:
project_id = query["project_id"]
file_type = query["type"]

assert file_type == "json", f"{file_type} not implemented"
assert file_type in IMPLEMENTED, f"{file_type} not implemented"

choice_map = {
"attribute": StatsServices.from_attribute,
"user": StatsServices.from_user
}

attributes = choice_map[choice](project_id)
export_map = {"json": JSON, "csv": CSV}

file = BytesIO()
stats, _ = choice_map[choice](project_id)
file = export_map[file_type](stats, choice)

prepared_data = bytes(dumps(attributes), encoding="utf-8")

file.write(prepared_data)
file.seek(0)

return file
return file.into_response()
3 changes: 1 addition & 2 deletions backend-app/file/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ def get_annotation(request: Request) -> Response:
@permission_classes((IsAuthenticated, ProjectStatsPermission))
def export_stats(request: Request) -> Response | FileResponse:
try:
query = request.query_params
response = form_export_file(query)
response = form_export_file(request.query_params)
return FileResponse(response)
except Exception as err: return Response(str(err), HTTP_400_BAD_REQUEST)
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ test("files stats component test", async () => {
expect(screen.getAllByRole("radio")[1].checked).toBeFalsy();
expect(screen.getAllByRole("radio")[0].value).toBe("attribute");
expect(screen.getAllByRole("radio")[1].value).toBe("user");
expect(screen.queryAllByRole("button")).toHaveLength(3);
screen.getByText("Attribute");

await act(async () => await fireEvent.click(screen.getAllByRole("radio")[1]));
Expand Down
6 changes: 3 additions & 3 deletions frontend-app/src/components/common/FileStats/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import "./styles.css";
const STAT_TYPES = ["attribute", "user"];

/** @type {string[]} */
const EXPORT_VARIANTS = ["cvs", "json", "xls"];
const EXPORT_VARIANTS = ["csv", "json", "xls"];

/**
* @param {{ image?: number, video?: number }} [a]
Expand Down Expand Up @@ -134,14 +134,14 @@ export default function FileStats({ pathID }) {
}
</fieldset>
<fieldset className="iss__stats__radio">
Export stats as:
Export as:
{
EXPORT_VARIANTS.map((type) => (
<button
type="button"
key={type}
className="iss__stats__exportButton"
style={type !== "json" ? {opacity: 0.3, pointerEvents: "none"} : undefined}
style={type === "xls" ? {opacity: 0.3, pointerEvents: "none"} : undefined}
onClick={() => exportStats(type)}
>{type}</button>
))
Expand Down

0 comments on commit 9acec66

Please sign in to comment.