diff --git a/backend/kernelCI_app/typeModels/commonDetails.py b/backend/kernelCI_app/typeModels/commonDetails.py index 981f9e13..e9c4ce3a 100644 --- a/backend/kernelCI_app/typeModels/commonDetails.py +++ b/backend/kernelCI_app/typeModels/commonDetails.py @@ -96,7 +96,7 @@ class TestSummary(BaseModel): failed_platforms: List[str] environment_compatible: Optional[Dict] = None environment_misc: Optional[Dict] = None - platforms: Optional[Dict[str, TestStatusCount]] + platforms: Optional[Dict[str, TestStatusCount]] = None class BuildSummary(BaseModel): diff --git a/backend/kernelCI_app/typeModels/treeDetails.py b/backend/kernelCI_app/typeModels/treeDetails.py index ae9d03c1..8e52987f 100644 --- a/backend/kernelCI_app/typeModels/treeDetails.py +++ b/backend/kernelCI_app/typeModels/treeDetails.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Optional, Set +from typing import Dict, List, Optional from kernelCI_app.typeModels.commonDetails import ( Summary, @@ -22,15 +22,15 @@ class TestSummary(BaseModel): configs: Dict[str, TestStatusCount] issues: List[TestIssuesItem] unknown_issues: int - enviroment_compatible: Dict[str, TestStatusCount] - enviroment_misc: Dict[str, TestStatusCount] + environment_compatible: Dict[str, TestStatusCount] + environment_misc: Dict[str, TestStatusCount] fail_reasons: Dict[str, int] failed_platforms: List[str] class TreeSummary(Summary): - hardware: Set[str] - tree_url: str + hardware: Optional[List[str]] + tree_url: Optional[str] git_commit_tags: Optional[List[str]] diff --git a/backend/kernelCI_app/views/treeDetailsSummaryView.py b/backend/kernelCI_app/views/treeDetailsSummaryView.py index ebabfbd5..282cdec2 100644 --- a/backend/kernelCI_app/views/treeDetailsSummaryView.py +++ b/backend/kernelCI_app/views/treeDetailsSummaryView.py @@ -165,8 +165,8 @@ def get(self, request, commit_hash: str | None): "configs": self.bootConfigs, "issues": self.bootIssues, "unknown_issues": self.failedBootsWithUnknownIssues, - "enviroment_compatible": self.bootEnvironmentCompatible, - "enviroment_misc": self.bootEnvironmentMisc, + "environment_compatible": self.bootEnvironmentCompatible, + "environment_misc": self.bootEnvironmentMisc, "fail_reasons": self.bootFailReasons, "failed_platforms": list(self.bootPlatformsFailing), }, @@ -176,8 +176,8 @@ def get(self, request, commit_hash: str | None): "configs": self.test_configs, "issues": self.testIssues, "unknown_issues": self.failedTestsWithUnknownIssues, - "enviroment_compatible": self.testEnvironmentCompatible, - "enviroment_misc": self.testEnvironmentMisc, + "environment_compatible": self.testEnvironmentCompatible, + "environment_misc": self.testEnvironmentMisc, "fail_reasons": self.testFailReasons, "failed_platforms": list(self.testPlatformsWithErrors), }, diff --git a/backend/kernelCI_app/views/treeDetailsView.py b/backend/kernelCI_app/views/treeDetailsView.py index 2982b53c..fe6a60f0 100644 --- a/backend/kernelCI_app/views/treeDetailsView.py +++ b/backend/kernelCI_app/views/treeDetailsView.py @@ -182,8 +182,8 @@ def get(self, request, commit_hash: str | None): "configs": self.bootConfigs, "issues": self.bootIssues, "unknown_issues": self.failedBootsWithUnknownIssues, - "enviroment_compatible": self.bootEnvironmentCompatible, - "enviroment_misc": self.bootEnvironmentMisc, + "environment_compatible": self.bootEnvironmentCompatible, + "environment_misc": self.bootEnvironmentMisc, "fail_reasons": self.bootFailReasons, "failed_platforms": list(self.bootPlatformsFailing), }, @@ -193,8 +193,8 @@ def get(self, request, commit_hash: str | None): "configs": self.test_configs, "issues": self.testIssues, "unknown_issues": self.failedTestsWithUnknownIssues, - "enviroment_compatible": self.testEnvironmentCompatible, - "enviroment_misc": self.testEnvironmentMisc, + "environment_compatible": self.testEnvironmentCompatible, + "environment_misc": self.testEnvironmentMisc, "fail_reasons": self.testFailReasons, "failed_platforms": list(self.testPlatformsWithErrors), }, diff --git a/backend/schema.yml b/backend/schema.yml index 78f7052f..4b993a37 100644 --- a/backend/schema.yml +++ b/backend/schema.yml @@ -368,7 +368,7 @@ components: properties: bootHistory: items: - $ref: '#/components/schemas/TestHistory' + $ref: '#/components/schemas/TestHistoryItem' title: Boothistory type: array required: @@ -415,7 +415,7 @@ components: - 'null' title: BuildConfigs type: object - BuildItem: + BuildHistoryItem: properties: id: title: Id @@ -491,7 +491,7 @@ components: - start_time - git_repository_url - git_repository_branch - title: BuildItem + title: BuildHistoryItem type: object BuildStatusCount: properties: @@ -568,7 +568,7 @@ components: properties: builds: items: - $ref: '#/components/schemas/BuildItem' + $ref: '#/components/schemas/BuildHistoryItem' title: Builds type: array required: @@ -578,34 +578,57 @@ components: HardwareDetailsFullResponse: properties: builds: + items: + $ref: '#/components/schemas/BuildHistoryItem' title: Builds - type: object + type: array + boots: + items: + $ref: '#/components/schemas/TestHistoryItem' + title: Boots + type: array tests: + items: + $ref: '#/components/schemas/TestHistoryItem' title: Tests - type: object + type: array + summary: + $ref: '#/components/schemas/HardwareSummary' + required: + - builds + - boots + - tests + - summary + title: HardwareDetailsFullResponse + type: object + HardwareSummary: + properties: + builds: + $ref: '#/components/schemas/BuildSummary' boots: - title: Boots - type: object + $ref: '#/components/schemas/TestSummary' + tests: + $ref: '#/components/schemas/TestSummary' + trees: + items: + $ref: '#/components/schemas/Tree' + title: Trees + type: array configs: items: type: string title: Configs type: array - archs: + architectures: items: type: string - title: Archs + title: Architectures type: array compilers: items: type: string title: Compilers type: array - trees: - items: - type: object - title: Trees - type: array compatibles: items: type: string @@ -613,14 +636,14 @@ components: type: array required: - builds - - tests - boots + - tests + - trees - configs - - archs + - architectures - compilers - - trees - compatibles - title: HardwareDetailsFullResponse + title: HardwareSummary type: object IncidentsInfo: properties: @@ -631,43 +654,19 @@ components: - incidentsCount title: IncidentsInfo type: object - Summary: + Misc: properties: - builds: - $ref: '#/components/schemas/BuildSummary' - boots: - $ref: '#/components/schemas/TestSummary' - tests: - $ref: '#/components/schemas/TestSummary' - hardware: - items: - type: string - title: Hardware - type: array - uniqueItems: true - tree_url: - title: Tree Url + platform: + title: Platform type: string - git_commit_tags: - anyOf: - - items: - type: string - type: array - - type: 'null' - title: Git Commit Tags required: - - builds - - boots - - tests - - hardware - - tree_url - - git_commit_tags - title: Summary + - platform + title: Misc type: object SummaryResponse: properties: summary: - $ref: '#/components/schemas/Summary' + $ref: '#/components/schemas/TreeSummary' required: - summary title: SummaryResponse @@ -688,75 +687,7 @@ components: - status title: TestArchSummaryItem type: object - TestEnvironmentCompatibleCount: - properties: - PASS: - anyOf: - - type: integer - - type: 'null' - default: null - title: Pass - ERROR: - anyOf: - - type: integer - - type: 'null' - default: null - title: Error - FAIL: - anyOf: - - type: integer - - type: 'null' - default: null - title: Fail - SKIP: - anyOf: - - type: integer - - type: 'null' - default: null - title: Skip - 'NULL': - anyOf: - - type: integer - - type: 'null' - default: null - title: 'Null' - title: TestEnvironmentCompatibleCount - type: object - TestEnvironmentMiscCount: - properties: - PASS: - anyOf: - - type: integer - - type: 'null' - default: null - title: Pass - ERROR: - anyOf: - - type: integer - - type: 'null' - default: null - title: Error - FAIL: - anyOf: - - type: integer - - type: 'null' - default: null - title: Fail - SKIP: - anyOf: - - type: integer - - type: 'null' - default: null - title: Skip - 'NULL': - anyOf: - - type: integer - - type: 'null' - default: null - title: 'Null' - title: TestEnvironmentMiscCount - type: object - TestHistory: + TestHistoryItem: properties: id: title: Id @@ -784,22 +715,51 @@ components: - type: string - type: 'null' title: Start Time - hardware: + environment_compatible: anyOf: - type: string - items: type: string type: array - type: 'null' - title: Hardware + default: null + title: Environment Compatible + config: + anyOf: + - type: string + - type: 'null' + default: null + title: Config + log_url: + anyOf: + - type: string + - type: 'null' + default: null + title: Log Url + architecture: + anyOf: + - type: string + - type: 'null' + default: null + title: Architecture + compiler: + anyOf: + - type: string + - type: 'null' + default: null + title: Compiler + misc: + anyOf: + - $ref: '#/components/schemas/Misc' + - type: 'null' + default: null required: - id - status - duration - path - start_time - - hardware - title: TestHistory + title: TestHistoryItem type: object TestIssuesItem: properties: @@ -829,7 +789,7 @@ components: properties: testHistory: items: - $ref: '#/components/schemas/TestHistory' + $ref: '#/components/schemas/TestHistoryItem' title: Testhistory type: array required: @@ -892,16 +852,6 @@ components: unknown_issues: title: Unknown Issues type: integer - enviroment_compatible: - additionalProperties: - $ref: '#/components/schemas/TestEnvironmentCompatibleCount' - title: Enviroment Compatible - type: object - enviroment_misc: - additionalProperties: - $ref: '#/components/schemas/TestEnvironmentMiscCount' - title: Enviroment Misc - type: object fail_reasons: additionalProperties: type: integer @@ -912,64 +862,125 @@ components: type: string title: Failed Platforms type: array + environment_compatible: + anyOf: + - type: object + - type: 'null' + default: null + title: Environment Compatible + environment_misc: + anyOf: + - type: object + - type: 'null' + default: null + title: Environment Misc + platforms: + anyOf: + - additionalProperties: + $ref: '#/components/schemas/TestStatusCount' + type: object + - type: 'null' + default: null + title: Platforms required: - status - architectures - configs - issues - unknown_issues - - enviroment_compatible - - enviroment_misc - fail_reasons - failed_platforms title: TestSummary type: object Tree: - type: object properties: - build_status: - type: object - additionalProperties: {} - readOnly: true - test_status: - type: object - additionalProperties: {} - readOnly: true - boot_status: - type: object - additionalProperties: {} - readOnly: true - git_commit_hash: + index: + title: Index type: string - git_commit_name: - type: string - git_commit_tags: - type: array - items: {} - patchset_hash: - type: string - tree_names: - type: array - items: {} + tree_name: + anyOf: + - type: string + - type: 'null' + title: Tree Name git_repository_branch: - type: string + anyOf: + - type: string + - type: 'null' + title: Git Repository Branch git_repository_url: - type: string - start_time: - type: string - format: date-time + anyOf: + - type: string + - type: 'null' + title: Git Repository Url + head_git_commit_name: + anyOf: + - type: string + - type: 'null' + title: Head Git Commit Name + head_git_commit_hash: + anyOf: + - type: string + - type: 'null' + title: Head Git Commit Hash + head_git_commit_tag: + anyOf: + - items: + type: string + type: array + - type: 'null' + title: Head Git Commit Tag + selected_commit_status: + anyOf: + - type: object + - type: 'null' + title: Selected Commit Status required: - - boot_status - - build_status - - git_commit_hash - - git_commit_name - - git_commit_tags + - index + - tree_name - git_repository_branch - git_repository_url - - patchset_hash - - start_time - - test_status - - tree_names + - head_git_commit_name + - head_git_commit_hash + - head_git_commit_tag + - selected_commit_status + title: Tree + type: object + TreeSummary: + properties: + builds: + $ref: '#/components/schemas/BuildSummary' + boots: + $ref: '#/components/schemas/TestSummary' + tests: + $ref: '#/components/schemas/TestSummary' + hardware: + anyOf: + - items: + type: string + type: array + - type: 'null' + title: Hardware + tree_url: + anyOf: + - type: string + - type: 'null' + title: Tree Url + git_commit_tags: + anyOf: + - items: + type: string + type: array + - type: 'null' + title: Git Commit Tags + required: + - builds + - boots + - tests + - hardware + - tree_url + - git_commit_tags + title: TreeSummary + type: object securitySchemes: basicAuth: type: http diff --git a/dashboard/src/api/treeDetails.ts b/dashboard/src/api/treeDetails.ts index 127803f1..01a3242b 100644 --- a/dashboard/src/api/treeDetails.ts +++ b/dashboard/src/api/treeDetails.ts @@ -8,6 +8,10 @@ import type { BuildCountsResponse, TTreeTestsFullData, LogFilesResponse, + TreeDetailsSummary, + TreeDetailsBoots, + TreeDetailsBuilds, + TreeDetailsTests, } from '@/types/tree/TreeDetails'; import { getTargetFilter, type TFilter } from '@/types/general'; @@ -43,11 +47,27 @@ function assertTreeSearchParameters( } } -const fetchTreeDetails = async ( - treeId: string, - treeSearchParameters: TreeSearchParameters, - filter: TTreeDetailsFilter = {}, -): Promise => { +type TreeDetailsVariants = 'full' | 'builds' | 'boots' | 'tests' | 'summary'; + +type TreeDetailsResponseTable = { + full: TTreeTestsFullData; + summary: TreeDetailsSummary; + builds: TreeDetailsBuilds; + boots: TreeDetailsBoots; + tests: TreeDetailsTests; +}; + +const fetchTreeDetails = async ({ + treeId, + treeSearchParameters, + filter = {}, + variant, +}: { + treeId: string; + treeSearchParameters: TreeSearchParameters; + filter: TTreeDetailsFilter; + variant: TreeDetailsVariants; +}): Promise => { assertTreeSearchParameters( treeSearchParameters, 'fetchTreeDetails - useSearchTab', @@ -62,22 +82,39 @@ const fetchTreeDetails = async ( ...backendCompatibleFilters, }; - const res = await http.get(`/api/tree/${treeId}/full`, { + const urlTable: Record = { + full: `/api/tree/${treeId}/full`, + builds: `/api/tree/${treeId}/builds`, + boots: `/api/tree/${treeId}/boots`, + tests: `/api/tree/${treeId}/tests`, + summary: `/api/tree/${treeId}/summary`, + }; + const res = await http.get(urlTable[variant], { params: params, }); return res.data; }; -export const useTreeDetails = ({ - treeId, - filter = {}, - enabled = true, -}: { +type TreeDetailsResponse = + TreeDetailsResponseTable[T]; + +export type UseTreeDetailsWithoutVariant = { treeId: string; filter?: TFilter; enabled?: boolean; -}): UseQueryResult => { +}; + +type UseTreeDetailsParameters = { + variant: T; +} & UseTreeDetailsWithoutVariant; + +export const useTreeDetails = ({ + treeId, + filter = {}, + enabled = true, + variant, +}: UseTreeDetailsParameters): UseQueryResult> => { const testFilter = getTargetFilter(filter, 'test'); const treeDetailsFilter = getTargetFilter(filter, 'treeDetails'); const treeSearchParameters = useTreeSearchParameters(); @@ -89,11 +126,17 @@ export const useTreeDetails = ({ treeSearchParameters, testFilter, treeDetailsFilter, + variant, ], queryFn: () => - fetchTreeDetails(treeId, treeSearchParameters, { - ...testFilter, - ...treeDetailsFilter, + fetchTreeDetails({ + treeId, + treeSearchParameters, + variant, + filter: { + ...testFilter, + ...treeDetailsFilter, + }, }), enabled, placeholderData: previousData => previousData, diff --git a/dashboard/src/components/Tabs/Builds/ConfigsCard.tsx b/dashboard/src/components/Tabs/Builds/ConfigsCard.tsx index 0439fa32..a3f1a02d 100644 --- a/dashboard/src/components/Tabs/Builds/ConfigsCard.tsx +++ b/dashboard/src/components/Tabs/Builds/ConfigsCard.tsx @@ -2,8 +2,6 @@ import { memo, useMemo } from 'react'; import { FormattedMessage } from 'react-intl'; -import type { ITreeDetails } from '@/pages/TreeDetails/TreeDetails'; - import { DumbListingContent } from '@/components/ListingContent/ListingContent'; import ListingItem from '@/components/ListingItem/ListingItem'; import { BuildStatus } from '@/components/Status/Status'; @@ -11,9 +9,10 @@ import BaseCard from '@/components/Cards/BaseCard'; import FilterLink from '@/components/Tabs/FilterLink'; import type { TFilter, TFilterObjectsKeys } from '@/types/general'; +import type { IBuildsTab } from '@/pages/TreeDetails/Tabs/Build/BuildTab'; interface IConfigsCard { - configs: ITreeDetails['configs']; + configs: IBuildsTab['configs']; toggleFilterBySection: ( value: string, filterSection: TFilterObjectsKeys, diff --git a/dashboard/src/components/Tabs/Builds/StatusCard.tsx b/dashboard/src/components/Tabs/Builds/StatusCard.tsx index 4104914c..ebd1d6f4 100644 --- a/dashboard/src/components/Tabs/Builds/StatusCard.tsx +++ b/dashboard/src/components/Tabs/Builds/StatusCard.tsx @@ -2,16 +2,15 @@ import { memo, useMemo } from 'react'; import { useIntl } from 'react-intl'; -import type { ITreeDetails } from '@/pages/TreeDetails/TreeDetails'; - import BaseCard from '@/components/Cards/BaseCard'; import StatusChartMemoized, { Colors, } from '@/components/StatusChart/StatusCharts'; import type { TFilterObjectsKeys } from '@/types/general'; +import type { IBuildsTab } from '@/pages/TreeDetails/Tabs/Build/BuildTab'; interface IStatusCard { - buildsSummary?: ITreeDetails['buildsSummary']; + buildsSummary?: IBuildsTab['buildsSummary']; toggleFilterBySection: ( value: string, filterSection: TFilterObjectsKeys, diff --git a/dashboard/src/hooks/useTreeDetailsLazyLoadQuery.ts b/dashboard/src/hooks/useTreeDetailsLazyLoadQuery.ts new file mode 100644 index 00000000..241136d4 --- /dev/null +++ b/dashboard/src/hooks/useTreeDetailsLazyLoadQuery.ts @@ -0,0 +1,138 @@ +import { useSearch } from '@tanstack/react-router'; + +import { useEffect, useState } from 'react'; + +import type { UseQueryResult } from '@tanstack/react-query'; + +import type { UseTreeDetailsWithoutVariant } from '@/api/treeDetails'; +import { useTreeDetails } from '@/api/treeDetails'; +import type { + PossibleTabs, + TreeDetailsBoots, + TreeDetailsBuilds, + TreeDetailsSummary, + TreeDetailsTests, +} from '@/types/tree/TreeDetails'; +import type { QuerySelectorStatus } from '@/components/QuerySwitcher/QuerySwitcher'; + +const canWeFetchAll = ( + isSummaryRead: boolean, + currentPageTab: PossibleTabs, + statusTable: Record, +): boolean => { + if (!isSummaryRead) { + return false; + } + + return statusTable[currentPageTab] ?? false; +}; + +export type TreeDetailsLazyLoaded = { + summary: { + data?: TreeDetailsSummary; + isLoading: boolean; + status: QuerySelectorStatus; + error: UseQueryResult['error']; + isPlaceholderData: boolean; + }; + builds: { + data?: TreeDetailsBuilds; + isLoading: boolean; + status: QuerySelectorStatus; + }; + boots: { + data?: TreeDetailsBoots; + isLoading: boolean; + status: QuerySelectorStatus; + }; + tests: { + data?: TreeDetailsTests; + isLoading: boolean; + status: QuerySelectorStatus; + }; +}; + +export const useTreeDetailsLazyLoadQuery = ( + useTreeDetailsArgs: UseTreeDetailsWithoutVariant, +): TreeDetailsLazyLoaded => { + const [fetchAll, setFetchAll] = useState(false); + + const summaryResult = useTreeDetails({ + ...useTreeDetailsArgs, + variant: 'summary', + }); + + const { currentPageTab } = useSearch({ + from: '/tree/$treeId/', + }); + + const buildsResult = useTreeDetails({ + ...useTreeDetailsArgs, + variant: 'builds', + enabled: + (!!summaryResult.data && currentPageTab === 'global.builds') || fetchAll, + }); + + const bootsResult = useTreeDetails({ + ...useTreeDetailsArgs, + variant: 'boots', + enabled: + (!!summaryResult.data && currentPageTab === 'global.boots') || fetchAll, + }); + + const testsResult = useTreeDetails({ + ...useTreeDetailsArgs, + variant: 'tests', + enabled: + (!!summaryResult.data && currentPageTab === 'global.tests') || fetchAll, + }); + + useEffect(() => { + if ( + canWeFetchAll(!!summaryResult.data, currentPageTab, { + 'global.builds': !!buildsResult.data, + 'global.boots': !!bootsResult.data, + 'global.tests': !!testsResult.data, + }) + ) { + setFetchAll(true); + } + }, [ + bootsResult.data, + bootsResult.isLoading, + buildsResult.data, + buildsResult.isLoading, + currentPageTab, + fetchAll, + setFetchAll, + summaryResult.data, + summaryResult.isLoading, + testsResult.data, + testsResult.isLoading, + ]); + + return { + summary: { + data: summaryResult.data, + isLoading: summaryResult.isLoading, + status: summaryResult.status, + isPlaceholderData: summaryResult.isPlaceholderData, + error: summaryResult.error, + }, + builds: { + data: buildsResult.data, + isLoading: buildsResult.isLoading, + status: buildsResult.status, + }, + boots: { + data: bootsResult.data, + isLoading: bootsResult.isLoading, + status: bootsResult.status, + }, + tests: { + data: testsResult.data, + isLoading: testsResult.isLoading, + status: testsResult.status, + }, + }; +}; diff --git a/dashboard/src/pages/TreeDetails/Tabs/Boots/BootsTab.tsx b/dashboard/src/pages/TreeDetails/Tabs/Boots/BootsTab.tsx index f0aefbf2..aa9a35f0 100644 --- a/dashboard/src/pages/TreeDetails/Tabs/Boots/BootsTab.tsx +++ b/dashboard/src/pages/TreeDetails/Tabs/Boots/BootsTab.tsx @@ -5,7 +5,6 @@ import { useNavigate, useParams, useSearch } from '@tanstack/react-router'; import { useCallback, useMemo } from 'react'; -import { useTreeDetails } from '@/api/treeDetails'; import BaseCard from '@/components/Cards/BaseCard'; import { Skeleton } from '@/components/Skeleton'; @@ -27,15 +26,17 @@ import MemoizedConfigList from '@/components/Tabs/Tests/ConfigsList'; import MemoizedErrorsSummary from '@/components/Tabs/Tests/ErrorsSummary'; import MemoizedStatusCard from '@/components/Tabs/Tests/StatusCard'; -import { RedirectFrom, type TFilter } from '@/types/general'; +import { RedirectFrom } from '@/types/general'; import TreeCommitNavigationGraph from '@/pages/TreeDetails/Tabs/TreeCommitNavigationGraph'; +import type { TreeDetailsLazyLoaded } from '@/hooks/useTreeDetailsLazyLoadQuery'; +import QuerySwitcher from '@/components/QuerySwitcher/QuerySwitcher'; interface BootsTabProps { - reqFilter: TFilter; + treeDetailsLazyLoaded: TreeDetailsLazyLoaded; } -const BootsTab = ({ reqFilter }: BootsTabProps): JSX.Element => { +const BootsTab = ({ treeDetailsLazyLoaded }: BootsTabProps): JSX.Element => { const { treeId } = useParams({ from: '/tree/$treeId', }); @@ -80,10 +81,8 @@ const BootsTab = ({ reqFilter }: BootsTabProps): JSX.Element => { [navigate], ); - const { isLoading, data, error } = useTreeDetails({ - treeId: treeId ?? '', - filter: reqFilter, - }); + const { isLoading, data, error } = treeDetailsLazyLoaded.summary; + const { data: bootsData, status: bootsStatus } = treeDetailsLazyLoaded.boots; const getRowLink = useCallback( (bootId: string): LinkProps => ({ @@ -99,12 +98,12 @@ const BootsTab = ({ reqFilter }: BootsTabProps): JSX.Element => { const hardwareData = useMemo(() => { return { - ...data?.summary.boots.enviroment_compatible, - ...data?.summary.boots.enviroment_misc, + ...data?.summary.boots.environment_compatible, + ...data?.summary.boots.environment_misc, }; }, [ - data?.summary.boots.enviroment_compatible, - data?.summary.boots.enviroment_misc, + data?.summary.boots.environment_compatible, + data?.summary.boots.environment_misc, ]); if (error || !treeId) { @@ -124,7 +123,7 @@ const BootsTab = ({ reqFilter }: BootsTabProps): JSX.Element => { if (!data) return
; - if (data.boots.length < 1) { + if (bootsData?.boots?.length === 0) { return ( } @@ -211,15 +210,17 @@ const BootsTab = ({ reqFilter }: BootsTabProps): JSX.Element => {
- + + + ); }; diff --git a/dashboard/src/pages/TreeDetails/Tabs/Build/BuildTab.tsx b/dashboard/src/pages/TreeDetails/Tabs/Build/BuildTab.tsx index 06f51154..b130abe2 100644 --- a/dashboard/src/pages/TreeDetails/Tabs/Build/BuildTab.tsx +++ b/dashboard/src/pages/TreeDetails/Tabs/Build/BuildTab.tsx @@ -1,11 +1,9 @@ import { FormattedMessage } from 'react-intl'; -import { useCallback } from 'react'; +import { useCallback, useMemo } from 'react'; import { useNavigate, useParams, useSearch } from '@tanstack/react-router'; -import type { ITreeDetails } from '@/pages/TreeDetails/TreeDetails'; - import TreeCommitNavigationGraph from '@/pages/TreeDetails/Tabs/TreeCommitNavigationGraph'; import MemoizedIssuesList from '@/components/Cards/IssuesList'; @@ -22,15 +20,45 @@ import { MobileGrid, } from '@/components/Tabs/TabGrid'; -import { RedirectFrom, type TFilterObjectsKeys } from '@/types/general'; +import type { TreeDetailsLazyLoaded } from '@/hooks/useTreeDetailsLazyLoadQuery'; + +import { + sanitizeArchs, + sanitizeConfigs, + sanitizeBuildsSummary, + sanitizeBuilds, +} from '@/utils/utils'; + +import QuerySwitcher from '@/components/QuerySwitcher/QuerySwitcher'; + +import type { ISummaryItem } from '@/components/Tabs/Summary'; +import type { IListingItem } from '@/components/ListingItem/ListingItem'; + +import { + RedirectFrom, + type BuildStatus, + type TFilterObjectsKeys, + type TIssue, +} from '@/types/general'; + +import type { AccordionItemBuilds } from '@/types/tree/TreeDetails'; import { TreeDetailsBuildsTable } from './TreeDetailsBuildsTable'; interface BuildTab { - treeDetailsData: ITreeDetails; + treeDetailsLazyLoaded: TreeDetailsLazyLoaded; +} + +export interface IBuildsTab { + architectures: ISummaryItem[]; + configs: IListingItem[]; + buildsSummary: BuildStatus; + buildsIssues: TIssue[]; + failedBuildsWithUnknownIssues?: number; + builds: AccordionItemBuilds[]; } -const BuildTab = ({ treeDetailsData }: BuildTab): JSX.Element => { +const BuildTab = ({ treeDetailsLazyLoaded }: BuildTab): JSX.Element => { const navigate = useNavigate({ from: '/tree/$treeId', }); @@ -41,6 +69,11 @@ const BuildTab = ({ treeDetailsData }: BuildTab): JSX.Element => { const { treeId } = useParams({ from: '/tree/$treeId' }); + const { builds: buildsQuery, summary: summaryQuery } = treeDetailsLazyLoaded; + + const summaryData = summaryQuery.data?.summary.builds; + const { data: buildsData, status: buildsStatus } = buildsQuery; + const toggleFilterBySection = useCallback( (filterSectionKey: string, filterSection: TFilterObjectsKeys): void => { navigate({ @@ -66,6 +99,24 @@ const BuildTab = ({ treeDetailsData }: BuildTab): JSX.Element => { [navigate], ); + const treeDetailsData: IBuildsTab = useMemo( + () => ({ + architectures: sanitizeArchs(summaryData?.architectures), + configs: sanitizeConfigs(summaryData?.configs), + buildsSummary: sanitizeBuildsSummary(summaryData?.status), + buildsIssues: summaryData?.issues || [], + failedBuildsWithUnknownIssues: summaryData?.unknown_issues, + builds: sanitizeBuilds(buildsData?.builds), + }), + [ + buildsData?.builds, + summaryData?.architectures, + summaryData?.configs, + summaryData?.issues, + summaryData?.status, + summaryData?.unknown_issues, + ], + ); return (
@@ -131,15 +182,14 @@ const BuildTab = ({ treeDetailsData }: BuildTab): JSX.Element => { /> - {treeDetailsData && ( +
-
- )} +
); }; diff --git a/dashboard/src/pages/TreeDetails/Tabs/Tests/TestsTab.tsx b/dashboard/src/pages/TreeDetails/Tabs/Tests/TestsTab.tsx index 4801ffc3..72bf35f7 100644 --- a/dashboard/src/pages/TreeDetails/Tabs/Tests/TestsTab.tsx +++ b/dashboard/src/pages/TreeDetails/Tabs/Tests/TestsTab.tsx @@ -7,7 +7,6 @@ import { useCallback, useMemo } from 'react'; import { Skeleton } from '@/components/Skeleton'; -import { useTreeDetails } from '@/api/treeDetails'; import BaseCard from '@/components/Cards/BaseCard'; import { @@ -29,20 +28,23 @@ import MemoizedConfigList from '@/components/Tabs/Tests/ConfigsList'; import MemoizedErrorsSummary from '@/components/Tabs/Tests/ErrorsSummary'; import MemoizedStatusCard from '@/components/Tabs/Tests/StatusCard'; -import { RedirectFrom, type TFilter } from '@/types/general'; +import { RedirectFrom } from '@/types/general'; import TreeCommitNavigationGraph from '@/pages/TreeDetails/Tabs/TreeCommitNavigationGraph'; +import type { TreeDetailsLazyLoaded } from '@/hooks/useTreeDetailsLazyLoadQuery'; +import QuerySwitcher from '@/components/QuerySwitcher/QuerySwitcher'; interface TestsTabProps { - reqFilter: TFilter; + treeDetailsLazyLoaded: TreeDetailsLazyLoaded; } -const TestsTab = ({ reqFilter }: TestsTabProps): JSX.Element => { +const TestsTab = ({ treeDetailsLazyLoaded }: TestsTabProps): JSX.Element => { const { treeId } = useParams({ from: '/tree/$treeId' }); - const { isLoading, data, error } = useTreeDetails({ - treeId: treeId ?? '', - filter: reqFilter, - }); + + const { tests: testsQuery, summary: summaryQuery } = treeDetailsLazyLoaded; + const { data, status, isLoading: testsIsLoading } = testsQuery; + const { isLoading: isSummaryLoading, error: summaryError } = summaryQuery; + const summaryData = treeDetailsLazyLoaded.summary.data?.summary.tests; const { tableFilter, diffFilter } = useSearch({ from: '/tree/$treeId', @@ -101,15 +103,12 @@ const TestsTab = ({ reqFilter }: TestsTabProps): JSX.Element => { const hardwareData = useMemo(() => { return { - ...data?.summary.tests.enviroment_compatible, - ...data?.summary.tests.enviroment_misc, + ...summaryData?.environment_compatible, + ...summaryData?.environment_misc, }; - }, [ - data?.summary.tests.enviroment_compatible, - data?.summary.tests.enviroment_misc, - ]); + }, [summaryData?.environment_compatible, summaryData?.environment_misc]); - if (error || !treeId) { + if (summaryError || !treeId) { return (
@@ -117,16 +116,16 @@ const TestsTab = ({ reqFilter }: TestsTabProps): JSX.Element => { ); } - if (isLoading) + if (isSummaryLoading) return ( ); - if (!data) return
; + if (!summaryData) return
; - if (data.tests.length < 1) { + if (!testsIsLoading && data?.tests.length === 0) { return ( } @@ -145,22 +144,22 @@ const TestsTab = ({ reqFilter }: TestsTabProps): JSX.Element => {
} - statusCounts={data.summary.tests.status} + statusCounts={summaryData.status} /> } - configStatusCounts={data.summary.tests.configs} + configStatusCounts={summaryData.configs} diffFilter={diffFilter} /> } - archCompilerErrors={data.summary.tests.architectures} + archCompilerErrors={summaryData.architectures} diffFilter={diffFilter} /> } - issues={data.summary.tests.issues} - failedWithUnknownIssues={data.summary.tests.unknown_issues} + issues={summaryData.issues} + failedWithUnknownIssues={summaryData.unknown_issues} diffFilter={diffFilter} issueFilterSection="testIssue" detailsId={treeId} @@ -179,25 +178,25 @@ const TestsTab = ({ reqFilter }: TestsTabProps): JSX.Element => { } - statusCounts={data.summary.tests.status} + statusCounts={summaryData.status} />
} - configStatusCounts={data.summary.tests.configs} + configStatusCounts={summaryData.configs} diffFilter={diffFilter} /> } - archCompilerErrors={data.summary.tests.architectures} + archCompilerErrors={summaryData.architectures} diffFilter={diffFilter} /> } - issues={data.summary.tests.issues} - failedWithUnknownIssues={data.summary.tests.unknown_issues} + issues={summaryData.issues} + failedWithUnknownIssues={summaryData.unknown_issues} diffFilter={diffFilter} issueFilterSection="testIssue" detailsId={treeId} @@ -214,15 +213,17 @@ const TestsTab = ({ reqFilter }: TestsTabProps): JSX.Element => { - + + +
); }; diff --git a/dashboard/src/pages/TreeDetails/Tabs/TreeDetailsTab.tsx b/dashboard/src/pages/TreeDetails/Tabs/TreeDetailsTab.tsx index 0dc0efc6..bd05ed75 100644 --- a/dashboard/src/pages/TreeDetails/Tabs/TreeDetailsTab.tsx +++ b/dashboard/src/pages/TreeDetails/Tabs/TreeDetailsTab.tsx @@ -8,9 +8,7 @@ import Tabs from '@/components/Tabs/Tabs'; import { zPossibleTabValidator } from '@/types/tree/TreeDetails'; -import type { ITreeDetails } from '@/pages/TreeDetails/TreeDetails'; - -import type { TFilter } from '@/types/general'; +import type { TreeDetailsLazyLoaded } from '@/hooks/useTreeDetailsLazyLoadQuery'; import BuildTab from './Build'; import BootsTab from './Boots'; @@ -22,17 +20,15 @@ export type TreeDetailsTabRightElement = Record< >; export interface ITreeDetailsTab { - treeDetailsData: ITreeDetails; + treeDetailsLazyLoaded: TreeDetailsLazyLoaded; filterListElement?: JSX.Element; - reqFilter: TFilter; countElements: TreeDetailsTabRightElement; } const TreeDetailsTab = ({ - treeDetailsData, filterListElement, - reqFilter, countElements, + treeDetailsLazyLoaded, }: ITreeDetailsTab): JSX.Element => { const { currentPageTab } = useSearch({ from: '/tree/$treeId', @@ -42,24 +38,24 @@ const TreeDetailsTab = ({ () => [ { name: 'global.builds', - content: , + content: , disabled: false, rightElement: countElements['global.builds'], }, { name: 'global.boots', - content: , + content: , disabled: false, rightElement: countElements['global.boots'], }, { name: 'global.tests', - content: , + content: , disabled: false, rightElement: countElements['global.tests'], }, ], - [countElements, reqFilter, treeDetailsData], + [countElements, treeDetailsLazyLoaded], ); const onValueChange: (value: string) => void = useCallback( diff --git a/dashboard/src/pages/TreeDetails/TreeDetails.tsx b/dashboard/src/pages/TreeDetails/TreeDetails.tsx index a9ef229b..4cece500 100644 --- a/dashboard/src/pages/TreeDetails/TreeDetails.tsx +++ b/dashboard/src/pages/TreeDetails/TreeDetails.tsx @@ -3,8 +3,6 @@ import { useCallback, useMemo } from 'react'; import { FormattedMessage } from 'react-intl'; -import type { IListingItem } from '@/components/ListingItem/ListingItem'; -import type { AccordionItemBuilds } from '@/types/tree/TreeDetails'; import { Breadcrumb, BreadcrumbItem, @@ -31,14 +29,7 @@ import { GroupedTestStatus, } from '@/components/Status/Status'; -import { - sanitizeArchs, - sanitizeBuilds, - sanitizeBuildsSummary, - sanitizeConfigs, -} from '@/utils/utils'; - -import type { BuildStatus, TFilter, TIssue } from '@/types/general'; +import type { TFilter } from '@/types/general'; import MemoizedHardwareUsed from '@/components/Cards/HardwareUsed'; @@ -46,10 +37,6 @@ import { mapFilterToReq } from '@/components/Tabs/Filters'; import DetailsFilterList from '@/components/Tabs/FilterList'; -import type { ISummaryItem } from '@/components/Tabs/Summary'; - -import { useTreeDetails } from '@/api/treeDetails'; - import { truncateUrl } from '@/lib/string'; import CopyButton from '@/components/Button/CopyButton'; @@ -57,19 +44,12 @@ import { MemoizedSectionError } from '@/components/DetailsPages/SectionError'; import { CommitTagTooltip } from '@/components/Tooltip/CommitTagTooltip'; +import { useTreeDetailsLazyLoadQuery } from '@/hooks/useTreeDetailsLazyLoadQuery'; + import TreeDetailsFilter from './TreeDetailsFilter'; import type { TreeDetailsTabRightElement } from './Tabs/TreeDetailsTab'; import TreeDetailsTab from './Tabs/TreeDetailsTab'; -export interface ITreeDetails { - architectures: ISummaryItem[]; - configs: IListingItem[]; - buildsSummary: BuildStatus; - builds: AccordionItemBuilds[]; - buildsIssues: TIssue[]; - failedBuildsWithUnknownIssues?: number; -} - interface ITreeHeader { treeNames?: string; gitUrl?: string; @@ -145,11 +125,18 @@ function TreeDetails(): JSX.Element { const reqFilter = mapFilterToReq(diffFilter); - const { isLoading, data, status, isPlaceholderData, error } = useTreeDetails({ + const treeDetailsLazyLoaded = useTreeDetailsLazyLoadQuery({ treeId: treeId ?? '', filter: reqFilter, }); + const { + data, + isLoading, + error, + status: summaryQueryStatus, + } = treeDetailsLazyLoaded.summary; + const onFilterChange = useCallback( (newFilter: TFilter) => { navigate({ @@ -181,10 +168,15 @@ function TreeDetails(): JSX.Element { filter={diffFilter} cleanFilters={cleanAll} navigate={onFilterChange} - isLoading={isPlaceholderData} + isLoading={treeDetailsLazyLoaded.summary.isPlaceholderData} /> ), - [cleanAll, diffFilter, isPlaceholderData, onFilterChange], + [ + cleanAll, + diffFilter, + onFilterChange, + treeDetailsLazyLoaded.summary.isPlaceholderData, + ], ); const tabsCounts: TreeDetailsTabRightElement = useMemo(() => { @@ -224,21 +216,9 @@ function TreeDetails(): JSX.Element { }; }, [data]); - const treeDetailsData: ITreeDetails = useMemo( - () => ({ - architectures: sanitizeArchs(data?.summary.builds.architectures), - configs: sanitizeConfigs(data?.summary.builds.configs), - builds: sanitizeBuilds(data?.builds), - buildsSummary: sanitizeBuildsSummary(data?.summary.builds.status), - buildsIssues: data?.summary.builds.issues || [], - failedBuildsWithUnknownIssues: data?.summary.builds.unknown_issues, - }), - [data], - ); - return (
- -
- } - hardwareUsed={data?.summary.hardware} - diffFilter={diffFilter} - /> -
-
+
+ } + hardwareUsed={data?.summary.hardware} + diffFilter={diffFilter} + /> +
{data?.summary.tree_url && (
@@ -307,9 +281,8 @@ function TreeDetails(): JSX.Element {
)}
diff --git a/dashboard/src/pages/TreeDetails/TreeDetailsFilter.tsx b/dashboard/src/pages/TreeDetails/TreeDetailsFilter.tsx index b055d3bb..8a03f7ef 100644 --- a/dashboard/src/pages/TreeDetails/TreeDetailsFilter.tsx +++ b/dashboard/src/pages/TreeDetails/TreeDetailsFilter.tsx @@ -140,6 +140,8 @@ const TreeDetailsFilter = ({ const { data, isLoading } = useTreeDetails({ treeId, + // TODO : use tree details summary + variant: 'full', }); const navigate = useNavigate({ diff --git a/dashboard/src/types/tree/TreeDetails.tsx b/dashboard/src/types/tree/TreeDetails.tsx index 1758dd37..f2edaa53 100644 --- a/dashboard/src/types/tree/TreeDetails.tsx +++ b/dashboard/src/types/tree/TreeDetails.tsx @@ -71,8 +71,8 @@ export type TestSummary = { unknown_issues: number; fail_reasons: Record; failed_platforms: string[]; - enviroment_compatible?: PropertyStatusCounts; - enviroment_misc?: PropertyStatusCounts; + environment_compatible?: PropertyStatusCounts; + environment_misc?: PropertyStatusCounts; platforms?: PropertyStatusCounts; }; @@ -100,6 +100,22 @@ export type TTreeTestsFullData = { summary: TreeSummary; }; +export type TreeDetailsSummary = { + summary: TreeSummary; +}; + +export type TreeDetailsBuilds = { + builds: BuildsTabBuild[]; +}; + +export type TreeDetailsTests = { + tests: TestHistory[]; +}; + +export type TreeDetailsBoots = { + boots: TestHistory[]; +}; + export const possibleTabs = [ 'global.builds', 'global.boots', @@ -135,6 +151,8 @@ export const zPossibleTabValidator = z .default(defaultValidadorValues.tab) .catch(defaultValidadorValues.tab); +export type PossibleTabs = z.infer; + export const zBuildsTableFilterValidator = z .enum(possibleBuildsTableFilter) .catch(defaultValidadorValues.buildsTableFilter);