Skip to content

Commit

Permalink
transfer stats adapter to backend + test
Browse files Browse the repository at this point in the history
  • Loading branch information
githubering182 committed Jul 12, 2024
1 parent 85c6f2d commit 68cc7b0
Show file tree
Hide file tree
Showing 12 changed files with 136 additions and 343 deletions.
40 changes: 19 additions & 21 deletions backend-app/file/file_tests/services_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,47 +369,45 @@ def test_from_attribute(self):
res, code = StatsServices.from_attribute(self.case.project.id)

check_against = {
'attribute__attributegroup__file__file_type': self.case.file_.file_type,
'attribute__attributegroup__file__status': self.case.file_.status,
'attribute__id': self.case.attribute.id,
'attribute__name': self.case.attribute.name,
'attribute__parent': self.case.attribute.parent,
'count': self.case.file_.attributegroup_set.first().attribute.count(),
'name': self.case.level.name,
'order': self.case.level.order
"id": self.case.attribute.id,
"levelName": self.case.level.name,
"order": self.case.level.order,
"name": self.case.attribute.name,
"parent": self.case.attribute.parent,
self.case.file_.status or "v": {
self.case.file_.file_type or "no data":
self.case.file_.attributegroup_set.first().attribute.count()
},
}

self.assertTrue(empty_code == code == no_proj_code == 200)
self.assertTrue(empty_res == no_proj_res == [])

self.assertEqual(len(res), self.case.project.file_set.count())
self.assertEqual(
set(res[0].keys()),
set(StatsServices._ATTRIUBE_QUERY_VALUES).union(["count"])
)
self.assertEqual(res[0], check_against)

def test_from_user(self):
empty_res, empty_code = StatsServices.from_user(self.empty_project.id)
no_proj_res, no_proj_code = StatsServices.from_user(9999)
res, code = StatsServices.from_user(self.case.project.id)

empty_res = list(empty_res)
no_proj_res = list(no_proj_res)
res = list(res)

check_against = {
"author_id": self.case.user.id,
"author__username": self.case.user.username,
"status": self.case.file_.status,
"file_type": self.case.file_.file_type,
"count": self.case.project.file_set.count()
"id": self.case.user.id,
"name": self.case.user.username,
self.case.file_.status or "v": {
self.case.file_.file_type or "no data":
self.case.project.file_set.count()
},
}

self.assertTrue(empty_code == code == no_proj_code == 200)
self.assertTrue(empty_res == no_proj_res == [])

self.assertEqual(len(res), self.case.project.file_set.count())
self.assertEqual(
set(res[0].keys()),
set(StatsServices._USER_QUERY_VALUES).union(["count"])
)
self.assertEqual(res[0], check_against)


Expand Down
89 changes: 86 additions & 3 deletions backend-app/file/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,25 @@ class StatsServices:
"file_type"
)

user_item = lambda row: (
row["author_id"],
row["author__username"],
row.get("count") or 0,
row.get("status") or "v",
row.get("file_type") or "no data",
)

attribute_item = lambda row: (
row["attribute__id"],
row.get("attribute__attributegroup__file__status") or "v",
row.get("attribute__name") or "no name",
row.get("attribute__attributegroup__file__file_type") or "no data",
row.get("attribute__parent"),
row["name"],
row.get("order") or 0,
row.get("count") or 0,
)

@classmethod
def from_attribute(cls, project_id: int) -> tuple[list[dict[str, Any]], int]:
stats: list[dict[str, Any]] = list(
Expand Down Expand Up @@ -291,7 +310,7 @@ def from_attribute(cls, project_id: int) -> tuple[list[dict[str, Any]], int]:

if empty_stats: stats.extend(empty_stats)

return stats, HTTP_200_OK
return cls._attribute_stat_adapt(stats), HTTP_200_OK

@classmethod
def from_user(cls, project_id: int):
Expand All @@ -303,7 +322,71 @@ def from_user(cls, project_id: int):
.annotate(count=Count("file_type"))
)

return stats, HTTP_200_OK
return cls._user_stat_adapt(stats), HTTP_200_OK

@classmethod
def _attribute_stat_adapt(cls, stat_data: list[dict[str, Any]]) -> list[dict[str, Any]]:
prepared_stats = {}

for row in stat_data:
a_id, \
a_status, \
a_name, \
a_type, \
a_parent, \
l_name, \
order, \
count = cls.attribute_item(row)

target = prepared_stats.get(a_id)

if not target: prepared_stats[a_id] = {
"id": a_id,
"levelName": l_name,
"name": a_name,
"parent": a_parent,
"order": order,
a_status: {a_type: count}
}
elif target.get(a_status):
prev_count = target[a_status].get(a_type, 0)
target[a_status][a_type] = prev_count + count
else: target[a_status] = {a_type: count}

for row in prepared_stats.values():
try:
parent = next((
parent for parent in prepared_stats.values()
if parent["id"] == row["parent"]
))
parent["children"] = parent.get("children", []) + [row]
except Exception: continue

return sorted(
filter(lambda r: not r.get("parent"), prepared_stats.values()),
key=lambda r: r["order"]
)

@classmethod
def _user_stat_adapt(cls, stats_data: list[dict[str, Any]]) -> list[dict[str, Any]]:
prepared_stats = {}

for row in stats_data:
a_id, name, count, status, f_type = cls.user_item(row)

target = prepared_stats.get(a_id)

if not target: prepared_stats[a_id] = {
"id": a_id,
"name": name,
status: {f_type: count}
}
elif target.get(status):
prev_count = target[status].get(f_type) or 0
target[status][f_type] = prev_count + count
else: target[status] = {f_type: count}

return prepared_stats.values()


def _annotate_files(request_data: dict[str, Any]) -> tuple[dict[str, Any], int]:
Expand All @@ -330,7 +413,7 @@ def _annotate_files(request_data: dict[str, Any]) -> tuple[dict[str, Any], int]:
def form_export_file(query: dict[str, Any]) -> BytesIO:
query_set = {"type", "project_id", "choice"}
assert not (
no_ps := [p for p in query if not query.get(p)]
no_ps := [p for p in query_set if not query.get(p)]
), f"{', '.join(no_ps)} must be provided"

choice = query["choice"]
Expand Down
8 changes: 2 additions & 6 deletions backend-app/file/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,10 @@ def get_annotation(request: Request) -> Response:


@api_view(("GET",))
@permission_classes((IsAuthenticated,))
@permission_classes((IsAuthenticated, ProjectStatsPermission))
def export_stats(request: Request) -> Response | FileResponse:
try:
query = request.query_params
response = form_export_file(query)
return FileResponse(
response,
filename="export." + query["type"],
as_attachment=True
)
return FileResponse(response)
except Exception as err: return Response(str(err), HTTP_400_BAD_REQUEST)
16 changes: 0 additions & 16 deletions frontend-app/src/adapters/adapters.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,11 @@ import {
inputFilesAdapter,
attributeAdapter,
attributeGroupsAdapter,
userStatsAdapter,
attributeStatsAdapter
} from '.';
import {
raw_project,
prepared_attributes,
raw_file_attributes,
raw_user_stats,
prepared_user_stats,
raw_attribute_stats,
prepared_attribute_stats
} from '../config/mock_data';

test("input files adapter test", () => {
Expand Down Expand Up @@ -43,13 +37,3 @@ test("attribute groups adapter test", () => {
'5738e31f-c43a-4195-b1c2-ea513424a309': { 0: [249, 259, 260], 1: [271] }
});
});

test("user stats adapter test", () => {
var preparedStats = userStatsAdapter(raw_user_stats);
expect(preparedStats).toEqual(prepared_user_stats);
});

test("attribute stats adapter test", () => {
var preparedStats = attributeStatsAdapter(raw_attribute_stats);
expect(preparedStats).toEqual(prepared_attribute_stats);
});
113 changes: 6 additions & 107 deletions frontend-app/src/adapters/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { deepCopy, extractFileMeta, formUID } from "../utils";
* @param {object} groups
* @returns {object}
*/
export function inputFilesAdapter(files, groups) {
export const inputFilesAdapter = (files, groups) => {
return files.reduce((acc, file) => {
acc[formUID()] = {
file,
Expand All @@ -15,13 +15,13 @@ export function inputFilesAdapter(files, groups) {
};
return acc;
}, {});
}
};

/**
* @param {object} data
* @returns {object}
*/
export function attributeAdapter(data) {
export const attributeAdapter = (data) => {
var attributes = deepCopy(data.attributes);

attributes.forEach((el) => {
Expand All @@ -37,13 +37,13 @@ export function attributeAdapter(data) {
...data,
preparedAttributes: attributes.filter(({ parent }) => !parent)
};
}
};

/**
* @param {{uid: string, attributes: object[] }[]} data
* @returns {object}
*/
export function attributeGroupsAdapter(data) {
export const attributeGroupsAdapter = (data) => {
return data.reduce((acc, { uid, attributes }) => {
return {
...acc,
Expand All @@ -54,105 +54,4 @@ export function attributeGroupsAdapter(data) {
}, {})
};
}, {});
}

/**
* @param {{
* author_id: number,
* author__username: string,
* file_type: string,
* status: string,
* count: number
* }[]} data
* @returns {{id: number, name: string, type: object}[]}
*/
export function userStatsAdapter(data) {
var preparedData = data.reduce((acc, item) => {
var {
author_id: id,
author__username: name,
file_type: type,
status,
count
} = item;

status = status || 'v';
type = type || "no data";

var target = acc[id];

if (!target) acc[id] = { id, name, [status]: { [type]: count } };
else if (target[status]) {
var prevCount = target[status][type];
target[status][type] = prevCount ? prevCount + count : count;
}
else target[status] = { [type]: count };

return acc;
}, {});

return Object.values(preparedData);
}

/**
* @param {{
* name: string,
* order: number,
* attribute__id: number,
* attribute__name: string,
* attribute__parent: number,
* attribute__attributegroup__file__file_type: string
* attribute__attributegroup__file__status: string
* count: number
* }[]} data
* @returns {{
* id: number,
* levelName: string,
* name: string,
* parent: number,
* order: number,
* status: object
* }[]}
*/
export function attributeStatsAdapter(data) {
const preparedData = data.reduce((acc, item) => {
var {
name: levelName,
order,
attribute__id: id,
attribute__name: name,
attribute__parent: parent,
attribute__attributegroup__file__file_type: type,
attribute__attributegroup__file__status: status,
count
} = item;

status = status || 'v';
name = name || 'no name';
type = type || 'no data';

var target = acc[id];

if (!target) {
acc[id] = { id, levelName, name, parent, order, [status]: { [type]: count } };
}
else if (target[status]) {
var prevCount = target[status][type];
target[status][type] = prevCount ? prevCount + count : count;
}
else target[status] = { [type]: count };

return acc;
}, {});

Object.values(preparedData).forEach((el) => {
var parent = Object.values(preparedData).find(({ id }) => el.parent === id);
if (parent) parent.children
? parent.children.push(el)
: parent.children = [el];
});

return Object.values(preparedData)
.filter(({ parent }) => !parent)
.sort(({ order: orderA }, { order: orderB }) => orderA > orderB ? 1 : -1);
}
};
12 changes: 6 additions & 6 deletions frontend-app/src/components/FilesUpload/index.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useBlocker } from "react-router-dom";
import { useState, useEffect, ReactElement } from 'react';
import { useFileInput } from '../../hooks';
import SelectorGroup from '../forms/SelectorGroup';
import FileInput from '../forms/FileInput';
import UploadView from '../common/UploadView';
import './styles.css';
import { useState, useEffect, ReactElement } from "react";
import { useFileInput } from "../../hooks";
import SelectorGroup from "../forms/SelectorGroup";
import FileInput from "../forms/FileInput";
import UploadView from "../common/UploadView";
import "./styles.css";

/**
* @param {object} props
Expand Down
Loading

0 comments on commit 68cc7b0

Please sign in to comment.