Skip to content

Commit

Permalink
Reporting environment issues (#213)
Browse files Browse the repository at this point in the history
* Add missing field from repr

* Add support for environment issues to the backend

* Require issue url be github, jira, or launchpad

* Add is_confirmed to environment issue

* Include environment issues in seed_script

* Fix seed script

* Environment issues frontend

* Minor improvements

* Some code improvements

* Add non-blocking provider preloader

* Refactor loading providers to a widget

* Fix missing event log expandable
  • Loading branch information
omar-selo authored Sep 18, 2024
1 parent d6c234d commit d877415
Show file tree
Hide file tree
Showing 34 changed files with 980 additions and 170 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Add EnvironmentIssue table
Revision ID: 505b96fd7731
Revises: ba6550a03bc8
Create Date: 2024-09-16 10:52:25.226261+00:00
"""
import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "505b96fd7731"
down_revision = "ba6550a03bc8"
branch_labels = None
depends_on = None


def upgrade() -> None:
op.create_table(
"environment_issue",
sa.Column("environment_name", sa.String(), nullable=False),
sa.Column("url", sa.String(), nullable=False),
sa.Column("description", sa.String(), nullable=False),
sa.Column("is_confirmed", sa.Boolean(), nullable=False),
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("updated_at", sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint("id", name=op.f("environment_issue_pkey")),
)


def downgrade() -> None:
op.drop_table("environment_issue")
45 changes: 38 additions & 7 deletions backend/scripts/seed_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
from sqlalchemy import select
from sqlalchemy.orm import Session

from test_observer.controllers.test_cases.models import ReportedIssueRequest
from test_observer.controllers.environments.models import (
EnvironmentReportedIssueRequest,
)
from test_observer.controllers.test_cases.models import TestReportedIssueRequest
from test_observer.controllers.test_executions.models import (
C3TestResult,
C3TestResultStatus,
Expand All @@ -28,6 +31,7 @@
END_TEST_EXECUTION_URL = f"{BASE_URL}/test-executions/end-test"
RERUN_TEST_EXECUTION_URL = f"{BASE_URL}/test-executions/reruns"
TEST_CASE_ISSUE_URL = f"{BASE_URL}/test-cases/reported-issues"
ENVIRONMENT_ISSUE_URL = f"{BASE_URL}/environments/reported-issues"

START_TEST_EXECUTION_REQUESTS = [
StartTestExecutionRequest(
Expand Down Expand Up @@ -288,20 +292,41 @@
]

TEST_CASE_ISSUE_REQUESTS = [
ReportedIssueRequest(
TestReportedIssueRequest(
template_id=END_TEST_EXECUTION_REQUESTS[0].test_results[2].template_id, # type: ignore
url=HttpUrl("http://bug1.link"),
url=HttpUrl("https://github.com"),
description="known issue 1",
),
ReportedIssueRequest(
TestReportedIssueRequest(
case_name=END_TEST_EXECUTION_REQUESTS[0].test_results[0].name,
url=HttpUrl("http://bug2.link"),
url=HttpUrl("https://warthogs.atlassian.net"),
description="known issue 2",
),
ReportedIssueRequest(
TestReportedIssueRequest(
case_name=END_TEST_EXECUTION_REQUESTS[0].test_results[1].name,
url=HttpUrl("http://bug3.link"),
url=HttpUrl("https://bugs.launchpad.net"),
description="known issue 3",
),
]

ENVIRONMENT_ISSUE_REQUESTS = [
EnvironmentReportedIssueRequest(
environment_name=START_TEST_EXECUTION_REQUESTS[0].environment,
url=HttpUrl("https://github.com"),
description="known issue 1",
is_confirmed=True,
),
EnvironmentReportedIssueRequest(
environment_name=START_TEST_EXECUTION_REQUESTS[1].environment,
url=HttpUrl("https://warthogs.atlassian.net"),
description="known issue 2",
is_confirmed=False,
),
EnvironmentReportedIssueRequest(
environment_name=START_TEST_EXECUTION_REQUESTS[2].environment,
url=HttpUrl("https://bugs.launchpad.net"),
description="known issue 3",
is_confirmed=True,
),
]

Expand Down Expand Up @@ -334,6 +359,12 @@ def seed_data(client: TestClient | requests.Session, session: Session | None = N
TEST_CASE_ISSUE_URL, json=case_issue_request.model_dump(mode="json")
).raise_for_status()

for environment_issue_request in ENVIRONMENT_ISSUE_REQUESTS:
client.post(
ENVIRONMENT_ISSUE_URL,
json=environment_issue_request.model_dump(mode="json"),
).raise_for_status()

_rerun_some_test_executions(client, test_executions)

_add_bugurl_and_duedate(session)
Expand Down
2 changes: 2 additions & 0 deletions backend/test_observer/common/constants.py
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
PREVIOUS_TEST_RESULT_COUNT = 10

VALID_ISSUE_HOSTS = {"github.com", "warthogs.atlassian.net", "bugs.launchpad.net"}
6 changes: 6 additions & 0 deletions backend/test_observer/controllers/environments/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from fastapi import APIRouter

from . import reported_issues

router = APIRouter(tags=["environments"])
router.include_router(reported_issues.router)
31 changes: 31 additions & 0 deletions backend/test_observer/controllers/environments/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from datetime import datetime

from pydantic import BaseModel, HttpUrl, field_validator

from test_observer.common.constants import VALID_ISSUE_HOSTS


class EnvironmentReportedIssueRequest(BaseModel):
environment_name: str
description: str
url: HttpUrl
is_confirmed: bool

@field_validator("url")
@classmethod
def url_host_must_be_allowed(
cls: type["EnvironmentReportedIssueRequest"], url: HttpUrl
) -> HttpUrl:
if url.host not in VALID_ISSUE_HOSTS:
raise ValueError(f"Issue url must belong to one of {VALID_ISSUE_HOSTS}")
return url


class EnvironmentReportedIssueResponse(BaseModel):
id: int
environment_name: str
description: str
url: HttpUrl
is_confirmed: bool
created_at: datetime
updated_at: datetime
52 changes: 52 additions & 0 deletions backend/test_observer/controllers/environments/reported_issues.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from fastapi import APIRouter, Depends
from sqlalchemy import select
from sqlalchemy.orm import Session

from test_observer.data_access.models import EnvironmentIssue
from test_observer.data_access.setup import get_db

from .models import EnvironmentReportedIssueRequest, EnvironmentReportedIssueResponse

router = APIRouter()

endpoint = "/reported-issues"


@router.get(endpoint, response_model=list[EnvironmentReportedIssueResponse])
def get_reported_issues(db: Session = Depends(get_db)):
return db.execute(select(EnvironmentIssue)).scalars()


@router.post(endpoint, response_model=EnvironmentReportedIssueResponse)
def create_reported_issue(
request: EnvironmentReportedIssueRequest, db: Session = Depends(get_db)
):
issue = EnvironmentIssue(
environment_name=request.environment_name,
url=request.url,
description=request.description,
is_confirmed=request.is_confirmed,
)
db.add(issue)
db.commit()

return issue


@router.put(endpoint + "/{issue_id}", response_model=EnvironmentReportedIssueResponse)
def update_reported_issue(
issue_id: int,
request: EnvironmentReportedIssueRequest,
db: Session = Depends(get_db),
):
issue = db.get(EnvironmentIssue, issue_id)
for field in request.model_fields:
setattr(issue, field, getattr(request, field))
db.commit()
return issue


@router.delete(endpoint + "/{issue_id}")
def delete_reported_issue(issue_id: int, db: Session = Depends(get_db)):
db.delete(db.get(EnvironmentIssue, issue_id))
db.commit()
3 changes: 2 additions & 1 deletion backend/test_observer/controllers/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

from test_observer.data_access.setup import get_db

from . import test_cases, test_executions
from . import environments, test_cases, test_executions
from .application import version
from .artefacts import artefacts
from .reports import reports
Expand All @@ -34,6 +34,7 @@
router.include_router(artefacts.router, prefix="/v1/artefacts")
router.include_router(reports.router, prefix="/v1/reports")
router.include_router(test_cases.router, prefix="/v1/test-cases")
router.include_router(environments.router, prefix="/v1/environments")


@router.get("/")
Expand Down
18 changes: 15 additions & 3 deletions backend/test_observer/controllers/test_cases/models.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from datetime import datetime

from pydantic import BaseModel, HttpUrl, model_validator
from pydantic import BaseModel, HttpUrl, field_validator, model_validator

from test_observer.common.constants import VALID_ISSUE_HOSTS

class ReportedIssueRequest(BaseModel):

class TestReportedIssueRequest(BaseModel):
template_id: str = ""
case_name: str = ""
description: str
Expand All @@ -13,10 +15,20 @@ class ReportedIssueRequest(BaseModel):
def check_a_or_b(self):
if not self.case_name and not self.template_id:
raise ValueError("Either case_name or template_id is required")

return self

@field_validator("url")
@classmethod
def name_must_contain_space(
cls: type["TestReportedIssueRequest"], url: HttpUrl
) -> HttpUrl:
if url.host not in VALID_ISSUE_HOSTS:
raise ValueError(f"Issue url must belong to one of {VALID_ISSUE_HOSTS}")
return url


class ReportedIssueResponse(BaseModel):
class TestReportedIssueResponse(BaseModel):
id: int
template_id: str = ""
case_name: str = ""
Expand Down
14 changes: 8 additions & 6 deletions backend/test_observer/controllers/test_cases/reported_issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
from test_observer.data_access.models import TestCaseIssue
from test_observer.data_access.setup import get_db

from .models import ReportedIssueRequest, ReportedIssueResponse
from .models import TestReportedIssueRequest, TestReportedIssueResponse

router = APIRouter()


endpoint = "/reported-issues"


@router.get(endpoint, response_model=list[ReportedIssueResponse])
@router.get(endpoint, response_model=list[TestReportedIssueResponse])
def get_reported_issues(
template_id: str | None = None,
case_name: str | None = None,
Expand All @@ -27,8 +27,10 @@ def get_reported_issues(
return db.execute(stmt).scalars()


@router.post(endpoint, response_model=ReportedIssueResponse)
def create_reported_issue(request: ReportedIssueRequest, db: Session = Depends(get_db)):
@router.post(endpoint, response_model=TestReportedIssueResponse)
def create_reported_issue(
request: TestReportedIssueRequest, db: Session = Depends(get_db)
):
issue = TestCaseIssue(
template_id=request.template_id,
url=request.url,
Expand All @@ -41,9 +43,9 @@ def create_reported_issue(request: ReportedIssueRequest, db: Session = Depends(g
return issue


@router.put(endpoint + "/{issue_id}", response_model=ReportedIssueResponse)
@router.put(endpoint + "/{issue_id}", response_model=TestReportedIssueResponse)
def update_reported_issue(
issue_id: int, request: ReportedIssueRequest, db: Session = Depends(get_db)
issue_id: int, request: TestReportedIssueRequest, db: Session = Depends(get_db)
):
issue = db.get(TestCaseIssue, issue_id)
for field in request.model_fields:
Expand Down
22 changes: 22 additions & 0 deletions backend/test_observer/data_access/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,28 @@ def __repr__(self) -> str:
return data_model_repr(
self,
"template_id",
"case_name",
"url",
"description",
)


class EnvironmentIssue(Base):
"""
A table to store issues reported on certain environments
"""

__tablename__ = "environment_issue"

environment_name: Mapped[str]
url: Mapped[str]
description: Mapped[str]
is_confirmed: Mapped[bool]

def __repr__(self) -> str:
return data_model_repr(
self,
"environment_name",
"url",
"description",
)
8 changes: 8 additions & 0 deletions backend/tests/asserts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from httpx import Response


def assert_fails_validation(response: Response, field: str, type: str) -> None:
assert response.status_code == 422
problem = response.json()["detail"][0]
assert problem["type"] == type
assert problem["loc"] == ["body", field]
Loading

0 comments on commit d877415

Please sign in to comment.