From 944aa6b8f2ddc640d1e54c30ee4707495d0f8d30 Mon Sep 17 00:00:00 2001 From: Thomas Desveaux Date: Mon, 25 Nov 2024 12:20:10 +0100 Subject: [PATCH 1/2] www: BuildView lazy get build changes /changes API is fairly expensive to call. It's used in BuildView for 'Changes' and 'Responsible Users' tabs. Most times, users only visit the Build steps tab. Lazily query changes if required to render relevant tabs. --- newsfragments/www-BuildView-lazy-changes.misc | 2 + www/base/src/views/BuildView/BuildView.tsx | 71 +++++++++++++------ 2 files changed, 53 insertions(+), 20 deletions(-) create mode 100644 newsfragments/www-BuildView-lazy-changes.misc diff --git a/newsfragments/www-BuildView-lazy-changes.misc b/newsfragments/www-BuildView-lazy-changes.misc new file mode 100644 index 000000000000..7a72c28efb2a --- /dev/null +++ b/newsfragments/www-BuildView-lazy-changes.misc @@ -0,0 +1,2 @@ +BuildView (`builders/:builderid/builds/:buildnumber`) now load 'Changes' and 'Responsible Users' on first access to the tab. +This lower unnecessary queries on the master. \ No newline at end of file diff --git a/www/base/src/views/BuildView/BuildView.tsx b/www/base/src/views/BuildView/BuildView.tsx index b8b04c946a12..6d71af618861 100644 --- a/www/base/src/views/BuildView/BuildView.tsx +++ b/www/base/src/views/BuildView/BuildView.tsx @@ -61,6 +61,7 @@ import {BuildSummary} from "../../components/BuildSummary/BuildSummary"; import {Tab, Table, Tabs} from "react-bootstrap"; import {buildTopbarItemsForBuilder} from "../../util/TopbarUtils"; import {BuildViewDebugTab} from "./BuildViewDebugTab"; +import {LoadingSpan} from '../../components/LoadingSpan/LoadingSpan'; const buildTopbarActions = ( build: Build | null, @@ -116,7 +117,7 @@ const buildTopbarActions = ( } const getResponsibleUsers = (propertiesQuery: DataPropertiesCollection, - changesQuery: DataCollection) => { + changesAuthors: Set) => { const responsibleUsers: {[name: string]: string | null} = {}; if (getPropertyValueOrDefault(propertiesQuery.properties, "scheduler", "") === "force") { const owner = getPropertyValueOrDefault(propertiesQuery.properties, "owner", ""); @@ -130,8 +131,8 @@ const getResponsibleUsers = (propertiesQuery: DataPropertiesCollection, } } - for (const change of changesQuery.array) { - const [name, email] = parseChangeAuthorNameAndEmail(change.author); + for (const author of changesAuthors) { + const [name, email] = parseChangeAuthorNameAndEmail(author); if (email !== null || !(name in responsibleUsers)) { responsibleUsers[name] = email; } @@ -140,6 +141,51 @@ const getResponsibleUsers = (propertiesQuery: DataPropertiesCollection, return responsibleUsers; } +type TabWidgetProps = { + build: Build | null; +} + +const ChangesTabWidget = ({build}: TabWidgetProps) => { + const changesQuery = useDataApiSingleElementQuery(build, [], b => b.getChanges()); + if (!changesQuery.isResolved()) { + return + } + + return +} + +type ResponsibleUsersTabWidgetProps = { + propertiesQuery: DataPropertiesCollection; +} & TabWidgetProps; + +const ResponsibleUsersTabWidget = ({build, propertiesQuery}: ResponsibleUsersTabWidgetProps) => { + const changesQuery = useDataApiSingleElementQuery( + build, [], + b => b.getChanges({subscribe: false, query: {field: 'author'}}) + ); + + if (!propertiesQuery.isResolved() || !changesQuery.isResolved()) { + return + } + + const responsibleUsers = computed(() => getResponsibleUsers( + propertiesQuery, + new Set(changesQuery.array.map(c => c.author)) + )).get(); + + return ( +
    + { + Object.entries(responsibleUsers).map(([author, email], index) => ( +
  • + +
  • + )) + } +
+ ); +} + const BuildView = observer(() => { const builderid = useParams<"builderid">().builderid; const buildnumber = Number.parseInt(useParams<"buildnumber">().buildnumber ?? ""); @@ -167,7 +213,6 @@ const BuildView = observer(() => { const build = findOrNull(buildsArray, b => b.number === buildnumber); const nextBuild = findOrNull(buildsArray, b => b.number === buildnumber + 1); - const changesQuery = useDataApiSingleElementQuery(build, [], b => b.getChanges()); const buildrequestsQuery = useDataApiSingleElementQuery(build, [], b => b.buildrequestid === null ? new DataCollection() @@ -243,7 +288,6 @@ const BuildView = observer(() => { } }, [builderid, navigate, shouldNavigateToBuilder]); - const responsibleUsers = computed(() => getResponsibleUsers(propertiesQuery, changesQuery)).get(); /* $window.document.title = $state.current.data.pageTitle({builder: builder['name'], build: buildnumber}); */ @@ -359,14 +403,6 @@ const BuildView = observer(() => { )); } - const renderResponsibleUsers = () => { - return Object.entries(responsibleUsers).map(([author, email], index) => ( -
  • - -
  • - )); - }; - return (
    @@ -396,15 +432,10 @@ const BuildView = observer(() => { -
      - {renderResponsibleUsers()} -
    +
    - {build !== null - ? - : <> - } + From 31c7e101129f5dd495548f436b012319e7f7971e Mon Sep 17 00:00:00 2001 From: Thomas Desveaux Date: Mon, 25 Nov 2024 19:53:15 +0100 Subject: [PATCH 2/2] www: BuildView limit changes fetched Uses ChangesTable feature to limit initial changes fetch, with possibility to load more after. --- .../www-BuildView-changes-load-more.misc | 1 + www/base/src/views/BuildView/BuildView.tsx | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 newsfragments/www-BuildView-changes-load-more.misc diff --git a/newsfragments/www-BuildView-changes-load-more.misc b/newsfragments/www-BuildView-changes-load-more.misc new file mode 100644 index 000000000000..a409847fac31 --- /dev/null +++ b/newsfragments/www-BuildView-changes-load-more.misc @@ -0,0 +1 @@ +BuildView's 'Changes' tab (`builders/:builderid/builds/:buildnumber`) now only load a limited number of changes, with the option to load more. \ No newline at end of file diff --git a/www/base/src/views/BuildView/BuildView.tsx b/www/base/src/views/BuildView/BuildView.tsx index 6d71af618861..ee8f8ae31b3c 100644 --- a/www/base/src/views/BuildView/BuildView.tsx +++ b/www/base/src/views/BuildView/BuildView.tsx @@ -21,7 +21,7 @@ import {FaSpinner} from "react-icons/fa"; import {AlertNotification} from "../../components/AlertNotification/AlertNotification"; import {useEffect, useState} from "react"; import {Link, NavigateFunction, useNavigate, useParams} from "react-router-dom"; -import {buildbotSetupPlugin} from "buildbot-plugin-support"; +import {buildbotGetSettings, buildbotSetupPlugin} from "buildbot-plugin-support"; import { Build, Buildrequest, @@ -54,6 +54,7 @@ import { useFavIcon, useTopbarItems, useTopbarActions, + useLoadMoreItemsState, } from "buildbot-ui"; import {PropertiesTable} from "../../components/PropertiesTable/PropertiesTable"; import {ChangesTable} from "../../components/ChangesTable/ChangesTable"; @@ -146,12 +147,23 @@ type TabWidgetProps = { } const ChangesTabWidget = ({build}: TabWidgetProps) => { - const changesQuery = useDataApiSingleElementQuery(build, [], b => b.getChanges()); + const initialChangesFetchLimit = buildbotGetSettings().getIntegerSetting("Changes.changesFetchLimit"); + const [changesFetchLimit, onLoadMoreChanges] = useLoadMoreItemsState( + initialChangesFetchLimit, initialChangesFetchLimit + ); + + const changesQuery = useDataApiSingleElementQuery( + build, [changesFetchLimit], + b => b.getChanges({query: {limit: changesFetchLimit, field: ['changeid']}}) + ); if (!changesQuery.isResolved()) { return } - return + return } type ResponsibleUsersTabWidgetProps = {