diff --git a/backend/kernelCI/settings.py b/backend/kernelCI/settings.py index 6d348c2cf..ef410b75b 100644 --- a/backend/kernelCI/settings.py +++ b/backend/kernelCI/settings.py @@ -167,9 +167,6 @@ def get_json_env_var(name, default): "CRONTAB_COMMAND_SUFFIX", ">> /proc/1/fd/1 2>&1" ) CRONJOBS = [ - # not using a monitoring_id in the first task since it should - # be removed once the denormalization is set in stone - ("0 * * * *", "kernelCI_app.tasks.update_checkout_cache"), ( "59 * * * *", "django.core.management.call_command", diff --git a/backend/kernelCI_app/management/commands/seed_test_data.py b/backend/kernelCI_app/management/commands/seed_test_data.py index c8445364b..004948cbd 100644 --- a/backend/kernelCI_app/management/commands/seed_test_data.py +++ b/backend/kernelCI_app/management/commands/seed_test_data.py @@ -9,6 +9,7 @@ from kernelCI_app.constants.general import UNKNOWN_STRING from kernelCI_app.helpers.system import get_running_instance +from kernelCI_app.management.commands.helpers.aggregation_helpers import simplify_status from kernelCI_app.management.commands.helpers.process_pending_helpers import ( accumulate_rollup_entry, extract_path_group, @@ -16,8 +17,11 @@ from kernelCI_app.models import ( Builds, Checkouts, + HardwareStatus, Incidents, Issues, + LatestCheckout, + SimplifiedStatusChoices, StatusChoices, Tests, TreeTestsRollup, @@ -31,6 +35,13 @@ ) from kernelCI_app.tests.factories.mocks import Build, Issue, Test +STATUS_FIELD_BY_SIMPLIFIED_STATUS = { + SimplifiedStatusChoices.PASS: "pass", + SimplifiedStatusChoices.FAIL: "failed", + SimplifiedStatusChoices.INCONCLUSIVE: "inc", + None: "inc", +} + class Command(BaseCommand): help = "Seed test database with realistic data for integration tests" @@ -74,6 +85,8 @@ def handle(self, *args, **options): issues = self.create_issues(count=options["issues"]) incidents = self.create_incidents(issues=issues, builds=builds, tests=tests) rollup_rows = self.create_tests_rollup(tests=tests, incidents=incidents) + latest_checkouts = self.create_latest_checkouts(checkouts=checkouts) + hardware_rows = self.create_hardware_status(tests=tests) self.stdout.write( self.style.SUCCESS( @@ -84,6 +97,8 @@ def handle(self, *args, **options): f"- {len(issues)} issues\n" f"- {len(incidents)} incidents\n" f"- {len(rollup_rows)} tree_tests_rollup rows\n" + f"- {len(latest_checkouts)} latest_checkout rows\n" + f"- {len(hardware_rows)} hardware_status rows\n" ) ) @@ -112,6 +127,8 @@ def _validate_clear_operation(self, *, skip_confirmation: bool) -> None: def clear_data(self) -> None: """Clear existing test data.""" + HardwareStatus.objects.all().delete() + LatestCheckout.objects.all().delete() TreeTestsRollup.objects.all().delete() Incidents.objects.all().delete() Issues.objects.all().delete() @@ -335,3 +352,90 @@ def create_tests_rollup( created = TreeTestsRollup.objects.bulk_create(rollup_objects) return created + + def create_latest_checkouts( + self, *, checkouts: list[Checkouts] + ) -> list[LatestCheckout]: + """Create latest_checkout rows used by listing queries.""" + self.stdout.write("Creating latest_checkout rows...") + + latest_by_tree: dict[tuple, Checkouts] = {} + for checkout in checkouts: + key = ( + checkout.origin, + checkout.tree_name, + checkout.git_repository_url, + checkout.git_repository_branch, + ) + current = latest_by_tree.get(key) + if current is None or checkout.start_time > current.start_time: + latest_by_tree[key] = checkout + + latest_checkouts = [ + LatestCheckout( + checkout_id=checkout.id, + start_time=checkout.start_time, + origin=checkout.origin, + tree_name=checkout.tree_name, + git_repository_url=checkout.git_repository_url, + git_repository_branch=checkout.git_repository_branch, + ) + for checkout in latest_by_tree.values() + ] + + return LatestCheckout.objects.bulk_create(latest_checkouts) + + def create_hardware_status(self, *, tests: list[Tests]) -> list[HardwareStatus]: + """Aggregate seeded tests into hardware_status rows.""" + self.stdout.write("Creating hardware_status aggregations...") + + hardware_data: dict[tuple, dict] = {} + counted_builds_by_key: dict[tuple, set[str]] = {} + + for test in tests: + platform = ( + test.environment_misc.get("platform") if test.environment_misc else None + ) + if platform is None: + continue + + checkout = test.build.checkout + key = (test.origin, platform, checkout.id) + if key not in hardware_data: + hardware_data[key] = { + "checkout_id": checkout.id, + "test_origin": test.origin, + "platform": platform, + "compatibles": test.environment_compatible, + "start_time": checkout.start_time, + "build_pass": 0, + "build_failed": 0, + "build_inc": 0, + "boot_pass": 0, + "boot_failed": 0, + "boot_inc": 0, + "test_pass": 0, + "test_failed": 0, + "test_inc": 0, + } + counted_builds_by_key[key] = set() + + record = hardware_data[key] + status_type = STATUS_FIELD_BY_SIMPLIFIED_STATUS[ + simplify_status(test.status) + ] + + test_prefix = "boot" if (test.path or "").startswith("boot") else "test" + record[f"{test_prefix}_{status_type}"] += 1 + + if test.build_id not in counted_builds_by_key[ + key + ] and not test.build_id.startswith("maestro:dummy_"): + counted_builds_by_key[key].add(test.build_id) + build_status_type = STATUS_FIELD_BY_SIMPLIFIED_STATUS[ + simplify_status(test.build.status) + ] + record[f"build_{build_status_type}"] += 1 + + hardware_rows = [HardwareStatus(**data) for data in hardware_data.values()] + return HardwareStatus.objects.bulk_create(hardware_rows) diff --git a/backend/kernelCI_app/queries/hardware.py b/backend/kernelCI_app/queries/hardware.py index 8ad3c99b4..aeecfce17 100644 --- a/backend/kernelCI_app/queries/hardware.py +++ b/backend/kernelCI_app/queries/hardware.py @@ -109,98 +109,6 @@ def _get_hardware_listing_count_clauses() -> str: return build_count_clause + boot_count_clause + test_count_clause -def get_hardware_listing_data( - *, - start_date: datetime, - end_date: datetime, - origin: str, - commits_list: Optional[list[str]] = None, -) -> list[dict]: - """ - Retrieves the listing of platform, compatibles, and - the status counts of builds, boots and tests - for the latest checkout of every tree. - The selected checkouts and tests are limited to the start_date/end_date interval. - When commits_list is set, tree heads are not used: checkouts qualify if - git_commit_hash is in the token list or git_commit_tags overlaps it (comma- - separated request values can be full SHAs or tag strings stored on checkouts). - Still scoped by the test start_time and origin filters below. - """ - - count_clauses = _get_hardware_listing_count_clauses() - - params = { - "start_date": start_date, - "end_date": end_date, - "origin": origin, - } - - if commits_list: - params["commits_list"] = commits_list - checkout_ids_select = """ - SELECT C.id - FROM checkouts C - WHERE C.git_commit_hash = ANY(%(commits_list)s) - OR ( - C.git_commit_tags IS NOT NULL - AND C.git_commit_tags && %(commits_list)s::text[] - ) - """ - else: - checkout_ids_select = _get_hardware_tree_heads_clause(id_only=True) - - selected_checkouts_cte = f""" - selected_checkouts AS ( - {checkout_ids_select} - ), - """ - - # The grouping by platform and compatibles is possible because a platform - # can dictate the array of compatibles, meaning that if the array of compatibles - # is different, then the platform should/must be different as well. - # - # It is possible that a platform appears for tests from origin A which are related - # to checkouts from origin B. It's for those cases that the origin filter is applied - # to the tests, not checkouts. There are no platforms being tested by multiple origins yet. - query = f""" - WITH - {selected_checkouts_cte} - relevant_tests AS ( - SELECT - "tests"."environment_compatible" AS hardware, - "tests"."environment_misc" ->> 'platform' AS platform, - "tests"."status", - "tests"."path", - "tests"."id", - b.id AS build_id, - b.status AS build_status - FROM - tests - INNER JOIN builds b ON tests.build_id = b.id - JOIN selected_checkouts AC ON b.checkout_id = AC.id - WHERE - "tests"."environment_misc" ->> 'platform' IS NOT NULL - AND "tests"."origin" = %(origin)s - AND "tests"."start_time" >= %(start_date)s - AND "tests"."start_time" <= %(end_date)s - ) - -- From the raw rows, selects platform, hardware, and performs the status grouping. - SELECT - platform, - hardware, - {count_clauses} - FROM - relevant_tests - GROUP BY - platform, - hardware - """ - - with connection.cursor() as cursor: - cursor.execute(query, params) - return cursor.fetchall() - - def get_hardware_selectors(origin: str) -> list[dict]: cache_key = "hardwareSelectors" cache_params = {"origin": origin} diff --git a/backend/kernelCI_app/queries/notifications.py b/backend/kernelCI_app/queries/notifications.py index f59a1a014..745236b41 100644 --- a/backend/kernelCI_app/queries/notifications.py +++ b/backend/kernelCI_app/queries/notifications.py @@ -368,7 +368,7 @@ def kcidb_last_test_without_issue(issue, incident): return kcidb_execute_query(query, params) -# Similar to get_tree_listing_data, but at the same time it has to be different. +# Similar to the tree listing summary query, but with notification-specific filters. # Only the "with", "join" and "where" clauses change def get_checkout_summary_data( *, diff --git a/backend/kernelCI_app/queries/tree.py b/backend/kernelCI_app/queries/tree.py index f46d7ab0a..52f755a1f 100644 --- a/backend/kernelCI_app/queries/tree.py +++ b/backend/kernelCI_app/queries/tree.py @@ -11,7 +11,6 @@ get_boot_test_duration_clause, get_build_duration_clause, ) -from kernelCI_app.utils import get_query_time_interval def _get_tree_listing_count_clause() -> str: @@ -120,184 +119,6 @@ def get_tree_listing_query(with_clause, join_clause, where_clause): return main_query -def get_tree_listing_data(origin: str, interval_in_days: int) -> Optional[list[dict]]: - params = { - "origin_param": origin, - "interval_param": interval_in_days, - } - - # TODO: reuse the FIRST_TREE_CHECKOUT query - with_clause = """ - WITH - ORDERED_CHECKOUTS_BY_TREE AS ( - SELECT - GIT_REPOSITORY_BRANCH, - GIT_REPOSITORY_URL, - GIT_COMMIT_HASH, - ROW_NUMBER() OVER ( - PARTITION BY - GIT_REPOSITORY_BRANCH, - GIT_REPOSITORY_URL - ORDER BY - START_TIME DESC - ) AS TIME_ORDER - FROM - CHECKOUTS - WHERE - ORIGIN = %(origin_param)s - AND START_TIME >= NOW() - INTERVAL '%(interval_param)s days' - ), - FIRST_TREE_CHECKOUT AS ( - SELECT - GIT_REPOSITORY_BRANCH, - GIT_REPOSITORY_URL, - GIT_COMMIT_HASH - FROM - ORDERED_CHECKOUTS_BY_TREE - WHERE - TIME_ORDER = 1 - ) - """ - - # The JOIN FTC with IS NOT DISTINCT FROM is necessary because the fields can be NULL, - # in which case a simple `WHERE (git_branch, git_url, git_hash) IN FTC` wouldn't work - # since the NULL comparison would return UNKNOWN - join_clause = """ - JOIN FIRST_TREE_CHECKOUT FTC ON ( - checkouts.git_repository_branch IS NOT DISTINCT FROM FTC.GIT_REPOSITORY_BRANCH - AND checkouts.git_repository_url IS NOT DISTINCT FROM FTC.GIT_REPOSITORY_URL - AND checkouts.git_commit_hash = FTC.GIT_COMMIT_HASH - ) - """ - - where_clause = """ - WHERE - checkouts.origin = %(origin_param)s - """ - - query = get_tree_listing_query( - with_clause=with_clause, - join_clause=join_clause, - where_clause=where_clause, - ) - - with connection.cursor() as cursor: - cursor.execute(query, params) - return dict_fetchall(cursor=cursor) - - -# TODO: rename and reuse this query -# It is being used virtually as "latest checkout from trees" -def get_tree_listing_fast( - *, origin: Optional[str] = None, interval: dict -) -> list[Checkouts]: - interval_timestamp = get_query_time_interval(**interval).timestamp() - params = {"interval": interval_timestamp} - - if origin: - origin_clause = "origin = %(origin)s AND" - params["origin"] = origin - else: - origin_clause = "" - - checkouts = Checkouts.objects.raw( - f""" - WITH ordered_checkouts AS ( - SELECT - id, - tree_name, - origin, - git_repository_branch, - git_repository_url, - git_commit_hash, - git_commit_name, - git_commit_tags, - patchset_hash, - start_time, - origin_builds_finish_time, - origin_tests_finish_time, - ROW_NUMBER() OVER ( - PARTITION BY - git_repository_branch, - git_repository_url, - origin - ORDER BY start_time DESC - ) AS time_order - FROM - checkouts - WHERE - {origin_clause} - start_time >= TO_TIMESTAMP(%(interval)s) - ) - SELECT - * - FROM - ordered_checkouts - WHERE - time_order = 1 - ORDER BY - tree_name ASC; - """, - params, - ) - - return list(checkouts) - - -def get_tree_listing_data_by_checkout_id(*, checkout_ids: list[str]) -> list[dict]: - if not checkout_ids: - return [] - - count_clauses = _get_tree_listing_count_clause() - - # TODO: check if those conditions of case, coalesce and group by are necessary - query = f""" - SELECT - MAX(checkouts.id) AS id, - checkouts.tree_name, - checkouts.git_repository_branch, - checkouts.git_repository_url, - checkouts.git_commit_hash, - checkouts.origin_builds_finish_time, - checkouts.origin_tests_finish_time, - CASE - WHEN COUNT(DISTINCT checkouts.git_commit_tags) > 0 THEN - COALESCE( - ARRAY_AGG(DISTINCT checkouts.git_commit_tags) FILTER ( - WHERE checkouts.git_commit_tags IS NOT NULL - AND checkouts.git_commit_tags::TEXT <> '{"{}"}' - ), - ARRAY[]::TEXT[] - ) - ELSE ARRAY[]::TEXT[] - END AS git_commit_tags, - MAX(checkouts.git_commit_name) AS git_commit_name, - MAX(checkouts.start_time) AS start_time, - {count_clauses} - checkouts.origin - FROM - checkouts - LEFT JOIN - builds ON builds.checkout_id = checkouts.id - LEFT JOIN - tests ON tests.build_id = builds.id - WHERE - checkouts.id IN ({", ".join(["%s"] * len(checkout_ids))}) - GROUP BY - checkouts.origin, - checkouts.git_commit_hash, - checkouts.git_repository_branch, - checkouts.git_repository_url, - checkouts.tree_name, - checkouts.origin_builds_finish_time, - checkouts.origin_tests_finish_time - """ - - with connection.cursor() as cursor: - cursor.execute(query, checkout_ids) - return dict_fetchall(cursor=cursor) - - def get_tree_listing_data_denormalized( *, origin: str, interval_in_days: int ) -> Optional[list[tuple]]: diff --git a/backend/kernelCI_app/tasks.py b/backend/kernelCI_app/tasks.py deleted file mode 100644 index c2720223e..000000000 --- a/backend/kernelCI_app/tasks.py +++ /dev/null @@ -1,158 +0,0 @@ -from datetime import timedelta - -from django.conf import settings -from django.utils.timezone import is_aware, make_aware, now - -from kernelCI_app.helpers.logger import out -from kernelCI_app.models import Checkouts -from kernelCI_app.queries.tree import ( - get_tree_listing_data_by_checkout_id, - get_tree_listing_fast, -) -from kernelCI_cache.checkouts import populate_checkouts_cache_db -from kernelCI_cache.constants import NO_CACHE_ORIGINS, UNSTABLE_CHECKOUT_THRESHOLD -from kernelCI_cache.queries.checkouts import get_cached_tree_listing_fast - -UPDATE_INTERVAL_IN_DAYS = 90 - -type TreeIdentifier = tuple[str | None, str | None, str | None] -"""A tuple to identify a unique tree: (tree_name, git_repository_branch, git_repository_url)""" - - -def _is_checkout_done(*, checkout: dict | Checkouts) -> bool: - if isinstance(checkout, Checkouts): - origin_builds_finish_time = checkout.origin_builds_finish_time - origin_tests_finish_time = checkout.origin_tests_finish_time - else: - origin_builds_finish_time = checkout.get("origin_builds_finish_time") - origin_tests_finish_time = checkout.get("origin_tests_finish_time") - - return ( - origin_builds_finish_time is not None and origin_tests_finish_time is not None - ) - - -def _is_checkout_unstable(*, checkout: dict | Checkouts) -> bool: - """ - Defines if a checkout is considered unstable or not. - - It will be *stable* if both origin_builds_finish_time and origin_tests_finish_time are set - or if it is older than UNSTABLE_CHECKOUT_THRESHOLD. - - Returns True if unstable, False if stable. - """ - unstable_threshold = now() - timedelta(days=UNSTABLE_CHECKOUT_THRESHOLD) - if isinstance(checkout, Checkouts): - start_time = checkout.start_time - else: - start_time = checkout.get("start_time") - - is_old = start_time is not None and start_time < unstable_threshold - is_done = _is_checkout_done(checkout=checkout) - if is_old or is_done: - return False - else: - return True - - -def _is_checkout_newer( - *, - cached_start_time, - old_checkout_start_time, -) -> bool: - if cached_start_time is None or old_checkout_start_time is None: - return False - - if not is_aware(cached_start_time): - cached_start_time = make_aware(cached_start_time) - - if not is_aware(old_checkout_start_time): - old_checkout_start_time = make_aware(old_checkout_start_time) - - return old_checkout_start_time > cached_start_time - - -def get_checkout_ids_for_update( - *, - kcidb_checkouts: list[Checkouts], - sqlite_tree_keys: set[TreeIdentifier], - sqlite_trees_map: dict[TreeIdentifier, dict], -) -> set[str]: - checkout_ids_for_update = set() - - for checkout in kcidb_checkouts: - if checkout.origin in NO_CACHE_ORIGINS: - continue - - checkout_key: TreeIdentifier = ( - checkout.tree_name, - checkout.git_repository_branch, - checkout.git_repository_url, - ) - - # If the equivalent tree in sqlite is unstable, update it nonetheless - same_tree_on_sqlite = sqlite_trees_map.get(checkout_key) - if same_tree_on_sqlite is not None and same_tree_on_sqlite.get("unstable"): - checkout_ids_for_update.add(same_tree_on_sqlite.get("checkout_id")) - - # Trees that aren't in the sqlite should be added - if checkout_key not in sqlite_tree_keys: - checkout_ids_for_update.add(checkout.id) - continue - - # If the current checkout is unstable, update it - if _is_checkout_unstable(checkout=checkout): - checkout_ids_for_update.add(checkout.id) - continue - - # Even if the current checkout is stable, if it is newer than the cached one, update it - if _is_checkout_newer( - cached_start_time=same_tree_on_sqlite.get("start_time"), - old_checkout_start_time=checkout.start_time, - ): - checkout_ids_for_update.add(checkout.id) - - return checkout_ids_for_update - - -def update_checkout_cache(): - checkout_ids_for_update: set[str] = set() - - out( - "Started Updating checkout cache at %s/cache.sqlite3" - % settings.BACKEND_VOLUME_DIR - ) - - kcidb_checkouts = get_tree_listing_fast(interval={"days": UPDATE_INTERVAL_IN_DAYS}) - - sqlite_checkouts = get_cached_tree_listing_fast() - sqlite_trees_map: dict[TreeIdentifier, dict] = {} - sqlite_tree_keys: set[TreeIdentifier] = set() - for cached_checkout in sqlite_checkouts: - tree_ident: TreeIdentifier = ( - cached_checkout.get("tree_name"), - cached_checkout.get("git_repository_branch"), - cached_checkout.get("git_repository_url"), - ) - sqlite_tree_keys.add(tree_ident) - sqlite_trees_map[tree_ident] = cached_checkout - - checkout_ids_for_update = get_checkout_ids_for_update( - kcidb_checkouts=kcidb_checkouts, - sqlite_tree_keys=sqlite_tree_keys, - sqlite_trees_map=sqlite_trees_map, - ) - - updated_checkouts_data = get_tree_listing_data_by_checkout_id( - checkout_ids=list(checkout_ids_for_update), - ) - - for checkout in updated_checkouts_data: - checkout.update({"unstable": _is_checkout_unstable(checkout=checkout)}) - - populate_checkouts_cache_db(data=updated_checkouts_data) - - out( - "Finished checkout cache update task, updated %d checkouts" - % len(checkout_ids_for_update) - ) diff --git a/backend/kernelCI_app/tests/integrationTests/treeListing_test.py b/backend/kernelCI_app/tests/integrationTests/treeListing_test.py index ce217007e..2842075ca 100644 --- a/backend/kernelCI_app/tests/integrationTests/treeListing_test.py +++ b/backend/kernelCI_app/tests/integrationTests/treeListing_test.py @@ -1,4 +1,3 @@ -import warnings from http import HTTPStatus from kernelCI_app.constants.localization import ClientStrings @@ -8,10 +7,8 @@ ) from kernelCI_app.tests.utils.client.treeClient import TreeClient from kernelCI_app.tests.utils.fields.tree import ( - tree_fast, tree_listing, - tree_listing_build_status, - tree_listing_test_status, + tree_listing_status, ) from kernelCI_app.utils import string_to_json @@ -83,45 +80,6 @@ def pytest_generate_tests(metafunc): ) -def test_tree_listing_fast( - pytestconfig, - tree_listing_input, -) -> None: - query, status_code, has_error_body = tree_listing_input - response = client.get_tree_listing_fast(query=query) - content = string_to_json(response.content.decode()) - assert_status_code_and_error_response( - response=response, - content=content, - status_code=status_code, - should_error=has_error_body, - ) - - if not has_error_body: - if ( - isinstance(content, dict) - and content.get("error") == ClientStrings.NO_TREES_FOUND - ): - warnings.warn( - "Integration tests are using REAL DATABASE DATA and may become outdated. " - "This is a temporary hotfix - will be replaced with dedicated test database.", - UserWarning, - stacklevel=2, - ) - return - assert_has_fields_in_response_content( - fields=tree_fast, - response_content=content[0], - ) - - if pytestconfig.getoption("--run-all") and len(content) > 1: - for tree in content[1:]: - assert_has_fields_in_response_content( - fields=tree_fast, - response_content=tree, - ) - - def test_tree_listing( pytestconfig, tree_listing_input, @@ -147,15 +105,15 @@ def test_tree_listing( response_content=content[0], ) assert_has_fields_in_response_content( - fields=tree_listing_test_status, + fields=tree_listing_status, response_content=content[0]["test_status"], ) assert_has_fields_in_response_content( - fields=tree_listing_test_status, + fields=tree_listing_status, response_content=content[0]["boot_status"], ) assert_has_fields_in_response_content( - fields=tree_listing_build_status, + fields=tree_listing_status, response_content=content[0]["build_status"], ) @@ -166,14 +124,14 @@ def test_tree_listing( response_content=tree, ) assert_has_fields_in_response_content( - fields=tree_listing_test_status, + fields=tree_listing_status, response_content=tree["test_status"], ) assert_has_fields_in_response_content( - fields=tree_listing_test_status, + fields=tree_listing_status, response_content=tree["boot_status"], ) assert_has_fields_in_response_content( - fields=tree_listing_build_status, + fields=tree_listing_status, response_content=tree["build_status"], ) diff --git a/backend/kernelCI_app/tests/unitTests/queries/hardware_queries_test.py b/backend/kernelCI_app/tests/unitTests/queries/hardware_queries_test.py index f3469d03f..6330bf223 100644 --- a/backend/kernelCI_app/tests/unitTests/queries/hardware_queries_test.py +++ b/backend/kernelCI_app/tests/unitTests/queries/hardware_queries_test.py @@ -5,7 +5,6 @@ _generate_query_params, get_hardware_commit_history, get_hardware_details_data, - get_hardware_listing_data, get_hardware_trees_data, query_records, ) @@ -19,25 +18,6 @@ END_DATE = datetime(2025, 11, 12) -class TestGetHardwareListingData: - @patch("kernelCI_app.queries.hardware.connection") - def test_get_hardware_listing_data_success(self, mock_connection): - expected_result = [ - ("platform", ["compatible"], 10, 5, 0, 0, 0, 0, 0, 20, 10, 0, 0, 0, 0, 0) - ] - mock_cursor = setup_mock_cursor(mock_connection) - mock_cursor.fetchall.return_value = expected_result - - result = get_hardware_listing_data( - start_date=datetime(2025, 11, 10), - end_date=datetime(2025, 11, 12), - origin="maestro", - ) - - assert result == expected_result - mock_cursor.execute.assert_called_once() - - class TestGetHardwareDetailsData: @patch("kernelCI_app.queries.hardware.get_query_cache") def test_get_hardware_details_data_from_cache(self, mock_get_cache): diff --git a/backend/kernelCI_app/tests/unitTests/queries/tree_test.py b/backend/kernelCI_app/tests/unitTests/queries/tree_test.py index edc3e05ab..d3ec7d853 100644 --- a/backend/kernelCI_app/tests/unitTests/queries/tree_test.py +++ b/backend/kernelCI_app/tests/unitTests/queries/tree_test.py @@ -1,11 +1,8 @@ -from unittest.mock import Mock, patch +from unittest.mock import patch from kernelCI_app.queries.tree import ( get_latest_tree, get_tree_details_data, - get_tree_listing_data, - get_tree_listing_data_by_checkout_id, - get_tree_listing_fast, ) from kernelCI_app.tests.unitTests.queries.conftest import ( setup_mock_cursor, @@ -13,62 +10,6 @@ ) -class TestGetTreeListingData: - @patch("kernelCI_app.queries.tree.dict_fetchall") - @patch("kernelCI_app.queries.tree.connection") - def test_get_tree_listing_data_success(self, mock_connection, mock_dict_fetchall): - expected_result = [{"checkout_id": "checkout", "tree_name": "mainline"}] - mock_dict_fetchall.return_value = expected_result - setup_mock_cursor(mock_connection) - - result = get_tree_listing_data(origin="maestro", interval_in_days=7) - - assert result == expected_result - - -class TestGetTreeListingFast: - @patch("kernelCI_app.queries.tree.get_query_time_interval") - @patch("kernelCI_app.queries.tree.Checkouts") - def test_get_tree_listing_fast_with_origin( - self, mock_checkouts_model, mock_get_interval - ): - mock_get_interval.return_value.timestamp.return_value = 1704067200.0 - mock_checkouts_model.objects.raw.return_value = [Mock(id="checkout")] - - result = get_tree_listing_fast(origin="maestro", interval={"days": 7}) - - assert len(result) == 1 - - @patch("kernelCI_app.queries.tree.get_query_time_interval") - @patch("kernelCI_app.queries.tree.Checkouts") - def test_get_tree_listing_fast_without_origin( - self, mock_checkouts_model, mock_get_interval - ): - mock_get_interval.return_value.timestamp.return_value = 1704067200.0 - mock_checkouts_model.objects.raw.return_value = [] - - result = get_tree_listing_fast(origin=None, interval={"days": 7}) - - assert result == [] - - -class TestGetTreeListingDataByCheckoutId: - @patch("kernelCI_app.queries.tree.dict_fetchall") - @patch("kernelCI_app.queries.tree.connection") - def test_get_tree_listing_data_by_checkout_id_success( - self, mock_connection, mock_dict_fetchall - ): - expected_result = [{"id": "checkout_1", "tree_name": "mainline"}] - mock_dict_fetchall.return_value = expected_result - setup_mock_cursor(mock_connection) - - result = get_tree_listing_data_by_checkout_id( - checkout_ids=["checkout_1", "checkout_2"] - ) - - assert result == expected_result - - class TestGetTreeDetailsData: @patch("kernelCI_app.queries.tree.get_query_cache") def test_get_tree_details_data_from_cache(self, mock_get_cache): diff --git a/backend/kernelCI_app/tests/unitTests/tasks_test.py b/backend/kernelCI_app/tests/unitTests/tasks_test.py deleted file mode 100644 index b8425ced9..000000000 --- a/backend/kernelCI_app/tests/unitTests/tasks_test.py +++ /dev/null @@ -1,394 +0,0 @@ -from datetime import datetime, timezone -from unittest.mock import MagicMock, patch - -from kernelCI_app.models import Checkouts -from kernelCI_app.tasks import ( - UPDATE_INTERVAL_IN_DAYS, - _is_checkout_done, - _is_checkout_newer, - _is_checkout_unstable, - get_checkout_ids_for_update, - update_checkout_cache, -) - - -class TestIsCheckoutDone: - def test_is_checkout_done_with_checkout_model(self): - """Test _is_checkout_done with Checkouts model instance.""" - mock_checkout = MagicMock(spec=Checkouts) - mock_checkout.origin_builds_finish_time = datetime.now() - mock_checkout.origin_tests_finish_time = datetime.now() - result = _is_checkout_done(checkout=mock_checkout) - - assert result is True - - def test_is_checkout_done_with_dict(self): - """Test _is_checkout_done with dictionary.""" - checkout_dict = { - "origin_builds_finish_time": datetime.now(), - "origin_tests_finish_time": datetime.now(), - } - - result = _is_checkout_done(checkout=checkout_dict) - - assert result is True - - def test_is_checkout_done_missing_builds_time(self): - """Test _is_checkout_done when builds finish time is None.""" - mock_checkout = MagicMock(spec=Checkouts) - mock_checkout.origin_builds_finish_time = None - mock_checkout.origin_tests_finish_time = datetime.now() - - result = _is_checkout_done(checkout=mock_checkout) - - assert result is False - - def test_is_checkout_done_missing_tests_time(self): - """Test _is_checkout_done when tests finish time is None.""" - mock_checkout = MagicMock(spec=Checkouts) - mock_checkout.origin_builds_finish_time = datetime.now() - mock_checkout.origin_tests_finish_time = None - - result = _is_checkout_done(checkout=mock_checkout) - - assert result is False - - def test_is_checkout_done_both_times_none(self): - """Test _is_checkout_done when both times are None.""" - checkout_dict = { - "origin_builds_finish_time": None, - "origin_tests_finish_time": None, - } - - result = _is_checkout_done(checkout=checkout_dict) - - assert result is False - - -class TestIsCheckoutUnstable: - @patch("kernelCI_app.tasks.now") - def test_is_checkout_unstable_with_checkout_model_done(self, mock_now): - """Test _is_checkout_unstable with done checkout.""" - mock_now.return_value = datetime(2024, 1, 15, 12, 0, 0) - mock_checkout = MagicMock(spec=Checkouts) - mock_checkout.start_time = datetime(2024, 1, 1, 12, 0, 0) - mock_checkout.origin_builds_finish_time = datetime.now() - mock_checkout.origin_tests_finish_time = datetime.now() - - with patch("kernelCI_app.tasks._is_checkout_done", return_value=True): - result = _is_checkout_unstable(checkout=mock_checkout) - - assert result is False - - @patch("kernelCI_app.tasks.now") - def test_is_checkout_unstable_with_dict_done(self, mock_now): - """Test _is_checkout_unstable with done checkout dict.""" - mock_now.return_value = datetime(2024, 1, 15, 12, 0, 0) - checkout_dict = { - "start_time": datetime(2024, 1, 1, 12, 0, 0), - "origin_builds_finish_time": datetime.now(), - "origin_tests_finish_time": datetime.now(), - } - - with patch("kernelCI_app.tasks._is_checkout_done", return_value=True): - result = _is_checkout_unstable(checkout=checkout_dict) - - assert result is False - - @patch("kernelCI_app.tasks.now") - def test_is_checkout_unstable_old_checkout(self, mock_now): - """Test _is_checkout_unstable with old checkout.""" - mock_now.return_value = datetime(2024, 1, 15, 12, 0, 0) - mock_checkout = MagicMock(spec=Checkouts) - mock_checkout.start_time = datetime(2023, 1, 1, 12, 0, 0) # Very old - mock_checkout.origin_builds_finish_time = None - mock_checkout.origin_tests_finish_time = None - - with patch("kernelCI_app.tasks._is_checkout_done", return_value=False): - result = _is_checkout_unstable(checkout=mock_checkout) - - assert result is False - - @patch("kernelCI_app.tasks.now") - def test_is_checkout_unstable_recent_unfinished(self, mock_now): - """Test _is_checkout_unstable with recent unfinished checkout.""" - mock_now.return_value = datetime(2024, 1, 15, 12, 0, 0) - mock_checkout = MagicMock(spec=Checkouts) - mock_checkout.start_time = datetime(2024, 1, 14, 12, 0, 0) # Recent - mock_checkout.origin_builds_finish_time = None - mock_checkout.origin_tests_finish_time = None - - with patch("kernelCI_app.tasks._is_checkout_done", return_value=False): - result = _is_checkout_unstable(checkout=mock_checkout) - - assert result is True - - @patch("kernelCI_app.tasks.now") - def test_is_checkout_unstable_none_start_time(self, mock_now): - """Test _is_checkout_unstable with None start_time.""" - mock_now.return_value = datetime(2024, 1, 15, 12, 0, 0) - mock_checkout = MagicMock(spec=Checkouts) - mock_checkout.start_time = None - mock_checkout.origin_builds_finish_time = None - mock_checkout.origin_tests_finish_time = None - - with patch("kernelCI_app.tasks._is_checkout_done", return_value=False): - result = _is_checkout_unstable(checkout=mock_checkout) - - assert result is True - - -class TestIsCheckoutNewer: - def test_cached_start_time_none_returns_false(self): - result = _is_checkout_newer( - cached_start_time=None, - old_checkout_start_time=datetime(2024, 1, 15, 12, 0, 0), - ) - - assert result is False - - def test_checkout_start_time_none_returns_false(self): - result = _is_checkout_newer( - cached_start_time=datetime(2024, 1, 14, 12, 0, 0), - old_checkout_start_time=None, - ) - - assert result is False - - def test_both_none_returns_false(self): - result = _is_checkout_newer( - cached_start_time=None, - old_checkout_start_time=None, - ) - - assert result is False - - def test_newer_checkout_returns_true(self): - result = _is_checkout_newer( - cached_start_time=datetime(2024, 1, 14, 12, 0, 0), - old_checkout_start_time=datetime(2024, 1, 15, 12, 0, 0, 0, timezone.utc), - ) - - assert result is True - - def test_older_checkout_returns_false(self): - result = _is_checkout_newer( - cached_start_time=datetime(2024, 1, 15, 12, 0, 0), - old_checkout_start_time=datetime(2024, 1, 14, 12, 0, 0, 0, timezone.utc), - ) - - assert result is False - - def test_equal_start_times_returns_false(self): - same_time = datetime(2024, 1, 15, 12, 0, 0, 0, timezone.utc) - - result = _is_checkout_newer( - cached_start_time=same_time, - old_checkout_start_time=same_time, - ) - - assert result is False - - def test_already_aware_datetimes_still_compared(self): - cached = datetime(2024, 1, 14, 12, 0, 0, tzinfo=timezone.utc) - newer = datetime(2024, 1, 15, 12, 0, 0, tzinfo=timezone.utc) - - result = _is_checkout_newer( - cached_start_time=cached, - old_checkout_start_time=newer, - ) - - assert result is True - - -class TestGetCheckoutIdsForUpdate: - def test_get_checkout_ids_for_update_no_cache_origins(self): - """Test that NO_CACHE_ORIGINS are skipped.""" - mock_checkout = MagicMock(spec=Checkouts) - mock_checkout.origin = "broonie" - mock_checkout.id = "checkout1" - - result = get_checkout_ids_for_update( - kcidb_checkouts=[mock_checkout], - sqlite_tree_keys=set(), - sqlite_trees_map={}, - ) - - assert result == set() - - def test_get_checkout_ids_for_update_new_tree(self): - """Test that new trees are added to update list.""" - mock_checkout = MagicMock(spec=Checkouts) - mock_checkout.origin = "valid_origin" - mock_checkout.id = "checkout1" - mock_checkout.tree_name = "tree1" - mock_checkout.git_repository_branch = "branch1" - mock_checkout.git_repository_url = "url1" - - result = get_checkout_ids_for_update( - kcidb_checkouts=[mock_checkout], - sqlite_tree_keys=set(), - sqlite_trees_map={}, - ) - - assert result == {"checkout1"} - - def test_get_checkout_ids_for_update_unstable_sqlite_tree(self): - """Test that unstable sqlite trees are updated.""" - mock_checkout = MagicMock(spec=Checkouts) - mock_checkout.origin = "valid_origin" - mock_checkout.id = "checkout1" - mock_checkout.tree_name = "tree1" - mock_checkout.git_repository_branch = "branch1" - mock_checkout.git_repository_url = "url1" - mock_checkout.start_time = None - - tree_key = ("tree1", "branch1", "url1") - sqlite_trees_map = { - tree_key: {"unstable": True, "checkout_id": "sqlite_checkout1"} - } - - result = get_checkout_ids_for_update( - kcidb_checkouts=[mock_checkout], - sqlite_tree_keys={tree_key}, - sqlite_trees_map=sqlite_trees_map, - ) - - assert "sqlite_checkout1" in result - - def test_get_checkout_ids_for_update_unstable_checkout(self): - """Test that unstable checkouts are updated.""" - mock_checkout = MagicMock(spec=Checkouts) - mock_checkout.origin = "valid_origin" - mock_checkout.id = "checkout1" - mock_checkout.tree_name = "tree1" - mock_checkout.git_repository_branch = "branch1" - mock_checkout.git_repository_url = "url1" - - tree_key = ("tree1", "branch1", "url1") - sqlite_trees_map = { - tree_key: {"unstable": False, "checkout_id": "sqlite_checkout1"} - } - - with patch("kernelCI_app.tasks._is_checkout_unstable", return_value=True): - result = get_checkout_ids_for_update( - kcidb_checkouts=[mock_checkout], - sqlite_tree_keys={tree_key}, - sqlite_trees_map=sqlite_trees_map, - ) - - assert "checkout1" in result - - def test_get_checkout_ids_for_update_newer_checkout(self): - """Test that newer checkouts are updated.""" - mock_checkout = MagicMock(spec=Checkouts) - mock_checkout.origin = "valid_origin" - mock_checkout.id = "checkout1" - mock_checkout.tree_name = "tree1" - mock_checkout.git_repository_branch = "branch1" - mock_checkout.git_repository_url = "url1" - mock_checkout.start_time = datetime(2024, 1, 15, 12, 0, 0, 0, timezone.utc) - - tree_key = ("tree1", "branch1", "url1") - sqlite_trees_map = { - tree_key: { - "unstable": False, - "checkout_id": "sqlite_checkout1", - "start_time": datetime(2024, 1, 14, 12, 0, 0), - } - } - - with patch("kernelCI_app.tasks._is_checkout_unstable", return_value=False): - result = get_checkout_ids_for_update( - kcidb_checkouts=[mock_checkout], - sqlite_tree_keys={tree_key}, - sqlite_trees_map=sqlite_trees_map, - ) - - assert "checkout1" in result - - -class TestUpdateCheckoutCache: - @patch("kernelCI_app.tasks._is_checkout_unstable") - @patch("kernelCI_app.tasks.populate_checkouts_cache_db") - @patch("kernelCI_app.tasks.get_tree_listing_data_by_checkout_id") - @patch("kernelCI_app.tasks.get_cached_tree_listing_fast") - @patch("kernelCI_app.tasks.get_tree_listing_fast") - @patch("kernelCI_app.tasks.get_checkout_ids_for_update") - def test_update_checkout_cache_with_sqlite_data( - self, - mock_get_checkout_ids, - mock_get_tree_listing_fast, - mock_get_cached_tree_listing_fast, - mock_get_tree_listing_data_by_checkout_id, - mock_populate_checkouts_cache_db, - mock_is_checkout_unstable, - ): - """Test the main update_checkout_cache function with sqlite_checkouts.""" - mock_get_tree_listing_fast.return_value = [] - mock_get_cached_tree_listing_fast.return_value = [ - { - "tree_name": "test_tree", - "git_repository_branch": "main", - "git_repository_url": "https://git.example.com/test.git", - "checkout_id": "sqlite_checkout1", - "unstable": False, - }, - { - "tree_name": "another_tree", - "git_repository_branch": "develop", - "git_repository_url": "https://git.example.com/another.git", - "checkout_id": "sqlite_checkout2", - "unstable": True, - }, - ] - mock_get_checkout_ids.return_value = {"checkout1", "checkout2"} - mock_get_tree_listing_data_by_checkout_id.return_value = [ - {"id": "checkout1"}, - {"id": "checkout2"}, - ] - - def mock_unstable_side_effect(checkout): - if checkout["id"] == "checkout1": - return False - elif checkout["id"] == "checkout2": - return True - return False - - mock_is_checkout_unstable.side_effect = mock_unstable_side_effect - - update_checkout_cache() - - mock_get_tree_listing_fast.assert_called_once_with( - interval={"days": UPDATE_INTERVAL_IN_DAYS} - ) - mock_get_cached_tree_listing_fast.assert_called_once() - mock_get_checkout_ids.assert_called_once() - - call_args = mock_get_checkout_ids.call_args[1] - assert "kcidb_checkouts" in call_args - assert "sqlite_tree_keys" in call_args - assert "sqlite_trees_map" in call_args - - sqlite_tree_keys = call_args["sqlite_tree_keys"] - expected_keys = { - ("test_tree", "main", "https://git.example.com/test.git"), - ("another_tree", "develop", "https://git.example.com/another.git"), - } - assert sqlite_tree_keys == expected_keys - - sqlite_trees_map = call_args["sqlite_trees_map"] - assert len(sqlite_trees_map) == 2 - assert ( - "test_tree", - "main", - "https://git.example.com/test.git", - ) in sqlite_trees_map - assert ( - "another_tree", - "develop", - "https://git.example.com/another.git", - ) in sqlite_trees_map - - mock_get_tree_listing_data_by_checkout_id.assert_called_once() - mock_populate_checkouts_cache_db.assert_called_once() diff --git a/backend/kernelCI_app/tests/unitTests/views/hardwareView_test.py b/backend/kernelCI_app/tests/unitTests/views/hardwareView_test.py index b9f88564b..066b4c4b4 100644 --- a/backend/kernelCI_app/tests/unitTests/views/hardwareView_test.py +++ b/backend/kernelCI_app/tests/unitTests/views/hardwareView_test.py @@ -14,10 +14,12 @@ def setUp(self): self.view = HardwareView() self.url = "/hardware" - @patch("kernelCI_app.views.hardwareView.get_hardware_listing_data") - def test_get_hardware_listing_success(self, mock_get_hardware_listing_data): - mock_get_hardware_listing_data.return_value = [ - ("platform1", "hardware1", *range(22)), + @patch( + "kernelCI_app.views.hardwareView.get_hardware_listing_data_from_status_table" + ) + def test_get_hardware_listing_success(self, mock_get_status_table_data): + mock_get_status_table_data.return_value = [ + ("platform1", "hardware1", *range(9)), ] query_params = { @@ -30,19 +32,19 @@ def test_get_hardware_listing_success(self, mock_get_hardware_listing_data): response = self.view.get(request) self.assertEqual(response.status_code, HTTPStatus.OK) - mock_get_hardware_listing_data.assert_called_once_with( + mock_get_status_table_data.assert_called_once_with( origin="origin1", start_date=ANY, end_date=ANY, commits_list=None, ) - @patch("kernelCI_app.views.hardwareView.get_hardware_listing_data") - def test_get_hardware_listing_passes_commits_list( - self, mock_get_hardware_listing_data - ): - mock_get_hardware_listing_data.return_value = [ - ("platform1", "hardware1", *range(22)), + @patch( + "kernelCI_app.views.hardwareView.get_hardware_listing_data_from_status_table" + ) + def test_get_hardware_listing_passes_commits_list(self, mock_get_status_table_data): + mock_get_status_table_data.return_value = [ + ("platform1", "hardware1", *range(9)), ] h1 = "a" * 40 h2 = "b" * 40 @@ -59,7 +61,7 @@ def test_get_hardware_listing_passes_commits_list( response = self.view.get(request) self.assertEqual(response.status_code, HTTPStatus.OK) - mock_get_hardware_listing_data.assert_called_once_with( + mock_get_status_table_data.assert_called_once_with( origin="origin1", start_date=ANY, end_date=ANY, @@ -76,11 +78,13 @@ def test_get_hardware_listing_invalid_query_params_returns_bad_request(self): self.assertIn("start_date", response.data) self.assertIn("end_date", response.data) - @patch("kernelCI_app.views.hardwareView.get_hardware_listing_data") + @patch( + "kernelCI_app.views.hardwareView.get_hardware_listing_data_from_status_table" + ) def test_get_hardware_listing_no_hardware_found_returns_ok_with_error( - self, mock_get_hardware_listing_data + self, mock_get_status_table_data ): - mock_get_hardware_listing_data.return_value = [] + mock_get_status_table_data.return_value = [] query_params = { "startTimestampInSeconds": "1741192200", @@ -94,12 +98,14 @@ def test_get_hardware_listing_no_hardware_found_returns_ok_with_error( self.assertEqual(response.status_code, HTTPStatus.OK) self.assertEqual(response.data, {"error": ClientStrings.NO_HARDWARE_FOUND}) - @patch("kernelCI_app.views.hardwareView.get_hardware_listing_data") + @patch( + "kernelCI_app.views.hardwareView.get_hardware_listing_data_from_status_table" + ) def test_get_hardware_listing_sanitize_validation_error_returns_internal_server_error( - self, mock_get_hardware_listing_data + self, mock_get_status_table_data ): - mock_get_hardware_listing_data.return_value = [ - (None, "hardware1", *range(22)), + mock_get_status_table_data.return_value = [ + (None, "hardware1", *range(9)), ] query_params = { diff --git a/backend/kernelCI_app/tests/utils/client/treeClient.py b/backend/kernelCI_app/tests/utils/client/treeClient.py index 9bdea5b57..c872f90c5 100644 --- a/backend/kernelCI_app/tests/utils/client/treeClient.py +++ b/backend/kernelCI_app/tests/utils/client/treeClient.py @@ -13,11 +13,6 @@ class TreeClient(BaseClient): - def get_tree_listing_fast(self, *, query: dict) -> requests.Response: - path = reverse("tree-fast") - url = self.get_endpoint(path=path, query=query) - return requests.get(url) - def get_tree_listing(self, *, query: dict) -> requests.Response: path = reverse("tree") url = self.get_endpoint(path=path, query=query) diff --git a/backend/kernelCI_app/tests/utils/fields/hardware.py b/backend/kernelCI_app/tests/utils/fields/hardware.py index f259d01d1..c27a2e121 100644 --- a/backend/kernelCI_app/tests/utils/fields/hardware.py +++ b/backend/kernelCI_app/tests/utils/fields/hardware.py @@ -6,7 +6,7 @@ "build_status_summary", ] -status_summary_fields = ["FAIL", "PASS", "SKIP", "ERROR", "MISS", "NULL", "DONE"] +status_summary_fields = ["PASS", "FAIL", "INCONCLUSIVE"] hardware_summary = ["common", "summary", "filters"] hardware_summary_common = ["trees", "compatibles"] diff --git a/backend/kernelCI_app/tests/utils/fields/tree.py b/backend/kernelCI_app/tests/utils/fields/tree.py index 25088be7f..e218efac6 100644 --- a/backend/kernelCI_app/tests/utils/fields/tree.py +++ b/backend/kernelCI_app/tests/utils/fields/tree.py @@ -38,26 +38,11 @@ "build_status", "test_status", "boot_status", - "origin_builds_finish_time", - "origin_tests_finish_time", ] -tree_listing_build_status = [ - "FAIL", - "ERROR", - "MISS", +tree_listing_status = [ "PASS", - "DONE", - "SKIP", - "NULL", -] -tree_listing_test_status = [ - "fail", - "error", - "miss", - "pass", - "done", - "skip", - "null", + "FAIL", + "INCONCLUSIVE", ] tree_commit_history = [ "git_commit_hash", diff --git a/backend/kernelCI_app/typeModels/commonListing.py b/backend/kernelCI_app/typeModels/commonListing.py index 28c0c1ff3..72f69116e 100644 --- a/backend/kernelCI_app/typeModels/commonListing.py +++ b/backend/kernelCI_app/typeModels/commonListing.py @@ -30,7 +30,7 @@ class ListingQueryParameters(ListingInterval): ] -class StatusCountV2(BaseModel): +class ListingStatusCount(BaseModel): PASS: Optional[int] = 0 FAIL: Optional[int] = 0 INCONCLUSIVE: Optional[int] = 0 diff --git a/backend/kernelCI_app/typeModels/hardwareListing.py b/backend/kernelCI_app/typeModels/hardwareListing.py index c07aa603b..972621a97 100644 --- a/backend/kernelCI_app/typeModels/hardwareListing.py +++ b/backend/kernelCI_app/typeModels/hardwareListing.py @@ -6,6 +6,7 @@ from kernelCI_app.constants.general import DEFAULT_ORIGIN from kernelCI_app.constants.localization import DocStrings from kernelCI_app.typeModels.common import StatusCount +from kernelCI_app.typeModels.commonListing import ListingStatusCount def _normalize_commits_list(value: object) -> Optional[list[str]]: @@ -25,7 +26,19 @@ class HardwareItem(BaseModel): build_status_summary: StatusCount +class HardwareListingItem(BaseModel): + hardware: Optional[Union[str, set[str]]] + platform: str + test_status_summary: ListingStatusCount + boot_status_summary: ListingStatusCount + build_status_summary: ListingStatusCount + + class HardwareListingResponse(BaseModel): + hardware: list[HardwareListingItem] + + +class HardwareListingByRevisionResponse(BaseModel): hardware: list[HardwareItem] diff --git a/backend/kernelCI_app/typeModels/hardwareListingV2.py b/backend/kernelCI_app/typeModels/hardwareListingV2.py deleted file mode 100644 index 8d711d7ae..000000000 --- a/backend/kernelCI_app/typeModels/hardwareListingV2.py +++ /dev/null @@ -1,63 +0,0 @@ -from datetime import datetime -from typing import Annotated, Optional, Union - -from pydantic import BaseModel, BeforeValidator, Field - -from kernelCI_app.constants.general import DEFAULT_ORIGIN -from kernelCI_app.constants.localization import DocStrings -from kernelCI_app.typeModels.commonListing import StatusCountV2 - - -def _normalize_commits_list(value: object) -> Optional[list[str]]: - if value is None: - return None - if isinstance(value, str): - cleaned = [part.strip() for part in value.split(",") if part.strip()] - return cleaned if cleaned else None - return None - - -class HardwareItemV2(BaseModel): - hardware: Optional[Union[str, set[str]]] - platform: str - test_status_summary: StatusCountV2 - boot_status_summary: StatusCountV2 - build_status_summary: StatusCountV2 - - -class HardwareListingResponseV2(BaseModel): - hardware: list[HardwareItemV2] - - -class HardwareQueryParamsV2DocumentationOnly(BaseModel): - origin: Annotated[ - str, - Field( - default=DEFAULT_ORIGIN, - description=DocStrings.HARDWARE_LISTING_ORIGIN_DESCRIPTION, - ), - ] - startTimestampInSeconds: str = Field( # noqa: N815 - description=DocStrings.DEFAULT_START_TS_DESCRIPTION - ) - endTimestampInSeconds: str = Field( # noqa: N815 - description=DocStrings.DEFAULT_END_TS_DESCRIPTION - ) - commitsList: Optional[str] = Field( # noqa: N815 - default=None, - description=DocStrings.HARDWARE_LISTING_COMMITS_LIST_DESCRIPTION, - ) - - -class HardwareQueryParamsV2(BaseModel): - origin: Annotated[ - str, - Field(default=DEFAULT_ORIGIN), - BeforeValidator(lambda o: DEFAULT_ORIGIN if o is None else o), - ] - start_date: datetime - end_date: datetime - commits_list: Annotated[ - Optional[list[str]], - BeforeValidator(_normalize_commits_list), - ] = Field(default=None) diff --git a/backend/kernelCI_app/typeModels/treeListing.py b/backend/kernelCI_app/typeModels/treeListing.py index 919f6f18b..9871368e3 100644 --- a/backend/kernelCI_app/typeModels/treeListing.py +++ b/backend/kernelCI_app/typeModels/treeListing.py @@ -1,10 +1,10 @@ -from typing import List, Optional +from typing import Optional from pydantic import BaseModel, Field, RootModel from kernelCI_app.helpers.logger import log_message from kernelCI_app.typeModels.common import StatusCount -from kernelCI_app.typeModels.commonListing import StatusCountV2 +from kernelCI_app.typeModels.commonListing import ListingStatusCount from kernelCI_app.typeModels.databases import ( Checkout__GitCommitHash, Checkout__GitCommitName, @@ -14,7 +14,6 @@ Checkout__Id, Checkout__OriginBuildsFinishTime, Checkout__OriginTestsFinishTime, - Checkout__PatchsetHash, Checkout__TreeName, Origin, Timestamp, @@ -79,26 +78,11 @@ class Checkout(CommonCheckouts): git_commit_tags: list[Checkout__GitCommitTags] -class CheckoutFast(CommonCheckouts): - id: Checkout__Id - tree_name: Checkout__TreeName - patchset_hash: Checkout__PatchsetHash - git_commit_tags: Checkout__GitCommitTags - - -class TreeListingResponse(RootModel): - root: List[Checkout] - - -class TreeListingFastResponse(RootModel): - root: List[CheckoutFast] - - class TreeListingItem(BaseModel): id: Checkout__Id = Field(validation_alias="checkout_id") - build_status: StatusCountV2 - boot_status: StatusCountV2 - test_status: StatusCountV2 + build_status: ListingStatusCount + boot_status: ListingStatusCount + test_status: ListingStatusCount tree_name: Checkout__TreeName git_commit_tags: Checkout__GitCommitTags origin: Origin @@ -109,5 +93,5 @@ class TreeListingItem(BaseModel): start_time: Timestamp -class TreeListingResponseV2(RootModel): +class TreeListingResponse(RootModel): root: list[TreeListingItem] diff --git a/backend/kernelCI_app/urls.py b/backend/kernelCI_app/urls.py index c4bafd12d..e4c98f958 100644 --- a/backend/kernelCI_app/urls.py +++ b/backend/kernelCI_app/urls.py @@ -22,8 +22,6 @@ def view_cache(view, timeout: int = settings.CACHE_TIMEOUT): ), path("test/", view_cache(views.TestDetails), name="testDetails"), path("tree/", view_cache(views.TreeView), name="tree"), - path("tree-v2/", view_cache(views.TreeViewV2), name="tree-v2"), - path("tree-fast/", view_cache(views.TreeViewFast), name="tree-fast"), path( "tree//full", views.TreeDetails.as_view(), @@ -114,7 +112,7 @@ def view_cache(view, timeout: int = settings.CACHE_TIMEOUT): path("log-downloader/", view_cache(views.LogDownloaderView), name="logDownloader"), path( "hardware/selectors/", - view_cache(views.HardwareSelectorsView, timeout=60 * 60), + view_cache(views.HardwareSelectorsView, timeout=settings.CACHE_TIMEOUT * 60), name="hardwareSelectors", ), path( @@ -153,7 +151,6 @@ def view_cache(view, timeout: int = settings.CACHE_TIMEOUT): view_cache(views.HardwareByRevisionView), name="hardwareByRevision", ), - path("hardware-v2/", view_cache(views.HardwareViewV2), name="hardware-v2"), path("issue/", view_cache(views.IssueView), name="issue"), path( "issue/extras/", view_cache(views.IssueExtraDetails), name="issueExtraDetails" diff --git a/backend/kernelCI_app/views/hardwareByRevisionView.py b/backend/kernelCI_app/views/hardwareByRevisionView.py index a26a935b9..931548b70 100644 --- a/backend/kernelCI_app/views/hardwareByRevisionView.py +++ b/backend/kernelCI_app/views/hardwareByRevisionView.py @@ -9,7 +9,7 @@ from kernelCI_app.queries.hardware import get_hardware_listing_data_by_revision from kernelCI_app.typeModels.hardwareListing import ( HardwareItem, - HardwareListingResponse, + HardwareListingByRevisionResponse, ) from kernelCI_app.typeModels.hardwareListingByRevision import ( HardwareListingByRevisionQueryParams, @@ -59,7 +59,7 @@ def _sanitize_records(self, hardwares_raw: list[dict]) -> list[HardwareItem]: @extend_schema( parameters=[HardwareListingByRevisionQueryParamsDocumentationOnly], - responses=HardwareListingResponse, + responses=HardwareListingByRevisionResponse, ) def get(self, request: Request): try: @@ -83,7 +83,7 @@ def get(self, request: Request): try: sanitized_records = self._sanitize_records(hardwares_raw=hardwares_raw) - result = HardwareListingResponse(hardware=sanitized_records) + result = HardwareListingByRevisionResponse(hardware=sanitized_records) except ValidationError as e: return Response(data=e.json(), status=HTTPStatus.INTERNAL_SERVER_ERROR) diff --git a/backend/kernelCI_app/views/hardwareView.py b/backend/kernelCI_app/views/hardwareView.py index 7dfe21dfc..f2ab36312 100644 --- a/backend/kernelCI_app/views/hardwareView.py +++ b/backend/kernelCI_app/views/hardwareView.py @@ -1,3 +1,4 @@ +from datetime import datetime from http import HTTPStatus from drf_spectacular.utils import extend_schema @@ -7,12 +8,11 @@ from rest_framework.views import APIView from kernelCI_app.constants.localization import ClientStrings -from kernelCI_app.helpers.errorHandling import ( - create_api_error_response, -) -from kernelCI_app.queries.hardware import get_hardware_listing_data +from kernelCI_app.helpers.errorHandling import create_api_error_response +from kernelCI_app.queries.hardware import get_hardware_listing_data_from_status_table +from kernelCI_app.typeModels.commonListing import ListingStatusCount from kernelCI_app.typeModels.hardwareListing import ( - HardwareItem, + HardwareListingItem, HardwareListingResponse, HardwareQueryParams, HardwareQueryParamsDocumentationOnly, @@ -20,40 +20,30 @@ class HardwareView(APIView): - def _sanitize_records(self, hardwares_raw: list[tuple]) -> list[HardwareItem]: + def _sanitize_records( + self, hardwares_raw: list[tuple] + ) -> list[HardwareListingItem]: hardwares = [] for hardware in hardwares_raw: hardwares.append( - HardwareItem( + HardwareListingItem( platform=hardware[0], hardware=hardware[1], - build_status_summary={ - "PASS": hardware[2], - "FAIL": hardware[3], - "NULL": hardware[4], - "ERROR": hardware[5], - "MISS": hardware[6], - "DONE": hardware[7], - "SKIP": hardware[8], - }, - boot_status_summary={ - "PASS": hardware[9], - "FAIL": hardware[10], - "NULL": hardware[11], - "ERROR": hardware[12], - "MISS": hardware[13], - "DONE": hardware[14], - "SKIP": hardware[15], - }, - test_status_summary={ - "PASS": hardware[16], - "FAIL": hardware[17], - "NULL": hardware[18], - "ERROR": hardware[19], - "MISS": hardware[20], - "DONE": hardware[21], - "SKIP": hardware[22], - }, + build_status_summary=ListingStatusCount( + PASS=hardware[2], + FAIL=hardware[3], + INCONCLUSIVE=hardware[4], + ), + boot_status_summary=ListingStatusCount( + PASS=hardware[5], + FAIL=hardware[6], + INCONCLUSIVE=hardware[7], + ), + test_status_summary=ListingStatusCount( + PASS=hardware[8], + FAIL=hardware[9], + INCONCLUSIVE=hardware[10], + ), ) ) @@ -71,13 +61,17 @@ def get(self, request: Request): origin=request.GET.get("origin"), commits_list=request.GET.get("commitsList"), ) + + start_date: datetime = query_params.start_date + end_date: datetime = query_params.end_date + origin = query_params.origin except ValidationError as e: return Response(data=e.json(), status=HTTPStatus.BAD_REQUEST) - hardwares_raw = get_hardware_listing_data( - origin=query_params.origin, - start_date=query_params.start_date, - end_date=query_params.end_date, + hardwares_raw = get_hardware_listing_data_from_status_table( + origin=origin, + start_date=start_date, + end_date=end_date, commits_list=query_params.commits_list, ) diff --git a/backend/kernelCI_app/views/hardwareViewV2.py b/backend/kernelCI_app/views/hardwareViewV2.py deleted file mode 100644 index 4ddf9237d..000000000 --- a/backend/kernelCI_app/views/hardwareViewV2.py +++ /dev/null @@ -1,92 +0,0 @@ -from datetime import datetime -from http import HTTPStatus - -from drf_spectacular.utils import extend_schema -from pydantic import ValidationError -from rest_framework.request import Request -from rest_framework.response import Response -from rest_framework.views import APIView - -from kernelCI_app.constants.localization import ClientStrings -from kernelCI_app.helpers.errorHandling import ( - create_api_error_response, -) -from kernelCI_app.queries.hardware import ( - get_hardware_listing_data_from_status_table, -) -from kernelCI_app.typeModels.hardwareListingV2 import ( - HardwareItemV2, - HardwareListingResponseV2, - HardwareQueryParamsV2, - HardwareQueryParamsV2DocumentationOnly, - StatusCountV2, -) - - -class HardwareViewV2(APIView): - def _sanitize_records(self, hardwares_raw: list[tuple]) -> list[HardwareItemV2]: - hardwares = [] - for hardware in hardwares_raw: - hardwares.append( - HardwareItemV2( - platform=hardware[0], - hardware=hardware[1], - build_status_summary=StatusCountV2( - PASS=hardware[2], - FAIL=hardware[3], - INCONCLUSIVE=hardware[4], - ), - boot_status_summary=StatusCountV2( - PASS=hardware[5], - FAIL=hardware[6], - INCONCLUSIVE=hardware[7], - ), - test_status_summary=StatusCountV2( - PASS=hardware[8], - FAIL=hardware[9], - INCONCLUSIVE=hardware[10], - ), - ) - ) - - return hardwares - - @extend_schema( - parameters=[HardwareQueryParamsV2DocumentationOnly], - responses=HardwareListingResponseV2, - ) - def get(self, request: Request): - try: - query_params = HardwareQueryParamsV2( - start_date=request.GET.get("startTimestampInSeconds"), - end_date=request.GET.get("endTimestampInSeconds"), - origin=request.GET.get("origin"), - commits_list=request.GET.get("commitsList"), - ) - - start_date: datetime = query_params.start_date - end_date: datetime = query_params.end_date - origin = query_params.origin - except ValidationError as e: - return Response(data=e.json(), status=HTTPStatus.BAD_REQUEST) - - hardwares_raw = get_hardware_listing_data_from_status_table( - origin=origin, - start_date=start_date, - end_date=end_date, - commits_list=query_params.commits_list, - ) - - try: - sanitized_records = self._sanitize_records(hardwares_raw=hardwares_raw) - result = HardwareListingResponseV2(hardware=sanitized_records) - - if len(result.hardware) < 1: - return create_api_error_response( - error_message=ClientStrings.NO_HARDWARE_FOUND, - status_code=HTTPStatus.OK, - ) - except ValidationError as e: - return Response(data=e.json(), status=HTTPStatus.INTERNAL_SERVER_ERROR) - - return Response(data=result.model_dump(), status=HTTPStatus.OK) diff --git a/backend/kernelCI_app/views/treeView.py b/backend/kernelCI_app/views/treeView.py index d3bd3e2b1..ce395c3e8 100644 --- a/backend/kernelCI_app/views/treeView.py +++ b/backend/kernelCI_app/views/treeView.py @@ -1,101 +1,84 @@ from http import HTTPStatus from django.http import HttpRequest -from django.utils.timezone import make_aware, now from drf_spectacular.utils import extend_schema from pydantic import ValidationError from rest_framework.response import Response from rest_framework.views import APIView from kernelCI_app.constants.localization import ClientStrings -from kernelCI_app.helpers.errorHandling import ( - create_api_error_response, +from kernelCI_app.helpers.errorHandling import create_api_error_response +from kernelCI_app.queries.tree import get_tree_listing_data_denormalized +from kernelCI_app.typeModels.commonListing import ( + ListingQueryParameters, + ListingStatusCount, ) -from kernelCI_app.helpers.trees import sanitize_tree -from kernelCI_app.queries.tree import get_tree_listing_data -from kernelCI_app.typeModels.commonListing import ListingQueryParameters -from kernelCI_app.typeModels.treeListing import ( - Checkout, - TreeListingResponse, -) -from kernelCI_cache.constants import UNSTABLE_CHECKOUT_THRESHOLD -from kernelCI_cache.queries.tree import get_cached_tree_listing_data +from kernelCI_app.typeModels.treeListing import TreeListingItem, TreeListingResponse class TreeView(APIView): + def _sanitize_records(self, trees_raw: list[tuple]) -> list[TreeListingItem]: + trees = [] + for tree in trees_raw: + trees.append( + TreeListingItem( + checkout_id=tree[0], + origin=tree[1], + tree_name=tree[2], + git_repository_url=tree[3], + git_repository_branch=tree[4], + git_commit_hash=tree[5], + git_commit_name=tree[6], + git_commit_tags=tree[7], + start_time=tree[8], + build_status=ListingStatusCount( + PASS=tree[9], + FAIL=tree[10], + INCONCLUSIVE=tree[11], + ), + boot_status=ListingStatusCount( + PASS=tree[12], + FAIL=tree[13], + INCONCLUSIVE=tree[14], + ), + test_status=ListingStatusCount( + PASS=tree[15], + FAIL=tree[16], + INCONCLUSIVE=tree[17], + ), + ) + ) + + return trees + @extend_schema( - responses=TreeListingResponse, parameters=[ListingQueryParameters], - methods=["GET"], + responses=TreeListingResponse, ) - def get(self, request: HttpRequest) -> Response: + def get(self, request: HttpRequest): try: - request_params = ListingQueryParameters( + query_params = ListingQueryParameters( origin=request.GET.get("origin"), interval_in_days=request.GET.get("interval_in_days"), ) except ValidationError as e: return Response(data=e.json(), status=HTTPStatus.BAD_REQUEST) - origin_param = request_params.origin - interval_param = request_params.interval_in_days - - cached_checkouts = get_cached_tree_listing_data( - origin=origin_param, - interval_in_days=interval_param, - min_age_in_days=min(interval_param, UNSTABLE_CHECKOUT_THRESHOLD), - ) - - has_cache = cached_checkouts is not None and len(cached_checkouts) > 0 - - if has_cache: - # Query kcidb from now up to the earliest cache entry so that we can - # avoid a cache gap between the last cache entry and UNSTABLE_CHECKOUT_THRESHOLD - earliest_cache_time = min( - checkout["start_time"] for checkout in cached_checkouts - ) - if earliest_cache_time.tzinfo is None: - earliest_cache_time = make_aware(earliest_cache_time) - days_to_earliest = (now() - earliest_cache_time).days - kcidb_interval = max(days_to_earliest, UNSTABLE_CHECKOUT_THRESHOLD) - else: - kcidb_interval = interval_param - - kcidb_checkouts = get_tree_listing_data( - origin=origin_param, - interval_in_days=kcidb_interval, + trees_raw = get_tree_listing_data_denormalized( + origin=query_params.origin, + interval_in_days=query_params.interval_in_days, ) - if not cached_checkouts and not kcidb_checkouts: + if not trees_raw: return create_api_error_response( - error_message=ClientStrings.NO_TREES_FOUND, status_code=HTTPStatus.OK - ) - - # This set is only meant to remove the duplicate cases when the - # checkout is present in both the cache and in kcidb in the last x days. - # It saves the kcidb tree_name as part of the key, such that it doesn't skip - # trees with the same branch and git_url but different tree_name. - unique_trees: set[tuple[str, str, str]] = set() - checkouts: list[Checkout] = [] - - for checkout in kcidb_checkouts + list(cached_checkouts): - tree_name = checkout["tree_name"] - git_branch = checkout["git_repository_branch"] - git_url = checkout["git_repository_url"] - - kcidb_identifier = ( - tree_name, - git_branch, - git_url, + error_message=ClientStrings.NO_TREES_FOUND, + status_code=HTTPStatus.OK, ) - if kcidb_identifier not in unique_trees: - unique_trees.add(kcidb_identifier) - typed_checkout = sanitize_tree(checkout=checkout) - checkouts.append(typed_checkout) try: - valid_response = TreeListingResponse(checkouts) + sanitized_records = self._sanitize_records(trees_raw=trees_raw) + result = TreeListingResponse(sanitized_records) except ValidationError as e: return Response(data=e.json(), status=HTTPStatus.INTERNAL_SERVER_ERROR) - return Response(valid_response.model_dump(by_alias=True)) + return Response(data=result.model_dump(), status=HTTPStatus.OK) diff --git a/backend/kernelCI_app/views/treeViewFast.py b/backend/kernelCI_app/views/treeViewFast.py deleted file mode 100644 index 1a57d5e66..000000000 --- a/backend/kernelCI_app/views/treeViewFast.py +++ /dev/null @@ -1,74 +0,0 @@ -from http import HTTPStatus - -from django.http import HttpRequest -from drf_spectacular.utils import extend_schema -from pydantic import ValidationError -from rest_framework.response import Response -from rest_framework.views import APIView - -from kernelCI_app.constants.localization import ClientStrings -from kernelCI_app.helpers.errorHandling import create_api_error_response -from kernelCI_app.queries.tree import get_tree_listing_fast -from kernelCI_app.typeModels.commonListing import ListingQueryParameters -from kernelCI_app.typeModels.treeListing import ( - CheckoutFast, - TreeListingFastResponse, -) - - -class TreeViewFast(APIView): - @extend_schema( - responses=TreeListingFastResponse, - parameters=[ListingQueryParameters], - methods=["GET"], - ) - def get(self, request: HttpRequest) -> Response: - try: - request_params = ListingQueryParameters( - origin=(request.GET.get("origin")), - interval_in_days=request.GET.get("interval_in_days"), - ) - except ValidationError as e: - return Response(data=e.json(), status=HTTPStatus.BAD_REQUEST) - - origin = request_params.origin - interval_days = request_params.interval_in_days - - interval_days_data = {"days": interval_days} - - checkouts = get_tree_listing_fast( - origin=origin, - interval=interval_days_data, - ) - - if not checkouts: - return create_api_error_response( - error_message=ClientStrings.NO_TREES_FOUND, status_code=HTTPStatus.OK - ) - - response_data: list[CheckoutFast] = [] - - for checkout in checkouts: - response_data.append( - CheckoutFast( - id=checkout.id, - tree_name=checkout.tree_name, - origin=checkout.origin, - git_repository_branch=checkout.git_repository_branch, - git_repository_url=checkout.git_repository_url, - git_commit_hash=checkout.git_commit_hash, - git_commit_name=checkout.git_commit_name, - git_commit_tags=checkout.git_commit_tags, - patchset_hash=checkout.patchset_hash, - start_time=checkout.start_time, - origin_builds_finish_time=checkout.origin_builds_finish_time, - origin_tests_finish_time=checkout.origin_tests_finish_time, - ) - ) - - try: - valid_response = TreeListingFastResponse(response_data) - except ValidationError as e: - return Response(data=e.json(), status=HTTPStatus.INTERNAL_SERVER_ERROR) - - return Response(valid_response.model_dump()) diff --git a/backend/kernelCI_app/views/treeViewV2.py b/backend/kernelCI_app/views/treeViewV2.py deleted file mode 100644 index cf5f93a35..000000000 --- a/backend/kernelCI_app/views/treeViewV2.py +++ /dev/null @@ -1,81 +0,0 @@ -from http import HTTPStatus - -from django.http import HttpRequest -from drf_spectacular.utils import extend_schema -from pydantic import ValidationError -from rest_framework.response import Response -from rest_framework.views import APIView - -from kernelCI_app.constants.localization import ClientStrings -from kernelCI_app.helpers.errorHandling import create_api_error_response -from kernelCI_app.queries.tree import get_tree_listing_data_denormalized -from kernelCI_app.typeModels.commonListing import ListingQueryParameters, StatusCountV2 -from kernelCI_app.typeModels.treeListing import TreeListingItem, TreeListingResponseV2 - - -class TreeViewV2(APIView): - def _sanitize_records(self, trees_raw: list[tuple]) -> list[TreeListingItem]: - trees = [] - for tree in trees_raw: - trees.append( - TreeListingItem( - checkout_id=tree[0], - origin=tree[1], - tree_name=tree[2], - git_repository_url=tree[3], - git_repository_branch=tree[4], - git_commit_hash=tree[5], - git_commit_name=tree[6], - git_commit_tags=tree[7], - start_time=tree[8], - build_status=StatusCountV2( - PASS=tree[9], - FAIL=tree[10], - INCONCLUSIVE=tree[11], - ), - boot_status=StatusCountV2( - PASS=tree[12], - FAIL=tree[13], - INCONCLUSIVE=tree[14], - ), - test_status=StatusCountV2( - PASS=tree[15], - FAIL=tree[16], - INCONCLUSIVE=tree[17], - ), - ) - ) - - return trees - - @extend_schema( - parameters=[ListingQueryParameters], - responses=TreeListingResponseV2, - ) - def get(self, request: HttpRequest): - try: - query_params = ListingQueryParameters( - origin=request.GET.get("origin"), - interval_in_days=request.GET.get("interval_in_days"), - ) - except ValidationError as e: - return Response(data=e.json(), status=HTTPStatus.BAD_REQUEST) - - trees_raw = get_tree_listing_data_denormalized( - origin=query_params.origin, - interval_in_days=query_params.interval_in_days, - ) - - if not trees_raw: - return create_api_error_response( - error_message=ClientStrings.NO_TREES_FOUND, - status_code=HTTPStatus.OK, - ) - - try: - sanitized_records = self._sanitize_records(trees_raw=trees_raw) - result = TreeListingResponseV2(sanitized_records) - except ValidationError as e: - return Response(data=e.json(), status=HTTPStatus.INTERNAL_SERVER_ERROR) - - return Response(data=result.model_dump(), status=HTTPStatus.OK) diff --git a/backend/kernelCI_cache/checkouts.py b/backend/kernelCI_cache/checkouts.py deleted file mode 100644 index 0058c35e9..000000000 --- a/backend/kernelCI_cache/checkouts.py +++ /dev/null @@ -1,66 +0,0 @@ -import json -from typing import Optional - -from kernelCI_app.typeModels.databases import Origin -from kernelCI_cache.models import CheckoutsCache -from kernelCI_cache.utils import get_current_timestamp_kcidb_format - - -# This function could be called from another thread if we face -# delays with endpoints in the future -def populate_checkouts_cache_db( - *, - data: list[dict], - origin: Optional[Origin] = None, -) -> None: - for checkout in data: - origin_field = origin if origin is not None else checkout["origin"] - - # TODO: When we add patchset_hash to the TreeView, it should be added here too - lookup_fields = { - "origin": origin_field, - "tree_name": checkout["tree_name"], - "git_commit_hash": checkout["git_commit_hash"], - "git_repository_url": checkout["git_repository_url"], - "git_repository_branch": checkout["git_repository_branch"], - } - - update_fields = { - "checkout_id": checkout["id"], - "start_time": checkout["start_time"], - "git_commit_tags": json.dumps(checkout["git_commit_tags"]), - "git_commit_name": checkout["git_commit_name"], - "origin_builds_finish_time": checkout["origin_builds_finish_time"], - "origin_tests_finish_time": checkout["origin_tests_finish_time"], - "pass_builds": checkout["pass_builds"], - "fail_builds": checkout["fail_builds"], - "done_builds": checkout["done_builds"], - "miss_builds": checkout["miss_builds"], - "skip_builds": checkout["skip_builds"], - "error_builds": checkout["error_builds"], - "null_builds": checkout["null_builds"], - "pass_boots": checkout["pass_boots"], - "fail_boots": checkout["fail_boots"], - "done_boots": checkout["done_boots"], - "miss_boots": checkout["miss_boots"], - "skip_boots": checkout["skip_boots"], - "error_boots": checkout["error_boots"], - "null_boots": checkout["null_boots"], - "pass_tests": checkout["pass_tests"], - "fail_tests": checkout["fail_tests"], - "done_tests": checkout["done_tests"], - "miss_tests": checkout["miss_tests"], - "skip_tests": checkout["skip_tests"], - "error_tests": checkout["error_tests"], - "null_tests": checkout["null_tests"], - "unstable": checkout["unstable"], - } - - create_fields = { - **update_fields, - "field_timestamp": get_current_timestamp_kcidb_format(), - } - - CheckoutsCache.objects.using("cache").update_or_create( - **lookup_fields, defaults=update_fields, create_defaults=create_fields - ) diff --git a/backend/kernelCI_cache/constants.py b/backend/kernelCI_cache/constants.py deleted file mode 100644 index eadac5a9c..000000000 --- a/backend/kernelCI_cache/constants.py +++ /dev/null @@ -1,2 +0,0 @@ -UNSTABLE_CHECKOUT_THRESHOLD = 3 -NO_CACHE_ORIGINS = ["broonie"] diff --git a/backend/kernelCI_cache/migrations/0013_delete_checkoutscache.py b/backend/kernelCI_cache/migrations/0013_delete_checkoutscache.py new file mode 100644 index 000000000..8c670e194 --- /dev/null +++ b/backend/kernelCI_cache/migrations/0013_delete_checkoutscache.py @@ -0,0 +1,15 @@ +# Generated by Django 5.2.11 on 2026-06-03 14:25 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("kernelCI_cache", "0012_add_checkout_report_identification_fields"), + ] + + operations = [ + migrations.DeleteModel( + name="CheckoutsCache", + ), + ] diff --git a/backend/kernelCI_cache/models.py b/backend/kernelCI_cache/models.py index 5f2b93f28..84ef35d35 100644 --- a/backend/kernelCI_cache/models.py +++ b/backend/kernelCI_cache/models.py @@ -3,62 +3,6 @@ from kernelCI_app.constants.general import DEFAULT_ORIGIN -class CheckoutsCache(models.Model): - field_timestamp = models.DateTimeField(db_column="_timestamp") - checkout_id = models.TextField() - origin = models.TextField() - tree_name = models.TextField(blank=True, null=True) - start_time = models.DateTimeField(blank=True, null=True) - git_repository_branch = models.TextField(blank=True, null=True) - git_repository_url = models.TextField(blank=True, null=True) - git_commit_hash = models.TextField(blank=True, null=True) - git_commit_name = models.TextField(blank=True, null=True) - git_commit_tags = models.TextField(blank=True, null=True) - origin_builds_finish_time = models.DateTimeField(blank=True, null=True) - origin_tests_finish_time = models.DateTimeField(blank=True, null=True) - - pass_builds = models.IntegerField(default=0) - fail_builds = models.IntegerField(default=0) - done_builds = models.IntegerField(default=0) - miss_builds = models.IntegerField(default=0) - skip_builds = models.IntegerField(default=0) - error_builds = models.IntegerField(default=0) - null_builds = models.IntegerField(default=0) - - pass_boots = models.IntegerField(default=0) - fail_boots = models.IntegerField(default=0) - done_boots = models.IntegerField(default=0) - miss_boots = models.IntegerField(default=0) - skip_boots = models.IntegerField(default=0) - error_boots = models.IntegerField(default=0) - null_boots = models.IntegerField(default=0) - - pass_tests = models.IntegerField(default=0) - fail_tests = models.IntegerField(default=0) - done_tests = models.IntegerField(default=0) - miss_tests = models.IntegerField(default=0) - skip_tests = models.IntegerField(default=0) - error_tests = models.IntegerField(default=0) - null_tests = models.IntegerField(default=0) - - unstable = models.BooleanField(default=True) - - class Meta: - db_table = "checkouts_cache" - constraints = [ - models.UniqueConstraint( - fields=[ - "origin", - "tree_name", - "git_commit_hash", - "git_repository_url", - "git_repository_branch", - ], - name="unique_checkout", - ) - ] - - class NotificationsCheckout(models.Model): notification_message_id = models.TextField() notification_sent = models.DateTimeField() diff --git a/backend/kernelCI_cache/queries/checkouts.py b/backend/kernelCI_cache/queries/checkouts.py deleted file mode 100644 index 888a17aec..000000000 --- a/backend/kernelCI_cache/queries/checkouts.py +++ /dev/null @@ -1,34 +0,0 @@ -from django.db import connections - -from kernelCI_app.helpers.database import dict_fetchall - - -def get_cached_tree_listing_fast(): - """Returns the most recent checkout of the trees that are cached in the sqlite database""" - - query = """ - SELECT * FROM ( - SELECT - checkout_id, - tree_name, - git_repository_branch, - git_repository_url, - start_time, - unstable, - ROW_NUMBER() OVER ( - PARTITION BY - tree_name, - git_repository_branch, - git_repository_url - ORDER BY start_time DESC - ) as rn - FROM - checkouts_cache - ) WHERE rn = 1 - """ - - with connections["cache"].cursor() as cursor: - cursor.execute(query) - records = dict_fetchall(cursor) - - return records diff --git a/backend/kernelCI_cache/queries/tree.py b/backend/kernelCI_cache/queries/tree.py deleted file mode 100644 index 64fd0cadf..000000000 --- a/backend/kernelCI_cache/queries/tree.py +++ /dev/null @@ -1,50 +0,0 @@ -from datetime import timedelta -from typing import Optional - -from django.db import connections -from django.utils.timezone import now - -from kernelCI_app.helpers.database import dict_fetchall - - -def get_cached_tree_listing_data( - *, - origin: str, - interval_in_days: int = None, - min_age_in_days: Optional[int] = None, -) -> list[dict]: - params = { - "origin": origin, - "max_start_time": now() - timedelta(days=interval_in_days), - } - - min_age_filter_clause = "" - if min_age_in_days is not None: - min_start_time_threshold = now() - timedelta(days=min_age_in_days) - min_age_filter_clause = "AND start_time <= %(min_start_time)s" - params["min_start_time"] = min_start_time_threshold - - # SQLite doesn't support DISTINCT ON, so we use ROW_NUMBER() instead - query = f""" - SELECT - * - FROM ( - SELECT - c.*, - ROW_NUMBER() OVER ( - PARTITION BY c.git_repository_branch, c.git_repository_url - ORDER BY c.start_time DESC - ) AS rn - FROM - checkouts_cache c - WHERE - origin = %(origin)s - AND start_time >= %(max_start_time)s - {min_age_filter_clause} - ) WHERE - rn = 1 - """ - - with connections["cache"].cursor() as cursor: - cursor.execute(query, params) - return dict_fetchall(cursor) diff --git a/backend/requests/hardware-listing-v2.sh b/backend/requests/hardware-listing-v2.sh deleted file mode 100644 index 5e299db62..000000000 --- a/backend/requests/hardware-listing-v2.sh +++ /dev/null @@ -1,42 +0,0 @@ -# http 'http://localhost:8000/api/hardware-v2/?startTimestampInSeconds=1736510400&endTimestampInSeconds=1736942400&origin=maestro&commitsList=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' -http 'http://localhost:8000/api/hardware-v2/?startTimestampInSeconds=1736510400&endTimestampInSeconds=1736942400&origin=maestro' - -# HTTP/1.1 200 OK -# Allow: GET, HEAD, OPTIONS -# Cache-Control: max-age=0 -# Content-Length: 19624 -# Content-Type: application/json -# Cross-Origin-Opener-Policy: same-origin -# Date: Thu, 24 Apr 2025 16:41:49 GMT -# Expires: Thu, 24 Apr 2025 16:41:49 GMT -# Referrer-Policy: same-origin -# Server: WSGIServer/0.2 CPython/3.12.7 -# Vary: Accept, Cookie, origin -# X-Content-Type-Options: nosniff -# X-Frame-Options: DENY - -# { -# "hardware": [ -# { -# "platform": "acer-cb317-1h-c3z6-dedede", -# "hardware": null, -# "build_status_summary": { -# "PASS": 90, -# "FAIL": 0, -# "INCONCLUSIVE": 0 -# }, -# "boot_status_summary": { -# "PASS": 390, -# "FAIL": 48, -# "INCONCLUSIVE": 1 -# }, -# "test_status_summary": { -# "PASS": 3298, -# "FAIL": 4975, -# "INCONCLUSIVE": 233 -# } -# }, -# ... -# ] -# } - diff --git a/backend/requests/tree-listing-fast.sh b/backend/requests/tree-listing-fast.sh deleted file mode 100755 index ba620662e..000000000 --- a/backend/requests/tree-listing-fast.sh +++ /dev/null @@ -1,48 +0,0 @@ -# If you want to see headers, add -p H to the http call: -# http -p H 'http://localhost:8000/api/tree/' origin==maestro - -# If you want to provide another limit to query: -http 'http://localhost:8000/api/tree-fast/' origin==maestro interval_in_days==4 - -# HTTP/1.1 200 OK -# Allow: GET, HEAD, OPTIONS -# Cache-Control: max-age=0 -# Content-Length: 16855 -# Content-Type: application/json -# Cross-Origin-Opener-Policy: same-origin -# Date: Thu, 03 Apr 2025 16:45:54 GMT -# Expires: Thu, 03 Apr 2025 16:45:54 GMT -# Referrer-Policy: same-origin -# Server: WSGIServer/0.2 CPython/3.12.7 -# Vary: Accept, Cookie, origin -# X-Content-Type-Options: nosniff -# X-Frame-Options: DENY - -# [ -# { -# "git_commit_hash": "49807ed87851916ef655f72e9562f96355183090", -# "git_commit_name": "amlogic-arm64-dt-for-v6.15-v2-22-g49807ed878519", -# "git_commit_tags": [], -# "git_repository_branch": "for-next", -# "git_repository_url": "https://git.kernel.org/pub/scm/linux/kernel/git/amlogic/linux.git", -# "id": "maestro:67ee57c9dd0dd865e91fc692", -# "origin_builds_finish_time": null, -# "origin_tests_finish_time": null, -# "patchset_hash": "", -# "start_time": "2025-04-03T09:41:29.389000Z", -# "tree_name": "amlogic" -# }, -# { -# "git_commit_hash": "3e6e324f5b472ff8460615f1403f380e05cf2b67", -# "git_commit_name": "android14-6.1.129_r00-54-g3e6e324f5b472", -# "git_commit_tags": [], -# "git_repository_branch": "android14-6.1", -# "git_repository_url": "https://android.googlesource.com/kernel/common", -# "id": "maestro:67ebdbe1dd0dd865e91b016c", -# "origin_builds_finish_time": null, -# "origin_tests_finish_time": null, -# "patchset_hash": "", -# "start_time": "2025-04-01T12:28:17.537000Z", -# "tree_name": "android" -# }, -# ... diff --git a/backend/schema.yml b/backend/schema.yml index 6bd36a9a4..a28abf497 100644 --- a/backend/schema.yml +++ b/backend/schema.yml @@ -171,55 +171,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/HardwareListingResponse' - description: '' - /api/hardware-v2/: - get: - operationId: hardware_v2_retrieve - parameters: - - in: query - name: commitsList - schema: - anyOf: - - type: string - - type: 'null' - default: null - title: Commitslist - description: 'Optional comma-separated git commit identifiers: full SHA(s) - and/or tag strings that appear in checkout.git_commit_tags.' - - in: query - name: endTimestampInSeconds - schema: - title: Endtimestampinseconds - type: string - description: Interval end timestamp in seconds for the results - required: true - - in: query - name: origin - schema: - default: maestro - title: Origin - type: string - description: Origin of the hardware - - in: query - name: startTimestampInSeconds - schema: - title: Starttimestampinseconds - type: string - description: Interval start timestamp in seconds for the results - required: true - tags: - - hardware-v2 - security: - - cookieAuth: [] - - basicAuth: [] - - {} - responses: - '200': - content: - application/json: - schema: - $ref: '#/components/schemas/HardwareListingResponseV2' + $ref: '#/components/schemas/HardwareListingByRevisionResponse' description: '' /api/hardware/{hardware_id}: post: @@ -685,6 +637,47 @@ paths: schema: $ref: '#/components/schemas/LogDownloaderResponse' description: '' + /api/metrics/: + get: + operationId: metrics_retrieve + parameters: + - in: query + name: end_days_ago + schema: + default: 0 + minimum: 0 + title: End Days Ago + type: integer + description: Number of days ago that marks the end of the metrics interval + - in: query + name: start_days_ago + schema: + default: 7 + minimum: 0 + title: Start Days Ago + type: integer + description: Number of days ago that marks the start of the metrics interval + tags: + - metrics + security: + - cookieAuth: [] + - basicAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/MetricsResponse' + description: '' + '400': + content: + application/json: + schema: + type: object + additionalProperties: + type: string + description: '' /api/origins/: get: operationId: origins_retrieve @@ -1050,38 +1043,6 @@ paths: schema: $ref: '#/components/schemas/TreeListingResponse' description: '' - /api/tree-fast/: - get: - operationId: tree_fast_retrieve - parameters: - - in: query - name: interval_in_days - schema: - default: 7 - exclusiveMinimum: 0 - title: Interval In Days - type: integer - description: Interval in days for the listing - - in: query - name: origin - schema: - default: maestro - title: Origin - type: string - description: Origin filter - tags: - - tree-fast - security: - - cookieAuth: [] - - basicAuth: [] - - {} - responses: - '200': - content: - application/json: - schema: - $ref: '#/components/schemas/TreeListingFastResponse' - description: '' /api/tree-report/: get: operationId: tree_report_retrieve @@ -1156,38 +1117,6 @@ paths: schema: $ref: '#/components/schemas/TreeReportResponse' description: '' - /api/tree-v2/: - get: - operationId: tree_v2_retrieve - parameters: - - in: query - name: interval_in_days - schema: - default: 7 - exclusiveMinimum: 0 - title: Interval In Days - type: integer - description: Interval in days for the listing - - in: query - name: origin - schema: - default: maestro - title: Origin - type: string - description: Origin filter - tags: - - tree-v2 - security: - - cookieAuth: [] - - basicAuth: [] - - {} - responses: - '200': - content: - application/json: - schema: - $ref: '#/components/schemas/TreeListingResponseV2' - description: '' /api/tree/{commit_hash}/boots: get: operationId: tree_boots_retrieve @@ -2180,6 +2109,27 @@ components: - git_repository_branch title: BuildHistoryItem type: object + BuildIncidentsCount: + properties: + total_incidents: + title: Total Incidents + type: integer + n_new_issues: + title: N New Issues + type: integer + n_existing_issues: + title: N Existing Issues + type: integer + n_total_issues: + title: N Total Issues + type: integer + required: + - total_incidents + - n_new_issues + - n_existing_issues + - n_total_issues + title: BuildIncidentsCount + type: object BuildSummary: properties: status: @@ -2322,97 +2272,6 @@ components: anyOf: - $ref: '#/components/schemas/StatusValues' - type: 'null' - Checkout: - properties: - git_repository_url: - $ref: '#/components/schemas/Checkout__GitRepositoryUrl' - git_commit_hash: - $ref: '#/components/schemas/Checkout__GitCommitHash' - git_commit_name: - $ref: '#/components/schemas/Checkout__GitCommitName' - origin: - $ref: '#/components/schemas/Origin' - git_repository_branch: - $ref: '#/components/schemas/Checkout__GitRepositoryBranch' - start_time: - $ref: '#/components/schemas/Timestamp' - origin_builds_finish_time: - $ref: '#/components/schemas/Checkout__OriginBuildsFinishTime' - origin_tests_finish_time: - $ref: '#/components/schemas/Checkout__OriginTestsFinishTime' - id: - $ref: '#/components/schemas/Checkout__Id' - build_status: - $ref: '#/components/schemas/StatusCount' - test_status: - $ref: '#/components/schemas/TestStatusCount' - boot_status: - $ref: '#/components/schemas/TestStatusCount' - tree_name: - $ref: '#/components/schemas/Checkout__TreeName' - git_commit_tags: - items: - $ref: '#/components/schemas/Checkout__GitCommitTags' - title: Git Commit Tags - type: array - required: - - git_repository_url - - git_commit_hash - - git_commit_name - - origin - - git_repository_branch - - start_time - - origin_builds_finish_time - - origin_tests_finish_time - - id - - build_status - - test_status - - boot_status - - tree_name - - git_commit_tags - title: Checkout - type: object - CheckoutFast: - properties: - git_repository_url: - $ref: '#/components/schemas/Checkout__GitRepositoryUrl' - git_commit_hash: - $ref: '#/components/schemas/Checkout__GitCommitHash' - git_commit_name: - $ref: '#/components/schemas/Checkout__GitCommitName' - origin: - $ref: '#/components/schemas/Origin' - git_repository_branch: - $ref: '#/components/schemas/Checkout__GitRepositoryBranch' - start_time: - $ref: '#/components/schemas/Timestamp' - origin_builds_finish_time: - $ref: '#/components/schemas/Checkout__OriginBuildsFinishTime' - origin_tests_finish_time: - $ref: '#/components/schemas/Checkout__OriginTestsFinishTime' - id: - $ref: '#/components/schemas/Checkout__Id' - tree_name: - $ref: '#/components/schemas/Checkout__TreeName' - patchset_hash: - $ref: '#/components/schemas/Checkout__PatchsetHash' - git_commit_tags: - $ref: '#/components/schemas/Checkout__GitCommitTags' - required: - - git_repository_url - - git_commit_hash - - git_commit_name - - origin - - git_repository_branch - - start_time - - origin_builds_finish_time - - origin_tests_finish_time - - id - - tree_name - - patchset_hash - - git_commit_tags - title: CheckoutFast - type: object CheckoutIssue: description: Represents a build issue found in a checkout. properties: @@ -2484,20 +2343,6 @@ components: - type: 'null' Checkout__Id: type: string - Checkout__OriginBuildsFinishTime: - anyOf: - - format: date-time - type: string - - type: 'null' - Checkout__OriginTestsFinishTime: - anyOf: - - format: date-time - type: string - - type: 'null' - Checkout__PatchsetHash: - anyOf: - - type: string - - type: 'null' Checkout__TreeName: anyOf: - type: string @@ -2994,7 +2839,6 @@ components: - items: type: string type: array - uniqueItems: true - type: 'null' title: Hardware platform: @@ -3014,7 +2858,18 @@ components: - build_status_summary title: HardwareItem type: object - HardwareItemV2: + HardwareListingByRevisionResponse: + properties: + hardware: + items: + $ref: '#/components/schemas/HardwareItem' + title: Hardware + type: array + required: + - hardware + title: HardwareListingByRevisionResponse + type: object + HardwareListingItem: properties: hardware: anyOf: @@ -3029,41 +2884,30 @@ components: title: Platform type: string test_status_summary: - $ref: '#/components/schemas/StatusCountV2' + $ref: '#/components/schemas/ListingStatusCount' boot_status_summary: - $ref: '#/components/schemas/StatusCountV2' + $ref: '#/components/schemas/ListingStatusCount' build_status_summary: - $ref: '#/components/schemas/StatusCountV2' + $ref: '#/components/schemas/ListingStatusCount' required: - hardware - platform - test_status_summary - boot_status_summary - build_status_summary - title: HardwareItemV2 + title: HardwareListingItem type: object HardwareListingResponse: properties: hardware: items: - $ref: '#/components/schemas/HardwareItem' + $ref: '#/components/schemas/HardwareListingItem' title: Hardware type: array required: - hardware title: HardwareListingResponse type: object - HardwareListingResponseV2: - properties: - hardware: - items: - $ref: '#/components/schemas/HardwareItemV2' - title: Hardware - type: array - required: - - hardware - title: HardwareListingResponseV2 - type: object HardwareSelectorBranch: properties: git_repository_url: @@ -3589,6 +3433,45 @@ components: - type: 'null' Issue__Version: type: integer + LabMetricsData: + properties: + builds: + title: Builds + type: integer + boots: + title: Boots + type: integer + tests: + title: Tests + type: integer + required: + - builds + - boots + - tests + title: LabMetricsData + type: object + ListingStatusCount: + properties: + PASS: + anyOf: + - type: integer + - type: 'null' + default: 0 + title: Pass + FAIL: + anyOf: + - type: integer + - type: 'null' + default: 0 + title: Fail + INCONCLUSIVE: + anyOf: + - type: integer + - type: 'null' + default: 0 + title: Inconclusive + title: ListingStatusCount + type: object LocalFilters: properties: issues: @@ -3663,6 +3546,77 @@ components: - log_files title: LogDownloaderResponse type: object + MetricsResponse: + properties: + n_trees: + title: N Trees + type: integer + n_checkouts: + title: N Checkouts + type: integer + n_builds: + title: N Builds + type: integer + n_tests: + title: N Tests + type: integer + n_issues: + title: N Issues + type: integer + n_incidents: + title: N Incidents + type: integer + build_incidents_by_origin: + additionalProperties: + $ref: '#/components/schemas/BuildIncidentsCount' + title: Build Incidents By Origin + type: object + top_issues_by_origin: + additionalProperties: + items: + $ref: '#/components/schemas/TopIssue' + type: array + title: Top Issues By Origin + type: object + lab_maps: + additionalProperties: + $ref: '#/components/schemas/LabMetricsData' + title: Lab Maps + type: object + prev_n_trees: + title: Prev N Trees + type: integer + prev_n_checkouts: + title: Prev N Checkouts + type: integer + prev_n_builds: + title: Prev N Builds + type: integer + prev_n_tests: + title: Prev N Tests + type: integer + prev_lab_maps: + additionalProperties: + $ref: '#/components/schemas/LabMetricsData' + title: Prev Lab Maps + type: object + required: + - n_trees + - n_checkouts + - n_builds + - n_tests + - n_issues + - n_incidents + - build_incidents_by_origin + - top_issues_by_origin + - lab_maps + - prev_n_trees + - prev_n_checkouts + - prev_n_builds + - prev_n_tests + - prev_lab_maps + title: MetricsResponse + type: object Origin: type: string OriginsResponse: @@ -3772,28 +3726,6 @@ components: title: 'Null' title: StatusCount type: object - StatusCountV2: - properties: - PASS: - anyOf: - - type: integer - - type: 'null' - default: 0 - title: Pass - FAIL: - anyOf: - - type: integer - - type: 'null' - default: 0 - title: Fail - INCONCLUSIVE: - anyOf: - - type: integer - - type: 'null' - default: 0 - title: Inconclusive - title: StatusCountV2 - type: object StatusValues: enum: - FAIL @@ -4210,6 +4142,27 @@ components: - format: date-time type: string - type: 'null' + TopIssue: + properties: + id: + title: Id + type: string + version: + title: Version + type: integer + comment: + title: Comment + type: string + total_incidents: + title: Total Incidents + type: integer + required: + - id + - version + - comment + - total_incidents + title: TopIssue + type: object Tree: properties: index: @@ -4435,21 +4388,16 @@ components: - git_repository_branch title: TreeLatestResponse type: object - TreeListingFastResponse: - items: - $ref: '#/components/schemas/CheckoutFast' - title: TreeListingFastResponse - type: array TreeListingItem: properties: id: $ref: '#/components/schemas/Checkout__Id' build_status: - $ref: '#/components/schemas/StatusCountV2' + $ref: '#/components/schemas/ListingStatusCount' boot_status: - $ref: '#/components/schemas/StatusCountV2' + $ref: '#/components/schemas/ListingStatusCount' test_status: - $ref: '#/components/schemas/StatusCountV2' + $ref: '#/components/schemas/ListingStatusCount' tree_name: $ref: '#/components/schemas/Checkout__TreeName' git_commit_tags: @@ -4482,14 +4430,9 @@ components: title: TreeListingItem type: object TreeListingResponse: - items: - $ref: '#/components/schemas/Checkout' - title: TreeListingResponse - type: array - TreeListingResponseV2: items: $ref: '#/components/schemas/TreeListingItem' - title: TreeListingResponseV2 + title: TreeListingResponse type: array TreeReportIssues: properties: diff --git a/dashboard/.env.example b/dashboard/.env.example index f9432475e..25f0a8dfa 100644 --- a/dashboard/.env.example +++ b/dashboard/.env.example @@ -4,7 +4,5 @@ VITE_API_BASE_URL=http://localhost:8000 # Feature Flags VITE_FEATURE_FLAG_SHOW_DEV=false -VITE_FEATURE_FLAG_TREE_LISTING_VERSION=v1 -VITE_FEATURE_FLAG_HARDWARE_LISTING_VERSION=v1 PLAYWRIGHT_TEST_BASE_URL=https://staging.dashboard.kernelci.org diff --git a/dashboard/README.md b/dashboard/README.md index 6a5e8971b..36d513bcf 100644 --- a/dashboard/README.md +++ b/dashboard/README.md @@ -74,5 +74,3 @@ They are used when we want to hide a feature for some users, without having to d Available feature flags: - `VITE_FEATURE_FLAG_SHOW_DEV` - Controls visibility of dev-only features (boolean, default: `false`) -- `VITE_FEATURE_FLAG_TREE_LISTING_VERSION` - Controls which tree listing version to display. Set to `"v1"` for the old version or `"v2"` for the new version (string, default: `"v1"`) -- `VITE_FEATURE_FLAG_HARDWARE_LISTING_VERSION` - Controls which hardware listing version to display. Set to `"v1"` for the old version or `"v2"` for the new version (string, default: `"v1"`) diff --git a/dashboard/src/api/hardware.ts b/dashboard/src/api/hardware.ts index e99bbdc0c..9dd9dc823 100644 --- a/dashboard/src/api/hardware.ts +++ b/dashboard/src/api/hardware.ts @@ -8,15 +8,30 @@ import type { HardwareRevisionSelection, HardwareSelectorsResponse, } from '@/types/hardware'; +import type { StatusCount } from '@/types/general'; +import { statusCountToShortStatusCount } from '@/utils/status'; import type { HardwareListingRoutesMap } from '@/utils/constants/hardwareListing'; import { RequestData } from './commonRequest'; +type HardwareListingByRevisionApiItem = { + hardware?: string[]; + platform: string; + build_status_summary: StatusCount; + test_status_summary: StatusCount; + boot_status_summary: StatusCount; +}; + +type HardwareListingByRevisionApiResponse = { + hardware: HardwareListingByRevisionApiItem[]; +}; + const fetchHardwareListing = async ( origin: string, startTimestampInSeconds: number, endTimestampInSeconds: number, + commitsList?: string[], ): Promise => { const data = await RequestData.get( '/api/hardware/', @@ -25,6 +40,7 @@ const fetchHardwareListing = async ( startTimestampInSeconds, endTimestampInSeconds, origin, + ...(commitsList?.length ? { commitsList: commitsList.join(',') } : {}), }, }, ); @@ -35,7 +51,8 @@ const fetchHardwareListing = async ( export const useHardwareListing = ( startTimestampInSeconds: number, endTimestampInSeconds: number, - searchFrom: HardwareListingRoutesMap['v1']['search'], + searchFrom: HardwareListingRoutesMap['search'], + commitsList?: string[], ): UseQueryResult => { const { origin } = useSearch({ from: searchFrom }); @@ -44,6 +61,7 @@ export const useHardwareListing = ( startTimestampInSeconds, endTimestampInSeconds, origin, + commitsList ?? null, ]; return useQuery({ @@ -53,6 +71,7 @@ export const useHardwareListing = ( origin, startTimestampInSeconds, endTimestampInSeconds, + commitsList, ), refetchOnWindowFocus: false, }); @@ -74,7 +93,7 @@ const fetchHardwareSelectors = async ( }; export const useHardwareSelectors = ( - searchFrom: HardwareListingRoutesMap['v2']['search'], + searchFrom: HardwareListingRoutesMap['search'], ): UseQueryResult => { const { origin } = useSearch({ from: searchFrom }); @@ -89,7 +108,7 @@ const fetchHardwareListingByRevision = async ( selection: HardwareRevisionSelection, origin: string, ): Promise => { - const data = await RequestData.get( + const data = await RequestData.get( '/api/hardware-by-revision/', { params: { @@ -102,12 +121,26 @@ const fetchHardwareListingByRevision = async ( }, ); - return data; + return { + hardware: data.hardware.map(item => ({ + hardware: item.hardware, + platform: item.platform, + build_status_summary: statusCountToShortStatusCount( + item.build_status_summary, + ), + test_status_summary: statusCountToShortStatusCount( + item.test_status_summary, + ), + boot_status_summary: statusCountToShortStatusCount( + item.boot_status_summary, + ), + })), + }; }; export const useHardwareListingByRevision = ( selection: HardwareRevisionSelection | null, - searchFrom: HardwareListingRoutesMap['v2']['search'], + searchFrom: HardwareListingRoutesMap['search'], ): UseQueryResult => { const { origin } = useSearch({ from: searchFrom }); diff --git a/dashboard/src/api/tree.ts b/dashboard/src/api/tree.ts index 3241b9c94..c7601e000 100644 --- a/dashboard/src/api/tree.ts +++ b/dashboard/src/api/tree.ts @@ -3,81 +3,13 @@ import { useQuery } from '@tanstack/react-query'; import { useSearch } from '@tanstack/react-router'; -import type { - Tree, - TreeFastPathResponse, - TreeLatestResponse, - TreeV2, -} from '@/types/tree/Tree'; +import type { TreeLatestResponse, TreeListingItem } from '@/types/tree/Tree'; import { DEFAULT_ORIGIN } from '@/types/general'; import type { TreeListingRoutesMap } from '@/utils/constants/treeListing'; import { RequestData } from './commonRequest'; -const fetchTreeCheckoutData = async ( - origin: string, - intervalInDays?: number, -): Promise => { - const params = { - origin: origin, - interval_in_days: intervalInDays, - }; - - const data = await RequestData.get('/api/tree/', { - params, - }); - return data; -}; - -export const useTreeTable = ({ - enabled, - searchFrom, -}: { - enabled: boolean; - searchFrom: TreeListingRoutesMap['v1']['search']; -}): UseQueryResult => { - const { origin, intervalInDays } = useSearch({ from: searchFrom }); - const queryKey = ['treeTable', origin, intervalInDays]; - - return useQuery({ - queryKey, - queryFn: () => fetchTreeCheckoutData(origin, intervalInDays), - enabled, - refetchOnWindowFocus: false, - }); -}; - -const fetchTreeFastCheckoutData = async ( - origin: string, - intervalInDays: number, -): Promise => { - const params = { - origin: origin, - interval_in_days: intervalInDays, - }; - - const data = await RequestData.get('/api/tree-fast/', { - params, - }); - return data; -}; - -export const useTreeTableFast = ({ - searchFrom, -}: { - searchFrom: TreeListingRoutesMap['v1']['search']; -}): UseQueryResult => { - const { origin, intervalInDays } = useSearch({ from: searchFrom }); - - const queryKey = ['treeTableFast', origin, intervalInDays]; - - return useQuery({ - queryKey, - queryFn: () => fetchTreeFastCheckoutData(origin, intervalInDays), - }); -}; - const fetchTreeLatest = async ( treeName: string, branch: string, @@ -109,32 +41,32 @@ export const useTreeLatest = ( }); }; -const fetchTreeListingV2 = async ( +const fetchTreeListing = async ( origin: string, intervalInDays: number, -): Promise => { +): Promise => { const params = { origin: origin, interval_in_days: intervalInDays, }; - const data = await RequestData.get('/api/tree-v2/', { + const data = await RequestData.get('/api/tree/', { params, }); return data; }; -export const useTreeListingV2 = ({ +export const useTreeListing = ({ searchFrom, }: { - searchFrom: TreeListingRoutesMap['v2']['search']; -}): UseQueryResult => { + searchFrom: TreeListingRoutesMap['search']; +}): UseQueryResult => { const { origin, intervalInDays } = useSearch({ from: searchFrom }); - const queryKey = ['treeTableV2', origin, intervalInDays]; + const queryKey = ['treeTable', origin, intervalInDays]; return useQuery({ queryKey, - queryFn: () => fetchTreeListingV2(origin, intervalInDays), + queryFn: () => fetchTreeListing(origin, intervalInDays), refetchOnWindowFocus: false, }); }; diff --git a/dashboard/src/components/Status/Status.tsx b/dashboard/src/components/Status/Status.tsx index fd3a2d358..03780176a 100644 --- a/dashboard/src/components/Status/Status.tsx +++ b/dashboard/src/components/Status/Status.tsx @@ -117,42 +117,6 @@ export const BaseGroupedStatusWithLink = ({ ); }; -interface ITestStatusWithLink extends ITestStatus, IStatusLinkProps {} - -export const GroupedTestStatusWithLink = ({ - pass, - error, - miss, - fail, - done, - skip, - nullStatus, - hideInconclusive = false, - passLinkProps, - failLinkProps, - inconclusiveLinkProps, -}: ITestStatusWithLink): JSX.Element => { - const groupedStatus = groupStatus({ - doneCount: done, - errorCount: error, - failCount: fail, - missCount: miss, - passCount: pass, - skipCount: skip, - nullCount: nullStatus, - }); - - return ( - - ); -}; - interface IBuildStatus { valid?: number; invalid?: number; diff --git a/dashboard/src/components/Table/ConditionalTableCell.tsx b/dashboard/src/components/Table/ConditionalTableCell.tsx index ad0ed9cd3..1debf8e93 100644 --- a/dashboard/src/components/Table/ConditionalTableCell.tsx +++ b/dashboard/src/components/Table/ConditionalTableCell.tsx @@ -7,10 +7,10 @@ import { memo, useMemo, type JSX } from 'react'; import { TableCell, TableCellWithLink } from '@/components/ui/table'; import CopyButton from '@/components/Button/CopyButton'; import { gitCommitValueSelector } from '@/components/Tooltip/CommitTagTooltip'; -import type { TreeTableBody } from '@/types/tree/Tree'; +import type { TreeListingItem } from '@/types/tree/Tree'; import type { HardwareItem } from '@/types/hardware'; -interface ConditionalTableCellProps { +interface ConditionalTableCellProps { cell: Cell; linkProps: LinkProps; linkClassName?: string; @@ -18,7 +18,7 @@ interface ConditionalTableCellProps { type ColumnType = 'status' | 'git_commit_tags' | 'regular'; -const isTreeTableBody = (data: unknown): data is TreeTableBody => { +const isTreeListingRow = (data: unknown): data is TreeListingItem => { return ( typeof data === 'object' && data !== null && @@ -34,7 +34,7 @@ const isTreeTableBody = (data: unknown): data is TreeTableBody => { * - Git commit tags: Uses TableCell with custom link + copy button * - Regular columns: Uses TableCellWithLink for navigation */ -const ConditionalTableCellComponent = ({ +const ConditionalTableCellComponent = ({ cell, linkProps, linkClassName = 'w-full inline-block h-full', @@ -65,7 +65,7 @@ const ConditionalTableCellComponent = ({ } const rowData = cell.row.original; - if (!isTreeTableBody(rowData)) { + if (!isTreeListingRow(rowData)) { return null; } @@ -119,7 +119,7 @@ const ConditionalTableCellComponent = ({ }; export const ConditionalTableCell = memo(ConditionalTableCellComponent) as < - T = TreeTableBody | HardwareItem, + T = TreeListingItem | HardwareItem, >( props: ConditionalTableCellProps, ) => JSX.Element; diff --git a/dashboard/src/components/TopBar/TopBar.tsx b/dashboard/src/components/TopBar/TopBar.tsx index d3ad3f8c0..b72b000f5 100644 --- a/dashboard/src/components/TopBar/TopBar.tsx +++ b/dashboard/src/components/TopBar/TopBar.tsx @@ -98,7 +98,6 @@ const TitleName = ({ basePath }: { basePath: string }): JSX.Element => { case 'tree': return ; case 'hardware': - case 'hardware/v1': return ; case 'issues': return ; diff --git a/dashboard/src/components/TreeListingPage/TreeListingPage.tsx b/dashboard/src/components/TreeListingPage/TreeListingPage.tsx index 5ec7f35fe..0c9f0d148 100644 --- a/dashboard/src/components/TreeListingPage/TreeListingPage.tsx +++ b/dashboard/src/components/TreeListingPage/TreeListingPage.tsx @@ -1,12 +1,8 @@ import { useMemo, type JSX } from 'react'; -import type { - Tree, - TreeFastPathResponse, - TreeTableBody, -} from '@/types/tree/Tree'; +import type { TreeListingItem } from '@/types/tree/Tree'; -import { useTreeTable, useTreeTableFast } from '@/api/tree'; +import { useTreeListing } from '@/api/tree'; import { Toaster } from '@/components/ui/toaster'; @@ -18,40 +14,23 @@ import type { TreeListingRoutesMap } from '@/utils/constants/treeListing'; import { TreeTable } from './TreeTable'; -function isCompleteTree( - data: Tree | TreeFastPathResponse[number], -): data is Tree { - return 'build_status' in data; -} - const TreeListingPage = ({ inputFilter, urlFromMap, }: { inputFilter: string; - urlFromMap: TreeListingRoutesMap['v1']; + urlFromMap: TreeListingRoutesMap; }): JSX.Element => { - //TODO: Combine these 2 hooks inside a single hook - const { - data: fastData, - status: fastStatus, - error: fastError, - isLoading: isFastLoading, - } = useTreeTableFast({ searchFrom: urlFromMap.search }); - const { data, error, status, isLoading } = useTreeTable({ - enabled: fastStatus === 'success' && !!fastData, + const { data, error, status, isLoading } = useTreeListing({ searchFrom: urlFromMap.search, }); - const listItems: TreeTableBody[] = useMemo(() => { - if (!fastData || fastStatus === 'error') { + const listItems: TreeListingItem[] = useMemo(() => { + if (!data) { return []; } - const hasCompleteData = !isLoading && !!data; - const currentData = hasCompleteData ? data : fastData; - - return currentData + return data .filter(tree => { return ( matchesRegexOrIncludes(tree.git_commit_hash, inputFilter) || @@ -60,22 +39,6 @@ const TreeListingPage = ({ matchesRegexOrIncludes(tree.tree_name, inputFilter) ); }) - .map((tree): TreeTableBody => { - if (!isCompleteTree(tree)) { - return { - git_commit_hash: tree.git_commit_hash, - patchset_hash: tree.patchset_hash, - tree_name: tree.tree_name, - git_repository_branch: tree.git_repository_branch, - start_time: tree.start_time, - git_repository_url: tree.git_repository_url, - git_commit_name: tree.git_commit_name, - git_commit_tags: tree.git_commit_tags ?? [], - }; - } - - return tree; - }) .sort((a, b) => { const currentATreeName = a.tree_name ?? ''; const currentBTreeName = b.tree_name ?? ''; @@ -100,7 +63,7 @@ const TreeListingPage = ({ new Date(b.start_time).getTime() - new Date(a.start_time).getTime() ); }); - }, [data, fastData, inputFilter, isLoading, fastStatus]); + }, [data, inputFilter]); const kcidevComponent = useMemo( () => ( @@ -109,26 +72,16 @@ const TreeListingPage = ({ [], ); - const hasPartialFailure = fastStatus === 'success' && status === 'error'; - // Only show error in QuerySwitcher if the first or both queries fail - const actualError = hasPartialFailure ? null : fastError || error; - const actualStatus = hasPartialFailure - ? fastStatus - : actualError - ? 'error' - : fastStatus ?? status; - return ( <>
diff --git a/dashboard/src/components/TreeListingPage/TreeListingV2.tsx b/dashboard/src/components/TreeListingPage/TreeListingV2.tsx deleted file mode 100644 index c69d74a71..000000000 --- a/dashboard/src/components/TreeListingPage/TreeListingV2.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { useMemo, type JSX } from 'react'; - -import type { TreeV2 } from '@/types/tree/Tree'; - -import { useTreeListingV2 } from '@/api/tree'; - -import { Toaster } from '@/components/ui/toaster'; - -import { matchesRegexOrIncludes } from '@/lib/string'; - -import { MemoizedKcidevFooter } from '@/components/Footer/KcidevFooter'; - -import type { TreeListingRoutesMap } from '@/utils/constants/treeListing'; - -import { TreeTableV2 } from './TreeTableV2'; - -const TreeListingV2 = ({ - inputFilter, - urlFromMap, -}: { - inputFilter: string; - urlFromMap: TreeListingRoutesMap['v2']; -}): JSX.Element => { - const { data, error, status, isLoading } = useTreeListingV2({ - searchFrom: urlFromMap.search, - }); - - const listItems: TreeV2[] = useMemo(() => { - if (!data) { - return []; - } - - return data - .filter(tree => { - return ( - matchesRegexOrIncludes(tree.git_commit_hash, inputFilter) || - matchesRegexOrIncludes(tree.git_repository_branch, inputFilter) || - matchesRegexOrIncludes(tree.git_repository_url, inputFilter) || - matchesRegexOrIncludes(tree.tree_name, inputFilter) - ); - }) - .sort((a, b) => { - const currentATreeName = a.tree_name ?? ''; - const currentBTreeName = b.tree_name ?? ''; - const treeNameComparison = - currentATreeName.localeCompare(currentBTreeName); - - if (treeNameComparison !== 0) { - return treeNameComparison; - } - - const currentABranch = a.git_repository_branch ?? ''; - const currentBBranch = b.git_repository_branch ?? ''; - const branchComparison = currentABranch.localeCompare(currentBBranch); - if (branchComparison !== 0) { - return branchComparison; - } - - if (a.start_time === undefined || b.start_time === undefined) { - return 0; - } - return ( - new Date(b.start_time).getTime() - new Date(a.start_time).getTime() - ); - }); - }, [data, inputFilter]); - - const kcidevComponent = useMemo( - () => ( - - ), - [], - ); - - return ( - <> - -
- -
- {kcidevComponent} - - ); -}; - -export default TreeListingV2; diff --git a/dashboard/src/components/TreeListingPage/TreeTable.tsx b/dashboard/src/components/TreeListingPage/TreeTable.tsx index 7ed850a1d..42dfd702b 100644 --- a/dashboard/src/components/TreeListingPage/TreeTable.tsx +++ b/dashboard/src/components/TreeListingPage/TreeTable.tsx @@ -21,9 +21,7 @@ import { FormattedMessage } from 'react-intl'; import type { LinkProps } from '@tanstack/react-router'; import { useNavigate, useSearch } from '@tanstack/react-router'; -import { TooltipDateTime } from '@/components/TooltipDateTime'; - -import type { TreeTableBody, TreeV2 } from '@/types/tree/Tree'; +import type { TreeListingItem } from '@/types/tree/Tree'; import { RedirectFrom } from '@/types/general'; import type { TFilter } from '@/types/general'; @@ -37,11 +35,7 @@ import BaseTable, { TableHead } from '@/components/Table/BaseTable'; import { TableBody, TableCell, TableRow } from '@/components/ui/table'; import { ConditionalTableCell } from '@/components/Table/ConditionalTableCell'; -import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/Tooltip'; - -import { sanitizeTableValue } from '@/components/Table/tableUtils'; - -import { GroupedTestStatusWithLink } from '@/components/Status/Status'; +import { BaseGroupedStatusWithLink } from '@/components/Status/Status'; import { TableHeader } from '@/components/Table/TableHeader'; import { ItemsPerPageSelector, @@ -49,23 +43,21 @@ import { PaginationButtons, PaginationInfo, } from '@/components/Table/PaginationInfo'; -import { CommitTagTooltip } from '@/components/Tooltip/CommitTagTooltip'; import type { ListingTableColumnMeta } from '@/types/table'; -import { statusCountToRequiredStatusCount } from '@/utils/status'; - -import { shouldShowRelativeDate } from '@/lib/date'; -import { valueOrEmpty } from '@/lib/string'; -import { PinnedTrees } from '@/utils/constants/tables'; -import { makeTreeIdentifierKey } from '@/utils/trees'; - import QuerySwitcher from '@/components/QuerySwitcher/QuerySwitcher'; import { MemoizedSectionError } from '@/components/DetailsPages/SectionError'; + import type { TreeListingRoutesMap } from '@/utils/constants/treeListing'; +import { + commonTreeTableColumns, + sortTreesWithPinnedFirst, +} from './treeTableUtils'; + const getLinkProps = ( - row: Row, + row: Row, origin: string, tabTarget?: string, diffFilter?: TFilter, @@ -125,118 +117,13 @@ const getLinkProps = ( ...s, ...stateParams, from: RedirectFrom.Tree, - treeStatusCount: { - builds: statusCountToRequiredStatusCount({ - PASS: row.original.build_status?.PASS, - FAIL: row.original.build_status?.FAIL, - NULL: row.original.build_status?.NULL, - DONE: row.original.build_status?.DONE, - ERROR: row.original.build_status?.ERROR, - MISS: row.original.build_status?.MISS, - SKIP: row.original.build_status?.SKIP, - }), - tests: statusCountToRequiredStatusCount({ - PASS: row.original.test_status?.pass, - FAIL: row.original.test_status?.fail, - NULL: row.original.test_status?.null, - DONE: row.original.test_status?.done, - ERROR: row.original.test_status?.error, - MISS: row.original.test_status?.miss, - SKIP: row.original.test_status?.skip, - }), - boots: statusCountToRequiredStatusCount({ - PASS: row.original.boot_status?.pass, - FAIL: row.original.boot_status?.fail, - NULL: row.original.boot_status?.null, - DONE: row.original.boot_status?.done, - ERROR: row.original.boot_status?.error, - MISS: row.original.boot_status?.miss, - SKIP: row.original.boot_status?.skip, - }), - }, }), }; }; -export const commonTreeTableColumns: ColumnDef[] = [ - { - accessorKey: 'tree_name', - header: ({ column }): JSX.Element => ( - - ), - cell: ({ row }): JSX.Element => { - return ( - - - {sanitizeTableValue(row.getValue('tree_name') ?? '', false)} - - - - {sanitizeTableValue(row.original.git_repository_url, false)} - - - - ); - }, - meta: { - tabTarget: 'global.builds', - }, - }, - { - accessorKey: 'git_repository_branch', - header: ({ column }): JSX.Element => ( - - ), - cell: ({ row }) => valueOrEmpty(row.getValue('git_repository_branch')), - meta: { - tabTarget: 'global.builds', - }, - }, - { - id: 'git_commit_tags', - accessorKey: 'git_commit_tags', - header: ({ column }): JSX.Element => ( - - ), - cell: ({ row }): JSX.Element => ( - - ), - meta: { - tabTarget: 'global.builds', - }, - }, - { - accessorKey: 'start_time', - header: ({ column }): JSX.Element => ( - - ), - cell: ({ row }): JSX.Element => ( - - ), - meta: { - tabTarget: 'global.builds', - }, - }, -]; - -const getColumns = ( - origin: string, - showStatusUnavailable?: boolean, -): ColumnDef[] => { +const getColumns = (origin: string): ColumnDef[] => { return [ - ...(commonTreeTableColumns as ColumnDef[]), + ...commonTreeTableColumns, { accessorKey: 'build_status.PASS', header: ({ column }): JSX.Element => ( @@ -249,15 +136,13 @@ const getColumns = ( cell: ({ column, row }): JSX.Element => { const tabTarget = (column.columnDef.meta as ListingTableColumnMeta) .tabTarget; - return row.original.build_status ? ( - - ) : showStatusUnavailable ? ( - - - ) : ( - ); }, meta: { @@ -285,7 +166,7 @@ const getColumns = ( }, }, { - accessorKey: 'boot_status.pass', + accessorKey: 'boot_status.PASS', header: ({ column }): JSX.Element => ( { const tabTarget = (column.columnDef.meta as ListingTableColumnMeta) .tabTarget; - return row.original.boot_status ? ( - - ) : showStatusUnavailable ? ( - - - ) : ( - ); }, meta: { @@ -332,7 +207,7 @@ const getColumns = ( }, }, { - accessorKey: 'test_status.pass', + accessorKey: 'test_status.PASS', header: ({ column }): JSX.Element => ( { const tabTarget = (column.columnDef.meta as ListingTableColumnMeta) .tabTarget; - return row.original.test_status ? ( - - ) : showStatusUnavailable ? ( - - - ) : ( - ); }, meta: { @@ -381,53 +250,24 @@ const getColumns = ( ]; }; -export const sortTreesWithPinnedFirst = ( - treeTableRows: T[], -): T[] => { - return treeTableRows.sort((a, b) => { - const aKey = makeTreeIdentifierKey({ - treeName: valueOrEmpty(a.tree_name), - gitRepositoryBranch: valueOrEmpty(a.git_repository_branch), - separator: '/', - }); - const bKey = makeTreeIdentifierKey({ - treeName: valueOrEmpty(b.tree_name), - gitRepositoryBranch: valueOrEmpty(b.git_repository_branch), - separator: '/', - }); - - const aIsPinned = PinnedTrees.some(regex => regex.test(aKey)); - const bIsPinned = PinnedTrees.some(regex => regex.test(bKey)); - - if (aIsPinned && !bIsPinned) { - return -1; - } - if (!aIsPinned && bIsPinned) { - return 1; - } - - return aKey.localeCompare(bKey); - }); -}; - export function TreeTable({ treeTableRows, status, queryData, error, isLoading, - showStatusUnavailable, urlFromMap, }: { - treeTableRows: TreeTableBody[]; + treeTableRows: TreeListingItem[]; status?: UseQueryResult['status']; queryData?: unknown; error?: Error | null; isLoading?: boolean; - showStatusUnavailable?: boolean; - urlFromMap: TreeListingRoutesMap['v1']; + urlFromMap: TreeListingRoutesMap; }): JSX.Element { - const { origin, listingSize } = useSearch({ from: urlFromMap.search }); + const { origin, listingSize } = useSearch({ + from: urlFromMap.search, + }); const navigate = useNavigate({ from: urlFromMap.navigate }); const [sorting, setSorting] = useState([]); @@ -437,15 +277,11 @@ export function TreeTable({ listingSize, ); - const orderedData = useMemo( - () => sortTreesWithPinnedFirst(treeTableRows), - [treeTableRows], - ); + const orderedData = useMemo(() => { + return sortTreesWithPinnedFirst(treeTableRows); + }, [treeTableRows]); - const columns = useMemo( - () => getColumns(origin, showStatusUnavailable), - [origin, showStatusUnavailable], - ); + const columns = useMemo(() => getColumns(origin), [origin]); const table = useReactTable({ data: orderedData, @@ -543,13 +379,6 @@ export function TreeTable({ - {showStatusUnavailable && ( -
-

- -

-
- )} , - origin: string, - tabTarget?: string, - diffFilter?: TFilter, -): LinkProps => { - const tree_name = row.original.tree_name; - const branch = row.original.git_repository_branch; - const hash = row.original.git_commit_hash; - const repositoryUrl = row.original.git_repository_url; - const commitName = row.original.git_commit_name; - - const canGoDirect = tree_name && branch && hash; - - const urlDirection: LinkProps = canGoDirect - ? { - to: '/tree/$treeName/$branch/$hash', - params: { - treeName: tree_name, - branch: branch, - hash: hash, - }, - search: previousSearch => ({ - origin: origin, - currentPageTab: zPossibleTabValidator.parse(tabTarget), - diffFilter: diffFilter ?? {}, - intervalInDays: previousSearch.intervalInDays, - }), - } - : { - to: '/tree/$treeId', - params: { treeId: hash }, - search: previousSearch => ({ - origin: origin, - currentPageTab: zPossibleTabValidator.parse(tabTarget), - diffFilter: diffFilter ?? {}, - treeInfo: { - ...(repositoryUrl && { gitUrl: repositoryUrl }), - ...(branch && { gitBranch: branch }), - ...(tree_name && { treeName: tree_name }), - ...(commitName && { commitName: commitName }), - headCommitHash: hash, - }, - intervalInDays: previousSearch.intervalInDays, - }), - }; - - const stateParams = canGoDirect - ? { - treeName: tree_name, - branch: branch, - id: hash, - } - : { id: hash }; - - return { - ...urlDirection, - state: s => ({ - ...s, - ...stateParams, - from: RedirectFrom.Tree, - }), - }; -}; - -const getColumns = (origin: string): ColumnDef[] => { - return [ - ...(commonTreeTableColumns as ColumnDef[]), - { - accessorKey: 'build_status.PASS', - header: ({ column }): JSX.Element => ( - - ), - cell: ({ column, row }): JSX.Element => { - const tabTarget = (column.columnDef.meta as ListingTableColumnMeta) - .tabTarget; - return ( - - ); - }, - meta: { - tabTarget: 'global.builds', - }, - }, - { - accessorKey: 'boot_status.pass', - header: ({ column }): JSX.Element => ( - - ), - cell: ({ column, row }): JSX.Element => { - const tabTarget = (column.columnDef.meta as ListingTableColumnMeta) - .tabTarget; - return ( - - ); - }, - meta: { - tabTarget: 'global.boots', - }, - }, - { - accessorKey: 'test_status.pass', - header: ({ column }): JSX.Element => ( - - ), - cell: ({ column, row }): JSX.Element => { - const tabTarget = (column.columnDef.meta as ListingTableColumnMeta) - .tabTarget; - return ( - - ); - }, - meta: { - tabTarget: 'global.tests', - }, - }, - ]; -}; - -export function TreeTableV2({ - treeTableRows, - status, - queryData, - error, - isLoading, - urlFromMap, -}: { - treeTableRows: TreeV2[]; - status?: UseQueryResult['status']; - queryData?: unknown; - error?: Error | null; - isLoading?: boolean; - showStatusUnavailable?: boolean; - urlFromMap: TreeListingRoutesMap['v2']; -}): JSX.Element { - const { origin, listingSize } = useSearch({ - from: urlFromMap.search, - }); - const navigate = useNavigate({ from: urlFromMap.navigate }); - - const [sorting, setSorting] = useState([]); - const [columnFilters, setColumnFilters] = useState([]); - const { pagination, paginationUpdater } = usePaginationState( - 'treeListing', - listingSize, - ); - - const orderedData = useMemo(() => { - return sortTreesWithPinnedFirst(treeTableRows); - }, [treeTableRows]); - - const columns = useMemo(() => getColumns(origin), [origin]); - - const table = useReactTable({ - data: orderedData, - columns, - onSortingChange: setSorting, - onColumnFiltersChange: setColumnFilters, - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), - onPaginationChange: paginationUpdater, - getSortedRowModel: getSortedRowModel(), - getFilteredRowModel: getFilteredRowModel(), - state: { - sorting, - columnFilters, - pagination, - }, - }); - - const groupHeaders = table.getHeaderGroups()[0].headers; - const tableHeaders = useMemo((): JSX.Element[] => { - return groupHeaders.map(header => { - return ( - - {header.isPlaceholder - ? null - : // the header must change the icon when sorting changes, - // but just the column dependency won't trigger the rerender - // so we pass an unused sorting prop here to force the useMemo dependency - flexRender(header.column.columnDef.header, { - ...header.getContext(), - sorting, - })} - - ); - }); - }, [groupHeaders, sorting]); - - const modelRows = table.getRowModel().rows; - const tableBody = useMemo((): JSX.Element[] | JSX.Element => { - return modelRows?.length ? ( - modelRows.map(row => ( - - {row.getVisibleCells().map(cell => { - const tabTarget = ( - cell.column.columnDef.meta as ListingTableColumnMeta - ).tabTarget; - return ( - - ); - })} - - )) - ) : ( - - - - - - ); - }, [modelRows, columns.length, origin]); - - const navigateWithPageSize = useCallback( - (pageSize: number) => { - navigate({ - search: prev => ({ ...prev, listingSize: pageSize }), - state: s => s, - }); - }, - [navigate], - ); - - return ( -
-
- - - -
- -
-
- - -
-
- - } - > - - {tableBody} - - - -
- ); -} diff --git a/dashboard/src/components/TreeListingPage/treeTableUtils.tsx b/dashboard/src/components/TreeListingPage/treeTableUtils.tsx new file mode 100644 index 000000000..820d2cc71 --- /dev/null +++ b/dashboard/src/components/TreeListingPage/treeTableUtils.tsx @@ -0,0 +1,115 @@ +import type { ColumnDef } from '@tanstack/react-table'; +import type { JSX } from 'react'; + +import { TooltipDateTime } from '@/components/TooltipDateTime'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/Tooltip'; +import { TableHeader } from '@/components/Table/TableHeader'; +import { CommitTagTooltip } from '@/components/Tooltip/CommitTagTooltip'; +import { sanitizeTableValue } from '@/components/Table/tableUtils'; +import type { TreeListingItem } from '@/types/tree/Tree'; +import { shouldShowRelativeDate } from '@/lib/date'; +import { valueOrEmpty } from '@/lib/string'; +import { PinnedTrees } from '@/utils/constants/tables'; +import { makeTreeIdentifierKey } from '@/utils/trees'; + +export const commonTreeTableColumns: ColumnDef[] = [ + { + accessorKey: 'tree_name', + header: ({ column }): JSX.Element => ( + + ), + cell: ({ row }): JSX.Element => { + return ( + + + {sanitizeTableValue(row.getValue('tree_name') ?? '', false)} + + + + {sanitizeTableValue(row.original.git_repository_url, false)} + + + + ); + }, + meta: { + tabTarget: 'global.builds', + }, + }, + { + accessorKey: 'git_repository_branch', + header: ({ column }): JSX.Element => ( + + ), + cell: ({ row }) => valueOrEmpty(row.getValue('git_repository_branch')), + meta: { + tabTarget: 'global.builds', + }, + }, + { + id: 'git_commit_tags', + accessorKey: 'git_commit_tags', + header: ({ column }): JSX.Element => ( + + ), + cell: ({ row }): JSX.Element => ( + + ), + meta: { + tabTarget: 'global.builds', + }, + }, + { + accessorKey: 'start_time', + header: ({ column }): JSX.Element => ( + + ), + cell: ({ row }): JSX.Element => ( + + ), + meta: { + tabTarget: 'global.builds', + }, + }, +]; + +export const sortTreesWithPinnedFirst = ( + treeTableRows: T[], +): T[] => { + return treeTableRows.sort((a, b) => { + const aKey = makeTreeIdentifierKey({ + treeName: valueOrEmpty(a.tree_name), + gitRepositoryBranch: valueOrEmpty(a.git_repository_branch), + separator: '/', + }); + const bKey = makeTreeIdentifierKey({ + treeName: valueOrEmpty(b.tree_name), + gitRepositoryBranch: valueOrEmpty(b.git_repository_branch), + separator: '/', + }); + + const aIsPinned = PinnedTrees.some(regex => regex.test(aKey)); + const bIsPinned = PinnedTrees.some(regex => regex.test(bKey)); + + if (aIsPinned && !bIsPinned) { + return -1; + } + if (!aIsPinned && bIsPinned) { + return 1; + } + + return aKey.localeCompare(bKey); + }); +}; diff --git a/dashboard/src/hooks/useFeatureFlag.ts b/dashboard/src/hooks/useFeatureFlag.ts index a526de866..5e0aca6fe 100644 --- a/dashboard/src/hooks/useFeatureFlag.ts +++ b/dashboard/src/hooks/useFeatureFlag.ts @@ -1,15 +1,9 @@ type FeatureFlags = { showDev: boolean; - treeListingVersion: string; - hardwareListingVersion: string; }; export const useFeatureFlag = (): FeatureFlags => { return { showDev: import.meta.env.VITE_FEATURE_FLAG_SHOW_DEV ?? false, - treeListingVersion: - import.meta.env.VITE_FEATURE_FLAG_TREE_LISTING_VERSION ?? 'v2', - hardwareListingVersion: - import.meta.env.VITE_FEATURE_FLAG_HARDWARE_LISTING_VERSION ?? 'v2', }; }; diff --git a/dashboard/src/main.tsx b/dashboard/src/main.tsx index cda37a659..6aaa400bb 100644 --- a/dashboard/src/main.tsx +++ b/dashboard/src/main.tsx @@ -21,7 +21,11 @@ import { routeTree } from './routeTree.gen'; import './index.css'; import { isDev } from './lib/utils/vite'; import { ToastProvider } from './components/ui/toast'; -import type { RedirectFrom, RequiredStatusCount } from './types/general'; +import type { + RedirectFrom, + RequiredStatusCount, + ShortStatusCount, +} from './types/general'; import { parseSearch, stringifySearch } from './utils/search'; import { retryHandler } from './utils/query'; import { MILLISECONDS_IN_ONE_SECOND } from './utils/date'; @@ -52,9 +56,9 @@ declare module '@tanstack/react-router' { tests?: RequiredStatusCount; }; hardwareStatusCount?: { - builds: RequiredStatusCount; - boots: RequiredStatusCount; - tests: RequiredStatusCount; + builds: ShortStatusCount; + boots: ShortStatusCount; + tests: ShortStatusCount; }; } } diff --git a/dashboard/src/pages/Hardware/Hardware.tsx b/dashboard/src/pages/Hardware/Hardware.tsx index a7d0a35ec..ccf0c0f0d 100644 --- a/dashboard/src/pages/Hardware/Hardware.tsx +++ b/dashboard/src/pages/Hardware/Hardware.tsx @@ -5,34 +5,25 @@ import { useSearch } from '@tanstack/react-router'; import HardwareListingPage from '@/pages/Hardware/HardwareListingPage'; import { MemoizedListingOGTags } from '@/components/OpenGraphTags/ListingOGTags'; -import { OldPageBanner } from '@/components/Banner/PageBanner'; -import { useFeatureFlag } from '@/hooks/useFeatureFlag'; import type { HardwareListingRoutesMap } from '@/utils/constants/hardwareListing'; +import { parseSearchIntent } from '@/lib/intent'; const Hardware = ({ urlFromMap, }: { - urlFromMap: HardwareListingRoutesMap['v1']; + urlFromMap: HardwareListingRoutesMap; }): JSX.Element => { - const { hardwareListingVersion } = useFeatureFlag(); const { hardwareSearch } = useSearch({ from: urlFromMap.search, }); + const intent = parseSearchIntent(hardwareSearch ?? ''); + return ( <> - {hardwareListingVersion !== 'v1' && ( - - )}
- +
); diff --git a/dashboard/src/pages/Hardware/HardwareListingPage.tsx b/dashboard/src/pages/Hardware/HardwareListingPage.tsx index 1bc4dd792..336344b34 100644 --- a/dashboard/src/pages/Hardware/HardwareListingPage.tsx +++ b/dashboard/src/pages/Hardware/HardwareListingPage.tsx @@ -1,91 +1,169 @@ -import { useEffect, useMemo, useState, type JSX } from 'react'; -import { roundToNearestMinutes } from 'date-fns'; +import { useEffect, useMemo, type JSX } from 'react'; +import { FormattedMessage } from 'react-intl'; -import { useSearch } from '@tanstack/react-router'; +import { useNavigate, useSearch } from '@tanstack/react-router'; import { Toaster } from '@/components/ui/toaster'; -import type { HardwareItem } from '@/types/hardware'; - -import { useHardwareListing } from '@/api/hardware'; +import type { HardwareItem, HardwareRevisionSelection } from '@/types/hardware'; +import { + useHardwareListingByRevision, + useHardwareSelectors, +} from '@/api/hardware'; import { dateObjectToTimestampInSeconds, daysToSeconds } from '@/utils/date'; - -import type { RequiredStatusCount, StatusCount } from '@/types/general'; - import { - matchesRegexOrIncludes, includesInAnStringOrStringArray, + matchesRegexOrIncludes, } from '@/lib/string'; import { MemoizedKcidevFooter } from '@/components/Footer/KcidevFooter'; +import { REDUCED_TIME_SEARCH } from '@/utils/constants/general'; import type { HardwareListingRoutesMap } from '@/utils/constants/hardwareListing'; +import type { SearchIntent } from '@/lib/intent'; import { HardwareTable } from './HardwareTable'; +import { + decodeBranchValue, + findSelectionByCommitTokens, + getBranchBySelection, + getSelectionForBranchChange, + getSelectionForTreeChange, + getTreeBySelection, + resolveHardwareSelection, + type HardwareRevisionSelectorValue, +} from './hardwareSelection'; interface HardwareListingPageProps { - inputFilter: string; - urlFromMap: HardwareListingRoutesMap['v1']; + intent: SearchIntent; + urlFromMap: HardwareListingRoutesMap; } -const calculateTimeStamp = ( - intervalInDays: number, -): { - startTimestampInSeconds: number; - endTimestampInSeconds: number; -} => { - // Rounding so cache key doesn't get invalidated every request - const endTimestampInSeconds = dateObjectToTimestampInSeconds( - roundToNearestMinutes(new Date(), { - nearestTo: 30, - }), +const HardwareListingPage = ({ + intent, + urlFromMap, +}: HardwareListingPageProps): JSX.Element => { + const navigate = useNavigate({ from: urlFromMap.navigate }); + const { origin, treeName, gitRepositoryUrl, gitBranch, gitCommitHash } = + useSearch({ from: urlFromMap.search }); + const inputFilter = intent.search; + const intentCommits = + intent.intent === 'commits' ? intent.commits : undefined; + + const { + data: selectorsData, + error: selectorsError, + status: selectorsStatus, + } = useHardwareSelectors(urlFromMap.search); + const selectors = useMemo(() => selectorsData?.trees ?? [], [selectorsData]); + + const hasSelectionParams = Boolean( + treeName || gitRepositoryUrl || gitBranch || gitCommitHash, ); - const startTimestampInSeconds = - endTimestampInSeconds - daysToSeconds(intervalInDays); - return { startTimestampInSeconds, endTimestampInSeconds }; -}; -const useHardwareListingTime = ( - searchFrom: HardwareListingPageProps['urlFromMap']['search'], -): { - startTimestampInSeconds: number; - endTimestampInSeconds: number; -} => { - const { intervalInDays } = useSearch({ from: searchFrom }); - const [timestamps, setTimeStamps] = useState(() => { - return calculateTimeStamp(intervalInDays); - }); + const resolvedSelection = useMemo(() => { + const selectionFromUrl = + treeName && gitRepositoryUrl && gitBranch && gitCommitHash + ? { + treeName, + gitRepositoryUrl, + gitBranch, + gitCommitHash, + } + : null; - useEffect(() => { - setTimeStamps(calculateTimeStamp(intervalInDays)); - }, [intervalInDays]); + return resolveHardwareSelection({ + trees: selectors, + selectionFromUrl, + hasSelectionParams, + intentCommits, + }); + }, [ + selectors, + treeName, + gitRepositoryUrl, + gitBranch, + gitCommitHash, + hasSelectionParams, + intentCommits, + ]); - const { startTimestampInSeconds, endTimestampInSeconds } = timestamps; + const intentMatchedSelection = useMemo(() => { + if (!intentCommits) { + return null; + } - return { startTimestampInSeconds, endTimestampInSeconds }; -}; + return ( + findSelectionByCommitTokens(selectors, intentCommits)?.selection ?? null + ); + }, [selectors, intentCommits]); -const HardwareListingPage = ({ - inputFilter, - urlFromMap, -}: HardwareListingPageProps): JSX.Element => { - const { startTimestampInSeconds, endTimestampInSeconds } = - useHardwareListingTime(urlFromMap.search); - const { origin } = useSearch({ from: urlFromMap.search }); + useEffect(() => { + if ( + !intentCommits || + selectorsStatus !== 'success' || + hasSelectionParams || + intentMatchedSelection === null + ) { + return; + } - const { data, error, status, isLoading } = useHardwareListing( - startTimestampInSeconds, - endTimestampInSeconds, + navigate({ + search: previousSearch => ({ + ...previousSearch, + treeName: intentMatchedSelection.treeName, + gitRepositoryUrl: intentMatchedSelection.gitRepositoryUrl, + gitBranch: intentMatchedSelection.gitBranch, + gitCommitHash: intentMatchedSelection.gitCommitHash, + }), + state: s => s, + replace: true, + }); + }, [ + hasSelectionParams, + intentCommits, + intentMatchedSelection, + navigate, + selectorsStatus, + ]); + + const { + data: listingData, + error: listingError, + status: listingStatus, + isLoading: isListingLoading, + } = useHardwareListingByRevision( + resolvedSelection.selection, urlFromMap.search, ); + const selectedTree = useMemo(() => { + if (resolvedSelection.selection === null) { + return null; + } + + return getTreeBySelection(selectors, resolvedSelection.selection.treeName); + }, [selectors, resolvedSelection.selection]); + + const selectedBranch = useMemo(() => { + if (resolvedSelection.selection === null || selectedTree === null) { + return null; + } + + return getBranchBySelection( + selectedTree, + resolvedSelection.selection.gitRepositoryUrl, + resolvedSelection.selection.gitBranch, + ); + }, [resolvedSelection.selection, selectedTree]); + const listItems: HardwareItem[] = useMemo(() => { - if (!data || error) { + if (!listingData || listingError) { return []; } - const currentData = data.hardware; + const currentData = listingData.hardware; return currentData .filter(hardware => { @@ -95,46 +173,26 @@ const HardwareListingPage = ({ ); }) .map((hardware): HardwareItem => { - const buildCount: RequiredStatusCount = { - PASS: hardware.build_status_summary?.PASS, - FAIL: hardware.build_status_summary?.FAIL, - NULL: hardware.build_status_summary?.NULL, - ERROR: hardware.build_status_summary?.ERROR, - MISS: hardware.build_status_summary?.MISS, - DONE: hardware.build_status_summary?.DONE, - SKIP: hardware.build_status_summary?.SKIP, - }; - - const testStatusCount: StatusCount = { - DONE: hardware.test_status_summary.DONE, - ERROR: hardware.test_status_summary.ERROR, - FAIL: hardware.test_status_summary.FAIL, - MISS: hardware.test_status_summary.MISS, - PASS: hardware.test_status_summary.PASS, - SKIP: hardware.test_status_summary.SKIP, - NULL: hardware.test_status_summary.NULL, - }; - - const bootStatusCount: StatusCount = { - DONE: hardware.boot_status_summary.DONE, - ERROR: hardware.boot_status_summary.ERROR, - FAIL: hardware.boot_status_summary.FAIL, - MISS: hardware.boot_status_summary.MISS, - PASS: hardware.boot_status_summary.PASS, - SKIP: hardware.boot_status_summary.SKIP, - NULL: hardware.boot_status_summary.NULL, - }; - return { hardware: hardware.hardware, platform: hardware.platform, - build_status_summary: buildCount, - test_status_summary: testStatusCount, - boot_status_summary: bootStatusCount, + build_status_summary: hardware.build_status_summary, + test_status_summary: hardware.test_status_summary, + boot_status_summary: hardware.boot_status_summary, }; }) .sort((a, b) => a.platform.localeCompare(b.platform)); - }, [data, error, inputFilter]); + }, [listingData, listingError, inputFilter]); + + const revisionStartTimestampInSeconds = resolvedSelection.revisionStartTime + ? dateObjectToTimestampInSeconds( + new Date(resolvedSelection.revisionStartTime), + ) + : 0; + + const revisionEndTimestampInSeconds = revisionStartTimestampInSeconds + ? revisionStartTimestampInSeconds + daysToSeconds(REDUCED_TIME_SEARCH) + : 0; const kcidevComponent = useMemo( () => ( @@ -146,20 +204,138 @@ const HardwareListingPage = ({ [origin], ); + const navigateToSelection = ( + nextSelection: HardwareRevisionSelection, + ): void => { + navigate({ + search: previousSearch => ({ + ...previousSearch, + treeName: nextSelection.treeName, + gitRepositoryUrl: nextSelection.gitRepositoryUrl, + gitBranch: nextSelection.gitBranch, + gitCommitHash: nextSelection.gitCommitHash, + hardwareSearch: '', + }), + state: s => s, + }); + }; + + const onTreeChange = ({ + tree, + branch, + revision, + }: HardwareRevisionSelectorValue): void => { + if (!tree) { + return; + } + + if (branch) { + const selectedTreeByName = getTreeBySelection(selectors, tree); + if (selectedTreeByName === null) { + return; + } + + const branchSelection = decodeBranchValue(branch); + if (branchSelection === null) { + return; + } + + if (revision) { + navigateToSelection({ + treeName: tree, + gitRepositoryUrl: branchSelection.gitRepositoryUrl, + gitBranch: branchSelection.gitBranch, + gitCommitHash: revision, + }); + return; + } + + const nextSelection = getSelectionForBranchChange({ + tree: selectedTreeByName, + gitRepositoryUrl: branchSelection.gitRepositoryUrl, + gitBranch: branchSelection.gitBranch, + }); + if (nextSelection === null) { + return; + } + + navigateToSelection(nextSelection); + return; + } + + const nextSelection = getSelectionForTreeChange({ + trees: selectors, + treeName: tree, + }); + if (nextSelection === null) { + return; + } + + navigateToSelection(nextSelection); + }; + + const hasSelectors = selectors.length > 0; + const hasListingRows = Boolean((listingData?.hardware.length ?? 0) > 0); + const tableEmptyMessageId = + !hasListingRows && inputFilter.length === 0 + ? 'hardwareListing.revisionEmpty' + : 'hardwareListing.notFound'; + return ( <>
- + {selectorsStatus === 'error' && ( +
+ + {selectorsError?.message} + +
+ )} + + {selectorsStatus === 'pending' && ( +
+ +
+ )} + + {selectorsStatus === 'success' && ( + <> + {!hasSelectors && ( +
+ +
+ )} + + {hasSelectors && ( + <> + + }} + /> + + + + + )} + + )}
{kcidevComponent} diff --git a/dashboard/src/pages/Hardware/HardwareListingPageV2.tsx b/dashboard/src/pages/Hardware/HardwareListingPageV2.tsx deleted file mode 100644 index a0c32db6f..000000000 --- a/dashboard/src/pages/Hardware/HardwareListingPageV2.tsx +++ /dev/null @@ -1,337 +0,0 @@ -import { useEffect, useMemo, type JSX } from 'react'; -import { FormattedMessage } from 'react-intl'; - -import { useNavigate, useSearch } from '@tanstack/react-router'; - -import { Toaster } from '@/components/ui/toaster'; - -import type { HardwareItem, HardwareRevisionSelection } from '@/types/hardware'; - -import { - useHardwareListingByRevision, - useHardwareSelectors, -} from '@/api/hardware'; - -import { dateObjectToTimestampInSeconds, daysToSeconds } from '@/utils/date'; - -import { - includesInAnStringOrStringArray, - matchesRegexOrIncludes, -} from '@/lib/string'; - -import { MemoizedKcidevFooter } from '@/components/Footer/KcidevFooter'; -import { REDUCED_TIME_SEARCH } from '@/utils/constants/general'; - -import type { HardwareListingRoutesMap } from '@/utils/constants/hardwareListing'; -import type { SearchIntent } from '@/lib/intent'; - -import { HardwareTable } from './HardwareTable'; -import { - decodeBranchValue, - findSelectionByCommitTokens, - getBranchBySelection, - getSelectionForBranchChange, - getSelectionForTreeChange, - getTreeBySelection, - resolveHardwareSelection, - type HardwareRevisionSelectorValue, -} from './hardwareSelection'; - -interface HardwareListingPageV2Props { - intent: SearchIntent; - urlFromMap: HardwareListingRoutesMap['v2']; -} - -const HardwareListingPageV2 = ({ - intent, - urlFromMap, -}: HardwareListingPageV2Props): JSX.Element => { - const navigate = useNavigate({ from: urlFromMap.navigate }); - const { origin, treeName, gitRepositoryUrl, gitBranch, gitCommitHash } = - useSearch({ from: urlFromMap.search }); - const inputFilter = intent.search; - const intentCommits = - intent.intent === 'commits' ? intent.commits : undefined; - - const { - data: selectorsData, - error: selectorsError, - status: selectorsStatus, - } = useHardwareSelectors(urlFromMap.search); - const selectors = useMemo(() => selectorsData?.trees ?? [], [selectorsData]); - - const hasSelectionParams = Boolean( - treeName || gitRepositoryUrl || gitBranch || gitCommitHash, - ); - - const resolvedSelection = useMemo(() => { - const selectionFromUrl = - treeName && gitRepositoryUrl && gitBranch && gitCommitHash - ? { - treeName, - gitRepositoryUrl, - gitBranch, - gitCommitHash, - } - : null; - - return resolveHardwareSelection({ - trees: selectors, - selectionFromUrl, - hasSelectionParams, - intentCommits, - }); - }, [ - selectors, - treeName, - gitRepositoryUrl, - gitBranch, - gitCommitHash, - hasSelectionParams, - intentCommits, - ]); - - const intentMatchedSelection = useMemo(() => { - if (!intentCommits) { - return null; - } - - return ( - findSelectionByCommitTokens(selectors, intentCommits)?.selection ?? null - ); - }, [selectors, intentCommits]); - - useEffect(() => { - if (!intentCommits || selectorsStatus !== 'success') { - return; - } - - if (intentMatchedSelection === null) { - return; - } - - navigate({ - search: previousSearch => ({ - ...previousSearch, - treeName: intentMatchedSelection.treeName, - gitRepositoryUrl: intentMatchedSelection.gitRepositoryUrl, - gitBranch: intentMatchedSelection.gitBranch, - gitCommitHash: intentMatchedSelection.gitCommitHash, - }), - state: s => s, - replace: true, - }); - }, [ - hasSelectionParams, - intentCommits, - intentMatchedSelection, - navigate, - selectorsStatus, - ]); - - const { - data: listingData, - error: listingError, - status: listingStatus, - isLoading: isListingLoading, - } = useHardwareListingByRevision( - resolvedSelection.selection, - urlFromMap.search, - ); - - const selectedTree = useMemo(() => { - if (resolvedSelection.selection === null) { - return null; - } - - return getTreeBySelection(selectors, resolvedSelection.selection.treeName); - }, [selectors, resolvedSelection.selection]); - - const selectedBranch = useMemo(() => { - if (resolvedSelection.selection === null || selectedTree === null) { - return null; - } - - return getBranchBySelection( - selectedTree, - resolvedSelection.selection.gitRepositoryUrl, - resolvedSelection.selection.gitBranch, - ); - }, [resolvedSelection.selection, selectedTree]); - - const listItems: HardwareItem[] = useMemo(() => { - if (!listingData || listingError) { - return []; - } - - const currentData = listingData.hardware; - - return currentData - .filter(hardware => { - return ( - matchesRegexOrIncludes(hardware.platform, inputFilter) || - includesInAnStringOrStringArray(hardware.hardware ?? '', inputFilter) - ); - }) - .sort((a, b) => a.platform.localeCompare(b.platform)); - }, [listingData, listingError, inputFilter]); - - const revisionStartTimestampInSeconds = resolvedSelection.revisionStartTime - ? dateObjectToTimestampInSeconds( - new Date(resolvedSelection.revisionStartTime), - ) - : 0; - - const revisionEndTimestampInSeconds = revisionStartTimestampInSeconds - ? revisionStartTimestampInSeconds + daysToSeconds(REDUCED_TIME_SEARCH) - : 0; - - const kcidevComponent = useMemo( - () => ( - - ), - [origin], - ); - - const navigateToSelection = ( - nextSelection: HardwareRevisionSelection, - ): void => { - navigate({ - search: previousSearch => ({ - ...previousSearch, - treeName: nextSelection.treeName, - gitRepositoryUrl: nextSelection.gitRepositoryUrl, - gitBranch: nextSelection.gitBranch, - gitCommitHash: nextSelection.gitCommitHash, - hardwareSearch: '', - }), - state: s => s, - }); - }; - - const onTreeChange = ({ - tree, - branch, - revision, - }: HardwareRevisionSelectorValue): void => { - if (!tree) { - return; - } - - if (branch) { - const selectedTreeByName = getTreeBySelection(selectors, tree); - if (selectedTreeByName === null) { - return; - } - - const branchSelection = decodeBranchValue(branch); - if (branchSelection === null) { - return; - } - - if (revision) { - navigateToSelection({ - treeName: tree, - gitRepositoryUrl: branchSelection.gitRepositoryUrl, - gitBranch: branchSelection.gitBranch, - gitCommitHash: revision, - }); - return; - } - - const nextSelection = getSelectionForBranchChange({ - tree: selectedTreeByName, - gitRepositoryUrl: branchSelection.gitRepositoryUrl, - gitBranch: branchSelection.gitBranch, - }); - if (nextSelection === null) { - return; - } - - navigateToSelection(nextSelection); - return; - } - - const nextSelection = getSelectionForTreeChange({ - trees: selectors, - treeName: tree, - }); - if (nextSelection === null) { - return; - } - - navigateToSelection(nextSelection); - }; - - const hasSelectors = selectors.length > 0; - const hasListingRows = Boolean((listingData?.hardware.length ?? 0) > 0); - const tableEmptyMessageId = - !hasListingRows && inputFilter.length === 0 - ? 'hardwareListing.revisionEmpty' - : 'hardwareListing.notFound'; - - return ( - <> - -
- {selectorsStatus === 'error' && ( -
- - {selectorsError?.message} - -
- )} - - {selectorsStatus === 'pending' && ( -
- -
- )} - - {selectorsStatus === 'success' && ( - <> - {!hasSelectors && ( -
- -
- )} - - {hasSelectors && ( - <> - - }} - /> - - - - - )} - - )} -
- {kcidevComponent} - - ); -}; - -export default HardwareListingPageV2; diff --git a/dashboard/src/pages/Hardware/HardwareTable.tsx b/dashboard/src/pages/Hardware/HardwareTable.tsx index 67e30cb6d..598e55d27 100644 --- a/dashboard/src/pages/Hardware/HardwareTable.tsx +++ b/dashboard/src/pages/Hardware/HardwareTable.tsx @@ -28,7 +28,7 @@ import type { MessagesKey } from '@/locales/messages'; import { TableBody, TableCell, TableRow } from '@/components/ui/table'; import { ConditionalTableCell } from '@/components/Table/ConditionalTableCell'; -import { GroupedTestStatusWithLink } from '@/components/Status/Status'; +import { BaseGroupedStatusWithLink } from '@/components/Status/Status'; import { TableHeader } from '@/components/Table/TableHeader'; import { ItemsPerPageSelector, @@ -44,7 +44,7 @@ import type { HardwareSelectorTree, } from '@/types/hardware'; -import { statusCountToRequiredStatusCount, sumStatus } from '@/utils/status'; +import { sumStatus } from '@/utils/status'; import { usePaginationState } from '@/hooks/usePaginationState'; @@ -82,7 +82,7 @@ interface IHardwareTable { onTreeChange?: (nextSelection: HardwareRevisionSelectorValue) => void; } -type HardwareListingRoutes = '/hardware' | '/hardware/v1' | '/hardware/v2'; +type HardwareListingRoutes = '/hardware'; const getLinkProps = ( row: Row, @@ -112,12 +112,8 @@ const getLinkProps = ( from: RedirectFrom.Hardware, hardwareStatusCount: { builds: row.original.build_status_summary, - tests: statusCountToRequiredStatusCount( - row.original.test_status_summary, - ), - boots: statusCountToRequiredStatusCount( - row.original.boot_status_summary, - ), + tests: row.original.test_status_summary, + boots: row.original.boot_status_summary, }, }), }; @@ -187,14 +183,12 @@ const getColumns = ( const tabTarget = (column.columnDef.meta as ListingTableColumnMeta) .tabTarget; return row.original.build_status_summary ? ( - { - const { hardwareListingVersion } = useFeatureFlag(); - const { hardwareSearch } = useSearch({ - from: urlFromMap.search, - }); - - const intent = parseSearchIntent(hardwareSearch ?? ''); - - return ( - <> - - {hardwareListingVersion !== 'v2' && ( - - )} -
- -
- - ); -}; - -export default HardwareV2; diff --git a/dashboard/src/pages/TreeListing/Trees.tsx b/dashboard/src/pages/TreeListing/Trees.tsx index bc43dc257..8d76ac498 100644 --- a/dashboard/src/pages/TreeListing/Trees.tsx +++ b/dashboard/src/pages/TreeListing/Trees.tsx @@ -5,16 +5,13 @@ import { useSearch } from '@tanstack/react-router'; import TreeListingPage from '@/components/TreeListingPage/TreeListingPage'; import { MemoizedListingOGTags } from '@/components/OpenGraphTags/ListingOGTags'; -import { OldPageBanner } from '@/components/Banner/PageBanner'; -import { useFeatureFlag } from '@/hooks/useFeatureFlag'; import type { TreeListingRoutesMap } from '@/utils/constants/treeListing'; const Trees = ({ urlFromMap, }: { - urlFromMap: TreeListingRoutesMap['v1']; + urlFromMap: TreeListingRoutesMap; }): JSX.Element => { - const { treeListingVersion } = useFeatureFlag(); const { treeSearch } = useSearch({ from: urlFromMap.search, }); @@ -22,9 +19,6 @@ const Trees = ({ return ( <> - {treeListingVersion !== 'v1' && ( - - )}
diff --git a/dashboard/src/pages/TreeListing/TreesV2.tsx b/dashboard/src/pages/TreeListing/TreesV2.tsx deleted file mode 100644 index a9570610a..000000000 --- a/dashboard/src/pages/TreeListing/TreesV2.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import type { JSX } from 'react'; -import { useSearch } from '@tanstack/react-router'; - -import { MemoizedListingOGTags } from '@/components/OpenGraphTags/ListingOGTags'; -import TreeListingV2 from '@/components/TreeListingPage/TreeListingV2'; -import { NewPageBanner } from '@/components/Banner/PageBanner'; -import { useFeatureFlag } from '@/hooks/useFeatureFlag'; -import type { TreeListingRoutesMap } from '@/utils/constants/treeListing'; - -const TreeListingPageV2 = ({ - urlFromMap, -}: { - urlFromMap: TreeListingRoutesMap['v2']; -}): JSX.Element => { - const { treeListingVersion } = useFeatureFlag(); - const { treeSearch } = useSearch({ - from: urlFromMap.search, - }); - - return ( - <> - - {treeListingVersion !== 'v2' && ( - - )} -
- -
- - ); -}; - -export default TreeListingPageV2; diff --git a/dashboard/src/pages/hardwareDetails/HardwareDetails.tsx b/dashboard/src/pages/hardwareDetails/HardwareDetails.tsx index c98785708..178f39cba 100644 --- a/dashboard/src/pages/hardwareDetails/HardwareDetails.tsx +++ b/dashboard/src/pages/hardwareDetails/HardwareDetails.tsx @@ -57,7 +57,7 @@ import { useHardwareDetailsLazyLoadQuery } from '@/hooks/useHardwareDetailsLazyL import { useQueryInconsistencyInvalidator } from '@/hooks/useQueryInconsistencyInvalidator'; import type { GroupedStatus } from '@/utils/status'; -import { groupStatus, statusCountToRequiredStatusCount } from '@/utils/status'; +import { groupStatus, statusCountToShortStatusCount } from '@/utils/status'; import PageWithTitle from '@/components/PageWithTitle'; @@ -266,9 +266,9 @@ function HardwareDetails(): JSX.Element { const { boots, builds, tests } = data.summary; return { - boots: statusCountToRequiredStatusCount(boots.status), - tests: statusCountToRequiredStatusCount(tests.status), - builds: builds.status, + boots: statusCountToShortStatusCount(boots.status), + tests: statusCountToShortStatusCount(tests.status), + builds: statusCountToShortStatusCount(builds.status), } satisfies HardwareStatusComparedState; }, [summaryResponse]); diff --git a/dashboard/src/routeTree.gen.ts b/dashboard/src/routeTree.gen.ts index 98bd4a772..c92bbaed9 100644 --- a/dashboard/src/routeTree.gen.ts +++ b/dashboard/src/routeTree.gen.ts @@ -21,22 +21,14 @@ import { Route as MainTreeIndexRouteImport } from './routes/_main/tree/index' import { Route as MainMetricsIndexRouteImport } from './routes/_main/metrics/index' import { Route as MainIssuesIndexRouteImport } from './routes/_main/issues/index' import { Route as MainHardwareIndexRouteImport } from './routes/_main/hardware/index' -import { Route as MainTreeV2RouteRouteImport } from './routes/_main/tree/v2/route' -import { Route as MainTreeV1RouteRouteImport } from './routes/_main/tree/v1/route' import { Route as MainTreeTreeIdRouteRouteImport } from './routes/_main/tree/$treeId/route' import { Route as MainTestTestIdRouteRouteImport } from './routes/_main/test/$testId/route' import { Route as MainIssueIssueIdRouteRouteImport } from './routes/_main/issue/$issueId/route' -import { Route as MainHardwareV2RouteRouteImport } from './routes/_main/hardware/v2/route' -import { Route as MainHardwareV1RouteRouteImport } from './routes/_main/hardware/v1/route' import { Route as MainHardwareHardwareIdRouteRouteImport } from './routes/_main/hardware/$hardwareId/route' import { Route as MainBuildBuildIdRouteRouteImport } from './routes/_main/build/$buildId/route' -import { Route as MainTreeV2IndexRouteImport } from './routes/_main/tree/v2/index' -import { Route as MainTreeV1IndexRouteImport } from './routes/_main/tree/v1/index' import { Route as MainTreeTreeIdIndexRouteImport } from './routes/_main/tree/$treeId/index' import { Route as MainTestTestIdIndexRouteImport } from './routes/_main/test/$testId/index' import { Route as MainIssueIssueIdIndexRouteImport } from './routes/_main/issue/$issueId/index' -import { Route as MainHardwareV2IndexRouteImport } from './routes/_main/hardware/v2/index' -import { Route as MainHardwareV1IndexRouteImport } from './routes/_main/hardware/v1/index' import { Route as MainHardwareHardwareIdIndexRouteImport } from './routes/_main/hardware/$hardwareId/index' import { Route as MainBuildBuildIdIndexRouteImport } from './routes/_main/build/$buildId/index' import { Route as MainalternativesIIndexRouteImport } from './routes/_main/(alternatives)/i/index' @@ -121,16 +113,6 @@ const MainHardwareIndexRoute = MainHardwareIndexRouteImport.update({ path: '/', getParentRoute: () => MainHardwareRouteRoute, } as any) -const MainTreeV2RouteRoute = MainTreeV2RouteRouteImport.update({ - id: '/v2', - path: '/v2', - getParentRoute: () => MainTreeRouteRoute, -} as any) -const MainTreeV1RouteRoute = MainTreeV1RouteRouteImport.update({ - id: '/v1', - path: '/v1', - getParentRoute: () => MainTreeRouteRoute, -} as any) const MainTreeTreeIdRouteRoute = MainTreeTreeIdRouteRouteImport.update({ id: '/$treeId', path: '/$treeId', @@ -146,16 +128,6 @@ const MainIssueIssueIdRouteRoute = MainIssueIssueIdRouteRouteImport.update({ path: '/issue/$issueId', getParentRoute: () => MainRouteRoute, } as any) -const MainHardwareV2RouteRoute = MainHardwareV2RouteRouteImport.update({ - id: '/v2', - path: '/v2', - getParentRoute: () => MainHardwareRouteRoute, -} as any) -const MainHardwareV1RouteRoute = MainHardwareV1RouteRouteImport.update({ - id: '/v1', - path: '/v1', - getParentRoute: () => MainHardwareRouteRoute, -} as any) const MainHardwareHardwareIdRouteRoute = MainHardwareHardwareIdRouteRouteImport.update({ id: '/$hardwareId', @@ -167,16 +139,6 @@ const MainBuildBuildIdRouteRoute = MainBuildBuildIdRouteRouteImport.update({ path: '/build/$buildId', getParentRoute: () => MainRouteRoute, } as any) -const MainTreeV2IndexRoute = MainTreeV2IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => MainTreeV2RouteRoute, -} as any) -const MainTreeV1IndexRoute = MainTreeV1IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => MainTreeV1RouteRoute, -} as any) const MainTreeTreeIdIndexRoute = MainTreeTreeIdIndexRouteImport.update({ id: '/', path: '/', @@ -192,16 +154,6 @@ const MainIssueIssueIdIndexRoute = MainIssueIssueIdIndexRouteImport.update({ path: '/', getParentRoute: () => MainIssueIssueIdRouteRoute, } as any) -const MainHardwareV2IndexRoute = MainHardwareV2IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => MainHardwareV2RouteRoute, -} as any) -const MainHardwareV1IndexRoute = MainHardwareV1IndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => MainHardwareV1RouteRoute, -} as any) const MainHardwareHardwareIdIndexRoute = MainHardwareHardwareIdIndexRouteImport.update({ id: '/', @@ -355,13 +307,9 @@ export interface FileRoutesByFullPath { '/tree': typeof MainTreeRouteRouteWithChildren '/build/$buildId': typeof MainBuildBuildIdRouteRouteWithChildren '/hardware/$hardwareId': typeof MainHardwareHardwareIdRouteRouteWithChildren - '/hardware/v1': typeof MainHardwareV1RouteRouteWithChildren - '/hardware/v2': typeof MainHardwareV2RouteRouteWithChildren '/issue/$issueId': typeof MainIssueIssueIdRouteRouteWithChildren '/test/$testId': typeof MainTestTestIdRouteRouteWithChildren '/tree/$treeId': typeof MainTreeTreeIdRouteRouteWithChildren - '/tree/v1': typeof MainTreeV1RouteRouteWithChildren - '/tree/v2': typeof MainTreeV2RouteRouteWithChildren '/hardware/': typeof MainHardwareIndexRoute '/issues/': typeof MainIssuesIndexRoute '/metrics/': typeof MainMetricsIndexRoute @@ -372,13 +320,9 @@ export interface FileRoutesByFullPath { '/i/': typeof MainalternativesIIndexRoute '/build/$buildId/': typeof MainBuildBuildIdIndexRoute '/hardware/$hardwareId/': typeof MainHardwareHardwareIdIndexRoute - '/hardware/v1/': typeof MainHardwareV1IndexRoute - '/hardware/v2/': typeof MainHardwareV2IndexRoute '/issue/$issueId/': typeof MainIssueIssueIdIndexRoute '/test/$testId/': typeof MainTestTestIdIndexRoute '/tree/$treeId/': typeof MainTreeTreeIdIndexRoute - '/tree/v1/': typeof MainTreeV1IndexRoute - '/tree/v2/': typeof MainTreeV2IndexRoute '/tree/$treeName/$branch/$hash': typeof MainTreeTreeNameBranchHashRouteRouteWithChildren '/b/$buildId/': typeof MainalternativesBBuildIdIndexRoute '/i/$issueId/': typeof MainalternativesIIssueIdIndexRoute @@ -409,13 +353,9 @@ export interface FileRoutesByTo { '/i': typeof MainalternativesIIndexRoute '/build/$buildId': typeof MainBuildBuildIdIndexRoute '/hardware/$hardwareId': typeof MainHardwareHardwareIdIndexRoute - '/hardware/v1': typeof MainHardwareV1IndexRoute - '/hardware/v2': typeof MainHardwareV2IndexRoute '/issue/$issueId': typeof MainIssueIssueIdIndexRoute '/test/$testId': typeof MainTestTestIdIndexRoute '/tree/$treeId': typeof MainTreeTreeIdIndexRoute - '/tree/v1': typeof MainTreeV1IndexRoute - '/tree/v2': typeof MainTreeV2IndexRoute '/b/$buildId': typeof MainalternativesBBuildIdIndexRoute '/i/$issueId': typeof MainalternativesIIssueIdIndexRoute '/t/$testId': typeof MainalternativesTTestIdIndexRoute @@ -446,13 +386,9 @@ export interface FileRoutesById { '/_main/': typeof MainIndexRoute '/_main/build/$buildId': typeof MainBuildBuildIdRouteRouteWithChildren '/_main/hardware/$hardwareId': typeof MainHardwareHardwareIdRouteRouteWithChildren - '/_main/hardware/v1': typeof MainHardwareV1RouteRouteWithChildren - '/_main/hardware/v2': typeof MainHardwareV2RouteRouteWithChildren '/_main/issue/$issueId': typeof MainIssueIssueIdRouteRouteWithChildren '/_main/test/$testId': typeof MainTestTestIdRouteRouteWithChildren '/_main/tree/$treeId': typeof MainTreeTreeIdRouteRouteWithChildren - '/_main/tree/v1': typeof MainTreeV1RouteRouteWithChildren - '/_main/tree/v2': typeof MainTreeV2RouteRouteWithChildren '/_main/hardware/': typeof MainHardwareIndexRoute '/_main/issues/': typeof MainIssuesIndexRoute '/_main/metrics/': typeof MainMetricsIndexRoute @@ -463,13 +399,9 @@ export interface FileRoutesById { '/_main/(alternatives)/i/': typeof MainalternativesIIndexRoute '/_main/build/$buildId/': typeof MainBuildBuildIdIndexRoute '/_main/hardware/$hardwareId/': typeof MainHardwareHardwareIdIndexRoute - '/_main/hardware/v1/': typeof MainHardwareV1IndexRoute - '/_main/hardware/v2/': typeof MainHardwareV2IndexRoute '/_main/issue/$issueId/': typeof MainIssueIssueIdIndexRoute '/_main/test/$testId/': typeof MainTestTestIdIndexRoute '/_main/tree/$treeId/': typeof MainTreeTreeIdIndexRoute - '/_main/tree/v1/': typeof MainTreeV1IndexRoute - '/_main/tree/v2/': typeof MainTreeV2IndexRoute '/_main/tree/$treeName/$branch/$hash': typeof MainTreeTreeNameBranchHashRouteRouteWithChildren '/_main/(alternatives)/b/$buildId/': typeof MainalternativesBBuildIdIndexRoute '/_main/(alternatives)/i/$issueId/': typeof MainalternativesIIssueIdIndexRoute @@ -501,13 +433,9 @@ export interface FileRouteTypes { | '/tree' | '/build/$buildId' | '/hardware/$hardwareId' - | '/hardware/v1' - | '/hardware/v2' | '/issue/$issueId' | '/test/$testId' | '/tree/$treeId' - | '/tree/v1' - | '/tree/v2' | '/hardware/' | '/issues/' | '/metrics/' @@ -518,13 +446,9 @@ export interface FileRouteTypes { | '/i/' | '/build/$buildId/' | '/hardware/$hardwareId/' - | '/hardware/v1/' - | '/hardware/v2/' | '/issue/$issueId/' | '/test/$testId/' | '/tree/$treeId/' - | '/tree/v1/' - | '/tree/v2/' | '/tree/$treeName/$branch/$hash' | '/b/$buildId/' | '/i/$issueId/' @@ -555,13 +479,9 @@ export interface FileRouteTypes { | '/i' | '/build/$buildId' | '/hardware/$hardwareId' - | '/hardware/v1' - | '/hardware/v2' | '/issue/$issueId' | '/test/$testId' | '/tree/$treeId' - | '/tree/v1' - | '/tree/v2' | '/b/$buildId' | '/i/$issueId' | '/t/$testId' @@ -591,13 +511,9 @@ export interface FileRouteTypes { | '/_main/' | '/_main/build/$buildId' | '/_main/hardware/$hardwareId' - | '/_main/hardware/v1' - | '/_main/hardware/v2' | '/_main/issue/$issueId' | '/_main/test/$testId' | '/_main/tree/$treeId' - | '/_main/tree/v1' - | '/_main/tree/v2' | '/_main/hardware/' | '/_main/issues/' | '/_main/metrics/' @@ -608,13 +524,9 @@ export interface FileRouteTypes { | '/_main/(alternatives)/i/' | '/_main/build/$buildId/' | '/_main/hardware/$hardwareId/' - | '/_main/hardware/v1/' - | '/_main/hardware/v2/' | '/_main/issue/$issueId/' | '/_main/test/$testId/' | '/_main/tree/$treeId/' - | '/_main/tree/v1/' - | '/_main/tree/v2/' | '/_main/tree/$treeName/$branch/$hash' | '/_main/(alternatives)/b/$buildId/' | '/_main/(alternatives)/i/$issueId/' @@ -727,20 +639,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof MainHardwareIndexRouteImport parentRoute: typeof MainHardwareRouteRoute } - '/_main/tree/v2': { - id: '/_main/tree/v2' - path: '/v2' - fullPath: '/tree/v2' - preLoaderRoute: typeof MainTreeV2RouteRouteImport - parentRoute: typeof MainTreeRouteRoute - } - '/_main/tree/v1': { - id: '/_main/tree/v1' - path: '/v1' - fullPath: '/tree/v1' - preLoaderRoute: typeof MainTreeV1RouteRouteImport - parentRoute: typeof MainTreeRouteRoute - } '/_main/tree/$treeId': { id: '/_main/tree/$treeId' path: '/$treeId' @@ -762,20 +660,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof MainIssueIssueIdRouteRouteImport parentRoute: typeof MainRouteRoute } - '/_main/hardware/v2': { - id: '/_main/hardware/v2' - path: '/v2' - fullPath: '/hardware/v2' - preLoaderRoute: typeof MainHardwareV2RouteRouteImport - parentRoute: typeof MainHardwareRouteRoute - } - '/_main/hardware/v1': { - id: '/_main/hardware/v1' - path: '/v1' - fullPath: '/hardware/v1' - preLoaderRoute: typeof MainHardwareV1RouteRouteImport - parentRoute: typeof MainHardwareRouteRoute - } '/_main/hardware/$hardwareId': { id: '/_main/hardware/$hardwareId' path: '/$hardwareId' @@ -790,20 +674,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof MainBuildBuildIdRouteRouteImport parentRoute: typeof MainRouteRoute } - '/_main/tree/v2/': { - id: '/_main/tree/v2/' - path: '/' - fullPath: '/tree/v2/' - preLoaderRoute: typeof MainTreeV2IndexRouteImport - parentRoute: typeof MainTreeV2RouteRoute - } - '/_main/tree/v1/': { - id: '/_main/tree/v1/' - path: '/' - fullPath: '/tree/v1/' - preLoaderRoute: typeof MainTreeV1IndexRouteImport - parentRoute: typeof MainTreeV1RouteRoute - } '/_main/tree/$treeId/': { id: '/_main/tree/$treeId/' path: '/' @@ -825,20 +695,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof MainIssueIssueIdIndexRouteImport parentRoute: typeof MainIssueIssueIdRouteRoute } - '/_main/hardware/v2/': { - id: '/_main/hardware/v2/' - path: '/' - fullPath: '/hardware/v2/' - preLoaderRoute: typeof MainHardwareV2IndexRouteImport - parentRoute: typeof MainHardwareV2RouteRoute - } - '/_main/hardware/v1/': { - id: '/_main/hardware/v1/' - path: '/' - fullPath: '/hardware/v1/' - preLoaderRoute: typeof MainHardwareV1IndexRouteImport - parentRoute: typeof MainHardwareV1RouteRoute - } '/_main/hardware/$hardwareId/': { id: '/_main/hardware/$hardwareId/' path: '/' @@ -1040,40 +896,14 @@ const MainHardwareHardwareIdRouteRouteWithChildren = MainHardwareHardwareIdRouteRouteChildren, ) -interface MainHardwareV1RouteRouteChildren { - MainHardwareV1IndexRoute: typeof MainHardwareV1IndexRoute -} - -const MainHardwareV1RouteRouteChildren: MainHardwareV1RouteRouteChildren = { - MainHardwareV1IndexRoute: MainHardwareV1IndexRoute, -} - -const MainHardwareV1RouteRouteWithChildren = - MainHardwareV1RouteRoute._addFileChildren(MainHardwareV1RouteRouteChildren) - -interface MainHardwareV2RouteRouteChildren { - MainHardwareV2IndexRoute: typeof MainHardwareV2IndexRoute -} - -const MainHardwareV2RouteRouteChildren: MainHardwareV2RouteRouteChildren = { - MainHardwareV2IndexRoute: MainHardwareV2IndexRoute, -} - -const MainHardwareV2RouteRouteWithChildren = - MainHardwareV2RouteRoute._addFileChildren(MainHardwareV2RouteRouteChildren) - interface MainHardwareRouteRouteChildren { MainHardwareHardwareIdRouteRoute: typeof MainHardwareHardwareIdRouteRouteWithChildren - MainHardwareV1RouteRoute: typeof MainHardwareV1RouteRouteWithChildren - MainHardwareV2RouteRoute: typeof MainHardwareV2RouteRouteWithChildren MainHardwareIndexRoute: typeof MainHardwareIndexRoute } const MainHardwareRouteRouteChildren: MainHardwareRouteRouteChildren = { MainHardwareHardwareIdRouteRoute: MainHardwareHardwareIdRouteRouteWithChildren, - MainHardwareV1RouteRoute: MainHardwareV1RouteRouteWithChildren, - MainHardwareV2RouteRoute: MainHardwareV2RouteRouteWithChildren, MainHardwareIndexRoute: MainHardwareIndexRoute, } @@ -1118,30 +948,6 @@ const MainTreeTreeIdRouteRouteChildren: MainTreeTreeIdRouteRouteChildren = { const MainTreeTreeIdRouteRouteWithChildren = MainTreeTreeIdRouteRoute._addFileChildren(MainTreeTreeIdRouteRouteChildren) -interface MainTreeV1RouteRouteChildren { - MainTreeV1IndexRoute: typeof MainTreeV1IndexRoute -} - -const MainTreeV1RouteRouteChildren: MainTreeV1RouteRouteChildren = { - MainTreeV1IndexRoute: MainTreeV1IndexRoute, -} - -const MainTreeV1RouteRouteWithChildren = MainTreeV1RouteRoute._addFileChildren( - MainTreeV1RouteRouteChildren, -) - -interface MainTreeV2RouteRouteChildren { - MainTreeV2IndexRoute: typeof MainTreeV2IndexRoute -} - -const MainTreeV2RouteRouteChildren: MainTreeV2RouteRouteChildren = { - MainTreeV2IndexRoute: MainTreeV2IndexRoute, -} - -const MainTreeV2RouteRouteWithChildren = MainTreeV2RouteRoute._addFileChildren( - MainTreeV2RouteRouteChildren, -) - interface MainTreeTreeNameBranchHashRouteRouteChildren { MainTreeTreeNameBranchHashIndexRoute: typeof MainTreeTreeNameBranchHashIndexRoute } @@ -1158,8 +964,6 @@ const MainTreeTreeNameBranchHashRouteRouteWithChildren = interface MainTreeRouteRouteChildren { MainTreeTreeIdRouteRoute: typeof MainTreeTreeIdRouteRouteWithChildren - MainTreeV1RouteRoute: typeof MainTreeV1RouteRouteWithChildren - MainTreeV2RouteRoute: typeof MainTreeV2RouteRouteWithChildren MainTreeIndexRoute: typeof MainTreeIndexRoute MainTreeTreeNameBranchHashRouteRoute: typeof MainTreeTreeNameBranchHashRouteRouteWithChildren MainTreeTreeNameBranchIndexRoute: typeof MainTreeTreeNameBranchIndexRoute @@ -1167,8 +971,6 @@ interface MainTreeRouteRouteChildren { const MainTreeRouteRouteChildren: MainTreeRouteRouteChildren = { MainTreeTreeIdRouteRoute: MainTreeTreeIdRouteRouteWithChildren, - MainTreeV1RouteRoute: MainTreeV1RouteRouteWithChildren, - MainTreeV2RouteRoute: MainTreeV2RouteRouteWithChildren, MainTreeIndexRoute: MainTreeIndexRoute, MainTreeTreeNameBranchHashRouteRoute: MainTreeTreeNameBranchHashRouteRouteWithChildren, diff --git a/dashboard/src/routes/_main/hardware/index.tsx b/dashboard/src/routes/_main/hardware/index.tsx index ec6a8e2de..486a69110 100644 --- a/dashboard/src/routes/_main/hardware/index.tsx +++ b/dashboard/src/routes/_main/hardware/index.tsx @@ -1,24 +1,11 @@ import { createFileRoute } from '@tanstack/react-router'; -import type { JSX } from 'react'; - -import HardwareV2 from '@/pages/Hardware/HardwareV2'; import Hardware from '@/pages/Hardware/Hardware'; -import { useFeatureFlag } from '@/hooks/useFeatureFlag'; -const HardwareListingComponent = (): JSX.Element => { - const { hardwareListingVersion } = useFeatureFlag(); - return hardwareListingVersion === 'v2' ? ( - - ) : ( +export const Route = createFileRoute('/_main/hardware/')({ + component: () => ( - ); -}; - -export const Route = createFileRoute('/_main/hardware/')({ - component: HardwareListingComponent, + ), }); diff --git a/dashboard/src/routes/_main/hardware/v1/index.tsx b/dashboard/src/routes/_main/hardware/v1/index.tsx deleted file mode 100644 index 0cdff6ebd..000000000 --- a/dashboard/src/routes/_main/hardware/v1/index.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { createFileRoute } from '@tanstack/react-router'; - -import Hardware from '@/pages/Hardware'; - -export const Route = createFileRoute('/_main/hardware/v1/')({ - component: () => ( - - ), -}); diff --git a/dashboard/src/routes/_main/hardware/v1/route.tsx b/dashboard/src/routes/_main/hardware/v1/route.tsx deleted file mode 100644 index 2747280f2..000000000 --- a/dashboard/src/routes/_main/hardware/v1/route.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { createFileRoute, stripSearchParams } from '@tanstack/react-router'; -import { z } from 'zod'; - -import { - makeZIntervalInDays, - zListingSize, - type SearchSchema, -} from '@/types/general'; -import { - DEFAULT_LISTING_ITEMS, - REDUCED_TIME_SEARCH, -} from '@/utils/constants/general'; - -const defaultValues = { - intervalInDays: REDUCED_TIME_SEARCH, - hardwareSearch: '', - listingSize: DEFAULT_LISTING_ITEMS, -}; - -const zHardwareSchema = z.object({ - intervalInDays: makeZIntervalInDays(REDUCED_TIME_SEARCH), - hardwareSearch: z.string().catch(''), - listingSize: zListingSize, -} satisfies SearchSchema); - -export const Route = createFileRoute('/_main/hardware/v1')({ - validateSearch: zHardwareSchema, - search: { middlewares: [stripSearchParams(defaultValues)] }, -}); diff --git a/dashboard/src/routes/_main/hardware/v2/index.tsx b/dashboard/src/routes/_main/hardware/v2/index.tsx deleted file mode 100644 index 893d21862..000000000 --- a/dashboard/src/routes/_main/hardware/v2/index.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { createFileRoute } from '@tanstack/react-router'; - -import HardwareV2 from '@/pages/Hardware/HardwareV2'; - -export const Route = createFileRoute('/_main/hardware/v2/')({ - component: () => ( - - ), -}); diff --git a/dashboard/src/routes/_main/hardware/v2/route.tsx b/dashboard/src/routes/_main/hardware/v2/route.tsx deleted file mode 100644 index 996e9e40d..000000000 --- a/dashboard/src/routes/_main/hardware/v2/route.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { createFileRoute, stripSearchParams } from '@tanstack/react-router'; -import { z } from 'zod'; - -import { - makeZIntervalInDays, - zListingSize, - type SearchSchema, -} from '@/types/general'; -import { - DEFAULT_LISTING_ITEMS, - REDUCED_TIME_SEARCH, -} from '@/utils/constants/general'; - -const defaultValues = { - intervalInDays: REDUCED_TIME_SEARCH, - hardwareSearch: '', - listingSize: DEFAULT_LISTING_ITEMS, -}; - -const zHardwareSchema = z.object({ - intervalInDays: makeZIntervalInDays(REDUCED_TIME_SEARCH), - hardwareSearch: z.string().catch(''), - listingSize: zListingSize, - treeName: z.optional(z.string()), - gitRepositoryUrl: z.optional(z.string()), - gitBranch: z.optional(z.string()), - gitCommitHash: z.optional(z.string()), -} satisfies SearchSchema); - -export const Route = createFileRoute('/_main/hardware/v2')({ - validateSearch: zHardwareSchema, - search: { middlewares: [stripSearchParams(defaultValues)] }, -}); diff --git a/dashboard/src/routes/_main/tree/index.tsx b/dashboard/src/routes/_main/tree/index.tsx index 6df9bab78..fdeff822c 100644 --- a/dashboard/src/routes/_main/tree/index.tsx +++ b/dashboard/src/routes/_main/tree/index.tsx @@ -2,25 +2,14 @@ import { createFileRoute } from '@tanstack/react-router'; import { z } from 'zod'; -import type { JSX } from 'react'; - -import TreeListingV2 from '@/pages/TreeListing/TreesV2'; -import TreeListingV1 from '@/pages/TreeListing/Trees'; -import { useFeatureFlag } from '@/hooks/useFeatureFlag'; +import Trees from '@/pages/TreeListing/Trees'; export const TreeSearchSchema = z.object({ treeSearch: z.string().catch(''), }); -const TreeListingComponent = (): JSX.Element => { - const { treeListingVersion } = useFeatureFlag(); - return treeListingVersion === 'v2' ? ( - - ) : ( - - ); -}; - export const Route = createFileRoute('/_main/tree/')({ - component: TreeListingComponent, + component: () => ( + + ), }); diff --git a/dashboard/src/routes/_main/tree/v1/index.tsx b/dashboard/src/routes/_main/tree/v1/index.tsx deleted file mode 100644 index 5f9792d47..000000000 --- a/dashboard/src/routes/_main/tree/v1/index.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { createFileRoute } from '@tanstack/react-router'; - -import { z } from 'zod'; - -import Trees from '@/pages/TreeListing/Trees'; - -export const TreeSearchSchema = z.object({ - treeSearch: z.string().catch(''), -}); - -export const Route = createFileRoute('/_main/tree/v1/')({ - component: () => ( - - ), -}); diff --git a/dashboard/src/routes/_main/tree/v1/route.tsx b/dashboard/src/routes/_main/tree/v1/route.tsx deleted file mode 100644 index 632820bb0..000000000 --- a/dashboard/src/routes/_main/tree/v1/route.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { createFileRoute, stripSearchParams } from '@tanstack/react-router'; - -import { z } from 'zod'; - -import { - makeZIntervalInDays, - zListingSize, - type SearchSchema, -} from '@/types/general'; -import { - DEFAULT_LISTING_INTERVAL_IN_DAYS, - DEFAULT_LISTING_ITEMS, -} from '@/utils/constants/general'; - -const defaultValues = { - intervalInDays: DEFAULT_LISTING_INTERVAL_IN_DAYS, - treeSearch: '', - listingSize: DEFAULT_LISTING_ITEMS, -}; - -export const RootSearchSchema = z.object({ - intervalInDays: makeZIntervalInDays(DEFAULT_LISTING_INTERVAL_IN_DAYS), - treeSearch: z.string().catch(''), - listingSize: zListingSize, -} satisfies SearchSchema); - -export const Route = createFileRoute('/_main/tree/v1')({ - validateSearch: RootSearchSchema, - search: { middlewares: [stripSearchParams(defaultValues)] }, -}); diff --git a/dashboard/src/routes/_main/tree/v2/index.tsx b/dashboard/src/routes/_main/tree/v2/index.tsx deleted file mode 100644 index a784772d2..000000000 --- a/dashboard/src/routes/_main/tree/v2/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { createFileRoute } from '@tanstack/react-router'; - -import { z } from 'zod'; - -import TreeListingNew from '@/pages/TreeListing/TreesV2'; - -export const TreeSearchSchema = z.object({ - treeSearch: z.string().catch(''), -}); - -export const Route = createFileRoute('/_main/tree/v2/')({ - component: () => ( - - ), -}); diff --git a/dashboard/src/routes/_main/tree/v2/route.tsx b/dashboard/src/routes/_main/tree/v2/route.tsx deleted file mode 100644 index d2e93aabc..000000000 --- a/dashboard/src/routes/_main/tree/v2/route.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { createFileRoute, stripSearchParams } from '@tanstack/react-router'; - -import { z } from 'zod'; - -import { - makeZIntervalInDays, - zListingSize, - type SearchSchema, -} from '@/types/general'; -import { - DEFAULT_LISTING_INTERVAL_IN_DAYS, - DEFAULT_LISTING_ITEMS, -} from '@/utils/constants/general'; - -const defaultValues = { - intervalInDays: DEFAULT_LISTING_INTERVAL_IN_DAYS, - treeSearch: '', - listingSize: DEFAULT_LISTING_ITEMS, -}; - -export const RootSearchSchema = z.object({ - intervalInDays: makeZIntervalInDays(DEFAULT_LISTING_INTERVAL_IN_DAYS), - treeSearch: z.string().catch(''), - listingSize: zListingSize, -} satisfies SearchSchema); - -export const Route = createFileRoute('/_main/tree/v2')({ - validateSearch: RootSearchSchema, - search: { middlewares: [stripSearchParams(defaultValues)] }, -}); diff --git a/dashboard/src/types/general.ts b/dashboard/src/types/general.ts index 081b55be1..3fc5d9cba 100644 --- a/dashboard/src/types/general.ts +++ b/dashboard/src/types/general.ts @@ -395,18 +395,11 @@ export const getTargetFilter = ( export enum RedirectFrom { Tree = 'tree', Hardware = 'hardware', - HardwareV1 = 'hardware/v1', Issues = 'issues', } export type ListingPaths = '/tree' | '/hardware' | '/issues'; -export type PossibleMonitorPath = - | ListingPaths - | '/hardware/v1' - | '/hardware/v2' - | '/tree/v1' - | '/tree/v2' - | '/metrics'; +export type PossibleMonitorPath = ListingPaths | '/metrics'; export type TreeEntityTypes = 'builds' | 'boots' | 'tests'; diff --git a/dashboard/src/types/hardware.ts b/dashboard/src/types/hardware.ts index 13e8cc8d1..6da6b0bd7 100644 --- a/dashboard/src/types/hardware.ts +++ b/dashboard/src/types/hardware.ts @@ -1,22 +1,14 @@ -import type { - RequiredStatusCount, - ShortStatusCount, - StatusCount, -} from './general'; +import type { ShortStatusCount } from './general'; export type HardwareItem = { hardware?: string[]; platform: string; - build_status_summary: RequiredStatusCount; - test_status_summary: StatusCount; - boot_status_summary: StatusCount; + build_status_summary: ShortStatusCount; + test_status_summary: ShortStatusCount; + boot_status_summary: ShortStatusCount; }; -export interface HardwareListingResponse { - hardware: HardwareItem[]; -} - -export type HardwareItemV2 = { +export type HardwareListingApiItem = { hardware?: string[]; platform: string; build_status_summary: ShortStatusCount; @@ -24,8 +16,8 @@ export type HardwareItemV2 = { boot_status_summary: ShortStatusCount; }; -export interface HardwareListingResponseV2 { - hardware: HardwareItemV2[]; +export interface HardwareListingResponse { + hardware: HardwareListingApiItem[]; } export type HardwareSelectorRevision = { diff --git a/dashboard/src/types/tree/Tree.tsx b/dashboard/src/types/tree/Tree.tsx index 3bbfc93d0..16b684731 100644 --- a/dashboard/src/types/tree/Tree.tsx +++ b/dashboard/src/types/tree/Tree.tsx @@ -1,16 +1,4 @@ -import type { RequiredStatusCount, ShortStatusCount } from '@/types/general'; - -export type TreeFastPathResponse = Array<{ - id: string; - tree_name?: string; - git_repository_branch: string; - git_repository_url: string; - git_commit_hash: string; - git_commit_name?: string; - git_commit_tags?: string[]; - patchset_hash?: string; - start_time: string; -}>; +import type { ShortStatusCount } from '@/types/general'; export type TableTestStatus = { done: number; @@ -33,17 +21,7 @@ type BaseTree = { git_commit_tags?: string[]; }; -type AllTabCounts = { - build_status?: RequiredStatusCount; - test_status?: TableTestStatus; - boot_status?: TableTestStatus; -}; - -export type TreeTableBody = BaseTree & AllTabCounts; - -export type Tree = BaseTree & Required; - -export type TreeV2 = BaseTree & { +export type TreeListingItem = BaseTree & { build_status: ShortStatusCount; test_status: ShortStatusCount; boot_status: ShortStatusCount; diff --git a/dashboard/src/utils/constants/hardwareListing.ts b/dashboard/src/utils/constants/hardwareListing.ts index f247a45f2..5c04f805f 100644 --- a/dashboard/src/utils/constants/hardwareListing.ts +++ b/dashboard/src/utils/constants/hardwareListing.ts @@ -4,14 +4,8 @@ type ValidHardwareNavigates = T; type ValidHardwareFroms = T; export type HardwareListingRoutesMap = { - v1: { - navigate: ValidHardwareNavigates<'/hardware' | '/hardware/v1'>; - search: ValidHardwareFroms<'/_main/hardware' | '/_main/hardware/v1'>; - }; - v2: { - navigate: ValidHardwareNavigates<'/hardware' | '/hardware/v2'>; - search: ValidHardwareFroms<'/_main/hardware' | '/_main/hardware/v2'>; - }; + navigate: ValidHardwareNavigates<'/hardware'>; + search: ValidHardwareFroms<'/_main/hardware'>; }; -export const hwListingCleanFullPaths = ['hardware', 'hardwarev1', 'hardwarev2']; +export const hwListingCleanFullPaths = ['hardware']; diff --git a/dashboard/src/utils/constants/treeListing.ts b/dashboard/src/utils/constants/treeListing.ts index 384ef0427..a51992086 100644 --- a/dashboard/src/utils/constants/treeListing.ts +++ b/dashboard/src/utils/constants/treeListing.ts @@ -4,14 +4,8 @@ type ValidTreeNavigates = T; type ValidTreeFroms = T; export type TreeListingRoutesMap = { - v1: { - navigate: ValidTreeNavigates<'/tree' | '/tree/v1'>; - search: ValidTreeFroms<'/_main/tree' | '/_main/tree/v1'>; - }; - v2: { - navigate: ValidTreeNavigates<'/tree' | '/tree/v2'>; - search: ValidTreeFroms<'/_main/tree' | '/_main/tree/v2'>; - }; + navigate: ValidTreeNavigates<'/tree'>; + search: ValidTreeFroms<'/_main/tree'>; }; -export const treeListingCleanFullPaths = ['tree', 'treev1', 'treev2']; +export const treeListingCleanFullPaths = ['tree']; diff --git a/dashboard/src/utils/status.ts b/dashboard/src/utils/status.ts index 7ea8f7542..b5aa5920f 100644 --- a/dashboard/src/utils/status.ts +++ b/dashboard/src/utils/status.ts @@ -1,5 +1,9 @@ import type { Status } from '@/types/database'; -import type { StatusCount, RequiredStatusCount } from '@/types/general'; +import type { + StatusCount, + RequiredStatusCount, + ShortStatusCount, +} from '@/types/general'; import type { AccordionItemBuilds } from '@/types/tree/TreeDetails'; type StatusGroups = 'success' | 'failed' | 'inconclusive'; @@ -78,3 +82,23 @@ export const statusCountToRequiredStatusCount = ( DONE: statusCount.DONE ?? 0, }; }; + +export const statusCountToShortStatusCount = ( + statusCount: StatusCount, +): ShortStatusCount => { + const groupedStatus = groupStatus({ + passCount: statusCount.PASS, + failCount: statusCount.FAIL, + missCount: statusCount.MISS, + skipCount: statusCount.SKIP, + errorCount: statusCount.ERROR, + nullCount: statusCount.NULL, + doneCount: statusCount.DONE, + }); + + return { + PASS: groupedStatus.successCount, + FAIL: groupedStatus.failedCount, + INCONCLUSIVE: groupedStatus.inconclusiveCount, + }; +};