Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions backend/alembic/versions/f04430af51e2_add_expected_res_for_funs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""Add expected res for funs

Revision ID: f04430af51e2
Revises: 8233dd7ca744
Create Date: 2026-01-19 18:56:12.824385

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = 'f04430af51e2'
down_revision: Union[str, Sequence[str], None] = '8233dd7ca744'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('function_results', sa.Column('expected_result', sa.Enum('UNKNOWN', 'EQUAL', 'NON_EQUAL', name='expectedresulttype'), nullable=True))
op.add_column('function_results', sa.Column('expected_result_changed_by', sa.String(), nullable=True))
# ### end Alembic commands ###


def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('function_results', 'expected_result_changed_by')
op.drop_column('function_results', 'expected_result')
# ### end Alembic commands ###
68 changes: 67 additions & 1 deletion backend/automation/db/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
from automation.models.results.result import ResultBase
from automation.models.results.results import ResultSubType
from automation.models.results.types import (ComparisonStatus,
DiffKempResultType)
DiffKempResultType,
ExpectedResultType)
from automation.models.results.versions import ResultVersion
from automation.utils import RESULTS_DB_PATH

Expand Down Expand Up @@ -48,6 +49,8 @@
Column("id", Integer, primary_key=True, autoincrement=True),
Column("name", String, nullable=False),
Column("diffkemp_result", Enum(DiffKempResultType), nullable=False),
Column("expected_result", Enum(ExpectedResultType)),
Column("expected_result_changed_by", String),
Column("result_id", Integer, ForeignKey("results.id"), nullable=False),
)

Expand Down Expand Up @@ -178,3 +181,66 @@ def _result_fun_list_to_dict(self, result: ResultSubType | None) -> None:
# Safe deletion
if hasattr(result, "_functions_list"):
delattr(result, "_functions_list")


class FunctionResultsRepo:
"""Repository for working with function results in DB."""

def getForCommit(
self,
config_name: str,
commit: str,
diffkemp_sha: str,
function_name: str,
) -> Optional[FunctionResult]:
"""Get a specific function result by filtering on parent result."""
with Session() as session:
query = (
session.query(FunctionResult)
.join(results_table,
function_results_table.c.result_id == results_table.c.id)
.where(
function_results_table.c.name == function_name,
results_table.c.config_file_name == config_name,
results_table.c.commit == commit,
results_table.c.diffkemp_sha == diffkemp_sha,
)
)
result = query.first()
if result:
session.expunge(result)
return result

def getForVersion(
self,
config_name: str,
old_tag: str,
new_tag: str,
diffkemp_sha: str,
function_name: str,
) -> Optional[FunctionResult]:
"""Get a specific function result by filtering on parent result."""
with Session() as session:
query = (
session.query(FunctionResult)
.join(results_table,
function_results_table.c.result_id == results_table.c.id)
.where(
function_results_table.c.name == function_name,
results_table.c.config_file_name == config_name,
results_table.c.old_tag == old_tag,
results_table.c.new_tag == new_tag,
results_table.c.diffkemp_sha == diffkemp_sha,
)
)
result = query.first()
if result:
session.expunge(result)
return result

def update(self, function_result: FunctionResult) -> None:
"""Update a function result in the database."""
with Session() as session:
# Merge the detached object back into the session
session.merge(function_result)
session.commit()
9 changes: 0 additions & 9 deletions backend/automation/models/results/commits.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,15 +124,6 @@ def get_commit_datetime(self) -> datetime:
def get_diff(self) -> str:
return self.get_project().repo.git.diff(f"{self.commit}^!")

@classmethod
def from_yaml(cls, result: dict) -> "ResultCommit":
kwargs = super()._parse_yaml_base(result)
return cls(
commit=result["commit"],
all_diffs_matched=result["all_diffs_matched"],
**kwargs
)


class ResultsCommits():
"""Class for parsing results for commits."""
Expand Down
29 changes: 13 additions & 16 deletions backend/automation/models/results/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,24 @@
:author: Lukas Petr
"""

from .types import DiffKempResultType
from typing import Optional

from .types import DiffKempResultType, ExpectedResultType


class FunctionResult:
"""Represents the comparison result for a single function."""

def __init__(self, name: str, diffkemp_result: DiffKempResultType):
def __init__(
self,
name: str,
diffkemp_result: DiffKempResultType,
expected_result: ExpectedResultType = ExpectedResultType.UNKNOWN,
) -> None:
self.name: str = name
self.diffkemp_result: DiffKempResultType = diffkemp_result
self.expected_result: Optional[ExpectedResultType] = expected_result
self.expected_result_changed_by: Optional[str] = None

def to_yaml(self) -> dict:
return {
"name": self.name,
"diffkemp_result": str(self.diffkemp_result),
}

@classmethod
def from_yaml(cls, result: dict) -> "FunctionResult":
name = result["name"]
diffkemp_result = result["diffkemp_result"]
return cls(
name=name,
diffkemp_result=DiffKempResultType(diffkemp_result),
)
def set_expected_result(self, result: ExpectedResultType) -> None:
self.expected_result = result
30 changes: 1 addition & 29 deletions backend/automation/models/results/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from abc import ABC, abstractmethod
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, Generic, Optional, TypeVar, cast
from typing import Dict, Generic, Optional, TypeVar, cast

from automation.models.projects.project import TProject
from automation.models.projects.projects import ProjectsManager
Expand Down Expand Up @@ -120,31 +120,3 @@ def get_diffkemp_out(self) -> Optional[DiffKempOutYaml]:
if not path.exists():
return None
return DiffKempOutYaml.from_file(path)

@staticmethod
def _parse_yaml_base(result: Dict[str, Any]) -> dict:
"""
Parse common fields from YAML representation.

This helper method extracts base class fields. Subclasses should
call this and add their specific fields.

:param result: YAML result.
"""
functions = {}
for fun_res in result["functions"]:
f = FunctionResult.from_yaml(fun_res)
functions[f.name] = f
return {
"name": result["name"],
"config_file_name": result["config_file_name"],
"diffkemp_sha": result["diffkemp_sha"],
"equal": int(result["equal"]),
"non_equal": int(result["non_equal"]),
"unknown": int(result["unknown"]),
"error": int(result["error"]),
"no_differing": int(result["no_differing"]),
"date": datetime.fromisoformat(result["date"]),
"functions": functions,
"comparison_status": ComparisonStatus(result["comparison_status"]),
}
7 changes: 7 additions & 0 deletions backend/automation/models/results/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ class DiffKempResultType(StrEnum):
ERROR = "error"


class ExpectedResultType(StrEnum):
"""Types of expected comparison results from DiffKemp analysis."""
UNKNOWN = "unknown"
EQUAL = "equal"
NON_EQUAL = "non equal"


class ComparisonStatus(StrEnum):
"""Status of the comparison."""

Expand Down
9 changes: 0 additions & 9 deletions backend/automation/models/results/versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,6 @@ def get_relative_viewer_path(self) -> Path:
f"{self.old_tag}-{self.new_tag}"
)

@classmethod
def from_yaml(cls, result: dict) -> "ResultVersion":
kwargs = super()._parse_yaml_base(result)
return cls(
old_tag=result["old_tag"],
new_tag=result["new_tag"],
**kwargs,
)


class ResultsVersions():
"""Class for parsing results for versions."""
Expand Down
2 changes: 2 additions & 0 deletions backend/automation/web/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def create_app() -> Flask:
load_dotenv()
app = Flask(__name__)

from .api import api_bp
from .auth import auth_bp
from .commits import commits_bp
from .versions import versions_bp
Expand All @@ -26,6 +27,7 @@ def create_app() -> Flask:
app.register_blueprint(commits_bp)
app.register_blueprint(view_results_bp)
app.register_blueprint(auth_bp)
app.register_blueprint(api_bp)

@app.route("/")
def index() -> ResponseReturnValue:
Expand Down
77 changes: 77 additions & 0 deletions backend/automation/web/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""File defining HTML REST API endpoints."""
from flask import Blueprint, Response, abort, request, session

from automation.db import FunctionResultsRepo
from automation.models.results.types import ExpectedResultType

api_bp = Blueprint("api_bp", __name__, url_prefix="/api")


@api_bp.put(
"/commits/<config_name>/<commit>/<diffkemp_sha>"
"/functions/<function_name>/expected-result"
)
def update_fun_expected_res_for_commit(
config_name: str,
commit: str,
diffkemp_sha: str,
function_name: str,
) -> Response:
"""Update the expected result for a function."""
# Check if user is logged in
if "user" not in session:
abort(403)

repo = FunctionResultsRepo()
function_result = repo.getForCommit(
config_name=config_name,
commit=commit,
diffkemp_sha=diffkemp_sha,
function_name=function_name,
)

if not function_result:
abort(404)

expected_result = ExpectedResultType(request.json["expected_result"])
function_result.expected_result = expected_result
function_result.expected_result_changed_by = session["user"]

repo.update(function_result)
return Response(status=200)


@api_bp.put(
"/versions/<config_name>/<old_tag>/<new_tag>/<diffkemp_sha>"
"/functions/<function_name>/expected-result"
)
def update_fun_expected_res_for_version(
config_name: str,
old_tag: str,
new_tag: str,
diffkemp_sha: str,
function_name: str,
) -> Response:
"""Update the expected result for a function."""
# Check if user is logged in
if "user" not in session:
abort(403)

repo = FunctionResultsRepo()
function_result = repo.getForVersion(
config_name=config_name,
old_tag=old_tag,
new_tag=new_tag,
diffkemp_sha=diffkemp_sha,
function_name=function_name,
)

if not function_result:
abort(404)

expected_result = ExpectedResultType(request.json["expected_result"])
function_result.expected_result = expected_result
function_result.expected_result_changed_by = session["user"]

repo.update(function_result)
return Response(status=200)
18 changes: 16 additions & 2 deletions backend/automation/web/templates/commits/result.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{% extends "commits/layout.html" %} {% block head %} {{ super() }} {% endblock
%} {% block subcontent %}
{% extends "commits/layout.html" %}
{% from "macros.html" import expected_fun_result_select %}

{% block subcontent %}
<h2>Results</h2>

<ul>
Expand All @@ -16,6 +18,7 @@ <h3>Compared functions</h3>
<tr>
<th>Compared function</th>
<th>DiffKemp result</th>
<th>Expected result</th>
</tr>
</thead>
<tbody>
Expand All @@ -27,6 +30,17 @@ <h3>Compared functions</h3>
{{ info.diffkemp_result.value.capitalize() }}
</span>
</td>
<td class="fs-5">
{% set api_url = url_for(
'api_bp.update_fun_expected_res_for_commit',
config_name=result.config_name,
commit=result.commit.SHA,
diffkemp_sha=result.diffkemp_sha,
function_name=function,
)
%}
{{ expected_fun_result_select(info, api_url) }}
</td>
</tr>
{% endfor %}
</tbody>
Expand Down
Loading