Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NCL-8675] Add latest Brew Push column to Group Build Builds list #486

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/common/buildEntityAttributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ interface IExtendedBuild extends Build {
'buildConfigRevision.buildScript': string;
'buildConfigRevision.brewPullActive': boolean;
parameters: any;
brewPush: any; // fetched externally
}

export const buildEntityAttributes = {
Expand Down Expand Up @@ -140,6 +141,10 @@ export const buildEntityAttributes = {
id: 'buildConfigRevision.brewPullActive',
title: 'Brew Pull Active',
},
brewPush: {
id: 'brewPush',
title: 'Latest Brew Push',
},
parameters: {
id: 'parameters',
title: 'Parameters',
Expand Down
22 changes: 19 additions & 3 deletions src/components/BuildsList/BuildsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { IServiceContainerState } from 'hooks/useServiceContainer';
import { ISortOptions, useSorting } from 'hooks/useSorting';

import { BuildName } from 'components/BuildName/BuildName';
import { BuildPushStatusLabelMapper } from 'components/BuildPushStatusLabelMapper/BuildPushStatusLabelMapper';
import { BuildStatusIcon } from 'components/BuildStatusIcon/BuildStatusIcon';
import { ContentBox } from 'components/ContentBox/ContentBox';
import { DateTime } from 'components/DateTime/DateTime';
Expand All @@ -31,6 +32,7 @@ import { ToolbarItem } from 'components/Toolbar/ToolbarItem';
import { TooltipWrapper } from 'components/TooltipWrapper/TooltipWrapper';
import { Username } from 'components/Username/Username';

import { isBuildWithBrewPush } from 'utils/entityRecognition';
import { areDatesEqual, calculateDuration } from 'utils/utils';

type TColumns = Array<keyof typeof buildEntityAttributes>;
Expand Down Expand Up @@ -120,8 +122,8 @@ const TimesList = ({ build, isCompactMode }: ITimesListProps) => {
);
};

interface IBuildsListProps {
serviceContainerBuilds: IServiceContainerState<BuildPage>;
interface IBuildsListProps<T extends BuildPage> {
serviceContainerBuilds: IServiceContainerState<T>;
columns?: TColumns;
componentId: string;
}
Expand All @@ -133,7 +135,11 @@ interface IBuildsListProps {
* @param columns - The columns to be displayed
* @param componentId - Component ID
*/
export const BuildsList = ({ serviceContainerBuilds, columns = defaultColumns, componentId }: IBuildsListProps) => {
export const BuildsList = <T extends BuildPage>({
serviceContainerBuilds,
columns = defaultColumns,
componentId,
}: IBuildsListProps<T>) => {
const sortOptions: ISortOptions = useMemo(
() =>
getSortOptions({
Expand Down Expand Up @@ -227,6 +233,9 @@ export const BuildsList = ({ serviceContainerBuilds, columns = defaultColumns, c
{buildEntityAttributes['user.username'].title}
</Th>
)}
{columns.includes(buildEntityAttributes.brewPush.id) && (
<Th width={10}>{buildEntityAttributes.brewPush.title}</Th>
)}
</Tr>
</Thead>
<Tbody>
Expand Down Expand Up @@ -257,6 +266,13 @@ export const BuildsList = ({ serviceContainerBuilds, columns = defaultColumns, c
{columns.includes(buildEntityAttributes['user.username'].id) && (
<Td>{build.user?.username && <Username text={build.user.username} />}</Td>
)}
{columns.includes(buildEntityAttributes.brewPush.id) && (
<Td>
{isBuildWithBrewPush(build) && build.brewPush && (
<BuildPushStatusLabelMapper status={build.brewPush.status} />
)}
</Td>
)}
</Tr>
))}
</Tbody>
Expand Down
39 changes: 34 additions & 5 deletions src/components/GroupBuildDetailPage/GroupBuildDetailPage.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';

import { Build, GroupBuild } from 'pnc-api-types-ts';

import { breadcrumbData } from 'common/breadcrumbData';
import { buildEntityAttributes } from 'common/buildEntityAttributes';
import { groupBuildEntityAttributes } from 'common/groupBuildEntityAttributes';

import { useComponentQueryParams } from 'hooks/useComponentQueryParams';
import { useParamsRequired } from 'hooks/useParamsRequired';
import {
hasBrewPushFinished,
hasBuildStarted,
hasBuildStatusChanged,
hasGroupBuildStatusChanged,
Expand Down Expand Up @@ -39,7 +41,18 @@ import * as groupBuildApi from 'services/groupBuildApi';

import { refreshPage } from 'utils/refreshHelper';
import { generatePageTitle } from 'utils/titleHelper';
import { createDateTime } from 'utils/utils';
import { createDateTime, debounce } from 'utils/utils';

const buildsListColumns = [
buildEntityAttributes.status.id,
buildEntityAttributes.name.id,
buildEntityAttributes.buildConfigName.id,
buildEntityAttributes.submitTime.id,
buildEntityAttributes.startTime.id,
buildEntityAttributes.endTime.id,
buildEntityAttributes['user.username'].id,
buildEntityAttributes.brewPush.id,
];

interface IGroupBuildDetailPageProps {
componentId?: string;
Expand All @@ -56,10 +69,15 @@ export const GroupBuildDetailPage = ({ componentId = 'gb2' }: IGroupBuildDetailP
const serviceContainerGroupBuildRunner = serviceContainerGroupBuild.run;
const serviceContainerGroupBuildSetter = serviceContainerGroupBuild.setData;

const serviceContainerGroupBuildBuilds = useServiceContainer(groupBuildApi.getBuilds);
const serviceContainerGroupBuildBuilds = useServiceContainer(groupBuildApi.getBuildsWithBrewPush);
const serviceContainerGroupBuildBuildsRunner = serviceContainerGroupBuildBuilds.run;
const serviceContainerGroupBuildBuildsSetter = serviceContainerGroupBuildBuilds.setData;

const serviceContainerGroupBuildBuildsRunnerDebounced = useMemo(
() => debounce(serviceContainerGroupBuildBuildsRunner),
[serviceContainerGroupBuildBuildsRunner]
);

const serviceContainerDependencyGraph = useServiceContainer(groupBuildApi.getDependencyGraph);
const serviceContainerDependencyGraphRunner = serviceContainerDependencyGraph.run;
const serviceContainerDependencyGraphSetter = serviceContainerDependencyGraph.setData;
Expand All @@ -83,7 +101,7 @@ export const GroupBuildDetailPage = ({ componentId = 'gb2' }: IGroupBuildDetailP
serviceContainerGroupBuildSetter(wsGroupBuild);
} else if (hasBuildStarted(wsData, { groupBuildId })) {
// very exceptional use case, mostly it means backend issues
serviceContainerGroupBuildBuildsRunner({
serviceContainerGroupBuildBuildsRunnerDebounced({
serviceData: { id: groupBuildId },
requestConfig: { params: groupBuildBuildsComponentQueryParamsObject },
});
Expand All @@ -102,15 +120,25 @@ export const GroupBuildDetailPage = ({ componentId = 'gb2' }: IGroupBuildDetailP
vertices: { ...serviceContainerDependencyGraph.data.vertices, [wsBuild.id]: updatedVertex },
});
}
} else if (
hasBrewPushFinished(wsData, {
buildIds: serviceContainerGroupBuildBuilds.data?.content?.map((build) => build.id) ?? [],
})
) {
serviceContainerGroupBuildBuildsRunnerDebounced({
serviceData: { id: groupBuildId },
requestConfig: { params: groupBuildBuildsComponentQueryParamsObject },
});
}
},
[
serviceContainerGroupBuildSetter,
groupBuildId,
serviceContainerGroupBuildBuildsRunner,
serviceContainerGroupBuildBuildsRunnerDebounced,
serviceContainerGroupBuildBuildsSetter,
serviceContainerDependencyGraphSetter,
serviceContainerDependencyGraph.data,
serviceContainerGroupBuildBuilds.data,
groupBuildBuildsComponentQueryParamsObject,
]
)
Expand Down Expand Up @@ -187,6 +215,7 @@ export const GroupBuildDetailPage = ({ componentId = 'gb2' }: IGroupBuildDetailP
{...{
serviceContainerBuilds: serviceContainerGroupBuildBuilds,
componentId,
columns: buildsListColumns,
}}
/>
</ContentBox>
Expand Down
7 changes: 5 additions & 2 deletions src/hooks/usePncWebSocketEffect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ export const hasGroupBuildStarted = (wsData: any, parameters: IGroupBuildParamet
*/
interface IBrewPushParameters {
buildId?: string;
buildIds?: string[];
}

/**
Expand All @@ -270,7 +271,7 @@ interface IBrewPushParameters {
* @param parameters - See {@link IBrewPushParameters}
* @returns true when Brew Push finished, otherwise false
*/
export const hasBrewPushFinished = (wsData: any, { buildId }: IBrewPushParameters = {}): boolean => {
export const hasBrewPushFinished = (wsData: any, { buildId, buildIds }: IBrewPushParameters = {}): boolean => {
if (wsData.job !== 'BREW_PUSH' || wsData.notificationType !== 'BREW_PUSH_RESULT') {
return false;
}
Expand All @@ -280,7 +281,9 @@ export const hasBrewPushFinished = (wsData: any, { buildId }: IBrewPushParameter
return false; // ignore changes when 'buildPushResult' is not available
}

return !buildId || buildId === wsData.buildPushResult?.buildId;
return (
(!buildId || buildId === wsData.buildPushResult.buildId) && (!buildIds || buildIds.includes(wsData.buildPushResult.buildId))
);
};

/**
Expand Down
34 changes: 33 additions & 1 deletion src/services/buildApi.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AxiosRequestConfig } from 'axios';
import axios, { AxiosRequestConfig, AxiosResponse, isAxiosError } from 'axios';

import {
ArtifactPage,
Expand Down Expand Up @@ -37,6 +37,38 @@ export const getBuilds = (requestConfig: AxiosRequestConfig = {}) => {
return pncClient.getHttpClient().get<BuildPage>('/builds', requestConfig);
};

export type BuildWithBrewPush = Build & { brewPush?: BuildPushResult };
export type BuildWithBrewPushPage = Omit<BuildPage, 'content'> & { content?: BuildWithBrewPush[] };

/**
* Gets all Builds along with latest Brew Push result.
*
* @param requestConfig - Axios based request config
*/
export const getBuildsWithBrewPush = async (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dead code, it should be removed until it's really need

requestConfig: AxiosRequestConfig = {}
): Promise<AxiosResponse<BuildWithBrewPushPage>> => {
const buildsResponse = await getBuilds(requestConfig);
if (!buildsResponse.data.content?.length) return buildsResponse;

const buildsWithBrewPush = await axios.all(
buildsResponse.data.content.map(async (build) => {
try {
const { data } = await getBrewPush({ id: build.id });
return data ? { ...build, brewPush: data } : build;
} catch (error) {
if (isAxiosError(error) && error.response?.status === 404) {
return build;
}

throw error;
}
})
);

return { ...buildsResponse, data: { ...buildsResponse.data, content: buildsWithBrewPush } };
};

/**
* Gets Builds of a User.
*
Expand Down
35 changes: 34 additions & 1 deletion src/services/groupBuildApi.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { AxiosRequestConfig } from 'axios';
import axios, { AxiosRequestConfig, AxiosResponse, isAxiosError } from 'axios';

import { BuildPage, BuildsGraph, GroupBuild, GroupBuildPage } from 'pnc-api-types-ts';

import { BuildWithBrewPushPage } from 'services/buildApi';
import * as buildApi from 'services/buildApi';

import { extendRequestConfig } from 'utils/requestConfigHelper';

import { pncClient } from './pncClient';
Expand Down Expand Up @@ -66,6 +69,36 @@ export const getBuilds = ({ id }: IGroupBuildApiData, requestConfig: AxiosReques
return pncClient.getHttpClient().get<BuildPage>(`/group-builds/${id}/builds`, requestConfig);
};

/**
* Gets Builds contained in the Group Build along with latest Brew Push result.
*
* @param requestConfig - Axios based request config
*/
export const getBuildsWithBrewPush = async (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current implementation is blocking whole lits just because of one column, even if some users / use-cases don't need that column at all. Anyway soon we will be implementing the similar functionality on other top-level pages, where blocking way would be very limiting and probably not possible due to performance issues.

Possible solution could be creating separated latestBrewPush component managing its state independently?

I understand there can be some other technical difficulties, if so, feel free to reach me directly and we can investigate other possible solutions together.

{ id }: IGroupBuildApiData,
requestConfig: AxiosRequestConfig = {}
): Promise<AxiosResponse<BuildWithBrewPushPage>> => {
const buildsResponse = await getBuilds({ id }, requestConfig);
if (!buildsResponse.data.content?.length) return buildsResponse;

const buildsWithBrewPush = await axios.all(
buildsResponse.data.content.map(async (build) => {
try {
const { data } = await buildApi.getBrewPush({ id: build.id });
return data ? { ...build, brewPush: data } : build;
} catch (error) {
if (isAxiosError(error) && error.response?.status === 404) {
return build;
}

throw error;
}
})
);

return { ...buildsResponse, data: { ...buildsResponse.data, content: buildsWithBrewPush } };
};

/**
* Gets dependency graph for a group build.
*
Expand Down
4 changes: 4 additions & 0 deletions src/utils/entityRecognition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
ProductVersion,
} from 'pnc-api-types-ts';

import { BuildWithBrewPush } from 'services/buildApi';

export const isBoolean = (value: unknown): value is boolean => typeof value === 'boolean';

export const isString = (value: unknown): value is string => typeof value === 'string';
Expand Down Expand Up @@ -37,3 +39,5 @@ interface ArtifactWithProductMilestone extends Artifact {

export const isArtifactWithProductMilestone = (artifact: Artifact): artifact is ArtifactWithProductMilestone =>
'product' in artifact && 'productVersion' in artifact && 'productMilestone' in artifact;

export const isBuildWithBrewPush = (build: Build): build is BuildWithBrewPush => 'brewPush' in build;