From ebe9099880b65152b4a3c2ded9428da738d2b33c Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Mon, 22 Apr 2024 12:23:30 +0530 Subject: [PATCH 1/6] Add method for team creation, updation, deletion on CoreRepo --- apiserver/dora/store/repos/core.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/apiserver/dora/store/repos/core.py b/apiserver/dora/store/repos/core.py index d26d06008..14d3d0fe5 100644 --- a/apiserver/dora/store/repos/core.py +++ b/apiserver/dora/store/repos/core.py @@ -51,10 +51,38 @@ def delete_team(self, team_id: str): self._db.session.commit() return self._db.session.query(Team).filter(Team.id == team_id).one_or_none() + @rollback_on_exc + def create_team(self, org_id: str, name: str, member_ids: List[str]) -> Team: + team = Team( + name=name, + org_id=org_id, + member_ids=member_ids or [], + is_deleted=False, + ) + self._db.session.add(team) + self._db.session.commit() + + return self.get_team(team.id) + + @rollback_on_exc + def update_team(self, team: Team) -> Team: + self._db.session.merge(team) + self._db.session.commit() + + return self.get_team(team.id) + @rollback_on_exc def get_user(self, user_id) -> Optional[Users]: return self._db.session.query(Users).filter(Users.id == user_id).one_or_none() + @rollback_on_exc + def get_users(self, user_ids: List[str]) -> List[Users]: + return ( + self._db.session.query(Users) + .filter(and_(Users.id.in_(user_ids), Users.is_deleted == False)) + .all() + ) + @rollback_on_exc def get_org_integrations_for_names(self, org_id: str, provider_names: List[str]): return ( From 2faac4dca1c1923e4cfc02c1fa9791de4b26d93c Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Mon, 22 Apr 2024 12:24:09 +0530 Subject: [PATCH 2/6] Make is_deleted to false by default in Team --- apiserver/dora/store/models/core/teams.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/dora/store/models/core/teams.py b/apiserver/dora/store/models/core/teams.py index 7a58b79b4..13884113b 100644 --- a/apiserver/dora/store/models/core/teams.py +++ b/apiserver/dora/store/models/core/teams.py @@ -20,7 +20,7 @@ class Team(db.Model): updated_at = db.Column( db.DateTime(timezone=True), server_default=func.now(), onupdate=func.now() ) - is_deleted = db.Column(db.Boolean) + is_deleted = db.Column(db.Boolean, default=False) def __hash__(self): return hash(self.id) From 7444aa3f811c227e444c27713fecae10eee9ac86 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Mon, 22 Apr 2024 12:24:52 +0530 Subject: [PATCH 3/6] Add TeamService --- apiserver/dora/service/core/teams.py | 35 ++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 apiserver/dora/service/core/teams.py diff --git a/apiserver/dora/service/core/teams.py b/apiserver/dora/service/core/teams.py new file mode 100644 index 000000000..c9b233298 --- /dev/null +++ b/apiserver/dora/service/core/teams.py @@ -0,0 +1,35 @@ +from typing import List, Optional +from dora.store.models.core.teams import Team +from dora.store.repos.core import CoreRepoService + + +class TeamService: + def __init__(self, core_repo_service: CoreRepoService): + self._core_repo_service = core_repo_service + + def get_team(self, team_id: str) -> Optional[Team]: + return self._core_repo_service.get_team(team_id) + + def delete_team(self, team_id: str) -> Optional[Team]: + return self._core_repo_service.delete_team(team_id) + + def create_team(self, org_id: str, name: str, member_ids: List[str] = None) -> Team: + return self._core_repo_service.create_team(org_id, name, member_ids or []) + + def update_team( + self, team_id: str, name: str = None, member_ids: List[str] = None + ) -> Team: + + team = self._core_repo_service.get_team(team_id) + + if name is not None: + team.name = name + + if member_ids is not None: + team.member_ids = member_ids + + return self._core_repo_service.update_team(team) + + +def get_team_service(): + return TeamService(CoreRepoService()) From be345edea1c967be37b4e63c3561eab284bc3b74 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Mon, 22 Apr 2024 12:25:24 +0530 Subject: [PATCH 4/6] Add users_validator method to QueryValidator. --- apiserver/dora/service/query_validator.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apiserver/dora/service/query_validator.py b/apiserver/dora/service/query_validator.py index b49733d7b..1f6834bc6 100644 --- a/apiserver/dora/service/query_validator.py +++ b/apiserver/dora/service/query_validator.py @@ -61,6 +61,17 @@ def user_validator(self, user_id: str) -> Users: raise NotFound(f"User {user_id} not found") return user + def users_validator(self, user_ids: List[str]) -> List[Users]: + users: List[Users] = self.repo_service.get_users(user_ids) + + if len(users) != len(user_ids): + query_user_ids = set(user_ids) + found_user_ids = set(map(lambda x: str(x.id), users)) + missing_user_ids = query_user_ids - found_user_ids + raise NotFound(f"User(s) not found: {missing_user_ids}") + + return users + def get_query_validator(): return QueryValidator(CoreRepoService()) From 42d763073c4aa9a3721743c7354c3bb1e91134d7 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Mon, 22 Apr 2024 12:25:53 +0530 Subject: [PATCH 5/6] Add adapt_team as a API layer adapter for teams --- apiserver/dora/api/resources/core_resources.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apiserver/dora/api/resources/core_resources.py b/apiserver/dora/api/resources/core_resources.py index 1a14e7d15..63330887d 100644 --- a/apiserver/dora/api/resources/core_resources.py +++ b/apiserver/dora/api/resources/core_resources.py @@ -1,4 +1,5 @@ from typing import Dict +from dora.store.models.core.teams import Team from dora.store.models import Users @@ -19,3 +20,14 @@ def adapt_user_info( "avatar_url": username_user_map[author].avatar_url, }, } + + +def adapt_team(team: Team): + return { + "id": str(team.id), + "org_id": str(team.org_id), + "name": team.name, + "member_ids": [str(member_id) for member_id in team.member_ids], + "created_at": team.created_at.isoformat(), + "updated_at": team.updated_at.isoformat(), + } From 4dd07a7a4156825afff180e3b0ad66f48a5b1603 Mon Sep 17 00:00:00 2001 From: Samad Yar Khan Date: Mon, 22 Apr 2024 12:26:57 +0530 Subject: [PATCH 6/6] Add and register crud apis for teams --- apiserver/app.py | 2 + apiserver/dora/api/teams.py | 79 +++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 apiserver/dora/api/teams.py diff --git a/apiserver/app.py b/apiserver/app.py index 3f6d9ce65..78c46cf18 100644 --- a/apiserver/app.py +++ b/apiserver/app.py @@ -10,6 +10,7 @@ from dora.api.incidents import app as incidents_api from dora.api.integrations import app as integrations_api from dora.api.deployment_analytics import app as deployment_analytics_api +from dora.api.teams import app as teams_api from dora.store.initialise_db import initialize_database @@ -21,6 +22,7 @@ app.register_blueprint(incidents_api) app.register_blueprint(deployment_analytics_api) app.register_blueprint(integrations_api) +app.register_blueprint(teams_api) configure_db_with_app(app) initialize_database(app) diff --git a/apiserver/dora/api/teams.py b/apiserver/dora/api/teams.py new file mode 100644 index 000000000..b2b484dc5 --- /dev/null +++ b/apiserver/dora/api/teams.py @@ -0,0 +1,79 @@ +from flask import Blueprint +from typing import List +from voluptuous import Required, Schema, Optional +from dora.api.resources.core_resources import adapt_team +from dora.store.models.core.teams import Team +from dora.service.core.teams import get_team_service + +from dora.api.request_utils import dataschema +from dora.service.query_validator import get_query_validator + +app = Blueprint("teams", __name__) + + +@app.route("/team/", methods={"GET"}) +def fetch_team(team_id): + + query_validator = get_query_validator() + team: Team = query_validator.team_validator(team_id) + + return adapt_team(team) + + +@app.route("/team/", methods={"PATCH"}) +@dataschema( + Schema( + { + Optional("name"): str, + Optional("member_ids"): list, + } + ), +) +def update_team_patch(team_id: str, name: str = None, member_ids: List[str] = None): + + query_validator = get_query_validator() + team: Team = query_validator.team_validator(team_id) + + if member_ids: + query_validator.users_validator(member_ids) + + team_service = get_team_service() + + team: Team = team_service.update_team(team_id, name, member_ids) + + return adapt_team(team) + + +@app.route("/org//team", methods={"POST"}) +@dataschema( + Schema( + { + Required("name"): str, + Required("member_ids"): list, + } + ), +) +def create_team(org_id: str, name: str, member_ids: List[str]): + + query_validator = get_query_validator() + query_validator.org_validator(org_id) + query_validator.users_validator(member_ids) + + team_service = get_team_service() + + team: Team = team_service.create_team(org_id, name, member_ids) + + return adapt_team(team) + + +@app.route("/team/", methods={"DELETE"}) +def delete_team(team_id: str): + + query_validator = get_query_validator() + team: Team = query_validator.team_validator(team_id) + + team_service = get_team_service() + + team = team_service.delete_team(team_id) + + return adapt_team(team)