This commit is contained in:
wuaho 2021-07-23 13:37:50 +08:00
parent 2950cf2648
commit c62ae3af9c
10 changed files with 158 additions and 65 deletions

View File

@ -1,5 +1,5 @@
import pymongo import pymongo
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends, Request
from motor.motor_asyncio import AsyncIOMotorDatabase from motor.motor_asyncio import AsyncIOMotorDatabase
import crud, schemas import crud, schemas
@ -56,33 +56,38 @@ async def move(
@router.post("/add_report") @router.post("/add_report")
async def add_report(data_in: schemas.AddReport, async def add_report(data_in: schemas.AddReport,
game:str, game: str,
db: AsyncIOMotorDatabase = Depends(get_database), db: AsyncIOMotorDatabase = Depends(get_database),
current_user: schemas.UserDB = Depends(deps.get_current_user) current_user: schemas.UserDB = Depends(deps.get_current_user)
): ):
"""添加报表""" """添加报表"""
reports = [item.dict() for item in data_in.report_ids]
res = await crud.dashboard.update_one(db, {'_id': data_in.id}, res = await crud.dashboard.update_one(db, {'_id': data_in.id},
{'$push': {'reports': {'$each': data_in.report_ids}}}) {'$push': {'reports': {'$each': reports}}})
return schemas.Msg(code=0, msg='ok', data='ok') return schemas.Msg(code=0, msg='ok', data='ok')
@router.post("/del_report") @router.post("/del_report")
async def del_report(data_in: schemas.DelReport, async def del_report(
db: AsyncIOMotorDatabase = Depends(get_database), game: str,
current_user: schemas.UserDB = Depends(deps.get_current_user) data_in: schemas.DelReport,
): db: AsyncIOMotorDatabase = Depends(get_database),
current_user: schemas.UserDB = Depends(deps.get_current_user)
):
"""删除报表""" """删除报表"""
for item in data_in.report_ids: del_item = {'report_id': data_in.report_id}
await crud.dashboard.update_one(db, {'_id': data_in.id}, {'$pull': {'reports': item}}) await crud.dashboard.update_one(db, {'_id': data_in.id}, {'$pull': {'reports': del_item}})
return schemas.Msg(code=0, msg='ok', data='ok') return schemas.Msg(code=0, msg='ok', data='ok')
@router.get("/") @router.post("/")
async def dashboards(_id: str, async def dashboards(request: Request,
game: str,
data_in: schemas.ReadDashboard,
db: AsyncIOMotorDatabase = Depends(get_database), db: AsyncIOMotorDatabase = Depends(get_database),
current_user: schemas.UserDB = Depends(deps.get_current_user) current_user: schemas.UserDB = Depends(deps.get_current_user)
): ):
"""获取一个看板""" """获取一个看板"""
res = await crud.dashboard.get(db, id=_id) res = await crud.dashboard.get(db, id=data_in.id)
res['reports'] = await crud.report.find_many(db, **{'$in': {'_id': res.get('reports')}}) # res['reports'] = await crud.report.find_many(db, **{'_id': {'$in': [item['report_id'] for item in res.get('reports')]}})
return schemas.Msg(code=0, msg='ok', data=res['reports']) return schemas.Msg(code=0, msg='ok', data=res['reports'])

View File

@ -223,7 +223,7 @@ async def read_kanban(
for d in dashboards: for d in dashboards:
res['kanban'][-1]['children'].append({ res['kanban'][-1]['children'].append({
'name': d['name'], 'name': d['name'],
'_id': item['_id'] '_id': d['_id']
}) })
# 我的空间 # 我的空间

View File

@ -11,6 +11,8 @@ from api import deps
router = APIRouter() router = APIRouter()
@router.post("/create") @router.post("/create")
async def create( async def create(
request: Request, request: Request,
@ -37,9 +39,24 @@ async def read_report(
current_user: schemas.UserDB = Depends(deps.get_current_user) current_user: schemas.UserDB = Depends(deps.get_current_user)
) -> Any: ) -> Any:
"""获取已建报表""" """获取已建报表"""
ext_where = dict()
dashboard = dict()
if data_in.report_id:
ext_where = {'_id': {'$in': data_in.report_id}}
if data_in.dashboard_id:
dashboard = await crud.dashboard.get(db, id=data_in.dashboard_id)
# projection = {'query': False}
projection = None
reports = await crud.report.read_report(db, user_id=request.user.id, project_id=data_in.project_id,
projection=projection, **ext_where)
data = await crud.report.read_report(db, user_id=request.user.id, project_id=data_in.project_id) for item in reports:
return schemas.Msg(code=0, msg='ok', data=data) item['added'] = False
added_ids = [item['report_id'] for item in dashboard.get('reports', [])]
if item['_id'] in added_ids:
item['added'] = True
return schemas.Msg(code=0, msg='ok', data=reports)
@router.post("/delete") @router.post("/delete")

View File

@ -76,7 +76,7 @@ class Settings(BaseSettings):
'median': lambda x: func.median(x), 'median': lambda x: func.median(x),
'max': lambda x: func.max(x), 'max': lambda x: func.max(x),
'min': lambda x: func.min(x), 'min': lambda x: func.min(x),
'distinct_count': lambda x: func.count(func.distinct(x)), 'distinct_count': lambda x: func.uniqCombined(x),
} }
CK_OPERATOR = { CK_OPERATOR = {
@ -244,6 +244,12 @@ class Settings(BaseSettings):
}, },
], ],
} }
ARITHMETIC = {
'+': lambda x, y: x + y,
'-': lambda x, y: x - y,
'x': lambda x, y: x * y,
'/': lambda x, y: x / y,
}
PROPHET_TIME_GRAIN_MAP = { PROPHET_TIME_GRAIN_MAP = {
"PT1S": "S", "PT1S": "S",

View File

@ -10,8 +10,8 @@ class CRUDBase:
async def get(self, db, id: Union[ObjectId, str]) -> dict: async def get(self, db, id: Union[ObjectId, str]) -> dict:
return (await db[self.coll_name].find_one({'_id': id})) or dict() return (await db[self.coll_name].find_one({'_id': id})) or dict()
async def find_one(self, db, filter=None, *args, **kwargs): async def find_one(self, db, projection=None, *args, **kwargs):
return (await db[self.coll_name].find_one(filter, *args, **kwargs)) or dict() return (await db[self.coll_name].find_one(projection, *args, **kwargs)) or dict()
async def read_have(self, db, v: str, **kwargs): async def read_have(self, db, v: str, **kwargs):
where = {'members': v} where = {'members': v}
@ -19,8 +19,8 @@ class CRUDBase:
cursor = db[self.coll_name].find(where) cursor = db[self.coll_name].find(where)
return await cursor.to_list(length=9999) return await cursor.to_list(length=9999)
async def find_many(self, db, **kwargs): async def find_many(self, db, projection=None, **kwargs):
cursor = db[self.coll_name].find(kwargs) cursor = db[self.coll_name].find(kwargs, projection)
return await cursor.to_list(length=9999) return await cursor.to_list(length=9999)
def find(self, db, *args, **kwargs): def find(self, db, *args, **kwargs):

View File

@ -21,8 +21,8 @@ class CRUDReport(CRUDBase):
[('project_id', pymongo.DESCENDING), ('name', pymongo.DESCENDING), ('user_id', pymongo.DESCENDING)], [('project_id', pymongo.DESCENDING), ('name', pymongo.DESCENDING), ('user_id', pymongo.DESCENDING)],
unique=True) unique=True)
async def read_report(self, db, user_id, project_id): async def read_report(self, db, user_id, project_id, projection=None, **kwargs):
res = await self.find_many(db, user_id=user_id, project_id=project_id) res = await self.find_many(db, user_id=user_id, project_id=project_id,projection=projection, **kwargs)
return res return res

View File

@ -9,8 +9,11 @@ import pandas as pd
from sqlalchemy import func, or_, and_, not_ from sqlalchemy import func, or_, and_, not_
import crud
import schemas import schemas
from core.config import settings from core.config import settings
from db import get_database
from db.redisdb import get_redis_pool, RedisDrive from db.redisdb import get_redis_pool, RedisDrive
@ -20,8 +23,9 @@ class BehaviorAnalysis:
self.rdb = rdb self.rdb = rdb
self.user_tbl = None self.user_tbl = None
self.event_tbl = None self.event_tbl = None
self.event_view = data_in.eventView self.data_in = data_in
self.events = data_in.events self.event_view = dict()
self.events = [dict()]
self.zone_time: int = 0 self.zone_time: int = 0
self.start_date = None self.start_date = None
@ -33,6 +37,15 @@ class BehaviorAnalysis:
self.unit_num = None self.unit_num = None
async def init(self): async def init(self):
if self.data_in.report_id:
db = get_database()
report = await crud.report.get(db, id=self.data_in.report_id)
self.event_view = report['query']['eventView']
self.events = report['query']['events']
else:
self.event_view = self.data_in.eventView
self.events = self.data_in.events
await self._init_table() await self._init_table()
self.zone_time = self._get_zone_time() self.zone_time = self._get_zone_time()
self.time_particle = self._get_time_particle_size() self.time_particle = self._get_time_particle_size()
@ -164,53 +177,92 @@ class BehaviorAnalysis:
'unit_num': self.unit_num 'unit_num': self.unit_num
} }
def custom_event(self, s):
def f(m):
if len(m) == 3:
event_name, attr, comp = m
return getattr(func, comp)(getattr(func, 'if')(getattr(self.event_tbl.c, '#event_name') == event_name,
getattr(self.event_tbl.c, attr), 0))
elif len(m) == 2:
event_name, comp = m
# 总次数
if comp == 'total_count':
return func.sum(getattr(func, 'if')(getattr(self.event_tbl.c, '#event_name') == event_name, 1, 0))
elif comp == 'touch_user_count':
return func.uniqCombined(getattr(func, 'if')(getattr(self.event_tbl.c, '#event_name') == event_name,
getattr(self.event_tbl.c, 'binduid'), 0))
elif comp == 'touch_user_avg':
return func.divide(
func.sum(getattr(func, 'if')(getattr(self.event_tbl.c, '#event_name') == event_name, 1, 0)),
func.uniqCombined(getattr(func, 'if')(getattr(self.event_tbl.c, '#event_name') == event_name,
getattr(self.event_tbl.c, 'binduid'), 0)))
opt = ({'+', '-', '*', '/'} & set(s)).pop()
a, b = s.split(opt)
r1 = a.split('.')
r2 = b.split('.')
return {'event_name': [r1[0], r2[0]],
'select': (settings.ARITHMETIC[opt](f(r1), f(r2))).label('values')
}
def event_model_sql(self): def event_model_sql(self):
sqls = [] sqls = []
event_time_col = getattr(self.event_tbl.c, '#event_time') event_time_col = getattr(self.event_tbl.c, '#event_time')
select_exprs = [
settings.TIME_GRAIN_EXPRESSIONS[self.time_particle](event_time_col, self.zone_time)]
select_exprs += self.groupby
for event in self.events: for event in self.events:
event_name = event['event_name'] select_exprs = [
event_name_col = getattr(self.event_tbl.c, '#event_name') settings.TIME_GRAIN_EXPRESSIONS[self.time_particle](event_time_col, self.zone_time)]
base_where = [ base_where = [
func.addHours(event_time_col, self.zone_time) >= self.start_date, func.addHours(event_time_col, self.zone_time) >= self.start_date,
func.addHours(event_time_col, self.zone_time) <= self.end_date, func.addHours(event_time_col, self.zone_time) <= self.end_date,
event_name_col == event_name
] ]
analysis = event['analysis'] event_name_col = getattr(self.event_tbl.c, '#event_name')
event_filter, user_filter = self.handler_filts(*event['filts']) if event.get('customEvent'):
formula = event.get('customEvent')
u_account_id_col = getattr(self.user_tbl.c, '#account_id') custom = self.custom_event(formula)
# 按账号聚合 event_name = custom['event_name']
e_account_id_col = getattr(self.event_tbl.c, '#account_id') where = [event_name_col.in_(event_name)]
event_filter, _ = self.handler_filts(*event['filts'])
# 聚合方式 qry = sa.select(
if analysis == 'total_count': *select_exprs,
selectd = select_exprs + [func.count().label('values')] custom['select']
elif analysis == 'touch_user_count': ).where(*base_where, *where, *event_filter)
selectd = select_exprs + [func.count(sa.distinct(e_account_id_col)).label('values')]
elif analysis == 'touch_user_avg':
selectd = select_exprs + [
func.round((func.count() / func.count(sa.distinct(e_account_id_col))), 2).label(
'values')]
elif analysis == 'distinct_count':
selectd = select_exprs + [
func.count(sa.distinct(getattr(self.event_tbl.c, event['event_attr_id']))).label('values')]
else: else:
selectd = select_exprs + [ event_name = event['event_name']
func.round(getattr(func, analysis)(getattr(self.event_tbl.c, event['event_attr_id'])), 2).label(
'values')]
if user_filter: select_exprs += self.groupby
qry = sa.select(selectd).select_from(
self.event_tbl.join(self.user_tbl, u_account_id_col == e_account_id_col)).where(
and_(*user_filter, *event_filter, *base_where))
else: base_where.append(event_name_col == event_name)
qry = sa.select(selectd).where(and_(*event_filter, *base_where))
analysis = event['analysis']
event_filter, user_filter = self.handler_filts(*event['filts'])
u_account_id_col = getattr(self.user_tbl.c, '#account_id')
# 按账号聚合
e_account_id_col = getattr(self.event_tbl.c, '#account_id')
# 聚合方式
if analysis == 'total_count':
selectd = select_exprs + [func.count().label('values')]
elif analysis == 'touch_user_count':
selectd = select_exprs + [func.count(sa.distinct(e_account_id_col)).label('values')]
elif analysis == 'touch_user_avg':
selectd = select_exprs + [
func.round((func.count() / func.count(sa.distinct(e_account_id_col))), 2).label(
'values')]
else:
selectd = select_exprs + [
func.round(getattr(func, analysis)(getattr(self.event_tbl.c, event['event_attr_id'])), 2).label(
'values')]
if user_filter:
qry = sa.select(selectd).select_from(
self.event_tbl.join(self.user_tbl, u_account_id_col == e_account_id_col)).where(
and_(*user_filter, *event_filter, *base_where))
else:
qry = sa.select(selectd).where(and_(*event_filter, *base_where))
qry = qry.group_by(*select_exprs) qry = qry.group_by(*select_exprs)
qry = qry.order_by(sa.Column('date')) qry = qry.order_by(sa.Column('date'))

View File

@ -1,7 +1,7 @@
import uuid import uuid
from datetime import datetime from datetime import datetime
from enum import Enum from enum import Enum
from typing import List from typing import List, Dict
from pydantic import BaseModel from pydantic import BaseModel
@ -20,6 +20,10 @@ class DashboardCreate(DashboardBase):
pid: str pid: str
class ReadDashboard(BaseModel):
id: str
class DashboardDelete(DBBase): class DashboardDelete(DBBase):
pass pass
@ -35,12 +39,18 @@ class DashboardMove(BaseModel):
cat: Category cat: Category
class Report(BaseModel):
report_id: str
graph_type: str
model: str
class AddReport(DBBase): class AddReport(DBBase):
report_ids: List[str] report_ids: List[Report]
class DelReport(DBBase): class DelReport(DBBase):
report_ids: List[str] report_id: str
# -------------------------------------------------------------- # --------------------------------------------------------------

View File

@ -29,6 +29,8 @@ class ReportDelete(DBBase):
class ReportRead(BaseModel): class ReportRead(BaseModel):
project_id: str project_id: str
report_id: List = []
dashboard_id: str = None
# -------------------------------------------------------------- # --------------------------------------------------------------

View File

@ -9,5 +9,6 @@ class Sql(BaseModel):
class CkQuery(BaseModel): class CkQuery(BaseModel):
eventView: dict eventView: dict = None
events: Union[List[dict],dict] events: Union[List[dict], dict] = None
report_id: str = None