diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1db56b0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.env +.venv +__pycache__ +venv +idea diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..19bf78b --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..ec5e7af --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/task-python.iml b/.idea/task-python.iml new file mode 100644 index 0000000..ea85759 --- /dev/null +++ b/.idea/task-python.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Repositories/ServiceRepo.py b/Repositories/ServiceRepo.py new file mode 100644 index 0000000..ed66c16 --- /dev/null +++ b/Repositories/ServiceRepo.py @@ -0,0 +1,137 @@ +import datetime +import uuid +import pymongo + +from db.db_connect import db +from model.service import Service + + +STATE_WORK = 'work' +STATE_NOT_WORK = 'notwork' + + +class ServiceRepo(): + + @staticmethod + async def get_all(): + """ Выводит список сервисов """ + items = db.get_collection('service').find() + list_items = [] + async for item in items: + list_items.append(item) + return list_items + + @staticmethod + async def get_actual_state_service(): + """ Выводит список сервисов с актуальным состоянием """ + + services = await db.service.distinct('name') + list_services = [] + for service in services: + actual_state = ( + db.get_collection('service') + .find({'name': service}) + .sort('state_dt', pymongo.DESCENDING) + .limit(1) + ) + list_services.append(await actual_state.to_list(1)) + return list_services + + @staticmethod + async def get_state_data(state: str): + """ Dсе данные по каждому состоянию """ + + states = ( + db.get_collection('service') + .find({'state': state}) + .sort('state_dt', pymongo.DESCENDING) + ) + list_state = [] + async for state in states: + list_state.append(state) + return list_state + + @staticmethod + async def get_service_by_name(name: str): + """ По имени сервиса выдает историю изменения состояния """ + + services = ( + db.get_collection('service') + .find({'name': name}) + .sort('state_dt', pymongo.DESCENDING) + ) + list_service = [] + async for service in services: + list_service.append(service) + return list_service + + @staticmethod + async def get_sla(name: str, date_start: datetime.datetime, date_end: datetime.datetime): + """ По указанному интервалу выдается информация о том сколько не работал сервис и считает SLA """ + + services = ( + db.get_collection('service') + .find({'name': name, 'state_dt': {'$gt': date_start, '$lt': date_end}}) + .sort('state_dt', pymongo.DESCENDING) + ) + previous_service = ( + db.get_collection('service') + .find({'name': name, 'state_dt': {'$lt': date_start}}) + .sort('state_dt', pymongo.DESCENDING) + .limit(1) + ) + list_state = [] + downtime = datetime.timedelta() + downtime_end = None + downtime_start = None + + async for service in previous_service: + if service['state'] == STATE_NOT_WORK: + list_state.append(service) + + async for service in services: + if service['state'] == STATE_NOT_WORK: + downtime_start = service['state_dt'] + if downtime_end: + downtime += downtime_end - downtime_start + downtime_end = None + continue + downtime += date_end - downtime_start + elif service['state'] == STATE_WORK: + downtime_end = service['state_dt'] + + if list_state and downtime_end: + downtime += downtime_end - date_start + list_state.pop() + elif list_state and downtime_start: + downtime += downtime_start - date_start + list_state.pop() + + sla = get_formatted_sla(date_start, date_end, downtime) + return sla + + + @staticmethod + async def insert(service: Service): + """ Получает и сохраняет данные: имя, состояние, описание """ + + id = str(uuid.uuid4()) + service.id = id + service = service.dict(exclude={}) + service['_id'] = id + service.pop('id') + await db.get_collection('service').insert_one(service) + return service + + @staticmethod + async def delete_one(id: str): + """ Удаление сервиса """ + + return await db.get_collection('service').delete_one({'_id': id}) + + +def get_formatted_sla(date_start: datetime.datetime, date_end: datetime.datetime, downtime: datetime.timedelta): + """ Форматирование строки SLA """ + + sla = ((date_end - date_start) - downtime) / (date_end - date_start) * 100 + return '{0:.0f} hours'.format(downtime.total_seconds() // 3600), '{0:.3f}%'.format(sla) diff --git a/config.py b/config.py new file mode 100644 index 0000000..5bdcccb --- /dev/null +++ b/config.py @@ -0,0 +1 @@ +DB_URL = "mongodb://localhost:27017" diff --git a/db/db_connect.py b/db/db_connect.py new file mode 100644 index 0000000..c6f9876 --- /dev/null +++ b/db/db_connect.py @@ -0,0 +1,8 @@ +import motor.motor_asyncio +from fastapi import FastAPI + +from config import DB_URL + + +app = FastAPI() +db = motor.motor_asyncio.AsyncIOMotorClient(DB_URL).service diff --git a/main.py b/main.py new file mode 100644 index 0000000..7391904 --- /dev/null +++ b/main.py @@ -0,0 +1,6 @@ +from fastapi import FastAPI +from routers import service + + +app = FastAPI() +app.include_router(service.router) diff --git a/model/service.py b/model/service.py new file mode 100644 index 0000000..f7f2d55 --- /dev/null +++ b/model/service.py @@ -0,0 +1,10 @@ +import datetime +from pydantic import BaseModel, Field + + +class Service(BaseModel): + id: str = '' + name: str = Field(include=True) + state: str = Field(include=True) + description: str = Field(include=True) + state_dt: datetime.datetime = Field(default=datetime.datetime.now()) diff --git a/routers/service.py b/routers/service.py new file mode 100644 index 0000000..e3c90a8 --- /dev/null +++ b/routers/service.py @@ -0,0 +1,44 @@ +from datetime import datetime +from fastapi import APIRouter + +from Repositories.ServiceRepo import ServiceRepo +from model.service import Service + + +router = APIRouter(prefix="/service", tags=['/service']) + + +@router.get("/") +async def get_all(): + return await ServiceRepo.get_all() + + +@router.get("/actual_state_service") +async def actual_state_service(): + return await ServiceRepo.get_actual_state_service() + + +@router.get("/get_state_data") +async def get_state_data(state: str): + return await ServiceRepo.get_state_data(state) + + +@router.get("/get_sla") +async def get_state_data(name: str, date_start: datetime, date_end: datetime): + return await ServiceRepo.get_sla(name, date_start, date_end) + + +@router.get("/get_service_by_name") +async def get_service_by_name(name: str): + return await ServiceRepo.get_service_by_name(name) + + +@router.post("/add_service", status_code=201) +async def add_tabel(service: Service): + _service = await ServiceRepo.insert(service) + return _service + + +@router.delete("/delete", status_code=200) +async def delete_user(id: str): + await ServiceRepo.delete_one(id)