diff --git a/backend/automation/db/__init__.py b/backend/automation/db/__init__.py index b54adf5..7f614b9 100644 --- a/backend/automation/db/__init__.py +++ b/backend/automation/db/__init__.py @@ -33,6 +33,7 @@ Column("no_differing", Integer), Column("date", DateTime), Column("comparison_status", Enum(ComparisonStatus)), + Column("note", String), # Column for distinguishing between ResultCommit and ResultVersion Column("type", String(20)), # ResultCommit specific columns @@ -182,6 +183,13 @@ def _result_fun_list_to_dict(self, result: ResultSubType | None) -> None: if hasattr(result, "_functions_list"): delattr(result, "_functions_list") + def update(self, result: ResultSubType) -> None: + """Update a function result in the database.""" + with Session() as session: + # Merge the detached object back into the session + session.merge(result) + session.commit() + class FunctionResultsRepo: """Repository for working with function results in DB.""" diff --git a/backend/automation/models/results/commits.py b/backend/automation/models/results/commits.py index d2dc194..712a42f 100644 --- a/backend/automation/models/results/commits.py +++ b/backend/automation/models/results/commits.py @@ -35,6 +35,7 @@ def __init__( date: Optional[datetime] = None, functions: Optional[Dict[str, FunctionResult]] = None, comparison_status: ComparisonStatus = ComparisonStatus.SUCCESS, + note: str = "", ): """ :param commit: For which commit are the results. @@ -56,6 +57,7 @@ def __init__( date=date, functions=functions, comparison_status=comparison_status, + note=note, ) @classmethod diff --git a/backend/automation/models/results/result.py b/backend/automation/models/results/result.py index 0421379..40bc7e0 100644 --- a/backend/automation/models/results/result.py +++ b/backend/automation/models/results/result.py @@ -41,6 +41,7 @@ def __init__( date: Optional[datetime] = None, functions: Optional[Dict[str, FunctionResult]] = None, comparison_status: ComparisonStatus = ComparisonStatus.SUCCESS, + note: str = "", ) -> None: """ :param name: Name of project (used primary for showing to user). @@ -66,6 +67,7 @@ def __init__( self.non_equal = non_equal self.unknown = unknown self.date = date if date else datetime.now() + self.note = note self.functions = functions if functions is not None else {} self.comparison_status = comparison_status diff --git a/backend/automation/models/results/versions.py b/backend/automation/models/results/versions.py index b744e55..b4998b5 100644 --- a/backend/automation/models/results/versions.py +++ b/backend/automation/models/results/versions.py @@ -40,6 +40,7 @@ def __init__( date: Optional[datetime] = None, functions: Optional[Dict[str, FunctionResult]] = None, comparison_status: ComparisonStatus = ComparisonStatus.SUCCESS, + note: str = "", ) -> None: """ :param old_tag: Old tag of the project, that was compared. @@ -61,6 +62,7 @@ def __init__( date=date, functions=functions, comparison_status=comparison_status, + note=note, ) @classmethod diff --git a/backend/automation/web/api.py b/backend/automation/web/api.py index b907ccc..92f2ff3 100644 --- a/backend/automation/web/api.py +++ b/backend/automation/web/api.py @@ -1,16 +1,30 @@ """File defining HTML REST API endpoints.""" +from functools import wraps +from typing import Any, Callable + from flask import Blueprint, Response, abort, request, session -from automation.db import FunctionResultsRepo +from automation.db import FunctionResultsRepo, ResultsRepo from automation.models.results.types import ExpectedResultType api_bp = Blueprint("api_bp", __name__, url_prefix="/api") +def login_required(f: Callable[..., Response]) -> Callable[..., Response]: + """Decorator to require user to be logged in.""" + @wraps(f) + def decorated_function(*args: Any, **kwargs: Any) -> Response: + if "user" not in session: + abort(403) + return f(*args, **kwargs) + return decorated_function + + @api_bp.put( "/commits///" "/functions//expected-result" ) +@login_required def update_fun_expected_res_for_commit( config_name: str, commit: str, @@ -18,10 +32,6 @@ def update_fun_expected_res_for_commit( 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, @@ -45,6 +55,7 @@ def update_fun_expected_res_for_commit( "/versions////" "/functions//expected-result" ) +@login_required def update_fun_expected_res_for_version( config_name: str, old_tag: str, @@ -53,10 +64,6 @@ def update_fun_expected_res_for_version( 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, @@ -75,3 +82,62 @@ def update_fun_expected_res_for_version( repo.update(function_result) return Response(status=200) + + +@api_bp.put( + "/commits///" + "/note" +) +@login_required +def update_res_note_for_commit( + config_name: str, + commit: str, + diffkemp_sha: str, +) -> Response: + """Update the result note for a commit result.""" + repo = ResultsRepo() + results = repo.get_commits( + config_file_name=config_name, + commit=commit, + diffkemp_sha=diffkemp_sha, + ) + + if not results: + abort(404) + + result = results[0] + note = request.json["note"] + + result.note = note + repo.update(result) + return Response(status=200) + + +@api_bp.put( + "/versions////" + "/note" +) +def update_res_note_for_version( + config_name: str, + old_tag: str, + new_tag: str, + diffkemp_sha: str, +) -> Response: + """Update the result note for version result.""" + repo = ResultsRepo() + results = repo.get_versions( + config_file_name=config_name, + old_tag=old_tag, + new_tag=new_tag, + diffkemp_sha=diffkemp_sha, + ) + + if not results: + abort(404) + + result = results[0] + note = request.json["note"] + + result.note = note + repo.update(result) + return Response(status=200) diff --git a/backend/automation/web/dtos/results/commits.py b/backend/automation/web/dtos/results/commits.py index ceb9435..0f4b3c9 100644 --- a/backend/automation/web/dtos/results/commits.py +++ b/backend/automation/web/dtos/results/commits.py @@ -74,6 +74,7 @@ class ResultDTO: differences_url: Optional[str] viewer_url: Optional[str] commit_info_url: str + note: str @classmethod def create( @@ -129,6 +130,7 @@ def create( differences_url=differences_url, viewer_url=viewer_url, commit_info_url=commit_info_url, + note=result.note, ) def get_function_bg_class(self, function_name: str) -> str: diff --git a/backend/automation/web/dtos/results/versions.py b/backend/automation/web/dtos/results/versions.py index d940ec3..eac873b 100644 --- a/backend/automation/web/dtos/results/versions.py +++ b/backend/automation/web/dtos/results/versions.py @@ -44,6 +44,7 @@ class ResultDTO: result_url: str differences_url: Optional[str] viewer_url: Optional[str] + note: str @classmethod def create( @@ -101,6 +102,7 @@ def create( result_url=result_url, differences_url=differences_url, viewer_url=viewer_url, + note=result.note, ) def get_function_bg_class(self, function_name: str) -> str: diff --git a/backend/automation/web/templates/commits/result.html b/backend/automation/web/templates/commits/result.html index d7a27d6..0f92fbe 100644 --- a/backend/automation/web/templates/commits/result.html +++ b/backend/automation/web/templates/commits/result.html @@ -1,5 +1,5 @@ {% extends "commits/layout.html" %} -{% from "macros.html" import expected_fun_result_select %} +{% from "macros.html" import expected_fun_result_select, result_note_text_area %} {% block subcontent %}

Results

@@ -11,6 +11,17 @@

Results

  • Commit summary: {{ result.commit.summary }}
  • +{{ result_note_text_area( + result.note, + url_for( + 'api_bp.update_res_note_for_commit', + config_name=result.config_name, + commit=result.commit.SHA, + diffkemp_sha=result.diffkemp_sha, + ) + ) +}} +

    Compared functions

    diff --git a/backend/automation/web/templates/layout.html b/backend/automation/web/templates/layout.html index 7460984..acc17ff 100644 --- a/backend/automation/web/templates/layout.html +++ b/backend/automation/web/templates/layout.html @@ -112,7 +112,6 @@ // to the one set before it was changed by the user. // This is just in case error occurs in the backend. const currentExpected = selectElement.dataset.currentExpected; - console.log(currentExpected); fetch(url, { method: "PUT", headers: { @@ -126,7 +125,6 @@ // Success, update the current expected result selectElement.dataset.currentExpected = expectedResult; }).catch(() => { - console.log("here"); alert( "Error occurred while changing the expected result for function " + `'${selectElement.dataset.function}' from '${currentExpected}' to ` + @@ -137,6 +135,42 @@ selectElement.value = currentExpected; }); } + + function updateNoteForResult() { + const noteTextArea = document.getElementById('result-note'); + const nextNote = noteTextArea.value; + const url = noteTextArea.dataset.apiUrl; + const currentNote = noteTextArea.dataset.currentNote; + fetch(url, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ "note": nextNote}) + }).then((response) => { + if (!response.ok) { + throw new Error('Something went wrong'); + } + // Success, update the current note + noteTextArea.dataset.currentNote = nextNote; + }).catch(() => { + alert( + "Error occurred while updating note for the result.\n" + + "Please try it again." + ); + }); + } + + window.addEventListener('beforeunload', (e) => { + const noteTextArea = document.getElementById('result-note'); + if (noteTextArea) { + const hasUnsavedNote = (noteTextArea.value !== noteTextArea.dataset.currentNote); + if (hasUnsavedNote) { + e.preventDefault(); + e.returnValue = ""; + } + } + }); {% block scripts %}{% endblock %} diff --git a/backend/automation/web/templates/macros.html b/backend/automation/web/templates/macros.html index a561339..3e75947 100644 --- a/backend/automation/web/templates/macros.html +++ b/backend/automation/web/templates/macros.html @@ -25,3 +25,19 @@ {% endif %} {% endmacro %} + +{% macro result_note_text_area(note, api_url) %} +
    + Note: + + +
    +{% endmacro %} diff --git a/backend/automation/web/templates/versions/result.html b/backend/automation/web/templates/versions/result.html index 4d7bcf3..4b17443 100644 --- a/backend/automation/web/templates/versions/result.html +++ b/backend/automation/web/templates/versions/result.html @@ -1,5 +1,5 @@ {% extends "versions/layout.html" %} -{% from "macros.html" import expected_fun_result_select %} +{% from "macros.html" import expected_fun_result_select, result_note_text_area %} {% block subcontent %} @@ -11,6 +11,18 @@

    Results

  • New tag: {{ result.new_tag }}
  • +{{ result_note_text_area( + result.note, + url_for( + 'api_bp.update_res_note_for_version', + config_name=result.config_name, + old_tag=result.old_tag, + new_tag=result.new_tag, + diffkemp_sha=result.diffkemp_sha, + ) + ) +}} +

    Compared functions