From 602abc8574634aa0fdb6a2156740a660c3879bff Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Wed, 8 May 2024 14:04:18 +0300 Subject: [PATCH 01/26] Remove unnecessary artefactId dependency --- frontend/lib/providers/filtered_test_execution_ids.dart | 3 ++- .../lib/ui/artefact_page/artefact_build_expandable.dart | 9 ++------- .../test/providers/filtered_test_execution_ids_test.dart | 4 +--- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/frontend/lib/providers/filtered_test_execution_ids.dart b/frontend/lib/providers/filtered_test_execution_ids.dart index 65ad5063..493216fc 100644 --- a/frontend/lib/providers/filtered_test_execution_ids.dart +++ b/frontend/lib/providers/filtered_test_execution_ids.dart @@ -3,6 +3,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../models/filters.dart'; import '../models/test_execution.dart'; +import '../routing.dart'; import 'artefact_builds.dart'; part 'filtered_test_execution_ids.g.dart'; @@ -10,9 +11,9 @@ part 'filtered_test_execution_ids.g.dart'; @riverpod Set filteredTestExecutionIds( FilteredTestExecutionIdsRef ref, - int artefactId, Uri pageUri, ) { + final artefactId = AppRoutes.artefactIdFromUri(pageUri); final builds = ref.watch(artefactBuildsProvider(artefactId)).requireValue; final testExecutions = [ for (final build in builds) diff --git a/frontend/lib/ui/artefact_page/artefact_build_expandable.dart b/frontend/lib/ui/artefact_page/artefact_build_expandable.dart index 45f5fdf6..2ff30ee8 100644 --- a/frontend/lib/ui/artefact_page/artefact_build_expandable.dart +++ b/frontend/lib/ui/artefact_page/artefact_build_expandable.dart @@ -9,14 +9,9 @@ import '../spacing.dart'; import 'test_execution_expandable.dart'; class ArtefactBuildExpandable extends ConsumerWidget { - const ArtefactBuildExpandable({ - super.key, - required this.artefactBuild, - required this.artefactId, - }); + const ArtefactBuildExpandable({super.key, required this.artefactBuild}); final ArtefactBuild artefactBuild; - final int artefactId; @override Widget build(BuildContext context, WidgetRef ref) { @@ -24,7 +19,7 @@ class ArtefactBuildExpandable extends ConsumerWidget { final revisionText = artefactBuild.revision == null ? '' : ' (${artefactBuild.revision})'; final filteredTestExecutionIds = - ref.watch(filteredTestExecutionIdsProvider(artefactId, pageUri)); + ref.watch(filteredTestExecutionIdsProvider(pageUri)); final filteredTestExecutions = [ for (final te in artefactBuild.testExecutions) if (filteredTestExecutionIds.contains(te.id)) te, diff --git a/frontend/test/providers/filtered_test_execution_ids_test.dart b/frontend/test/providers/filtered_test_execution_ids_test.dart index c5d3764d..6f2e1f44 100644 --- a/frontend/test/providers/filtered_test_execution_ids_test.dart +++ b/frontend/test/providers/filtered_test_execution_ids_test.dart @@ -22,7 +22,7 @@ void main() { await container.read(artefactBuildsProvider(artefactId).future); final filteredTestExecutionIds = - container.read(filteredTestExecutionIdsProvider(artefactId, Uri())); + container.read(filteredTestExecutionIdsProvider(Uri())); final builds = await apiMock.getArtefactBuilds(artefactId); final allTestExecutionIds = { for (final build in builds) @@ -44,7 +44,6 @@ void main() { final filteredTestExecutionIds = container.read( filteredTestExecutionIdsProvider( - artefactId, Uri( queryParameters: { 'Review status': 'Undecided', @@ -68,7 +67,6 @@ void main() { final filteredTestExecutionIds = container.read( filteredTestExecutionIdsProvider( - artefactId, Uri( queryParameters: { 'Execution status': 'Failed', From 1cca514d2645346ccc108594ecfdf7d258fcd85f Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Wed, 8 May 2024 14:37:00 +0300 Subject: [PATCH 02/26] Add dummy RerunFilteredEnvironmentsButton --- .../ui/artefact_page/artefact_page_body.dart | 16 +++++++++--- .../rerun_filtered_environments_button.dart | 26 +++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 frontend/lib/ui/artefact_page/rerun_filtered_environments_button.dart diff --git a/frontend/lib/ui/artefact_page/artefact_page_body.dart b/frontend/lib/ui/artefact_page/artefact_page_body.dart index 15e53ffe..f7541588 100644 --- a/frontend/lib/ui/artefact_page/artefact_page_body.dart +++ b/frontend/lib/ui/artefact_page/artefact_page_body.dart @@ -7,6 +7,7 @@ import '../../providers/artefact_builds.dart'; import '../page_filters/page_filters.dart'; import '../spacing.dart'; import 'artefact_build_expandable.dart'; +import 'rerun_filtered_environments_button.dart'; class ArtefactPageBody extends ConsumerWidget { const ArtefactPageBody({super.key, required this.artefact}); @@ -28,9 +29,17 @@ class ArtefactPageBody extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: Spacing.level3), - Text( - 'Environments', - style: Theme.of(context).textTheme.headlineSmall, + Row( + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: [ + Text( + 'Environments', + style: Theme.of(context).textTheme.headlineSmall, + ), + const Spacer(), + const RerunFilteredEnvironmentsButton(), + ], ), Expanded( child: ListView.builder( @@ -40,7 +49,6 @@ class ArtefactPageBody extends ConsumerWidget { padding: const EdgeInsets.only(right: Spacing.level3), child: ArtefactBuildExpandable( artefactBuild: artefactBuilds[i], - artefactId: artefact.id, ), ), ), diff --git a/frontend/lib/ui/artefact_page/rerun_filtered_environments_button.dart b/frontend/lib/ui/artefact_page/rerun_filtered_environments_button.dart new file mode 100644 index 00000000..fdf7d789 --- /dev/null +++ b/frontend/lib/ui/artefact_page/rerun_filtered_environments_button.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; + +import '../../providers/filtered_test_execution_ids.dart'; + +class RerunFilteredEnvironmentsButton extends ConsumerWidget { + const RerunFilteredEnvironmentsButton({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final pageUri = GoRouterState.of(context).uri; + + final filteredTestExecutionCount = ref.watch( + filteredTestExecutionIdsProvider(pageUri).select((ids) => ids.length), + ); + + return TextButton( + onPressed: () {}, + child: Text( + 'Rerun $filteredTestExecutionCount Filtered Environments', + textScaler: const TextScaler.linear(1.2), + ), + ); + } +} From f878e2a46098ea4ed83b4d1f61028c7ad0c5134a Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Wed, 8 May 2024 11:42:44 +0000 Subject: [PATCH 03/26] Add artefact test executions rerun filter by environment name --- .../controllers/artefacts/artefacts.py | 4 ++++ .../controllers/artefacts/models.py | 1 + .../controllers/artefacts/test_artefacts.py | 20 +++++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/backend/test_observer/controllers/artefacts/artefacts.py b/backend/test_observer/controllers/artefacts/artefacts.py index fbefd221..f44b9368 100644 --- a/backend/test_observer/controllers/artefacts/artefacts.py +++ b/backend/test_observer/controllers/artefacts/artefacts.py @@ -166,6 +166,10 @@ def rerun_artefact_test_executions( test_executions = ( te for te in test_executions if set(te.review_decision) == decision ) + if environment := request.test_execution_environment_contains: + test_executions = ( + te for te in test_executions if environment in te.environment.name + ) for te in test_executions: get_or_create(db, TestExecutionRerunRequest, {"test_execution_id": te.id}) diff --git a/backend/test_observer/controllers/artefacts/models.py b/backend/test_observer/controllers/artefacts/models.py index 78469a27..2e35de3e 100644 --- a/backend/test_observer/controllers/artefacts/models.py +++ b/backend/test_observer/controllers/artefacts/models.py @@ -98,3 +98,4 @@ class ArtefactPatch(BaseModel): class RerunArtefactTestExecutionsRequest(BaseModel): test_execution_status: TestExecutionStatus | None = None test_execution_review_decision: set[TestExecutionReviewDecision] | None = None + test_execution_environment_contains: str | None = None diff --git a/backend/tests/controllers/artefacts/test_artefacts.py b/backend/tests/controllers/artefacts/test_artefacts.py index aaf50fb4..1cae928f 100644 --- a/backend/tests/controllers/artefacts/test_artefacts.py +++ b/backend/tests/controllers/artefacts/test_artefacts.py @@ -435,3 +435,23 @@ def test_rerun_filters_ignore_review_decisions_order( assert response.status_code == 200 assert test_execution.rerun_request + + +def test_rerun_filters_by_environment_name( + test_client: TestClient, generator: DataGenerator +): + a = generator.gen_artefact("candidate") + ab = generator.gen_artefact_build(a) + e1 = generator.gen_environment(name="laptop") + e2 = generator.gen_environment(name="server") + te1 = generator.gen_test_execution(ab, e1) + te2 = generator.gen_test_execution(ab, e2) + + response = test_client.post( + f"/v1/artefacts/{a.id}/reruns", + json={"test_execution_environment_contains": "serv"}, + ) + + assert response.status_code == 200 + assert te1.rerun_request is None + assert te2.rerun_request From 9939cd6cf954895248186d2623c30115f3537f11 Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Wed, 8 May 2024 11:48:55 +0000 Subject: [PATCH 04/26] Revert "Add dummy RerunFilteredEnvironmentsButton" This reverts commit 4df766535497f748356be8c1083c4aa8efd20d73. --- .../ui/artefact_page/artefact_page_body.dart | 16 +++--------- .../rerun_filtered_environments_button.dart | 26 ------------------- 2 files changed, 4 insertions(+), 38 deletions(-) delete mode 100644 frontend/lib/ui/artefact_page/rerun_filtered_environments_button.dart diff --git a/frontend/lib/ui/artefact_page/artefact_page_body.dart b/frontend/lib/ui/artefact_page/artefact_page_body.dart index f7541588..15e53ffe 100644 --- a/frontend/lib/ui/artefact_page/artefact_page_body.dart +++ b/frontend/lib/ui/artefact_page/artefact_page_body.dart @@ -7,7 +7,6 @@ import '../../providers/artefact_builds.dart'; import '../page_filters/page_filters.dart'; import '../spacing.dart'; import 'artefact_build_expandable.dart'; -import 'rerun_filtered_environments_button.dart'; class ArtefactPageBody extends ConsumerWidget { const ArtefactPageBody({super.key, required this.artefact}); @@ -29,17 +28,9 @@ class ArtefactPageBody extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: Spacing.level3), - Row( - crossAxisAlignment: CrossAxisAlignment.baseline, - textBaseline: TextBaseline.alphabetic, - children: [ - Text( - 'Environments', - style: Theme.of(context).textTheme.headlineSmall, - ), - const Spacer(), - const RerunFilteredEnvironmentsButton(), - ], + Text( + 'Environments', + style: Theme.of(context).textTheme.headlineSmall, ), Expanded( child: ListView.builder( @@ -49,6 +40,7 @@ class ArtefactPageBody extends ConsumerWidget { padding: const EdgeInsets.only(right: Spacing.level3), child: ArtefactBuildExpandable( artefactBuild: artefactBuilds[i], + artefactId: artefact.id, ), ), ), diff --git a/frontend/lib/ui/artefact_page/rerun_filtered_environments_button.dart b/frontend/lib/ui/artefact_page/rerun_filtered_environments_button.dart deleted file mode 100644 index fdf7d789..00000000 --- a/frontend/lib/ui/artefact_page/rerun_filtered_environments_button.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:go_router/go_router.dart'; - -import '../../providers/filtered_test_execution_ids.dart'; - -class RerunFilteredEnvironmentsButton extends ConsumerWidget { - const RerunFilteredEnvironmentsButton({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final pageUri = GoRouterState.of(context).uri; - - final filteredTestExecutionCount = ref.watch( - filteredTestExecutionIdsProvider(pageUri).select((ids) => ids.length), - ); - - return TextButton( - onPressed: () {}, - child: Text( - 'Rerun $filteredTestExecutionCount Filtered Environments', - textScaler: const TextScaler.linear(1.2), - ), - ); - } -} From 2013b05946f9bf68045985ef24b44488f584b0ec Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Wed, 8 May 2024 11:49:44 +0000 Subject: [PATCH 05/26] Revert "Add artefact test executions rerun filter by environment name" This reverts commit 48047717d2a81aa8bc9c69fa519abed69971f705. --- .../controllers/artefacts/artefacts.py | 4 ---- .../controllers/artefacts/models.py | 1 - .../controllers/artefacts/test_artefacts.py | 20 ------------------- 3 files changed, 25 deletions(-) diff --git a/backend/test_observer/controllers/artefacts/artefacts.py b/backend/test_observer/controllers/artefacts/artefacts.py index f44b9368..fbefd221 100644 --- a/backend/test_observer/controllers/artefacts/artefacts.py +++ b/backend/test_observer/controllers/artefacts/artefacts.py @@ -166,10 +166,6 @@ def rerun_artefact_test_executions( test_executions = ( te for te in test_executions if set(te.review_decision) == decision ) - if environment := request.test_execution_environment_contains: - test_executions = ( - te for te in test_executions if environment in te.environment.name - ) for te in test_executions: get_or_create(db, TestExecutionRerunRequest, {"test_execution_id": te.id}) diff --git a/backend/test_observer/controllers/artefacts/models.py b/backend/test_observer/controllers/artefacts/models.py index 2e35de3e..78469a27 100644 --- a/backend/test_observer/controllers/artefacts/models.py +++ b/backend/test_observer/controllers/artefacts/models.py @@ -98,4 +98,3 @@ class ArtefactPatch(BaseModel): class RerunArtefactTestExecutionsRequest(BaseModel): test_execution_status: TestExecutionStatus | None = None test_execution_review_decision: set[TestExecutionReviewDecision] | None = None - test_execution_environment_contains: str | None = None diff --git a/backend/tests/controllers/artefacts/test_artefacts.py b/backend/tests/controllers/artefacts/test_artefacts.py index 1cae928f..aaf50fb4 100644 --- a/backend/tests/controllers/artefacts/test_artefacts.py +++ b/backend/tests/controllers/artefacts/test_artefacts.py @@ -435,23 +435,3 @@ def test_rerun_filters_ignore_review_decisions_order( assert response.status_code == 200 assert test_execution.rerun_request - - -def test_rerun_filters_by_environment_name( - test_client: TestClient, generator: DataGenerator -): - a = generator.gen_artefact("candidate") - ab = generator.gen_artefact_build(a) - e1 = generator.gen_environment(name="laptop") - e2 = generator.gen_environment(name="server") - te1 = generator.gen_test_execution(ab, e1) - te2 = generator.gen_test_execution(ab, e2) - - response = test_client.post( - f"/v1/artefacts/{a.id}/reruns", - json={"test_execution_environment_contains": "serv"}, - ) - - assert response.status_code == 200 - assert te1.rerun_request is None - assert te2.rerun_request From 67692027650d1013802ed09b1593af527a50e614 Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Wed, 8 May 2024 12:04:04 +0000 Subject: [PATCH 06/26] Revert "Revert "Add dummy RerunFilteredEnvironmentsButton"" This reverts commit b25cf3646d8a79b1680253d78ab89fbebc651e8c. --- .../ui/artefact_page/artefact_page_body.dart | 16 +++++++++--- .../rerun_filtered_environments_button.dart | 26 +++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 frontend/lib/ui/artefact_page/rerun_filtered_environments_button.dart diff --git a/frontend/lib/ui/artefact_page/artefact_page_body.dart b/frontend/lib/ui/artefact_page/artefact_page_body.dart index 15e53ffe..f7541588 100644 --- a/frontend/lib/ui/artefact_page/artefact_page_body.dart +++ b/frontend/lib/ui/artefact_page/artefact_page_body.dart @@ -7,6 +7,7 @@ import '../../providers/artefact_builds.dart'; import '../page_filters/page_filters.dart'; import '../spacing.dart'; import 'artefact_build_expandable.dart'; +import 'rerun_filtered_environments_button.dart'; class ArtefactPageBody extends ConsumerWidget { const ArtefactPageBody({super.key, required this.artefact}); @@ -28,9 +29,17 @@ class ArtefactPageBody extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: Spacing.level3), - Text( - 'Environments', - style: Theme.of(context).textTheme.headlineSmall, + Row( + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: [ + Text( + 'Environments', + style: Theme.of(context).textTheme.headlineSmall, + ), + const Spacer(), + const RerunFilteredEnvironmentsButton(), + ], ), Expanded( child: ListView.builder( @@ -40,7 +49,6 @@ class ArtefactPageBody extends ConsumerWidget { padding: const EdgeInsets.only(right: Spacing.level3), child: ArtefactBuildExpandable( artefactBuild: artefactBuilds[i], - artefactId: artefact.id, ), ), ), diff --git a/frontend/lib/ui/artefact_page/rerun_filtered_environments_button.dart b/frontend/lib/ui/artefact_page/rerun_filtered_environments_button.dart new file mode 100644 index 00000000..fdf7d789 --- /dev/null +++ b/frontend/lib/ui/artefact_page/rerun_filtered_environments_button.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; + +import '../../providers/filtered_test_execution_ids.dart'; + +class RerunFilteredEnvironmentsButton extends ConsumerWidget { + const RerunFilteredEnvironmentsButton({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final pageUri = GoRouterState.of(context).uri; + + final filteredTestExecutionCount = ref.watch( + filteredTestExecutionIdsProvider(pageUri).select((ids) => ids.length), + ); + + return TextButton( + onPressed: () {}, + child: Text( + 'Rerun $filteredTestExecutionCount Filtered Environments', + textScaler: const TextScaler.linear(1.2), + ), + ); + } +} From 905ba9a5769927dbca6261ff2b08623854d6846c Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Wed, 8 May 2024 12:55:33 +0000 Subject: [PATCH 07/26] Revert "Revert "Add artefact test executions rerun filter by environment name"" This reverts commit b72ed72a6debdcb172f3d11dd0f29a997855686b. --- .../controllers/artefacts/artefacts.py | 4 ++++ .../controllers/artefacts/models.py | 1 + .../controllers/artefacts/test_artefacts.py | 20 +++++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/backend/test_observer/controllers/artefacts/artefacts.py b/backend/test_observer/controllers/artefacts/artefacts.py index fbefd221..f44b9368 100644 --- a/backend/test_observer/controllers/artefacts/artefacts.py +++ b/backend/test_observer/controllers/artefacts/artefacts.py @@ -166,6 +166,10 @@ def rerun_artefact_test_executions( test_executions = ( te for te in test_executions if set(te.review_decision) == decision ) + if environment := request.test_execution_environment_contains: + test_executions = ( + te for te in test_executions if environment in te.environment.name + ) for te in test_executions: get_or_create(db, TestExecutionRerunRequest, {"test_execution_id": te.id}) diff --git a/backend/test_observer/controllers/artefacts/models.py b/backend/test_observer/controllers/artefacts/models.py index 78469a27..2e35de3e 100644 --- a/backend/test_observer/controllers/artefacts/models.py +++ b/backend/test_observer/controllers/artefacts/models.py @@ -98,3 +98,4 @@ class ArtefactPatch(BaseModel): class RerunArtefactTestExecutionsRequest(BaseModel): test_execution_status: TestExecutionStatus | None = None test_execution_review_decision: set[TestExecutionReviewDecision] | None = None + test_execution_environment_contains: str | None = None diff --git a/backend/tests/controllers/artefacts/test_artefacts.py b/backend/tests/controllers/artefacts/test_artefacts.py index aaf50fb4..1cae928f 100644 --- a/backend/tests/controllers/artefacts/test_artefacts.py +++ b/backend/tests/controllers/artefacts/test_artefacts.py @@ -435,3 +435,23 @@ def test_rerun_filters_ignore_review_decisions_order( assert response.status_code == 200 assert test_execution.rerun_request + + +def test_rerun_filters_by_environment_name( + test_client: TestClient, generator: DataGenerator +): + a = generator.gen_artefact("candidate") + ab = generator.gen_artefact_build(a) + e1 = generator.gen_environment(name="laptop") + e2 = generator.gen_environment(name="server") + te1 = generator.gen_test_execution(ab, e1) + te2 = generator.gen_test_execution(ab, e2) + + response = test_client.post( + f"/v1/artefacts/{a.id}/reruns", + json={"test_execution_environment_contains": "serv"}, + ) + + assert response.status_code == 200 + assert te1.rerun_request is None + assert te2.rerun_request From 64acc7f898ef753629fa266381cc1a9bd003f0b5 Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Thu, 9 May 2024 08:15:41 +0000 Subject: [PATCH 08/26] extend test executions rerun endpoint to rerun multiple --- .../controllers/test_executions/models.py | 2 +- .../controllers/test_executions/reruns.py | 11 +++-- .../test_executions/test_reruns.py | 40 +++++++++++++++---- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/backend/test_observer/controllers/test_executions/models.py b/backend/test_observer/controllers/test_executions/models.py index 35610cca..36df1821 100644 --- a/backend/test_observer/controllers/test_executions/models.py +++ b/backend/test_observer/controllers/test_executions/models.py @@ -157,7 +157,7 @@ class TestResultDTO(BaseModel): class RerunRequest(BaseModel): - test_execution_id: int + test_execution_ids: set[int] class PendingRerun(BaseModel): diff --git a/backend/test_observer/controllers/test_executions/reruns.py b/backend/test_observer/controllers/test_executions/reruns.py index 08a78db9..f097204e 100644 --- a/backend/test_observer/controllers/test_executions/reruns.py +++ b/backend/test_observer/controllers/test_executions/reruns.py @@ -18,10 +18,15 @@ @router.post("/reruns") -def create_a_rerun_request(request: RerunRequest, db: Session = Depends(get_db)): - te = db.get(TestExecution, request.test_execution_id) +def create_rerun_requests(request: RerunRequest, db: Session = Depends(get_db)): + for test_execution_id in request.test_execution_ids: + _create_rerun_request(test_execution_id, db) + + +def _create_rerun_request(test_execution_id: int, db: Session) -> None: + te = db.get(TestExecution, test_execution_id) if not te: - msg = f"No test execution with id {request.test_execution_id} found" + msg = f"No test execution with id {test_execution_id} found" raise HTTPException(status_code=404, detail=msg) get_or_create(db, TestExecutionRerunRequest, {"test_execution_id": te.id}) diff --git a/backend/tests/controllers/test_executions/test_reruns.py b/backend/tests/controllers/test_executions/test_reruns.py index 0f985ef8..903e13b5 100644 --- a/backend/tests/controllers/test_executions/test_reruns.py +++ b/backend/tests/controllers/test_executions/test_reruns.py @@ -45,13 +45,13 @@ def test_post_no_data_returns_422(post: Post): def test_post_invalid_id_returns_404_with_message(post: Post): - response = post({"test_execution_id": 1}) + response = post({"test_execution_ids": [1]}) assert response.status_code == 404 assert response.json()["detail"] == "No test execution with id 1 found" def test_valid_post_returns_200(post: Post, test_execution: TestExecution): - assert post({"test_execution_id": test_execution.id}).status_code == 200 + assert post({"test_execution_ids": [test_execution.id]}).status_code == 200 def test_get_returns_200_with_empty_list(get: Get): @@ -63,7 +63,7 @@ def test_get_returns_200_with_empty_list(get: Get): def test_get_after_one_post(get: Get, post: Post, test_execution: TestExecution): test_execution.ci_link = "ci.link" - post({"test_execution_id": test_execution.id}) + post({"test_execution_ids": [test_execution.id]}) assert get().json() == [ { @@ -79,8 +79,8 @@ def test_get_after_two_identical_posts( ): test_execution.ci_link = "ci.link" - post({"test_execution_id": test_execution.id}) - post({"test_execution_id": test_execution.id}) + post({"test_execution_ids": [test_execution.id]}) + post({"test_execution_ids": [test_execution.id]}) assert get().json() == [ { @@ -100,8 +100,34 @@ def test_get_after_two_different_posts( e2 = generator.gen_environment("desktop") te2 = generator.gen_test_execution(te1.artefact_build, e2, ci_link="ci2.link") - post({"test_execution_id": te1.id}) - post({"test_execution_id": te2.id}) + post({"test_execution_ids": [te1.id]}) + post({"test_execution_ids": [te2.id]}) + + assert get().json() == [ + { + "test_execution_id": te1.id, + "ci_link": te1.ci_link, + "family": te1.artefact_build.artefact.stage.family.name, + }, + { + "test_execution_id": te2.id, + "ci_link": te2.ci_link, + "family": te2.artefact_build.artefact.stage.family.name, + }, + ] + + +def test_get_after_post_with_two_test_execution_ids( + get: Get, post: Post, generator: DataGenerator +): + a = generator.gen_artefact("beta") + ab = generator.gen_artefact_build(a) + e1 = generator.gen_environment("e1") + e2 = generator.gen_environment("e2") + te1 = generator.gen_test_execution(ab, e1, ci_link="ci1.link") + te2 = generator.gen_test_execution(ab, e2, ci_link="ci2.link") + + post({"test_execution_ids": [te1.id, te2.id]}) assert get().json() == [ { From d09742fe753f83fa5a21ced5bcb3d1b4eec66f88 Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Thu, 9 May 2024 08:17:04 +0000 Subject: [PATCH 09/26] remove artefact rerun endpoint as it's not necessary --- .../controllers/artefacts/artefacts.py | 27 ----- .../controllers/artefacts/test_artefacts.py | 112 ------------------ 2 files changed, 139 deletions(-) diff --git a/backend/test_observer/controllers/artefacts/artefacts.py b/backend/test_observer/controllers/artefacts/artefacts.py index f44b9368..aa9bebc1 100644 --- a/backend/test_observer/controllers/artefacts/artefacts.py +++ b/backend/test_observer/controllers/artefacts/artefacts.py @@ -146,30 +146,3 @@ def get_artefact_builds(artefact_id: int, db: Session = Depends(get_db)): ) return latest_builds - - -@router.post("/{artefact_id}/reruns") -def rerun_artefact_test_executions( - request: RerunArtefactTestExecutionsRequest | None = None, - artefact: Artefact = Depends(_get_artefact_from_db), - db: Session = Depends(get_db), -): - latest_builds = db.scalars( - queries.latest_artefact_builds.where(ArtefactBuild.artefact_id == artefact.id) - ) - test_executions = (te for ab in latest_builds for te in ab.test_executions) - - if request: - if status := request.test_execution_status: - test_executions = (te for te in test_executions if te.status == status) - if (decision := request.test_execution_review_decision) is not None: - test_executions = ( - te for te in test_executions if set(te.review_decision) == decision - ) - if environment := request.test_execution_environment_contains: - test_executions = ( - te for te in test_executions if environment in te.environment.name - ) - - for te in test_executions: - get_or_create(db, TestExecutionRerunRequest, {"test_execution_id": te.id}) diff --git a/backend/tests/controllers/artefacts/test_artefacts.py b/backend/tests/controllers/artefacts/test_artefacts.py index 1cae928f..c9a3cf72 100644 --- a/backend/tests/controllers/artefacts/test_artefacts.py +++ b/backend/tests/controllers/artefacts/test_artefacts.py @@ -343,115 +343,3 @@ def test_artefact_signoff_ignore_old_build_on_reject( ) assert response.status_code == 400 - - -def test_rerun_all_artefact_test_executions( - test_client: TestClient, test_execution: TestExecution -): - artefact_id = test_execution.artefact_build.artefact_id - - response = test_client.post(f"/v1/artefacts/{artefact_id}/reruns") - - assert response.status_code == 200 - assert test_execution.rerun_request - - -def test_rerun_skips_test_executions_of_old_builds( - test_client: TestClient, generator: DataGenerator -): - a = generator.gen_artefact("candidate") - ab1 = generator.gen_artefact_build(a, revision=1) - ab2 = generator.gen_artefact_build(a, revision=2) - e = generator.gen_environment() - te1 = generator.gen_test_execution(ab1, e) - te2 = generator.gen_test_execution(ab2, e) - - response = test_client.post(f"/v1/artefacts/{a.id}/reruns") - - assert response.status_code == 200 - assert te1.rerun_request is None - assert te2.rerun_request - - -def test_rerun_failed_artefact_test_executions( - test_client: TestClient, generator: DataGenerator -): - a = generator.gen_artefact("candidate") - ab = generator.gen_artefact_build(a) - e1 = generator.gen_environment(name="laptop") - e2 = generator.gen_environment(name="server") - te1 = generator.gen_test_execution(ab, e1) - te2 = generator.gen_test_execution(ab, e2, status=TestExecutionStatus.FAILED) - - response = test_client.post( - f"/v1/artefacts/{a.id}/reruns", - json={"test_execution_status": TestExecutionStatus.FAILED}, - ) - - assert response.status_code == 200 - assert te1.rerun_request is None - assert te2.rerun_request - - -def test_rerun_undecided_artefact_test_executions( - test_client: TestClient, generator: DataGenerator -): - a = generator.gen_artefact("candidate") - ab = generator.gen_artefact_build(a) - e1 = generator.gen_environment(name="laptop") - e2 = generator.gen_environment(name="server") - te1 = generator.gen_test_execution( - ab, e1, review_decision=[TestExecutionReviewDecision.APPROVED_ALL_TESTS_PASS] - ) - te2 = generator.gen_test_execution(ab, e2, review_decision=[]) - - response = test_client.post( - f"/v1/artefacts/{a.id}/reruns", - json={"test_execution_review_decision": []}, - ) - - assert response.status_code == 200 - assert te1.rerun_request is None - assert te2.rerun_request - - -def test_rerun_filters_ignore_review_decisions_order( - test_client: TestClient, test_execution: TestExecution -): - test_execution.review_decision = [ - TestExecutionReviewDecision.APPROVED_INCONSISTENT_TEST, - TestExecutionReviewDecision.APPROVED_FAULTY_HARDWARE, - ] - - response = test_client.post( - f"/v1/artefacts/{test_execution.artefact_build.artefact_id}/reruns", - json={ - "test_execution_review_decision": [ - TestExecutionReviewDecision.APPROVED_FAULTY_HARDWARE, - TestExecutionReviewDecision.APPROVED_INCONSISTENT_TEST, - ] - }, - ) - - assert response.status_code == 200 - assert test_execution.rerun_request - - -def test_rerun_filters_by_environment_name( - test_client: TestClient, generator: DataGenerator -): - a = generator.gen_artefact("candidate") - ab = generator.gen_artefact_build(a) - e1 = generator.gen_environment(name="laptop") - e2 = generator.gen_environment(name="server") - te1 = generator.gen_test_execution(ab, e1) - te2 = generator.gen_test_execution(ab, e2) - - response = test_client.post( - f"/v1/artefacts/{a.id}/reruns", - json={"test_execution_environment_contains": "serv"}, - ) - - assert response.status_code == 200 - assert te1.rerun_request is None - assert te2.rerun_request From 497db31394bdf8c42aaf028ab31c950a35c021f7 Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Thu, 9 May 2024 08:29:25 +0000 Subject: [PATCH 10/26] fix seed script --- backend/scripts/seed_data.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/scripts/seed_data.py b/backend/scripts/seed_data.py index a1c97e92..a3657a7d 100644 --- a/backend/scripts/seed_data.py +++ b/backend/scripts/seed_data.py @@ -315,11 +315,11 @@ def seed_data(client: TestClient | requests.Session, session: Session | None = N def _rerun_some_test_executions( client: TestClient | requests.Session, test_executions: list[dict] ) -> None: - for te in test_executions[::2]: - client.post( - RERUN_TEST_EXECUTION_URL, - json={"test_execution_id": te["id"]}, - ).raise_for_status() + te_ids = [te["id"] for te in test_executions[::2]] + client.post( + RERUN_TEST_EXECUTION_URL, + json={"test_execution_ids": te_ids}, + ).raise_for_status() def _add_bugurl_and_duedate(session: Session) -> None: From 021bcc6b65871caef97171da4aee7939292f1282 Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Thu, 9 May 2024 11:34:51 +0300 Subject: [PATCH 11/26] update test executions rerun endpoint --- frontend/lib/providers/artefact_builds.dart | 16 ++++++++-------- frontend/lib/repositories/api_repository.dart | 4 ++-- .../artefact_page/test_execution_expandable.dart | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/frontend/lib/providers/artefact_builds.dart b/frontend/lib/providers/artefact_builds.dart index 399a7f6f..527c5360 100644 --- a/frontend/lib/providers/artefact_builds.dart +++ b/frontend/lib/providers/artefact_builds.dart @@ -26,21 +26,21 @@ class ArtefactBuilds extends _$ArtefactBuilds { reviewComment, ); - await _updateTestExecution(testExecutionId, (_) => testExecution); + await _updateTestExecutions({testExecutionId}, (_) => testExecution); } - Future rerunTestExecution(int testExecutionId) async { + Future rerunTestExecutions(Set testExecutionIds) async { final api = ref.read(apiProvider); - await api.rerunTestExecution(testExecutionId); + await api.rerunTestExecutions(testExecutionIds); - await _updateTestExecution( - testExecutionId, + await _updateTestExecutions( + testExecutionIds, (te) => te.copyWith(isRerunRequested: true), ); } - Future _updateTestExecution( - int testExecutionId, + Future _updateTestExecutions( + Set testExecutionIds, TestExecution Function(TestExecution) update, ) async { final artefactBuilds = await future; @@ -48,7 +48,7 @@ class ArtefactBuilds extends _$ArtefactBuilds { for (final ab in artefactBuilds) { final newTestExecutions = [ for (final te in ab.testExecutions) - if (te.id == testExecutionId) update(te) else te, + if (testExecutionIds.contains(te.id)) update(te) else te, ]; newArtefactBuilds.add(ab.copyWith(testExecutions: newTestExecutions)); diff --git a/frontend/lib/repositories/api_repository.dart b/frontend/lib/repositories/api_repository.dart index a08d0840..57d5d3bc 100644 --- a/frontend/lib/repositories/api_repository.dart +++ b/frontend/lib/repositories/api_repository.dart @@ -65,10 +65,10 @@ class ApiRepository { return testResults; } - Future rerunTestExecution(int testExecutionId) async { + Future rerunTestExecutions(Set testExecutionIds) async { await dio.post( '/v1/test-executions/reruns', - data: {'test_execution_id': testExecutionId}, + data: {'test_execution_ids': testExecutionIds.toList()}, ); } } diff --git a/frontend/lib/ui/artefact_page/test_execution_expandable.dart b/frontend/lib/ui/artefact_page/test_execution_expandable.dart index 684feebb..92b66c46 100644 --- a/frontend/lib/ui/artefact_page/test_execution_expandable.dart +++ b/frontend/lib/ui/artefact_page/test_execution_expandable.dart @@ -107,7 +107,7 @@ class _RerunButton extends ConsumerWidget { onPressed: () { ref .read(artefactBuildsProvider(artefactId).notifier) - .rerunTestExecution(testExecution.id); + .rerunTestExecutions({testExecution.id}); context.pop(); }, child: const Text('yes'), From 1a083a3a0cb191a29acfc0242ccaeff01aa0183b Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Thu, 9 May 2024 08:38:20 +0000 Subject: [PATCH 12/26] remove unused imports --- backend/test_observer/controllers/artefacts/artefacts.py | 4 +--- backend/tests/controllers/artefacts/test_artefacts.py | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/backend/test_observer/controllers/artefacts/artefacts.py b/backend/test_observer/controllers/artefacts/artefacts.py index aa9bebc1..eb8f0348 100644 --- a/backend/test_observer/controllers/artefacts/artefacts.py +++ b/backend/test_observer/controllers/artefacts/artefacts.py @@ -25,10 +25,9 @@ Artefact, ArtefactBuild, TestExecution, - TestExecutionRerunRequest, ) from test_observer.data_access.models_enums import ArtefactStatus, FamilyName -from test_observer.data_access.repository import get_artefacts_by_family, get_or_create +from test_observer.data_access.repository import get_artefacts_by_family from test_observer.data_access.setup import get_db from .logic import ( @@ -39,7 +38,6 @@ ArtefactBuildDTO, ArtefactDTO, ArtefactPatch, - RerunArtefactTestExecutionsRequest, ) router = APIRouter(tags=["artefacts"]) diff --git a/backend/tests/controllers/artefacts/test_artefacts.py b/backend/tests/controllers/artefacts/test_artefacts.py index c9a3cf72..308f9ea9 100644 --- a/backend/tests/controllers/artefacts/test_artefacts.py +++ b/backend/tests/controllers/artefacts/test_artefacts.py @@ -25,7 +25,6 @@ from test_observer.data_access.models_enums import ( ArtefactStatus, TestExecutionReviewDecision, - TestExecutionStatus, ) from tests.data_generator import DataGenerator From 8a5c80b4913467c6640edae75a18158add23e3fe Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Thu, 9 May 2024 12:40:34 +0300 Subject: [PATCH 13/26] fix tests --- .../filtered_test_execution_ids_test.dart | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/frontend/test/providers/filtered_test_execution_ids_test.dart b/frontend/test/providers/filtered_test_execution_ids_test.dart index 6f2e1f44..f8d2051c 100644 --- a/frontend/test/providers/filtered_test_execution_ids_test.dart +++ b/frontend/test/providers/filtered_test_execution_ids_test.dart @@ -21,8 +21,9 @@ void main() { // Wait on artefact builds to load cause test execution filters uses requireValue await container.read(artefactBuildsProvider(artefactId).future); - final filteredTestExecutionIds = - container.read(filteredTestExecutionIdsProvider(Uri())); + final filteredTestExecutionIds = container.read( + filteredTestExecutionIdsProvider(Uri.parse('/snaps/$artefactId')), + ); final builds = await apiMock.getArtefactBuilds(artefactId); final allTestExecutionIds = { for (final build in builds) @@ -44,11 +45,7 @@ void main() { final filteredTestExecutionIds = container.read( filteredTestExecutionIdsProvider( - Uri( - queryParameters: { - 'Review status': 'Undecided', - }, - ), + Uri.parse('/snaps/$artefactId?Review+status=Undecided'), ), ); @@ -67,11 +64,7 @@ void main() { final filteredTestExecutionIds = container.read( filteredTestExecutionIdsProvider( - Uri( - queryParameters: { - 'Execution status': 'Failed', - }, - ), + Uri.parse('/snaps/$artefactId?Execution+status=Failed'), ), ); From 0537f25ad1755d24f6dc8670d5caab44aa2672a8 Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Thu, 9 May 2024 13:06:32 +0300 Subject: [PATCH 14/26] Show filtered test executions confirmation dialog --- .../providers/filtered_test_executions.dart | 23 ++++++++++ .../rerun_filtered_environments_button.dart | 46 ++++++++++++++++--- 2 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 frontend/lib/providers/filtered_test_executions.dart diff --git a/frontend/lib/providers/filtered_test_executions.dart b/frontend/lib/providers/filtered_test_executions.dart new file mode 100644 index 00000000..ddac39b1 --- /dev/null +++ b/frontend/lib/providers/filtered_test_executions.dart @@ -0,0 +1,23 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import 'artefact_builds.dart'; + +import '../models/test_execution.dart'; +import '../routing.dart'; +import 'filtered_test_execution_ids.dart'; + +part 'filtered_test_executions.g.dart'; + +@riverpod +List filteredTestExecutions( + FilteredTestExecutionsRef ref, Uri pageUri) { + final artefactId = AppRoutes.artefactIdFromUri(pageUri); + final builds = ref.watch(artefactBuildsProvider(artefactId)).requireValue; + final filteredTestExecutionIds = + ref.watch(filteredTestExecutionIdsProvider(pageUri)); + return [ + for (final build in builds) + for (final te in build.testExecutions) + if (filteredTestExecutionIds.contains(te.id)) te, + ]; +} diff --git a/frontend/lib/ui/artefact_page/rerun_filtered_environments_button.dart b/frontend/lib/ui/artefact_page/rerun_filtered_environments_button.dart index fdf7d789..c4bb7b23 100644 --- a/frontend/lib/ui/artefact_page/rerun_filtered_environments_button.dart +++ b/frontend/lib/ui/artefact_page/rerun_filtered_environments_button.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:intersperse/intersperse.dart'; -import '../../providers/filtered_test_execution_ids.dart'; +import '../../providers/filtered_test_executions.dart'; +import '../spacing.dart'; class RerunFilteredEnvironmentsButton extends ConsumerWidget { const RerunFilteredEnvironmentsButton({super.key}); @@ -11,14 +13,46 @@ class RerunFilteredEnvironmentsButton extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final pageUri = GoRouterState.of(context).uri; - final filteredTestExecutionCount = ref.watch( - filteredTestExecutionIdsProvider(pageUri).select((ids) => ids.length), - ); + final filteredTestExecutions = ref + .watch( + filteredTestExecutionsProvider(pageUri), + ) + .toList(); + + handlePress() => showDialog( + context: context, + builder: (_) => AlertDialog( + scrollable: true, + title: Text( + 'Are you sure you want to rerun the following' + ' ${filteredTestExecutions.length} environments?', + ), + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: filteredTestExecutions + .map((te) => Text(te.environment.name)) + .intersperse(const SizedBox(height: Spacing.level2)) + .toList(), + ), + actions: [ + TextButton( + onPressed: () { + context.pop(); + }, + child: const Text('yes'), + ), + TextButton( + onPressed: () => context.pop(), + child: const Text('no'), + ), + ], + ), + ); return TextButton( - onPressed: () {}, + onPressed: handlePress, child: Text( - 'Rerun $filteredTestExecutionCount Filtered Environments', + 'Rerun ${filteredTestExecutions.length} Filtered Environments', textScaler: const TextScaler.linear(1.2), ), ); From c246e1ddb64997351a21c6c6a7c2a54a0c288e4b Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Thu, 9 May 2024 13:16:27 +0300 Subject: [PATCH 15/26] Remove filteredTestExecutionIds provider --- .../filtered_test_execution_ids.dart | 43 ------------------- .../providers/filtered_test_executions.dart | 35 ++++++++++++--- .../artefact_build_expandable.dart | 10 ++--- ...art => filtered_test_executions_test.dart} | 36 ++++++++++------ 4 files changed, 55 insertions(+), 69 deletions(-) delete mode 100644 frontend/lib/providers/filtered_test_execution_ids.dart rename frontend/test/providers/{filtered_test_execution_ids_test.dart => filtered_test_executions_test.dart} (78%) diff --git a/frontend/lib/providers/filtered_test_execution_ids.dart b/frontend/lib/providers/filtered_test_execution_ids.dart deleted file mode 100644 index 493216fc..00000000 --- a/frontend/lib/providers/filtered_test_execution_ids.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:dartx/dartx.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; - -import '../models/filters.dart'; -import '../models/test_execution.dart'; -import '../routing.dart'; -import 'artefact_builds.dart'; - -part 'filtered_test_execution_ids.g.dart'; - -@riverpod -Set filteredTestExecutionIds( - FilteredTestExecutionIdsRef ref, - Uri pageUri, -) { - final artefactId = AppRoutes.artefactIdFromUri(pageUri); - final builds = ref.watch(artefactBuildsProvider(artefactId)).requireValue; - final testExecutions = [ - for (final build in builds) - for (final testExecution in build.testExecutions) testExecution, - ]; - final filters = - emptyTestExecutionFilters.copyWithQueryParams(pageUri.queryParametersAll); - final searchValue = pageUri.queryParameters['q'] ?? ''; - - return testExecutions - .filter( - (testExecution) => - _testExecutionPassesSearch(testExecution, searchValue) && - filters.doesObjectPassFilters(testExecution), - ) - .map((te) => te.id) - .toSet(); -} - -bool _testExecutionPassesSearch( - TestExecution testExecution, - String searchValue, -) { - return testExecution.environment.name - .toLowerCase() - .contains(searchValue.toLowerCase()); -} diff --git a/frontend/lib/providers/filtered_test_executions.dart b/frontend/lib/providers/filtered_test_executions.dart index ddac39b1..7ae9159b 100644 --- a/frontend/lib/providers/filtered_test_executions.dart +++ b/frontend/lib/providers/filtered_test_executions.dart @@ -1,23 +1,44 @@ +import 'package:dartx/dartx.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; +import '../models/filters.dart'; import 'artefact_builds.dart'; import '../models/test_execution.dart'; import '../routing.dart'; -import 'filtered_test_execution_ids.dart'; part 'filtered_test_executions.g.dart'; @riverpod List filteredTestExecutions( - FilteredTestExecutionsRef ref, Uri pageUri) { + FilteredTestExecutionsRef ref, + Uri pageUri, +) { final artefactId = AppRoutes.artefactIdFromUri(pageUri); + final filters = + emptyTestExecutionFilters.copyWithQueryParams(pageUri.queryParametersAll); + final searchValue = pageUri.queryParameters['q'] ?? ''; + final builds = ref.watch(artefactBuildsProvider(artefactId)).requireValue; - final filteredTestExecutionIds = - ref.watch(filteredTestExecutionIdsProvider(pageUri)); - return [ + final testExecutions = [ for (final build in builds) - for (final te in build.testExecutions) - if (filteredTestExecutionIds.contains(te.id)) te, + for (final testExecution in build.testExecutions) testExecution, ]; + + return testExecutions + .filter( + (testExecution) => + _testExecutionPassesSearch(testExecution, searchValue) && + filters.doesObjectPassFilters(testExecution), + ) + .toList(); +} + +bool _testExecutionPassesSearch( + TestExecution testExecution, + String searchValue, +) { + return testExecution.environment.name + .toLowerCase() + .contains(searchValue.toLowerCase()); } diff --git a/frontend/lib/ui/artefact_page/artefact_build_expandable.dart b/frontend/lib/ui/artefact_page/artefact_build_expandable.dart index 2ff30ee8..50b698c9 100644 --- a/frontend/lib/ui/artefact_page/artefact_build_expandable.dart +++ b/frontend/lib/ui/artefact_page/artefact_build_expandable.dart @@ -4,7 +4,7 @@ import 'package:go_router/go_router.dart'; import 'package:intersperse/intersperse.dart'; import '../../models/artefact_build.dart'; -import '../../providers/filtered_test_execution_ids.dart'; +import '../../providers/filtered_test_executions.dart'; import '../spacing.dart'; import 'test_execution_expandable.dart'; @@ -18,12 +18,8 @@ class ArtefactBuildExpandable extends ConsumerWidget { final pageUri = GoRouterState.of(context).uri; final revisionText = artefactBuild.revision == null ? '' : ' (${artefactBuild.revision})'; - final filteredTestExecutionIds = - ref.watch(filteredTestExecutionIdsProvider(pageUri)); - final filteredTestExecutions = [ - for (final te in artefactBuild.testExecutions) - if (filteredTestExecutionIds.contains(te.id)) te, - ]; + final filteredTestExecutions = + ref.watch(filteredTestExecutionsProvider(pageUri)); return ExpansionTile( initiallyExpanded: true, diff --git a/frontend/test/providers/filtered_test_execution_ids_test.dart b/frontend/test/providers/filtered_test_executions_test.dart similarity index 78% rename from frontend/test/providers/filtered_test_execution_ids_test.dart rename to frontend/test/providers/filtered_test_executions_test.dart index f8d2051c..1feda2c8 100644 --- a/frontend/test/providers/filtered_test_execution_ids_test.dart +++ b/frontend/test/providers/filtered_test_executions_test.dart @@ -4,7 +4,7 @@ import 'package:testcase_dashboard/models/artefact_build.dart'; import 'package:testcase_dashboard/models/test_execution.dart'; import 'package:testcase_dashboard/providers/api.dart'; import 'package:testcase_dashboard/providers/artefact_builds.dart'; -import 'package:testcase_dashboard/providers/filtered_test_execution_ids.dart'; +import 'package:testcase_dashboard/providers/filtered_test_executions.dart'; import 'package:testcase_dashboard/repositories/api_repository.dart'; import '../dummy_data.dart'; @@ -21,16 +21,16 @@ void main() { // Wait on artefact builds to load cause test execution filters uses requireValue await container.read(artefactBuildsProvider(artefactId).future); - final filteredTestExecutionIds = container.read( - filteredTestExecutionIdsProvider(Uri.parse('/snaps/$artefactId')), + final filteredTestExecutions = container.read( + filteredTestExecutionsProvider(Uri.parse('/snaps/$artefactId')), ); final builds = await apiMock.getArtefactBuilds(artefactId); - final allTestExecutionIds = { + final allTestExecutions = { for (final build in builds) - for (final testExecution in build.testExecutions) testExecution.id, + for (final testExecution in build.testExecutions) testExecution, }; - expect(filteredTestExecutionIds, allTestExecutionIds); + expect(filteredTestExecutions, allTestExecutions); }); test('it filters test executions by review status', () async { @@ -43,13 +43,19 @@ void main() { // Wait on artefact builds to load cause test execution filters uses requireValue await container.read(artefactBuildsProvider(artefactId).future); - final filteredTestExecutionIds = container.read( - filteredTestExecutionIdsProvider( + final filteredTestExecutions = container.read( + filteredTestExecutionsProvider( Uri.parse('/snaps/$artefactId?Review+status=Undecided'), ), ); - expect(filteredTestExecutionIds, {1}); + expect(filteredTestExecutions, { + dummyTestExecution.copyWith( + id: 1, + reviewDecision: [], + status: TestExecutionStatus.failed, + ), + }); }); test('it filters test executions by test execution status', () async { @@ -62,13 +68,19 @@ void main() { // Wait on artefact builds to load cause test execution filters uses requireValue await container.read(artefactBuildsProvider(artefactId).future); - final filteredTestExecutionIds = container.read( - filteredTestExecutionIdsProvider( + final filteredTestExecutions = container.read( + filteredTestExecutionsProvider( Uri.parse('/snaps/$artefactId?Execution+status=Failed'), ), ); - expect(filteredTestExecutionIds, {1}); + expect(filteredTestExecutions, { + dummyTestExecution.copyWith( + id: 1, + reviewDecision: [], + status: TestExecutionStatus.failed, + ), + }); }); } From 8d32e1c968af7b055338e6d881f1f4b583a0a580 Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Thu, 9 May 2024 13:24:59 +0300 Subject: [PATCH 16/26] Fix bug --- .../lib/ui/artefact_page/artefact_build_expandable.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/frontend/lib/ui/artefact_page/artefact_build_expandable.dart b/frontend/lib/ui/artefact_page/artefact_build_expandable.dart index 50b698c9..758af2e4 100644 --- a/frontend/lib/ui/artefact_page/artefact_build_expandable.dart +++ b/frontend/lib/ui/artefact_page/artefact_build_expandable.dart @@ -16,10 +16,15 @@ class ArtefactBuildExpandable extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final pageUri = GoRouterState.of(context).uri; + final revisionText = artefactBuild.revision == null ? '' : ' (${artefactBuild.revision})'; + final filteredTestExecutions = - ref.watch(filteredTestExecutionsProvider(pageUri)); + ref.watch(filteredTestExecutionsProvider(pageUri)).toSet(); + final artefactBuildTestExecutions = artefactBuild.testExecutions.toSet(); + final relevantTestExecutions = + filteredTestExecutions.intersection(artefactBuildTestExecutions); return ExpansionTile( initiallyExpanded: true, @@ -49,7 +54,7 @@ class ArtefactBuildExpandable extends ConsumerWidget { .intersperse(const SizedBox(width: Spacing.level4)), ], ), - children: filteredTestExecutions + children: relevantTestExecutions .map((te) => TestExecutionExpandable(testExecution: te)) .toList(), ); From 91382be7d93740ab3d06c6db6ecee62cf948e9a7 Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Thu, 9 May 2024 13:25:23 +0300 Subject: [PATCH 17/26] Submit rerun request for filtered test executions --- .../rerun_filtered_environments_button.dart | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/frontend/lib/ui/artefact_page/rerun_filtered_environments_button.dart b/frontend/lib/ui/artefact_page/rerun_filtered_environments_button.dart index c4bb7b23..ca0ca5b1 100644 --- a/frontend/lib/ui/artefact_page/rerun_filtered_environments_button.dart +++ b/frontend/lib/ui/artefact_page/rerun_filtered_environments_button.dart @@ -3,7 +3,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:intersperse/intersperse.dart'; +import '../../providers/artefact_builds.dart'; import '../../providers/filtered_test_executions.dart'; +import '../../routing.dart'; import '../spacing.dart'; class RerunFilteredEnvironmentsButton extends ConsumerWidget { @@ -12,6 +14,7 @@ class RerunFilteredEnvironmentsButton extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final pageUri = GoRouterState.of(context).uri; + final artefactId = AppRoutes.artefactIdFromUri(pageUri); final filteredTestExecutions = ref .watch( @@ -37,6 +40,12 @@ class RerunFilteredEnvironmentsButton extends ConsumerWidget { actions: [ TextButton( onPressed: () { + final testExecutionIds = { + for (final te in filteredTestExecutions) te.id, + }; + ref + .read(artefactBuildsProvider(artefactId).notifier) + .rerunTestExecutions(testExecutionIds); context.pop(); }, child: const Text('yes'), From c3d1e9fe53e37709402343e93c3abcb490f6c74f Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Thu, 9 May 2024 13:31:56 +0300 Subject: [PATCH 18/26] Some code cleanup --- .../rerun_filtered_environments_button.dart | 88 +++++++++++-------- .../test_execution_expandable.dart | 56 +++++++----- 2 files changed, 88 insertions(+), 56 deletions(-) diff --git a/frontend/lib/ui/artefact_page/rerun_filtered_environments_button.dart b/frontend/lib/ui/artefact_page/rerun_filtered_environments_button.dart index ca0ca5b1..9f906151 100644 --- a/frontend/lib/ui/artefact_page/rerun_filtered_environments_button.dart +++ b/frontend/lib/ui/artefact_page/rerun_filtered_environments_button.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:intersperse/intersperse.dart'; +import '../../models/test_execution.dart'; import '../../providers/artefact_builds.dart'; import '../../providers/filtered_test_executions.dart'; import '../../routing.dart'; @@ -16,45 +17,14 @@ class RerunFilteredEnvironmentsButton extends ConsumerWidget { final pageUri = GoRouterState.of(context).uri; final artefactId = AppRoutes.artefactIdFromUri(pageUri); - final filteredTestExecutions = ref - .watch( - filteredTestExecutionsProvider(pageUri), - ) - .toList(); + final filteredTestExecutions = + ref.watch(filteredTestExecutionsProvider(pageUri)).toList(); handlePress() => showDialog( context: context, - builder: (_) => AlertDialog( - scrollable: true, - title: Text( - 'Are you sure you want to rerun the following' - ' ${filteredTestExecutions.length} environments?', - ), - content: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: filteredTestExecutions - .map((te) => Text(te.environment.name)) - .intersperse(const SizedBox(height: Spacing.level2)) - .toList(), - ), - actions: [ - TextButton( - onPressed: () { - final testExecutionIds = { - for (final te in filteredTestExecutions) te.id, - }; - ref - .read(artefactBuildsProvider(artefactId).notifier) - .rerunTestExecutions(testExecutionIds); - context.pop(); - }, - child: const Text('yes'), - ), - TextButton( - onPressed: () => context.pop(), - child: const Text('no'), - ), - ], + builder: (_) => _ConfirmationDialog( + filteredTestExecutions: filteredTestExecutions, + artefactId: artefactId, ), ); @@ -67,3 +37,49 @@ class RerunFilteredEnvironmentsButton extends ConsumerWidget { ); } } + +class _ConfirmationDialog extends ConsumerWidget { + const _ConfirmationDialog({ + required this.filteredTestExecutions, + required this.artefactId, + }); + + final List filteredTestExecutions; + final int artefactId; + + @override + Widget build(BuildContext context, WidgetRef ref) { + handleYes() { + final testExecutionIds = {for (final te in filteredTestExecutions) te.id}; + ref + .read(artefactBuildsProvider(artefactId).notifier) + .rerunTestExecutions(testExecutionIds); + context.pop(); + } + + return AlertDialog( + scrollable: true, + title: Text( + 'Are you sure you want to rerun the following' + ' ${filteredTestExecutions.length} environments?', + ), + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: filteredTestExecutions + .map((te) => Text(te.environment.name)) + .intersperse(const SizedBox(height: Spacing.level2)) + .toList(), + ), + actions: [ + TextButton( + onPressed: handleYes, + child: const Text('yes'), + ), + TextButton( + onPressed: () => context.pop(), + child: const Text('no'), + ), + ], + ); + } +} diff --git a/frontend/lib/ui/artefact_page/test_execution_expandable.dart b/frontend/lib/ui/artefact_page/test_execution_expandable.dart index 92b66c46..e54bdf63 100644 --- a/frontend/lib/ui/artefact_page/test_execution_expandable.dart +++ b/frontend/lib/ui/artefact_page/test_execution_expandable.dart @@ -97,26 +97,9 @@ class _RerunButton extends ConsumerWidget { ? null : () => showDialog( context: context, - builder: (_) => AlertDialog( - title: const Text( - 'Are you sure you want to rerun this test execution?', - ), - actions: [ - TextButton( - autofocus: true, - onPressed: () { - ref - .read(artefactBuildsProvider(artefactId).notifier) - .rerunTestExecutions({testExecution.id}); - context.pop(); - }, - child: const Text('yes'), - ), - TextButton( - onPressed: () => context.pop(), - child: const Text('no'), - ), - ], + builder: (_) => _RerunConfirmationDialog( + artefactId: artefactId, + testExecutionId: testExecution.id, ), ); @@ -129,3 +112,36 @@ class _RerunButton extends ConsumerWidget { ); } } + +class _RerunConfirmationDialog extends ConsumerWidget { + const _RerunConfirmationDialog( + {required this.artefactId, required this.testExecutionId}); + + final int artefactId; + final int testExecutionId; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return AlertDialog( + title: const Text( + 'Are you sure you want to rerun this test execution?', + ), + actions: [ + TextButton( + autofocus: true, + onPressed: () { + ref + .read(artefactBuildsProvider(artefactId).notifier) + .rerunTestExecutions({testExecutionId}); + context.pop(); + }, + child: const Text('yes'), + ), + TextButton( + onPressed: () => context.pop(), + child: const Text('no'), + ), + ], + ); + } +} From a9a88241750186e9f7fa3ab9cf87f8976699a6bf Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Thu, 9 May 2024 13:36:26 +0300 Subject: [PATCH 19/26] Add required trailing comma --- .../lib/ui/artefact_page/test_execution_expandable.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/lib/ui/artefact_page/test_execution_expandable.dart b/frontend/lib/ui/artefact_page/test_execution_expandable.dart index e54bdf63..f848a4cf 100644 --- a/frontend/lib/ui/artefact_page/test_execution_expandable.dart +++ b/frontend/lib/ui/artefact_page/test_execution_expandable.dart @@ -114,8 +114,10 @@ class _RerunButton extends ConsumerWidget { } class _RerunConfirmationDialog extends ConsumerWidget { - const _RerunConfirmationDialog( - {required this.artefactId, required this.testExecutionId}); + const _RerunConfirmationDialog({ + required this.artefactId, + required this.testExecutionId, + }); final int artefactId; final int testExecutionId; From b89d71b6af94fe38f92e02c3a35dd5b8c3d700e1 Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Fri, 10 May 2024 09:54:58 +0000 Subject: [PATCH 20/26] Remove unused model --- backend/test_observer/controllers/artefacts/models.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/backend/test_observer/controllers/artefacts/models.py b/backend/test_observer/controllers/artefacts/models.py index 2e35de3e..8f22450e 100644 --- a/backend/test_observer/controllers/artefacts/models.py +++ b/backend/test_observer/controllers/artefacts/models.py @@ -93,9 +93,3 @@ class ArtefactBuildDTO(BaseModel): class ArtefactPatch(BaseModel): status: ArtefactStatus - - -class RerunArtefactTestExecutionsRequest(BaseModel): - test_execution_status: TestExecutionStatus | None = None - test_execution_review_decision: set[TestExecutionReviewDecision] | None = None - test_execution_environment_contains: str | None = None From e610c238b46576364ccc27795f44ab0025e9d189 Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Fri, 10 May 2024 11:17:46 +0000 Subject: [PATCH 21/26] fix test case after rebase --- backend/tests/controllers/test_executions/test_reruns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/tests/controllers/test_executions/test_reruns.py b/backend/tests/controllers/test_executions/test_reruns.py index 903e13b5..47380108 100644 --- a/backend/tests/controllers/test_executions/test_reruns.py +++ b/backend/tests/controllers/test_executions/test_reruns.py @@ -147,7 +147,7 @@ def test_post_delete_get( get: Get, post: Post, delete: Delete, test_execution: TestExecution ): test_execution.ci_link = "ci.link" - post({"test_execution_id": test_execution.id}) + post({"test_execution_ids": [test_execution.id]}) response = delete({"test_execution_ids": [test_execution.id]}) assert response.status_code == 200 From 3155bdd72ae29f3b2acccd5c69303c841b5d6269 Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Fri, 10 May 2024 11:57:01 +0000 Subject: [PATCH 22/26] Deal properly with multiple rerun requests --- .../controllers/test_executions/reruns.py | 37 +++++++++++++++---- .../test_executions/test_reruns.py | 22 +++++++++-- 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/backend/test_observer/controllers/test_executions/reruns.py b/backend/test_observer/controllers/test_executions/reruns.py index f097204e..a5a95580 100644 --- a/backend/test_observer/controllers/test_executions/reruns.py +++ b/backend/test_observer/controllers/test_executions/reruns.py @@ -1,4 +1,6 @@ -from fastapi import APIRouter, Depends, HTTPException +import contextlib + +from fastapi import APIRouter, Depends, HTTPException, Response, status from sqlalchemy import delete, select from sqlalchemy.orm import Session, joinedload @@ -17,19 +19,35 @@ router = APIRouter() -@router.post("/reruns") -def create_rerun_requests(request: RerunRequest, db: Session = Depends(get_db)): +@router.post("/reruns", response_model=list[PendingRerun]) +def create_rerun_requests( + request: RerunRequest, response: Response, db: Session = Depends(get_db) +): + rerun_requests = [] for test_execution_id in request.test_execution_ids: - _create_rerun_request(test_execution_id, db) + with contextlib.suppress(_TestExecutionNotFound): + rerun_requests.append(_create_rerun_request(test_execution_id, db)) + + if not rerun_requests: + raise HTTPException( + status.HTTP_404_NOT_FOUND, + "Didn't find test executions with provided ids", + ) + + if len(rerun_requests) != len(request.test_execution_ids): + response.status_code = status.HTTP_207_MULTI_STATUS + return rerun_requests -def _create_rerun_request(test_execution_id: int, db: Session) -> None: + +def _create_rerun_request( + test_execution_id: int, db: Session +) -> TestExecutionRerunRequest: te = db.get(TestExecution, test_execution_id) if not te: - msg = f"No test execution with id {test_execution_id} found" - raise HTTPException(status_code=404, detail=msg) + raise _TestExecutionNotFound - get_or_create(db, TestExecutionRerunRequest, {"test_execution_id": te.id}) + return get_or_create(db, TestExecutionRerunRequest, {"test_execution_id": te.id}) @router.get("/reruns", response_model=list[PendingRerun]) @@ -52,3 +70,6 @@ def delete_rerun_requests(request: DeleteReruns, db: Session = Depends(get_db)): TestExecutionRerunRequest.test_execution_id.in_(request.test_execution_ids) ) ) + + +class _TestExecutionNotFound(ValueError): ... diff --git a/backend/tests/controllers/test_executions/test_reruns.py b/backend/tests/controllers/test_executions/test_reruns.py index 47380108..85a68d70 100644 --- a/backend/tests/controllers/test_executions/test_reruns.py +++ b/backend/tests/controllers/test_executions/test_reruns.py @@ -47,11 +47,27 @@ def test_post_no_data_returns_422(post: Post): def test_post_invalid_id_returns_404_with_message(post: Post): response = post({"test_execution_ids": [1]}) assert response.status_code == 404 - assert response.json()["detail"] == "No test execution with id 1 found" + assert response.json()["detail"] == "Didn't find test executions with provided ids" -def test_valid_post_returns_200(post: Post, test_execution: TestExecution): - assert post({"test_execution_ids": [test_execution.id]}).status_code == 200 +def test_valid_post(post: Post, test_execution: TestExecution): + test_execution.ci_link = "ci.link" + response = post({"test_execution_ids": [test_execution.id]}) + + assert response.status_code == 200 + assert response.json() == [ + { + "test_execution_id": test_execution.id, + "ci_link": test_execution.ci_link, + "family": test_execution.artefact_build.artefact.stage.family.name, + } + ] + + +def test_post_with_valid_and_invalid_ids(post: Post, test_execution: TestExecution): + test_execution.ci_link = "ci.link" + response = post({"test_execution_ids": [test_execution.id, test_execution.id + 1]}) + assert response.status_code == 207 def test_get_returns_200_with_empty_list(get: Get): From 9cbc78a02d686f0247842ae59a854a7c40897fe8 Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Fri, 10 May 2024 12:03:10 +0000 Subject: [PATCH 23/26] Ignore useless ruff rule --- backend/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/pyproject.toml b/backend/pyproject.toml index f607078f..662dd6f3 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -54,7 +54,7 @@ select = [ "PLE", "TID252", ] -ignore = ["ANN201", "ANN003", "N999", "ANN101", "ANN204"] +ignore = ["ANN201", "ANN003", "N999", "ANN101", "ANN204", "N818"] [tool.ruff.flake8-bugbear] extend-immutable-calls = [ From 996a25ebb496c535b30c13a2f344cbb48c48b121 Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Fri, 10 May 2024 12:07:46 +0000 Subject: [PATCH 24/26] fix black formatting --- backend/test_observer/controllers/test_executions/reruns.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/test_observer/controllers/test_executions/reruns.py b/backend/test_observer/controllers/test_executions/reruns.py index a5a95580..2eb8acd8 100644 --- a/backend/test_observer/controllers/test_executions/reruns.py +++ b/backend/test_observer/controllers/test_executions/reruns.py @@ -72,4 +72,5 @@ def delete_rerun_requests(request: DeleteReruns, db: Session = Depends(get_db)): ) -class _TestExecutionNotFound(ValueError): ... +class _TestExecutionNotFound(ValueError): + ... From 5618a1ebb8b8b817002c9e1d6c0a72c0d0092d42 Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Fri, 10 May 2024 15:09:00 +0300 Subject: [PATCH 25/26] Use consistent wording as Environment --- frontend/lib/ui/artefact_page/test_execution_expandable.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/lib/ui/artefact_page/test_execution_expandable.dart b/frontend/lib/ui/artefact_page/test_execution_expandable.dart index f848a4cf..89082610 100644 --- a/frontend/lib/ui/artefact_page/test_execution_expandable.dart +++ b/frontend/lib/ui/artefact_page/test_execution_expandable.dart @@ -126,7 +126,7 @@ class _RerunConfirmationDialog extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return AlertDialog( title: const Text( - 'Are you sure you want to rerun this test execution?', + 'Are you sure you want to rerun this environment?', ), actions: [ TextButton( From 23dbd659521a122ab605a1b4a28701092f456845 Mon Sep 17 00:00:00 2001 From: Omar Selo Date: Fri, 10 May 2024 15:21:39 +0300 Subject: [PATCH 26/26] Only mark successful rerun requests --- frontend/lib/models/rerun_request.dart | 15 +++++++++++++++ frontend/lib/providers/artefact_builds.dart | 4 ++-- frontend/lib/repositories/api_repository.dart | 11 +++++++++-- 3 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 frontend/lib/models/rerun_request.dart diff --git a/frontend/lib/models/rerun_request.dart b/frontend/lib/models/rerun_request.dart new file mode 100644 index 00000000..5421e1aa --- /dev/null +++ b/frontend/lib/models/rerun_request.dart @@ -0,0 +1,15 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'rerun_request.freezed.dart'; +part 'rerun_request.g.dart'; + +@freezed +class RerunRequest with _$RerunRequest { + const factory RerunRequest({ + @JsonKey(name: 'test_execution_id') required int testExecutionId, + @JsonKey(name: 'ci_link') required String ciLink, + }) = _RerunRequest; + + factory RerunRequest.fromJson(Map json) => + _$RerunRequestFromJson(json); +} diff --git a/frontend/lib/providers/artefact_builds.dart b/frontend/lib/providers/artefact_builds.dart index 527c5360..780cf165 100644 --- a/frontend/lib/providers/artefact_builds.dart +++ b/frontend/lib/providers/artefact_builds.dart @@ -31,10 +31,10 @@ class ArtefactBuilds extends _$ArtefactBuilds { Future rerunTestExecutions(Set testExecutionIds) async { final api = ref.read(apiProvider); - await api.rerunTestExecutions(testExecutionIds); + final rerunRequests = await api.rerunTestExecutions(testExecutionIds); await _updateTestExecutions( - testExecutionIds, + rerunRequests.map((rr) => rr.testExecutionId).toSet(), (te) => te.copyWith(isRerunRequested: true), ); } diff --git a/frontend/lib/repositories/api_repository.dart b/frontend/lib/repositories/api_repository.dart index 57d5d3bc..54946ae7 100644 --- a/frontend/lib/repositories/api_repository.dart +++ b/frontend/lib/repositories/api_repository.dart @@ -4,6 +4,7 @@ import '../models/artefact.dart'; import '../models/artefact_build.dart'; import '../models/family_name.dart'; +import '../models/rerun_request.dart'; import '../models/test_execution.dart'; import '../models/test_result.dart'; @@ -65,10 +66,16 @@ class ApiRepository { return testResults; } - Future rerunTestExecutions(Set testExecutionIds) async { - await dio.post( + Future> rerunTestExecutions( + Set testExecutionIds, + ) async { + final response = await dio.post( '/v1/test-executions/reruns', data: {'test_execution_ids': testExecutionIds.toList()}, ); + final List rerunsJson = response.data; + final reruns = + rerunsJson.map((json) => RerunRequest.fromJson(json)).toList(); + return reruns; } }